chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from . import variable_dispatch # noqa: F401
|
||||
@@ -0,0 +1,71 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This file stores the customized function that will be called by the dispatch mechanism.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ...utils import BreakGraphError, BreakGraphReasonBase, FallbackError
|
||||
|
||||
|
||||
def create_raise_break_graph_handler(reason: BreakGraphReasonBase):
|
||||
def raise_break_graph_fn(*args, **kwarg):
|
||||
raise BreakGraphError(reason)
|
||||
|
||||
return raise_break_graph_fn
|
||||
|
||||
|
||||
def raise_not_implement_fn(*args, **kwarg):
|
||||
raise FallbackError("raise by raise_break_graph_fn.")
|
||||
|
||||
|
||||
# just a function for operator.in
|
||||
def operator_in(left, right):
|
||||
return left in right
|
||||
|
||||
|
||||
def operator_not_in(left, right):
|
||||
return left not in right
|
||||
|
||||
|
||||
def operator_exception_match(left, right):
|
||||
pass
|
||||
|
||||
|
||||
def operator_BAD(left, right):
|
||||
pass
|
||||
|
||||
|
||||
def operator_is_none(val):
|
||||
pass
|
||||
|
||||
|
||||
def operator_is_not_none(val):
|
||||
pass
|
||||
|
||||
|
||||
def tensor_dim(x):
|
||||
pass
|
||||
|
||||
|
||||
def generator_send(x):
|
||||
pass
|
||||
|
||||
|
||||
def place_get_device_id():
|
||||
pass
|
||||
|
||||
|
||||
def place_get_device_type():
|
||||
pass
|
||||
@@ -0,0 +1,298 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import inspect
|
||||
import operator
|
||||
from functools import cached_property, reduce
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
|
||||
from ...utils import InnerError, NameGenerator, hashable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
T = TypeVar("T")
|
||||
Args = tuple[T, ...]
|
||||
Kwargs = dict[str, T]
|
||||
|
||||
|
||||
def format_type(type_: type[Any] | tuple[type[Any], ...]) -> str:
|
||||
if not isinstance(type_, tuple):
|
||||
type_ = (type_,)
|
||||
return " | ".join([t.__name__ for t in type_])
|
||||
|
||||
|
||||
def format_param(param: Parameter) -> str:
|
||||
kind = param.kind
|
||||
if kind == inspect.Parameter.VAR_POSITIONAL:
|
||||
return f"*{format_type(param.type)}"
|
||||
elif kind == inspect.Parameter.VAR_KEYWORD:
|
||||
return f"**{format_type(param.type)}"
|
||||
else:
|
||||
return format_type(param.type)
|
||||
|
||||
|
||||
def convert_annotation_to_type(type_str: str) -> tuple[type[Any], ...]:
|
||||
"""
|
||||
Convert type annotation to runtime value. Because we are using :pep:`563`
|
||||
to use the future annotation syntax, we cannot use `get_type_hints <https://docs.python.org/3.8/library/typing.html#typing.get_type_hints>`_
|
||||
directly. Currently, only the builtins and variables namespaces are supported.
|
||||
|
||||
Returns:
|
||||
tuple: The converted type.
|
||||
"""
|
||||
|
||||
import builtins
|
||||
|
||||
from . import variables
|
||||
|
||||
type_str = type_str.strip()
|
||||
if type_str == "Any":
|
||||
type_str = "object"
|
||||
|
||||
if "|" in type_str:
|
||||
return reduce(
|
||||
operator.add, map(convert_annotation_to_type, type_str.split("|"))
|
||||
)
|
||||
|
||||
search_namespaces = [variables, builtins]
|
||||
for namespace in search_namespaces:
|
||||
if hasattr(namespace, type_str):
|
||||
return (getattr(namespace, type_str),)
|
||||
raise InnerError(f"Cannot find type {type_str} in {search_namespaces}")
|
||||
|
||||
|
||||
class Parameter:
|
||||
name_gen = NameGenerator("param_")
|
||||
annotation: str
|
||||
name: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
annotation: str,
|
||||
*,
|
||||
kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
name: str | None = None,
|
||||
default: Any = inspect._empty,
|
||||
):
|
||||
self.name = name if name is not None else Parameter.name_gen.next()
|
||||
self.annotation = annotation
|
||||
self.kind = kind
|
||||
self.default = default
|
||||
|
||||
def to_parameter(self) -> inspect.Parameter:
|
||||
return inspect.Parameter(
|
||||
self.name,
|
||||
kind=self.kind,
|
||||
annotation=self.annotation,
|
||||
default=copy.copy(self.default),
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def type(self) -> tuple[type[Any], ...]:
|
||||
return convert_annotation_to_type(self.annotation)
|
||||
|
||||
def match_arg(self, arg: Any) -> bool:
|
||||
if self.kind == inspect.Parameter.VAR_POSITIONAL:
|
||||
is_tuple = isinstance(arg, tuple)
|
||||
return is_tuple and all(isinstance(a, self.type) for a in arg)
|
||||
elif self.kind == inspect.Parameter.VAR_KEYWORD:
|
||||
is_dict = isinstance(arg, dict)
|
||||
return is_dict and all(
|
||||
isinstance(a, self.type) for a in arg.values()
|
||||
)
|
||||
else:
|
||||
return isinstance(arg, self.type)
|
||||
|
||||
@staticmethod
|
||||
def from_str(annotation: str) -> Parameter:
|
||||
return Parameter(annotation)
|
||||
|
||||
@staticmethod
|
||||
def from_parameter(parameter: inspect.Parameter) -> Parameter:
|
||||
if parameter.annotation != parameter.empty and not isinstance(
|
||||
parameter.annotation, str
|
||||
):
|
||||
raise InnerError(
|
||||
f"Parameter {parameter} has annotation {parameter.annotation} "
|
||||
"which is not a string. Please add `from __future__ import annotations` "
|
||||
"to the top of your file."
|
||||
)
|
||||
annotation = (
|
||||
parameter.annotation
|
||||
if parameter.annotation != parameter.empty
|
||||
else "Any"
|
||||
)
|
||||
|
||||
return Parameter(
|
||||
annotation,
|
||||
kind=parameter.kind,
|
||||
name=parameter.name,
|
||||
default=parameter.default,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
default_repr = f"= {self.default!r}"
|
||||
return f"Parameter({', '.join([self.annotation, default_repr])})"
|
||||
|
||||
|
||||
def optional(annotation: str, default: Any = None) -> Parameter:
|
||||
return Parameter(annotation, default=default)
|
||||
|
||||
|
||||
class Pattern:
|
||||
parameters: dict[str, Parameter]
|
||||
signature: inspect.Signature
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*parameters: Parameter,
|
||||
):
|
||||
self.parameters = {
|
||||
parameter.name: parameter for parameter in parameters
|
||||
}
|
||||
self.signature = inspect.Signature(
|
||||
[parameter.to_parameter() for parameter in self.parameters.values()]
|
||||
)
|
||||
|
||||
def match_inputs(self, /, *args: Any, **kwargs: Any) -> bool:
|
||||
"""
|
||||
Match the input parameters of the function.
|
||||
|
||||
Returns:
|
||||
bool: Whether the input parameters match the pattern.
|
||||
"""
|
||||
try:
|
||||
bound_args = self.signature.bind(*args, **kwargs)
|
||||
except TypeError:
|
||||
return False
|
||||
for arg_name, arg_value in bound_args.arguments.items():
|
||||
if arg_name not in self.parameters:
|
||||
continue
|
||||
if not self.parameters[arg_name].match_arg(arg_value):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self) -> str:
|
||||
types_repr = ", ".join(
|
||||
[format_param(param) for param in self.parameters.values()]
|
||||
)
|
||||
return f"Pattern({types_repr})"
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
"""
|
||||
Used for pattern registration and distribution.
|
||||
|
||||
For more design ideas, refer to the `Builtin dispatcher <https://github.com/PaddlePaddle/PaddleSOT/blob/develop/docs/design/builtin-dispatcher.md>`_ for details.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> def builtin_add(a: int, b: int) -> int: ...
|
||||
>>> Dispatcher.register(builtin_add, ("int", "int"), lambda a, b: a + b)
|
||||
>>> handler = Dispatcher.dispatch(builtin_add, 1, 2)
|
||||
>>> handler(1, 2)
|
||||
3
|
||||
"""
|
||||
|
||||
handlers: dict[
|
||||
Callable[..., Any], list[tuple[Pattern, Callable[..., Any]]]
|
||||
] = {}
|
||||
graph: Any = None
|
||||
|
||||
@classmethod
|
||||
def register(
|
||||
cls,
|
||||
fn: Callable[..., Any],
|
||||
parameters: tuple[str | Parameter, ...],
|
||||
handler: Callable[..., Any],
|
||||
):
|
||||
"""
|
||||
Registering function signature.
|
||||
|
||||
Args:
|
||||
fn: The function to be registered.
|
||||
parameters: The parameters of the function to be registered.
|
||||
handler: The handler function.
|
||||
"""
|
||||
_parameters = tuple(
|
||||
(
|
||||
Parameter.from_str(parameter)
|
||||
if isinstance(parameter, str)
|
||||
else parameter
|
||||
)
|
||||
for parameter in parameters
|
||||
)
|
||||
if fn not in cls.handlers:
|
||||
cls.handlers[fn] = []
|
||||
cls.handlers[fn].append((Pattern(*_parameters), handler))
|
||||
|
||||
@classmethod
|
||||
def register_decorator(cls, fn: Callable[..., Any]):
|
||||
"""
|
||||
Decorator mode of register, Used to register some complex functions.
|
||||
|
||||
Args:
|
||||
fn: The function to be registered.
|
||||
|
||||
Examples:
|
||||
>>> def builtin_add(a: int, b: int) -> int: ...
|
||||
>>> @Dispatcher.register_decorator(builtin_add)
|
||||
... def builtin_add_dispatcher(a: int, b: int) -> int:
|
||||
... return a + b
|
||||
>>> handler = Dispatcher.dispatch(builtin_add, 1, 2)
|
||||
>>> handler(1, 2)
|
||||
3
|
||||
"""
|
||||
|
||||
def decorator(handler: Callable[..., Any]):
|
||||
signature = inspect.signature(handler)
|
||||
parameters = tuple(
|
||||
Parameter.from_parameter(parameter)
|
||||
for parameter in signature.parameters.values()
|
||||
)
|
||||
cls.register(fn, parameters, handler)
|
||||
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def call(cls, fn, *args, **kwargs):
|
||||
func = cls.dispatch(fn, *args, **kwargs)
|
||||
if func is None:
|
||||
raise InnerError(
|
||||
f"Cannot find handler for {fn} with args {args} and kwargs {kwargs}"
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def dispatch(
|
||||
cls, fn: Callable[..., Any], *args: Any, **kwargs: Any
|
||||
) -> Callable[..., Any] | None:
|
||||
"""
|
||||
Find the matching handler from the registered functions.
|
||||
|
||||
Args:
|
||||
fn: The function to be dispatched.
|
||||
args: The args of the function.
|
||||
kwargs: The kwargs of the function.
|
||||
"""
|
||||
if not hashable(fn) or fn not in cls.handlers:
|
||||
return None
|
||||
for pattern, handler in cls.handlers[fn]:
|
||||
if pattern.match_inputs(*args, **kwargs):
|
||||
return handler
|
||||
return None
|
||||
@@ -0,0 +1,129 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...utils import InnerError
|
||||
from .variables import ConstantVariable, ExceptionVariable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .function_graph import FunctionGraph
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExceptionStack:
|
||||
# This data structure manages exceptions as in CPython, primarily handling
|
||||
# the __context__ attribute of SotCapturedException.
|
||||
|
||||
_exception_stack: list[ExceptionVariable] = dataclasses.field(
|
||||
default_factory=list
|
||||
)
|
||||
_current_exception: ExceptionVariable | None = dataclasses.field(
|
||||
default=None
|
||||
)
|
||||
|
||||
def clear_current_exception(self):
|
||||
self._current_exception = None
|
||||
|
||||
def set_current_exception(
|
||||
self, val: ExceptionVariable, graph: FunctionGraph
|
||||
):
|
||||
self._set_context_and_break_context_reference_cycle(val, graph)
|
||||
self._current_exception = val
|
||||
|
||||
def move_current_exception_to_stack(self):
|
||||
self.push(self.get_current_exception())
|
||||
self.clear_current_exception()
|
||||
|
||||
def get_current_exception(self):
|
||||
if self._current_exception is None:
|
||||
raise InnerError("Current exception should not be None")
|
||||
return self._current_exception
|
||||
|
||||
def _set_context_and_break_context_reference_cycle(
|
||||
self, val: ExceptionVariable, graph: FunctionGraph
|
||||
):
|
||||
# set Exception.__context__
|
||||
self._set_context_recursive(val, len(self._exception_stack) - 1)
|
||||
self._break_context_reference_cycle(val, graph)
|
||||
|
||||
def _set_context_recursive(self, val: ExceptionVariable, prev_idx: int):
|
||||
# Recursively sets the __context__ attribute for ExceptionVariable objects
|
||||
# in self._exception_stack. Ensures that __context__ is properly linked
|
||||
# to the previous exception in the stack.
|
||||
if (ctx := val.__context__) and not isinstance(ctx, ConstantVariable):
|
||||
return val
|
||||
if (
|
||||
len(self._exception_stack) + prev_idx > 0
|
||||
): # Prevent invalid negative indexing
|
||||
prev = self._exception_stack[prev_idx]
|
||||
self._set_context_recursive(prev, prev_idx - 1)
|
||||
val.setattr("__context__", prev)
|
||||
return val
|
||||
|
||||
def _break_context_reference_cycle(
|
||||
self, val: ExceptionVariable, graph: FunctionGraph
|
||||
):
|
||||
# Detects and breaks cycles in exception __context__ chains using Floyd's algorithm,
|
||||
# following CPython's implementation.
|
||||
|
||||
fast = slow = val
|
||||
slow_update_toggle = False
|
||||
while True:
|
||||
context = fast.__context__
|
||||
if isinstance(
|
||||
context, ConstantVariable
|
||||
): # End of the chain; no context set
|
||||
break
|
||||
|
||||
if context is val:
|
||||
# The chain loops back to the original exception; break the cycle.
|
||||
fast.setattr(
|
||||
"__context__", ConstantVariable.wrap_literal(None, graph)
|
||||
)
|
||||
break
|
||||
|
||||
fast = context
|
||||
if fast is slow:
|
||||
# Cycle detected; all exceptions on the path have been visited and checked.
|
||||
break
|
||||
|
||||
if slow_update_toggle:
|
||||
slow = slow.__context__
|
||||
slow_update_toggle = not slow_update_toggle
|
||||
|
||||
def pop(self) -> ExceptionVariable:
|
||||
return self._exception_stack.pop()
|
||||
|
||||
def push(self, val: ExceptionVariable) -> None:
|
||||
self._exception_stack.append(val)
|
||||
|
||||
def empty(self) -> bool:
|
||||
return len(self._exception_stack) == 0
|
||||
|
||||
def __len__(self):
|
||||
return len(self._exception_stack)
|
||||
|
||||
def __repr__(self):
|
||||
return f"ExceptionStack({self._exception_stack})"
|
||||
|
||||
def __getitem__(self, idx: int) -> ExceptionVariable:
|
||||
return self._exception_stack[idx]
|
||||
|
||||
def cleanup(self) -> None:
|
||||
self._exception_stack[:] = []
|
||||
self._current_exception = None
|
||||
@@ -0,0 +1,483 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import gc
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import paddle
|
||||
|
||||
from ...profiler import EventGuard, event_register
|
||||
from ...psdb import NO_FALLBACK_CODES
|
||||
from ...utils import (
|
||||
ENV_SOT_ALLOW_DYNAMIC_SHAPE,
|
||||
ENV_SOT_ENABLE_COMPILE_TIME_LIMIT,
|
||||
ENV_SOT_ENABLE_GUARD_TREE,
|
||||
ENV_SOT_ENABLE_STRICT_GUARD_CHECK,
|
||||
ENV_SOT_UNSAFE_CACHE_FASTPATH,
|
||||
BreakGraphError,
|
||||
CompileCountInfo,
|
||||
ConditionalFallbackError,
|
||||
FallbackError,
|
||||
InfoCollector,
|
||||
InnerError,
|
||||
Singleton,
|
||||
SotCapturedException,
|
||||
is_strict_mode,
|
||||
log,
|
||||
log_do,
|
||||
log_once,
|
||||
)
|
||||
from ..custom_code import CustomCode
|
||||
from .function_graph import FunctionGraph
|
||||
from .guard import Guard
|
||||
from .opcode_executor import OpcodeExecutor, OpcodeExecutorBase
|
||||
from .virtual_frame import VirtualFrame
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import types
|
||||
|
||||
GuardedFunction = tuple[CustomCode, Guard]
|
||||
GuardedFunctions = list[GuardedFunction]
|
||||
GuardChain = list[paddle.framework.core.GuardNodeBase]
|
||||
GuardChainList = list[GuardChain]
|
||||
|
||||
dummy_guard: Guard = lambda frame: True
|
||||
dummy_guard.expr = "lambda frame: True"
|
||||
dummy_guard.inlined_expr = "lambda frame: True"
|
||||
if ENV_SOT_ENABLE_STRICT_GUARD_CHECK.get():
|
||||
dummy_guard.mirror_guard = lambda frame: True
|
||||
|
||||
|
||||
class OpcodeExecutorCache(metaclass=Singleton):
|
||||
"""
|
||||
A singleton class that implements a cache for translated instructions.
|
||||
This cache is used to store previously translated instructions along with their corresponding guard functions.
|
||||
|
||||
Attributes:
|
||||
cache (dict): A dictionary that maps code objects to tuples of a cache getter function and a list of guarded functions.
|
||||
translate_count (int): The count of how many instructions have been translated. It is used to test whether the cache hits.
|
||||
"""
|
||||
|
||||
MAX_CACHE_SIZE = 20
|
||||
MAX_COMPILE_TIME_PER_CODE = 40
|
||||
MAX_COMPILE_TIME_TOTAL = 15 * 60
|
||||
CACHE_HIT_FASTPATH_THRESHOLD = 32
|
||||
cache: dict[
|
||||
types.CodeType, tuple[GuardedFunctions, paddle.framework.core.GuardTree]
|
||||
]
|
||||
translate_count: int
|
||||
code_symbolic_inputs: dict[types.CodeType, dict[str, None | dict[int, int]]]
|
||||
compile_time_stats: dict[types.CodeType, float]
|
||||
consecutive_cache_hit_count: defaultdict[types.CodeType, int]
|
||||
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
self.translate_count = 0
|
||||
self.code_symbolic_inputs = {}
|
||||
self.compile_time_stats = {}
|
||||
self.consecutive_cache_hit_count = defaultdict(int)
|
||||
|
||||
def get_symbolic_inputs(
|
||||
self, code: types.CodeType
|
||||
) -> dict[str, dict[int, int] | None]:
|
||||
self.code_symbolic_inputs.setdefault(code, {})
|
||||
return self.code_symbolic_inputs[code]
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clears the cache and resets the translate count.
|
||||
"""
|
||||
self.cache.clear()
|
||||
self.translate_count = 0
|
||||
self.code_symbolic_inputs.clear()
|
||||
self.compile_time_stats.clear()
|
||||
|
||||
def dump_state(self):
|
||||
return {
|
||||
"cache": self.cache,
|
||||
"translate_count": self.translate_count,
|
||||
"code_symbolic_inputs": self.code_symbolic_inputs,
|
||||
"compile_time_stats": self.compile_time_stats,
|
||||
}
|
||||
|
||||
def load_state(self, state):
|
||||
self.cache = state["cache"]
|
||||
self.translate_count = state["translate_count"]
|
||||
self.code_symbolic_inputs = state["code_symbolic_inputs"]
|
||||
self.compile_time_stats = state["compile_time_stats"]
|
||||
|
||||
def __call__(self, frame: types.FrameType, **kwargs) -> CustomCode:
|
||||
code: types.CodeType = frame.f_code
|
||||
if code not in self.cache:
|
||||
log(2, f"[Cache] Firstly call {code}\n")
|
||||
new_custom_code, guard_fn, guard_chain = self.translate(
|
||||
frame, **kwargs
|
||||
)
|
||||
assert guard_fn is not None
|
||||
assert guard_chain is not None
|
||||
self.cache[code] = (
|
||||
[(new_custom_code, guard_fn)],
|
||||
paddle.framework.core.GuardTree([guard_chain]),
|
||||
)
|
||||
return new_custom_code
|
||||
guarded_fns, guard_tree = self.cache[code]
|
||||
compile_time_for_code = self.compile_time_stats.get(code, 0)
|
||||
compile_time_total = sum(self.compile_time_stats.values())
|
||||
return self.lookup(
|
||||
frame,
|
||||
guarded_fns,
|
||||
guard_tree,
|
||||
compile_time_for_code,
|
||||
compile_time_total,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def is_fastpath_threshold_reached(self, code):
|
||||
# Returns True if the number of consecutive cache hits for the given code
|
||||
# exceeds the UNSAFE_CACHE_FASTPATH threshold.
|
||||
return (
|
||||
self.consecutive_cache_hit_count.get(code, 0)
|
||||
>= self.CACHE_HIT_FASTPATH_THRESHOLD
|
||||
)
|
||||
|
||||
@event_register("lookup")
|
||||
def lookup(
|
||||
self,
|
||||
frame: types.FrameType,
|
||||
guarded_fns: GuardedFunctions,
|
||||
guard_tree: paddle.framework.core.GuardTree,
|
||||
compile_time_for_code: float,
|
||||
compile_time_total: float,
|
||||
**kwargs,
|
||||
) -> CustomCode:
|
||||
"""
|
||||
Looks up the cache for a matching code object and returns a custom code object if a matching guard function is found, otherwise None.
|
||||
|
||||
Args:
|
||||
frame (types.FrameType): The frame whose code object needs to be looked up in the cache.
|
||||
guarded_fns (GuardedFunctions): The list of guarded functions associated with the code object.
|
||||
|
||||
Returns:
|
||||
CustomCode: The custom code object if a matching guard function is found, otherwise None.
|
||||
"""
|
||||
code: types.CodeType = frame.f_code
|
||||
|
||||
if len(guarded_fns) >= self.MAX_CACHE_SIZE:
|
||||
log(2, "[Cache] Exceed max cache size, skip it\n")
|
||||
return CustomCode(None, False)
|
||||
|
||||
enable_strict_guard = ENV_SOT_ENABLE_STRICT_GUARD_CHECK.get()
|
||||
enable_guard_tree = ENV_SOT_ENABLE_GUARD_TREE.get()
|
||||
enable_unsafe_cache_fastpath = ENV_SOT_UNSAFE_CACHE_FASTPATH.get()
|
||||
enable_compile_time_limit = ENV_SOT_ENABLE_COMPILE_TIME_LIMIT.get()
|
||||
|
||||
if enable_unsafe_cache_fastpath and (
|
||||
self.is_fastpath_threshold_reached(code)
|
||||
):
|
||||
# NOTE: In inference scenarios, cache misses are generally rare, so we can enable this unsafe short path.
|
||||
log(
|
||||
2,
|
||||
"[Cache] The CACHE_HIT_FASTPATH_THRESHOLD has been reached, so fast path is now enabled\n",
|
||||
)
|
||||
return guarded_fns[0][0]
|
||||
|
||||
cache_index = None
|
||||
if enable_strict_guard or enable_guard_tree:
|
||||
log(4, f"[Cache] Guard tree: \n{guard_tree.stringify()}")
|
||||
cache_index = guard_tree.lookup(frame)
|
||||
|
||||
if not enable_strict_guard and enable_guard_tree:
|
||||
if cache_index is not None:
|
||||
# TODO(zrr1999): add a mapping between custom_code and cache_index
|
||||
return guarded_fns[cache_index][0]
|
||||
else:
|
||||
log(2, "[Cache] all guards missed (guard tree mode)\n")
|
||||
if (
|
||||
enable_compile_time_limit
|
||||
and compile_time_for_code >= self.MAX_COMPILE_TIME_PER_CODE
|
||||
):
|
||||
log(
|
||||
2,
|
||||
"[Cache] Exceed max compile time per code, skip it\n",
|
||||
)
|
||||
return CustomCode(None, False)
|
||||
if (
|
||||
enable_compile_time_limit
|
||||
and compile_time_total >= self.MAX_COMPILE_TIME_TOTAL
|
||||
):
|
||||
log_once(
|
||||
f"[SOT] Current total compile time is {compile_time_total}, exceed max compile time total {self.MAX_COMPILE_TIME_TOTAL}, fallback new function to dygraph"
|
||||
)
|
||||
log(
|
||||
2,
|
||||
"[Cache] Exceed max compile time total, skip it\n",
|
||||
)
|
||||
return CustomCode(None, False)
|
||||
new_custom_code, guard_fn, guard_chain = self.translate(
|
||||
frame, **kwargs
|
||||
)
|
||||
if guard_fn is not None:
|
||||
assert guard_chain is not None
|
||||
guarded_fns.append((new_custom_code, guard_fn))
|
||||
guard_tree.add_guard_chain(guard_chain)
|
||||
return new_custom_code
|
||||
|
||||
for index, (custom_code, guard_fn) in enumerate(guarded_fns):
|
||||
if enable_strict_guard:
|
||||
mirror_guard_error = None
|
||||
try:
|
||||
with EventGuard("try mirror guard"):
|
||||
mirror_guard_result = guard_fn.mirror_guard(frame)
|
||||
except Exception as e:
|
||||
log(2, f"[Cache] Mirror guard error: {e}\n")
|
||||
mirror_guard_error = e
|
||||
|
||||
try:
|
||||
with EventGuard("try guard"):
|
||||
guard_result = guard_fn(frame)
|
||||
if enable_strict_guard and (not enable_unsafe_cache_fastpath):
|
||||
assert mirror_guard_result == guard_result, (
|
||||
"faster guard result is not equal to guard result, "
|
||||
f"guard_expr: {getattr(guard_fn, 'expr', 'None')} \n"
|
||||
f"faster_guard_expr: {getattr(guard_fn.mirror_guard, 'expr', 'None')},"
|
||||
)
|
||||
if guard_result:
|
||||
log(
|
||||
2,
|
||||
f"[Cache] Cache hit, Guard is \n{getattr(guard_fn, 'expr', 'None')}\n",
|
||||
)
|
||||
if not enable_unsafe_cache_fastpath:
|
||||
# TODO(zrr1999): cache_index should be equal to index when enable_strict_guard.
|
||||
assert cache_index is None or index == cache_index, (
|
||||
f"cache_index({cache_index}) is not equal to index({index})"
|
||||
)
|
||||
|
||||
if enable_unsafe_cache_fastpath:
|
||||
if index == 0:
|
||||
self.consecutive_cache_hit_count[code] += 1
|
||||
else:
|
||||
# Move the current hit to the front
|
||||
# Note: Be cautious when modifying the order of elements in a list during iteration,
|
||||
# as it can lead to unexpected behavior.
|
||||
guarded_fns[:] = [
|
||||
guarded_fns[index],
|
||||
*guarded_fns[:index],
|
||||
*guarded_fns[index + 1 :],
|
||||
]
|
||||
self.consecutive_cache_hit_count[code] = 0
|
||||
|
||||
return custom_code
|
||||
else:
|
||||
log_do(
|
||||
4,
|
||||
self.analyse_guard_global_object(guard_fn),
|
||||
)
|
||||
log(
|
||||
2,
|
||||
f"[Cache] Cache miss, Guard is \n{getattr(guard_fn, 'expr', 'None')}\n",
|
||||
)
|
||||
log_do(
|
||||
2,
|
||||
self.analyse_guard_error(guard_fn, frame),
|
||||
)
|
||||
except Exception as e:
|
||||
log(2, f"[Cache] Guard function error: {e}\n")
|
||||
log(
|
||||
2,
|
||||
f"[Cache] Guard is \n{getattr(guard_fn, 'expr', 'None')}\n",
|
||||
)
|
||||
log_do(
|
||||
2,
|
||||
self.analyse_guard_error(guard_fn, frame),
|
||||
)
|
||||
if enable_strict_guard and (not enable_unsafe_cache_fastpath):
|
||||
assert type(e) == type(mirror_guard_error) and str(
|
||||
e
|
||||
) == str(mirror_guard_error), (
|
||||
"mirror guard error is not equal to guard error, "
|
||||
f"guard_error: {e} \n"
|
||||
f"mirror_guard_error: {mirror_guard_error},"
|
||||
)
|
||||
|
||||
log(2, "[Cache] all guards missed\n")
|
||||
if (
|
||||
enable_compile_time_limit
|
||||
and compile_time_for_code >= self.MAX_COMPILE_TIME_PER_CODE
|
||||
):
|
||||
log(2, "[Cache] Exceed max compile time per code, skip it\n")
|
||||
return CustomCode(None, False)
|
||||
if (
|
||||
enable_compile_time_limit
|
||||
and compile_time_total >= self.MAX_COMPILE_TIME_TOTAL
|
||||
):
|
||||
log_once(
|
||||
f"[SOT] Current compile time total is {compile_time_total}, exceed max compile time total {self.MAX_COMPILE_TIME_TOTAL}, fallback new function to dygraph"
|
||||
)
|
||||
log(
|
||||
2,
|
||||
"[Cache] Exceed max compile time total, skip it\n",
|
||||
)
|
||||
return CustomCode(None, False)
|
||||
new_custom_code, guard_fn, guard_chain = self.translate(frame, **kwargs)
|
||||
if guard_fn is not None:
|
||||
assert guard_chain is not None
|
||||
guarded_fns.append((new_custom_code, guard_fn))
|
||||
guard_tree.add_guard_chain(guard_chain)
|
||||
return new_custom_code
|
||||
|
||||
def before_translate_hook(self, frame: types.FrameType):
|
||||
if not ENV_SOT_ALLOW_DYNAMIC_SHAPE.get():
|
||||
return
|
||||
|
||||
def translate(
|
||||
self, frame: types.FrameType, **kwargs
|
||||
) -> tuple[CustomCode, Guard | None, GuardChain | None]:
|
||||
"""
|
||||
Translates the given frame's code object and returns the cache getter function and a guarded function for the translated code object.
|
||||
|
||||
Args:
|
||||
frame (types.FrameType): The frame whose code object needs to be translated.
|
||||
|
||||
Returns:
|
||||
tuple[CustomCode, Guard]: The cache getter function and a guarded function for the translated code object.
|
||||
"""
|
||||
self.before_translate_hook(frame)
|
||||
self.translate_count += 1
|
||||
custom_new_code, guard_fn, guard_chain = start_translate(
|
||||
frame, **kwargs
|
||||
)
|
||||
return custom_new_code, guard_fn, guard_chain
|
||||
|
||||
def analyse_guard_global_object(self, guard_fn):
|
||||
def inner():
|
||||
for key in guard_fn.__globals__.keys():
|
||||
if key.startswith("__object"):
|
||||
print(
|
||||
f"[Cache] meet global object: {key} : {guard_fn.__globals__[key]}",
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
def analyse_guard_error(self, guard_fn, frame):
|
||||
def inner():
|
||||
guard_expr = guard_fn.inlined_expr
|
||||
lambda_head = "lambda frame: "
|
||||
guard_expr = guard_expr.replace(lambda_head, "")
|
||||
guards = guard_expr.split(" and ")
|
||||
for guard_str in guards:
|
||||
guard = eval(lambda_head + guard_str, guard_fn.__globals__)
|
||||
result = False
|
||||
try:
|
||||
result = guard(frame)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[Cache] Error occurred when checking guard {guard_str}: {e}"
|
||||
)
|
||||
return
|
||||
if result is False:
|
||||
print(f"[Cache] missed at {guard_str}")
|
||||
return
|
||||
print("[Cache] missed guard not found.")
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def start_translate(
|
||||
frame: types.FrameType,
|
||||
**kwargs,
|
||||
) -> tuple[CustomCode, Guard | None, GuardChain | None]:
|
||||
"""
|
||||
Starts the translation process for the given frame and returns the translated code object, its guard function and its guard tree node, or None if translation fails.
|
||||
|
||||
Args:
|
||||
frame: The frame to be translated.
|
||||
|
||||
Returns:
|
||||
tuple[CustomCode, Guard | None, GuardChain | None]: The translated code object, its guard function and its guard tree node, or None if translation fails.
|
||||
"""
|
||||
simulator = None
|
||||
graph = FunctionGraph(frame.f_code, frame.f_globals, **kwargs)
|
||||
try:
|
||||
vframe = VirtualFrame.from_real_frame(frame, graph)
|
||||
simulator = OpcodeExecutor(vframe, graph)
|
||||
simulator.check_code_simulatable()
|
||||
InfoCollector().attach(CompileCountInfo, frame.f_code)
|
||||
|
||||
new_custom_code, guard_fn = simulator.transform(frame)
|
||||
if ENV_SOT_ENABLE_STRICT_GUARD_CHECK.get():
|
||||
assert guard_fn(frame)
|
||||
assert guard_fn.mirror_guard(frame)
|
||||
|
||||
if not simulator._graph.need_cache:
|
||||
return (
|
||||
CustomCode(None, True),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
guard_chain = simulator.guard_chain
|
||||
if len(guard_chain) == 0:
|
||||
guard_chain: GuardChain = [paddle.framework.core.DummyGuardNode()]
|
||||
return new_custom_code, guard_fn, guard_chain
|
||||
# TODO(0x45f): handle BreakGraphError to trigger fallback
|
||||
except BreakGraphError as e:
|
||||
raise RuntimeError(
|
||||
f"Found BreakGraphError raised, it should not be catch at start_translate!\n{e}"
|
||||
)
|
||||
except FallbackError as e:
|
||||
if frame.f_code in NO_FALLBACK_CODES:
|
||||
raise InnerError(
|
||||
f"{frame.f_code.co_name} should not fallback, but got '{e}'"
|
||||
)
|
||||
if is_strict_mode():
|
||||
raise
|
||||
log(
|
||||
2,
|
||||
f"Unsupported Frame is {frame.f_code}, error message is: \n"
|
||||
+ "".join(traceback.format_exception(type(e), e, e.__traceback__)),
|
||||
)
|
||||
dummy_guard_chain: GuardChain = [paddle.framework.core.DummyGuardNode()]
|
||||
guard, guard_chain = dummy_guard, dummy_guard_chain
|
||||
|
||||
if isinstance(e, ConditionalFallbackError):
|
||||
# Guard global variables only
|
||||
graph.input_variables.clear()
|
||||
guard = graph.guard_fn
|
||||
guard_chain = graph.guard_chain
|
||||
|
||||
return (
|
||||
CustomCode(None, e.disable_eval_frame),
|
||||
guard,
|
||||
guard_chain,
|
||||
)
|
||||
except SotCapturedException as e:
|
||||
log(
|
||||
1,
|
||||
"Note: This fallback may be triggered by user code, or it could result from an internal "
|
||||
"SOT exception being incorrectly captured. Please investigate carefully.\n",
|
||||
)
|
||||
if is_strict_mode():
|
||||
raise
|
||||
dummy_guard_chain: GuardChain = [paddle.framework.core.DummyGuardNode()]
|
||||
return (CustomCode(None, True), dummy_guard, dummy_guard_chain)
|
||||
except Exception as e:
|
||||
raise InnerError(OpcodeExecutorBase.error_message_summary(e)) from e
|
||||
finally:
|
||||
if simulator is not None:
|
||||
simulator.cleanup()
|
||||
del simulator
|
||||
gc.collect()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,327 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import types
|
||||
import weakref
|
||||
from collections.abc import Callable
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
|
||||
import paddle
|
||||
|
||||
from ...profiler import EventGuard
|
||||
from ...utils import (
|
||||
ENV_SOT_ENABLE_FASTER_GUARD,
|
||||
ENV_SOT_ENABLE_STRICT_GUARD_CHECK,
|
||||
current_symbol_registry,
|
||||
log,
|
||||
log_do,
|
||||
)
|
||||
|
||||
Guard = Callable[[types.FrameType], bool]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .variables import VariableBase
|
||||
|
||||
GuardBase = paddle.framework.core.GuardBase
|
||||
CheckGuardInputT = TypeVar("CheckGuardInputT", bound=VariableBase)
|
||||
|
||||
# NOTE(SigureMo): [How to write Stringified Guard?]
|
||||
# 1. we should capture free variables manually, the string cannot capture free
|
||||
# variables automatically.
|
||||
# 2. Be aware that the comparison logic before and after stringify may be different.
|
||||
# 3. we should compute as much as possible at "compile time" and encode the
|
||||
# computation in the Guard string, rather than passing it to runtime to minimize
|
||||
# runtime overhead.
|
||||
|
||||
|
||||
class StringifiedExpression:
|
||||
"""
|
||||
Used to store string based expressions for generating Guard.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
expr_template: str,
|
||||
sub_exprs: list[StringifiedExpression],
|
||||
free_vars: dict[str, Any],
|
||||
):
|
||||
self.expr_template = expr_template
|
||||
expr = self.expr_template.format(
|
||||
*[sub_expr.symbol for sub_expr in sub_exprs]
|
||||
)
|
||||
self.registered_expr = expr
|
||||
self.symbol = current_symbol_registry().request_symbol(expr)
|
||||
self.sub_exprs = sub_exprs
|
||||
self.free_vars = free_vars
|
||||
|
||||
@cached_property
|
||||
def inlined_expr(self):
|
||||
return self.expr_template.format(
|
||||
*[sub_expr.inlined_expr for sub_expr in self.sub_exprs]
|
||||
)
|
||||
|
||||
def gen_expr(self):
|
||||
def gen_expr_fn():
|
||||
return self.expr_template.format(
|
||||
*[sub_expr.gen_expr() for sub_expr in self.sub_exprs]
|
||||
)
|
||||
|
||||
return current_symbol_registry().gen_expr(
|
||||
self.registered_expr, gen_expr_fn
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
if self.free_vars:
|
||||
return hash((self.inlined_expr, id(self)))
|
||||
else:
|
||||
return hash(self.inlined_expr)
|
||||
|
||||
|
||||
class FasterStringifiedExpression(StringifiedExpression):
|
||||
def __init__(
|
||||
self,
|
||||
expr_template: str,
|
||||
faster_guard: GuardBase,
|
||||
sub_exprs: list[StringifiedExpression],
|
||||
free_vars: dict[str, Any],
|
||||
):
|
||||
self.faster_guard = faster_guard
|
||||
if ENV_SOT_ENABLE_FASTER_GUARD.get():
|
||||
if ENV_SOT_ENABLE_STRICT_GUARD_CHECK.get():
|
||||
self.py_guard_expr_template = original_expr_template = (
|
||||
expr_template
|
||||
)
|
||||
else:
|
||||
original_expr_template = expr_template
|
||||
expr_template, free_vars = gen_faster_guard_expr_template(
|
||||
faster_guard, sub_exprs, free_vars
|
||||
)
|
||||
log(
|
||||
3,
|
||||
f"[FasterGuard] transform {original_expr_template} to {expr_template}\n",
|
||||
)
|
||||
|
||||
super().__init__(expr_template, sub_exprs, free_vars)
|
||||
|
||||
def gen_mirror_guard(
|
||||
self, enable_faster_gurad: bool
|
||||
) -> StringifiedExpression:
|
||||
if not enable_faster_gurad:
|
||||
# gen faster_guard_expr
|
||||
expr_template, expr_free_vars = gen_faster_guard_expr_template(
|
||||
self.faster_guard,
|
||||
self.sub_exprs,
|
||||
self.free_vars,
|
||||
)
|
||||
return StringifiedExpression(
|
||||
expr_template, self.sub_exprs, expr_free_vars
|
||||
)
|
||||
# gen pyGuard_expr
|
||||
return StringifiedExpression(
|
||||
self.py_guard_expr_template, self.sub_exprs, self.free_vars
|
||||
)
|
||||
|
||||
|
||||
def gen_faster_guard_expr_template(
|
||||
faster_guard: GuardBase,
|
||||
sub_exprs: list[StringifiedExpression],
|
||||
free_vars: dict[str, Any],
|
||||
) -> tuple[str, dict[str, Any]]:
|
||||
guard_cls_name = faster_guard.__class__.__name__
|
||||
guard_name = f"{guard_cls_name}_{id(faster_guard)}"
|
||||
expr_template = guard_name + "(" + ", ".join(["{}"] * len(sub_exprs)) + ")"
|
||||
free_vars = union_free_vars(free_vars, {guard_name: faster_guard.check})
|
||||
return expr_template, free_vars
|
||||
|
||||
|
||||
def union_free_vars(*free_vars: dict[str, Any]):
|
||||
return {k: v for d in free_vars for k, v in d.items()}
|
||||
|
||||
|
||||
def make_guard(stringified_guards: list[StringifiedExpression]) -> Guard:
|
||||
"""
|
||||
Make a guard from a list of StringifiedExpression.
|
||||
|
||||
For more design ideas, refer to the `Stringified guard <https://github.com/PaddlePaddle/PaddleSOT/blob/develop/docs/design/stringify-guard.md>`_ for details.
|
||||
|
||||
Args:
|
||||
stringified_guards: a list of StringifiedExpression.
|
||||
"""
|
||||
with EventGuard("make_guard"):
|
||||
num_guards = len(stringified_guards)
|
||||
if not num_guards:
|
||||
guard = lambda frame: True
|
||||
guard.expr = "lambda frame: True"
|
||||
guard.original_guard = guard
|
||||
if ENV_SOT_ENABLE_STRICT_GUARD_CHECK.get():
|
||||
guard.mirror_guard = lambda frame: True
|
||||
return guard
|
||||
|
||||
free_vars = union_free_vars(
|
||||
*(expr.free_vars for expr in stringified_guards)
|
||||
)
|
||||
inlined_guard_expr = "lambda frame: " + " and ".join(
|
||||
[expr.inlined_expr for expr in stringified_guards]
|
||||
)
|
||||
guard_expr: str = "lambda frame: " + " and ".join(
|
||||
[expr.gen_expr() for expr in stringified_guards]
|
||||
)
|
||||
|
||||
guard = eval(guard_expr, free_vars)
|
||||
|
||||
log(3, f"[Guard] {inlined_guard_expr}\n")
|
||||
guard.inlined_expr = inlined_guard_expr
|
||||
guard.expr = guard_expr
|
||||
|
||||
def check_guard_callable(guard: GuardBase):
|
||||
assert callable(guard), "guard must be callable."
|
||||
|
||||
if ENV_SOT_ENABLE_STRICT_GUARD_CHECK.get():
|
||||
mirror_guard_expr_list: list[str] = []
|
||||
mirror_guard_temp_free_vars: dict[str, Any] = {}
|
||||
enable_faster_gurad = ENV_SOT_ENABLE_FASTER_GUARD.get()
|
||||
for expr in stringified_guards:
|
||||
if isinstance(expr, FasterStringifiedExpression):
|
||||
expr = expr.gen_mirror_guard(enable_faster_gurad)
|
||||
mirror_guard_expr_list.append(expr.inlined_expr)
|
||||
mirror_guard_temp_free_vars.update(expr.free_vars)
|
||||
mirror_guard_expr = "lambda frame: " + " and ".join(
|
||||
mirror_guard_expr_list
|
||||
)
|
||||
mirror_guard_free_vars = union_free_vars(
|
||||
mirror_guard_temp_free_vars
|
||||
)
|
||||
guard.mirror_guard = eval(mirror_guard_expr, mirror_guard_free_vars)
|
||||
guard.mirror_guard.expr = mirror_guard_expr
|
||||
check_guard_callable(guard.mirror_guard)
|
||||
|
||||
check_guard_callable(guard)
|
||||
|
||||
return guard
|
||||
|
||||
|
||||
def support_weak_ref(obj):
|
||||
if isinstance(obj, types.FunctionType):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# TODO(zrr1999): unify check_guard and check_faster_guard
|
||||
def check_guard(
|
||||
fn: Callable[[CheckGuardInputT], list[StringifiedExpression]],
|
||||
) -> Callable[[CheckGuardInputT], list[StringifiedExpression]]:
|
||||
def wrapper(self: CheckGuardInputT) -> list[StringifiedExpression]:
|
||||
assert self.tracker.is_traceable(), (
|
||||
"Cannot make guard from a non-tracable guard variable."
|
||||
)
|
||||
|
||||
def guard_log():
|
||||
frame_value_tracer = self.tracker.trace_value_from_frame()
|
||||
print(
|
||||
f"[Guard] guard_fn for {self}, tracker={self.tracker.__class__.__name__}, value={frame_value_tracer.registered_expr}"
|
||||
)
|
||||
|
||||
log_do(4, guard_log)
|
||||
return fn(self)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def check_faster_guard(
|
||||
fn: Callable[[CheckGuardInputT], list[paddle.framework.core.GuardNodeBase]],
|
||||
) -> Callable[[CheckGuardInputT], list[paddle.framework.core.GuardNodeBase]]:
|
||||
def wrapper(
|
||||
self: CheckGuardInputT,
|
||||
) -> list[paddle.framework.core.GuardNodeBase]:
|
||||
assert self.tracker.is_traceable(), (
|
||||
"Cannot make guard from a non-tracable guard variable."
|
||||
)
|
||||
|
||||
def guard_log():
|
||||
frame_value_tracer = self.tracker.trace_value_from_frame()
|
||||
print(
|
||||
f"[Guard Tree] guard_fn for {self}, tracker={self.tracker.__class__.__name__}, value={frame_value_tracer.registered_expr}"
|
||||
)
|
||||
|
||||
log_do(4, guard_log)
|
||||
return fn(self)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@check_guard
|
||||
def object_equal_stringified_guard(self) -> list[StringifiedExpression]:
|
||||
frame_value_tracer = self.tracker.trace_value_from_frame()
|
||||
|
||||
obj_free_var_name = f"__{self.id}"
|
||||
weak_ref_obj = self.get_py_value()
|
||||
if support_weak_ref(weak_ref_obj):
|
||||
weak_ref_obj = weakref.ref(self.get_py_value())
|
||||
return [
|
||||
FasterStringifiedExpression(
|
||||
f"{obj_free_var_name}() is not None and {{}} == {obj_free_var_name}()",
|
||||
paddle.framework.core.WeakRefMatchGuard(self.get_py_value()),
|
||||
[frame_value_tracer],
|
||||
union_free_vars(
|
||||
frame_value_tracer.free_vars,
|
||||
{obj_free_var_name: weak_ref_obj},
|
||||
),
|
||||
)
|
||||
]
|
||||
return [
|
||||
FasterStringifiedExpression(
|
||||
f"{{}} == {obj_free_var_name}",
|
||||
paddle.framework.core.ValueMatchGuard(weak_ref_obj),
|
||||
[frame_value_tracer],
|
||||
union_free_vars(
|
||||
frame_value_tracer.free_vars,
|
||||
{obj_free_var_name: self.get_py_value()},
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@check_faster_guard
|
||||
def object_equal_faster_guard(
|
||||
self,
|
||||
) -> list[paddle.framework.core.GuardNodeBase]:
|
||||
expr_node = self.tracker.guard_tree_expr_node()
|
||||
|
||||
weak_ref_obj = self.get_py_value()
|
||||
if support_weak_ref(weak_ref_obj):
|
||||
weak_ref_obj = weakref.ref(self.get_py_value())
|
||||
return [
|
||||
paddle.framework.core.GuardNode(
|
||||
paddle.framework.core.WeakRefMatchGuard(self.get_py_value()),
|
||||
[expr_node],
|
||||
)
|
||||
]
|
||||
return [
|
||||
paddle.framework.core.GuardNode(
|
||||
paddle.framework.core.ValueMatchGuard(weak_ref_obj),
|
||||
[expr_node],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def stringify_pyobject(obj: object) -> tuple[str, dict[str, Any]]:
|
||||
if isinstance(obj, paddle.core.VarDesc.VarType):
|
||||
return f"paddle.core.VarDesc.VarType({obj.value})", {"paddle": paddle}
|
||||
elif isinstance(obj, paddle.core.DataType):
|
||||
return f"paddle.core.DataType({obj.value})", {"paddle": paddle}
|
||||
# For builtin values
|
||||
return f"{obj!r}", {}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# flags for instructions
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FORMAT_VALUE_FLAG:
|
||||
FVC_MASK = 0x3
|
||||
FVC_NONE = 0x0
|
||||
FVC_STR = 0x1
|
||||
FVC_REPR = 0x2
|
||||
FVC_ASCII = 0x3
|
||||
FVS_MASK = 0x4
|
||||
FVS_HAVE_SPEC = 0x4
|
||||
|
||||
|
||||
class CONVERT_VALUE_FLAG:
|
||||
CV_STR = 1
|
||||
CV_REPR = 2
|
||||
CV_ASCII = 3
|
||||
|
||||
|
||||
# https://github.com/python/cpython/blob/3.14/Include/internal/pycore_opcode_utils.h#L63-L68
|
||||
class MAKE_FUNCTION_FLAG:
|
||||
MF_HAS_ANNOTATE = 0x10
|
||||
MF_HAS_CLOSURE = 0x08
|
||||
MF_HAS_ANNOTATION = 0x04
|
||||
MF_HAS_KWDEFAULTS = 0x02
|
||||
MF_HAS_DEFAULTS = 0x01
|
||||
|
||||
|
||||
class CALL_FUNCTION_EX_FLAG:
|
||||
CFE_HAS_KWARGS = 0x01
|
||||
|
||||
|
||||
# see https://github.com/python/cpython/blob/3.12/Python/intrinsics.c#L211-L225
|
||||
class IntrinsicsUnaryFunctions(Enum):
|
||||
INTRINSIC_1_INVALID = 0
|
||||
INTRINSIC_PRINT = 1 # no support, only non-interactive mode
|
||||
INTRINSIC_IMPORT_STAR = 2 # no support, `from module import *`
|
||||
INTRINSIC_STOPITERATION_ERROR = 3 # no support, generator or coroutine
|
||||
INTRINSIC_ASYNC_GEN_WRAP = 4 # no support, async
|
||||
INTRINSIC_UNARY_POSITIVE = 5
|
||||
INTRINSIC_LIST_TO_TUPLE = 6
|
||||
INTRINSIC_TYPEVAR = 7 # no support, PEP 695
|
||||
INTRINSIC_PARAMSPEC = 8 # no support, PEP 695
|
||||
INTRINSIC_TYPEVARTUPLE = 9 # no support, PEP 695
|
||||
INTRINSIC_SUBSCRIPT_GENERIC = 10 # no support, PEP 695
|
||||
INTRINSIC_TYPEALIAS = 11 # no support, PEP 695
|
||||
|
||||
|
||||
# https://github.com/python/cpython/blob/3.14/Include/internal/pycore_opcode_utils.h#L70-L76
|
||||
# All are attributes of 'builtins'
|
||||
LOAD_COMMON_CONSTANT_FLAG = (
|
||||
"AssertionError",
|
||||
"NotImplementedError",
|
||||
"tuple",
|
||||
"all",
|
||||
"any",
|
||||
)
|
||||
@@ -0,0 +1,306 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Concatenate, Generic, TypeAlias, TypeVar
|
||||
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
R = TypeVar("R")
|
||||
|
||||
MutableDataT = TypeVar("MutableDataT", bound="MutableData")
|
||||
DataGetter: TypeAlias = Callable[[MutableDataT, Any], Any]
|
||||
|
||||
InnerMutableDataT = TypeVar(
|
||||
"InnerMutableDataT", bound="dict[str, Any] | list[Any]"
|
||||
)
|
||||
|
||||
|
||||
class Mutation:
|
||||
ABBR: str
|
||||
|
||||
|
||||
class MutationSet(Mutation):
|
||||
"""
|
||||
Setting a value.
|
||||
This mutation is used for MutableDictLikeData and MutableListLikeData.
|
||||
"""
|
||||
|
||||
ABBR = "S"
|
||||
|
||||
def __init__(self, key, value):
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"MutationSet({self.key}, {self.value})"
|
||||
|
||||
|
||||
class MutationDel(Mutation):
|
||||
"""
|
||||
Deleting a value.
|
||||
This mutation is used for MutableDictLikeData and MutableListLikeData.
|
||||
"""
|
||||
|
||||
ABBR = "D"
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
def __repr__(self):
|
||||
return f"MutationDel({self.key})"
|
||||
|
||||
|
||||
class MutationNew(Mutation):
|
||||
"""
|
||||
Adding a new value.
|
||||
This mutation is only used for MutableDictLikeData.
|
||||
"""
|
||||
|
||||
ABBR = "N"
|
||||
|
||||
def __init__(self, key, value):
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"MutationNew({self.key}, {self.value})"
|
||||
|
||||
|
||||
class MutationInsert(Mutation):
|
||||
"""
|
||||
Inserting a value.
|
||||
This mutation is only used for MutableListLikeData.
|
||||
"""
|
||||
|
||||
ABBR = "I"
|
||||
|
||||
def __init__(self, index, value):
|
||||
self.index = index
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"MutationInsert({self.index}, {self.value})"
|
||||
|
||||
|
||||
class MutationPermutate(Mutation):
|
||||
"""
|
||||
Permutating all the values.
|
||||
This mutation is only used for MutableListLikeData.
|
||||
"""
|
||||
|
||||
ABBR = "P"
|
||||
|
||||
def __init__(self, permutation):
|
||||
self.permutation = permutation
|
||||
|
||||
def __repr__(self):
|
||||
return f"MutationPermutate({self.permutation})"
|
||||
|
||||
|
||||
def record_mutation(
|
||||
mutation_fn: Callable[Concatenate[MutableDataT, P], Mutation],
|
||||
) -> Callable[Concatenate[MutableDataT, P], None]:
|
||||
def wrapper(self, *args: P.args, **kwargs: P.kwargs):
|
||||
mutation = mutation_fn(self, *args, **kwargs)
|
||||
self.records.append(mutation)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class MutableData(Generic[InnerMutableDataT]):
|
||||
"""
|
||||
An intermediate data structure between data and variable, it records all the mutations.
|
||||
"""
|
||||
|
||||
read_cache: InnerMutableDataT
|
||||
|
||||
class Empty:
|
||||
def __repr__(self):
|
||||
return "Empty()"
|
||||
|
||||
def __init__(self, data: Any, getter: DataGetter):
|
||||
self.original_data = data
|
||||
self.getter = getter
|
||||
self.records: list[Mutation] = []
|
||||
|
||||
def is_empty(self, value):
|
||||
return isinstance(value, MutableData.Empty)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return len(self.records)
|
||||
|
||||
@property
|
||||
def has_changed(self):
|
||||
return self.version != 0
|
||||
|
||||
def check_changed(self, key: Any) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def rollback(self, version: int):
|
||||
assert version <= self.version
|
||||
self.records[:] = self.records[:version]
|
||||
|
||||
def get(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def set(self, key, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def apply(self, mutation: Mutation, write_cache: InnerMutableDataT):
|
||||
raise NotImplementedError
|
||||
|
||||
def reproduce(self, version: int | None = None) -> InnerMutableDataT:
|
||||
if version is None:
|
||||
version = self.version
|
||||
write_cache = self.read_cache.copy()
|
||||
for mutation in self.records[:version]:
|
||||
self.apply(mutation, write_cache)
|
||||
return write_cache
|
||||
|
||||
def __repr__(self) -> str:
|
||||
records_abbrs = "".join([mutation.ABBR for mutation in self.records])
|
||||
return f"{self.__class__.__name__}({records_abbrs})"
|
||||
|
||||
|
||||
class MutableDictLikeData(MutableData["dict[str, Any]"]):
|
||||
def __init__(self, data: Any, getter: DataGetter):
|
||||
super().__init__(data, getter)
|
||||
self.read_cache = {}
|
||||
|
||||
def clear_read_cache(self):
|
||||
self.read_cache.clear()
|
||||
|
||||
def check_changed(self, key: Any) -> bool:
|
||||
if not self.has_changed:
|
||||
return False
|
||||
for mutation in self.records:
|
||||
if (
|
||||
isinstance(mutation, (MutationNew, MutationDel, MutationSet))
|
||||
and mutation.key == key
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get(self, key: Any):
|
||||
# TODO(SigureMo): Optimize performance of this.
|
||||
write_cache = self.reproduce(self.version)
|
||||
if key not in write_cache:
|
||||
self.read_cache[key] = self.getter(self, key)
|
||||
return self.reproduce(self.version)[key]
|
||||
|
||||
def get_all(self):
|
||||
original_keys = list(self.original_data.keys())
|
||||
for mutation in self.records:
|
||||
if isinstance(mutation, MutationNew):
|
||||
original_keys.append(mutation.key)
|
||||
elif isinstance(mutation, MutationDel):
|
||||
original_keys.remove(mutation.key)
|
||||
return {key: self.get(key) for key in original_keys}
|
||||
|
||||
@record_mutation
|
||||
def set(self, key: Any, value: Any) -> Mutation:
|
||||
is_new = False
|
||||
if self.is_empty(self.get(key)):
|
||||
is_new = True
|
||||
return (
|
||||
MutationSet(key, value) if not is_new else MutationNew(key, value)
|
||||
)
|
||||
|
||||
@record_mutation
|
||||
def delete(self, key):
|
||||
return MutationDel(key)
|
||||
|
||||
def apply(self, mutation: Mutation, write_cache: dict[str, Any]):
|
||||
if isinstance(mutation, MutationNew):
|
||||
write_cache[mutation.key] = mutation.value
|
||||
elif isinstance(mutation, MutationSet):
|
||||
write_cache[mutation.key] = mutation.value
|
||||
elif isinstance(mutation, MutationDel):
|
||||
write_cache[mutation.key] = MutableData.Empty()
|
||||
else:
|
||||
raise ValueError(f"Unknown mutation type {mutation}")
|
||||
|
||||
def reproduce(self, version: int | None = None):
|
||||
if version is None:
|
||||
version = self.version
|
||||
write_cache = self.read_cache.copy()
|
||||
for mutation in self.records[:version]:
|
||||
self.apply(mutation, write_cache)
|
||||
return write_cache
|
||||
|
||||
|
||||
class MutableListLikeData(MutableData["list[Any]"]):
|
||||
def __init__(self, data: Any, getter: DataGetter):
|
||||
super().__init__(data, getter)
|
||||
self.read_cache = [
|
||||
self.getter(self, idx) for idx in range(len(self.original_data))
|
||||
]
|
||||
|
||||
def clear_read_cache(self):
|
||||
self.read_cache[:] = []
|
||||
|
||||
def check_changed(self, key: Any) -> bool:
|
||||
return self.has_changed
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return len(self.reproduce())
|
||||
|
||||
def get(self, key):
|
||||
write_cache = self.reproduce(self.version)
|
||||
return write_cache[key]
|
||||
|
||||
def get_all(self) -> list[Any]:
|
||||
items = self.reproduce(self.version)
|
||||
return items
|
||||
|
||||
@record_mutation
|
||||
def set(self, key: int, value: Any):
|
||||
return MutationSet(self._regularize_index(key), value)
|
||||
|
||||
@record_mutation
|
||||
def delete(self, key: int):
|
||||
return MutationDel(self._regularize_index(key))
|
||||
|
||||
@record_mutation
|
||||
def insert(self, index: int, value: Any):
|
||||
return MutationInsert(self._regularize_index(index), value)
|
||||
|
||||
@record_mutation
|
||||
def permutate(self, permutation: list[int]):
|
||||
return MutationPermutate(permutation)
|
||||
|
||||
def _regularize_index(self, index: int):
|
||||
if index < 0:
|
||||
index += self.length
|
||||
return index
|
||||
|
||||
def apply(self, mutation: Mutation, write_cache: list[Any]):
|
||||
if isinstance(mutation, MutationSet):
|
||||
write_cache[mutation.key] = mutation.value
|
||||
elif isinstance(mutation, MutationDel):
|
||||
write_cache[:] = (
|
||||
write_cache[: mutation.key] + write_cache[mutation.key + 1 :]
|
||||
)
|
||||
elif isinstance(mutation, MutationInsert):
|
||||
write_cache.insert(mutation.index, mutation.value)
|
||||
elif isinstance(mutation, MutationPermutate):
|
||||
write_cache[:] = [write_cache[i] for i in mutation.permutation]
|
||||
else:
|
||||
raise ValueError(f"Unknown mutation type {mutation}")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...utils import (
|
||||
BreakGraphError,
|
||||
DataDependencyControlFlowBreak,
|
||||
FallbackError,
|
||||
UnsupportedIteratorBreak,
|
||||
)
|
||||
from ...utils.exceptions import SotCapturedStopIteration
|
||||
from ..instruction_utils import Instruction
|
||||
from .dispatch_functions import generator_send
|
||||
from .opcode_executor import OpcodeExecutorBase, Stop
|
||||
from .tracker import DanglingTracker
|
||||
from .variables import (
|
||||
BuiltinVariable,
|
||||
ConstantVariable,
|
||||
GeneratorVariable,
|
||||
IterVariable,
|
||||
ObjectVariable,
|
||||
UserDefinedIterVariable,
|
||||
VariableBase,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .function_graph import FunctionGraph
|
||||
from .virtual_frame import VirtualFrame
|
||||
|
||||
|
||||
def inline_for_iter_impl(exe: OpcodeExecutorBase, instr: Instruction):
|
||||
iterator = exe.stack.top
|
||||
assert isinstance(iterator, IterVariable)
|
||||
|
||||
exe._graph.add_global_guarded_variable(iterator)
|
||||
|
||||
# simply get next
|
||||
if not isinstance(iterator, UserDefinedIterVariable):
|
||||
try:
|
||||
exe.stack.push(iterator.next())
|
||||
except SotCapturedStopIteration:
|
||||
exe.stack.pop()
|
||||
assert isinstance(instr.jump_to, Instruction)
|
||||
exe.vframe.lasti = exe.indexof(instr.jump_to)
|
||||
if sys.version_info >= (3, 12):
|
||||
assert exe._instructions[exe.vframe.lasti].opname == "END_FOR"
|
||||
skip_n_instrs = 2 if sys.version_info >= (3, 13) else 1
|
||||
exe.vframe.lasti += skip_n_instrs
|
||||
|
||||
else:
|
||||
exe._graph.remove_global_guarded_variable(iterator)
|
||||
raise BreakGraphError(
|
||||
UnsupportedIteratorBreak(
|
||||
reason_str=f"Found {iterator.__class__.__name__} as iterator."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class OpcodeInlineExecutor(OpcodeExecutorBase):
|
||||
"""
|
||||
A class that represents an executor for inlined opcode operations.
|
||||
|
||||
Args:
|
||||
fn_variable: The function variable.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vframe: VirtualFrame,
|
||||
code_var: VariableBase,
|
||||
graph: FunctionGraph,
|
||||
):
|
||||
super().__init__(vframe, graph)
|
||||
self.return_value: VariableBase | None = None
|
||||
self._code_var = code_var
|
||||
self._name = "InlineFn"
|
||||
|
||||
def inline_call(self) -> VariableBase:
|
||||
"""
|
||||
Execute the inline call of the function.
|
||||
"""
|
||||
self._graph.add_global_guarded_variable(self._code_var)
|
||||
self.run()
|
||||
assert self.return_value is not None
|
||||
return self.return_value
|
||||
|
||||
def RETURN_VALUE(self, instr: Instruction):
|
||||
assert len(self.stack) == 1, (
|
||||
f"Stack must have one element, but get {len(self.stack)} elements."
|
||||
)
|
||||
self.return_value = self.stack.pop()
|
||||
return Stop(state="Return")
|
||||
|
||||
def RETURN_CONST(self, instr: Instruction):
|
||||
self.return_value = self.vframe.consts[instr.arg]
|
||||
return Stop(state="Return")
|
||||
|
||||
def _break_graph_when_if(self, result, instr: Instruction):
|
||||
"""
|
||||
Helper method to raise a BreakGraphError when breaking the graph in a jump operation.
|
||||
|
||||
Args:
|
||||
result: The result of the operation.
|
||||
instr (Instruction): The jump instruction.
|
||||
"""
|
||||
|
||||
raise BreakGraphError(DataDependencyControlFlowBreak())
|
||||
|
||||
def FOR_ITER(self, instr: Instruction):
|
||||
return inline_for_iter_impl(self, instr)
|
||||
|
||||
|
||||
class OpcodeInlineGeneratorExecutor(OpcodeExecutorBase):
|
||||
def __init__(
|
||||
self,
|
||||
vframe: VirtualFrame,
|
||||
code_var: VariableBase,
|
||||
graph: FunctionGraph,
|
||||
):
|
||||
super().__init__(vframe, graph)
|
||||
self.return_value: VariableBase | None = None
|
||||
self._code_var = code_var
|
||||
self._name = "InlineGen"
|
||||
|
||||
def inline_call(self) -> VariableBase:
|
||||
self._graph.add_global_guarded_variable(self._code_var)
|
||||
self.run()
|
||||
assert self.return_value is not None
|
||||
return self.return_value
|
||||
|
||||
def RETURN_GENERATOR(self, instr: Instruction):
|
||||
vframe = self.vframe
|
||||
code_var = self._code_var
|
||||
# NOTE: we set the real tracker in calling function
|
||||
self.return_value = GeneratorVariable(
|
||||
code_var, vframe, self._graph, DanglingTracker()
|
||||
)
|
||||
return Stop(state="Return")
|
||||
|
||||
def SEND(self, instr: Instruction):
|
||||
assert len(self.stack) >= 2
|
||||
recv = self.stack.pop()
|
||||
source_obj = self.stack.top
|
||||
if not isinstance(source_obj, IterVariable):
|
||||
raise FallbackError(
|
||||
"Yield from for non-generator object is not supported."
|
||||
)
|
||||
self.stack.push(
|
||||
BuiltinVariable(generator_send, self._graph, DanglingTracker())(
|
||||
source_obj, recv
|
||||
)
|
||||
)
|
||||
|
||||
def END_SEND(self, instr: Instruction):
|
||||
value = self.stack.pop()
|
||||
receiver = self.stack.pop() # pop the receiver
|
||||
self.stack.push(value)
|
||||
|
||||
def GEN_START(self, instr: Instruction):
|
||||
tos = self.stack.pop()
|
||||
assert isinstance(tos, ConstantVariable)
|
||||
assert tos.value is None
|
||||
|
||||
def YIELD_VALUE(self, instr: Instruction):
|
||||
assert len(self.stack) >= 1
|
||||
self.return_value = self.stack.pop()
|
||||
return Stop(state="Yield")
|
||||
|
||||
def GET_YIELD_FROM_ITER(self, instr: Instruction):
|
||||
source_obj = self.stack.top
|
||||
if isinstance(source_obj, ObjectVariable) and inspect.iscoroutine(
|
||||
source_obj.value
|
||||
):
|
||||
raise FallbackError(
|
||||
"Get yield from iter for coroutine object is not supported."
|
||||
)
|
||||
if isinstance(source_obj, GeneratorVariable):
|
||||
return
|
||||
source_obj = self.stack.pop()
|
||||
iter_variable = BuiltinVariable(iter, self._graph, DanglingTracker())(
|
||||
source_obj
|
||||
)
|
||||
self.stack.push(iter_variable)
|
||||
|
||||
def YIELD_FROM(self, instr: Instruction):
|
||||
recv = self.stack.pop()
|
||||
source_obj = self.stack.top
|
||||
if not isinstance(source_obj, IterVariable):
|
||||
raise FallbackError(
|
||||
"Yield from for non-generator object is not supported."
|
||||
)
|
||||
self.return_value = BuiltinVariable(
|
||||
generator_send, self._graph, DanglingTracker()
|
||||
)(source_obj, recv)
|
||||
assert self.vframe.lasti > 0
|
||||
self.vframe.lasti -= 1
|
||||
return Stop(state="Yield")
|
||||
|
||||
def FOR_ITER(self, instr: Instruction):
|
||||
return inline_for_iter_impl(self, instr)
|
||||
|
||||
def RETURN_VALUE(self, instr: Instruction):
|
||||
assert len(self.stack) == 1, (
|
||||
f"Stack must have one element, but get {len(self.stack)} elements."
|
||||
)
|
||||
self.return_value = self.stack.pop()
|
||||
return Stop(state="Return")
|
||||
|
||||
def RETURN_CONST(self, instr: Instruction):
|
||||
self.return_value = self.vframe.consts[instr.arg]
|
||||
return Stop(state="Return")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,237 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import TypeAlias
|
||||
|
||||
from .mutable_data import DataGetter, MutableData
|
||||
from .pycode_generator import PyCodeGen
|
||||
from .variables import VariableBase
|
||||
|
||||
IdGetter: TypeAlias = Callable[[Any], int]
|
||||
MutableDataT = TypeVar("MutableDataT", bound=MutableData)
|
||||
|
||||
|
||||
class SideEffectsState(NamedTuple):
|
||||
data_id_to_proxy: dict[int, MutableData]
|
||||
proxy_variables: list[VariableBase]
|
||||
mutable_variables: list[VariableBase]
|
||||
proxy_versions: list[int]
|
||||
mutable_attrs: list[dict[str, Any]]
|
||||
|
||||
|
||||
class SideEffects:
|
||||
def __init__(self):
|
||||
self.data_id_to_proxy: dict[int, MutableData] = {}
|
||||
self.proxy_variables: list[VariableBase] = []
|
||||
self.mutable_variables: list[VariableBase] = []
|
||||
|
||||
def record_proxy_variable(self, variable: VariableBase):
|
||||
if variable not in self.proxy_variables:
|
||||
self.proxy_variables.append(variable)
|
||||
|
||||
def record_mutable_variable(self, variable: VariableBase):
|
||||
if variable not in self.mutable_variables:
|
||||
self.mutable_variables.append(variable)
|
||||
|
||||
def get_proxy(
|
||||
self,
|
||||
proxy_type: type[MutableDataT],
|
||||
data: Any,
|
||||
getter: DataGetter,
|
||||
id_getter: IdGetter = id,
|
||||
) -> MutableDataT:
|
||||
data_id = id_getter(data)
|
||||
if data_id not in self.data_id_to_proxy:
|
||||
self.data_id_to_proxy[data_id] = proxy_type(data, getter)
|
||||
return self.data_id_to_proxy[data_id] # type: ignore
|
||||
|
||||
def get_state(self):
|
||||
return SideEffectsState(
|
||||
self.data_id_to_proxy.copy(),
|
||||
self.proxy_variables.copy(),
|
||||
self.mutable_variables.copy(),
|
||||
[proxy.version for proxy in self.data_id_to_proxy.values()],
|
||||
[
|
||||
{attr: getattr(var, attr)}
|
||||
for var in self.mutable_variables
|
||||
for attr in var.mutable_attrs
|
||||
],
|
||||
)
|
||||
|
||||
def restore_state(self, state: SideEffectsState):
|
||||
self.data_id_to_proxy = state.data_id_to_proxy
|
||||
self.proxy_variables = state.proxy_variables
|
||||
self.mutable_variables = state.mutable_variables
|
||||
# NOTE(SigureMo): We can use the `strict=True` option in zip after
|
||||
# Python 3.10.
|
||||
assert len(self.data_id_to_proxy.values()) == len(
|
||||
state.proxy_versions
|
||||
), "proxy_versions length not match"
|
||||
assert sum(
|
||||
len(var.mutable_attrs) for var in self.mutable_variables
|
||||
) == len(state.mutable_attrs), "mutable_attrs length not match"
|
||||
|
||||
for proxy, version in zip(
|
||||
self.data_id_to_proxy.values(), state.proxy_versions
|
||||
):
|
||||
proxy.rollback(version)
|
||||
|
||||
for (variable, attr), attr_dict in zip(
|
||||
(
|
||||
(var, attr)
|
||||
for var in self.mutable_variables
|
||||
for attr in var.mutable_attrs
|
||||
),
|
||||
(attr_dict for attr_dict in state.mutable_attrs),
|
||||
):
|
||||
setattr(variable, attr, attr_dict[attr])
|
||||
|
||||
|
||||
class SideEffectRestorer:
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
raise NotImplementedError
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DictSideEffectRestorer(SideEffectRestorer):
|
||||
"""
|
||||
old_dict.clear()
|
||||
old_dict.update(new_dict)
|
||||
"""
|
||||
|
||||
def __init__(self, var: VariableBase):
|
||||
super().__init__()
|
||||
self.var = var
|
||||
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
# Reference to the original dict.
|
||||
# load old_dict.update and new_dict to stack.
|
||||
self.var.reconstruct(codegen)
|
||||
codegen.gen_load_method("update")
|
||||
# Generate dict by each key-value pair.
|
||||
self.var.reconstruct(codegen, use_tracker=False)
|
||||
# load old_dict.clear to stack.
|
||||
self.var.reconstruct(codegen)
|
||||
codegen.gen_load_method("clear")
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
# Call methods to apply side effects.
|
||||
codegen.gen_call_method(0) # call clear
|
||||
codegen.gen_pop_top()
|
||||
codegen.gen_call_method(1) # call update
|
||||
codegen.gen_pop_top()
|
||||
|
||||
|
||||
class ListSideEffectRestorer(SideEffectRestorer):
|
||||
"""
|
||||
old_list[:] = new_list
|
||||
"""
|
||||
|
||||
def __init__(self, var: VariableBase):
|
||||
super().__init__()
|
||||
self.var = var
|
||||
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
# Reference to the original list.
|
||||
# load new_list to stack.
|
||||
self.var.reconstruct(codegen, use_tracker=False)
|
||||
# load old_list[:] to stack.
|
||||
self.var.reconstruct(codegen)
|
||||
codegen.gen_load_const(None)
|
||||
codegen.gen_load_const(None)
|
||||
codegen.gen_build_slice(2)
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
# Call STORE_SUBSCR to apply side effects.
|
||||
codegen.gen_store_subscr()
|
||||
|
||||
|
||||
class GlobalSetSideEffectRestorer(SideEffectRestorer):
|
||||
"""
|
||||
global_var = new_value
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, var: VariableBase):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.var = var
|
||||
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
self.var.reconstruct(codegen)
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
codegen.gen_store_global(self.name)
|
||||
|
||||
|
||||
class GlobalDelSideEffectRestorer(SideEffectRestorer):
|
||||
"""
|
||||
del global_var
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
# do nothing
|
||||
...
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
codegen.gen_delete_global(self.name)
|
||||
|
||||
|
||||
class ObjSetSideEffectRestorer(SideEffectRestorer):
|
||||
"""
|
||||
obj.attr = new_value
|
||||
"""
|
||||
|
||||
def __init__(self, obj: VariableBase, name: str, var: VariableBase):
|
||||
super().__init__()
|
||||
self.obj = obj
|
||||
self.name = name
|
||||
self.var = var
|
||||
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
# value
|
||||
self.var.reconstruct(codegen)
|
||||
# obj
|
||||
self.obj.reconstruct(codegen)
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
codegen.gen_store_attr(self.name)
|
||||
|
||||
|
||||
class ObjDelSideEffectRestorer(SideEffectRestorer):
|
||||
"""
|
||||
del obj.attr
|
||||
"""
|
||||
|
||||
def __init__(self, obj: VariableBase, name: str):
|
||||
super().__init__()
|
||||
self.obj = obj
|
||||
self.name = name
|
||||
|
||||
def pre_gen(self, codegen: PyCodeGen):
|
||||
self.obj.reconstruct(codegen)
|
||||
|
||||
def post_gen(self, codegen: PyCodeGen):
|
||||
codegen.gen_delete_attr(self.name)
|
||||
@@ -0,0 +1,619 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import sys
|
||||
from itertools import chain
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import paddle
|
||||
|
||||
from ...utils import InnerError, NameGenerator
|
||||
from .guard import StringifiedExpression, stringify_pyobject, union_free_vars
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
from ...utils.magic_methods import BinaryOp, UnaryOp
|
||||
from .pycode_generator import PyCodeGen
|
||||
from .variables import FunctionVariable, VariableBase
|
||||
|
||||
|
||||
class Tracker:
|
||||
"""
|
||||
Tracker is a base class responsible for tracking variables or objects in Python code.
|
||||
It is used to identify how a variable is derived from the initial state of the frame.
|
||||
|
||||
Args:
|
||||
inputs: The list of variables to be tracked.
|
||||
|
||||
Note:
|
||||
It serves as an abstract class and should not be instantiated directly.
|
||||
"""
|
||||
|
||||
inputs: Sequence[VariableBase]
|
||||
name_generator = NameGenerator("tracker_")
|
||||
|
||||
def __init__(self, inputs: Sequence[VariableBase], changed: bool = False):
|
||||
self.inputs = inputs
|
||||
self.changed = changed
|
||||
self.id = Tracker.name_generator.next()
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen) -> None:
|
||||
"""
|
||||
Generate instructions based on the tracked variables.
|
||||
|
||||
Args:
|
||||
codegen (PyCodeGen): An instance of PyCodeGen to generate instructions.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
raise NotImplementedError(
|
||||
f"{self.__class__.__name__} has no guard_tree_expr_node"
|
||||
)
|
||||
|
||||
# TODO(xiongkun): trace_value_from_frame is not a good name, it should be more related to guard but not traceable.
|
||||
def trace_value_from_frame(self) -> StringifiedExpression:
|
||||
"""
|
||||
Trace the value of the tracked variables from the frame. It used for generating the guard.
|
||||
|
||||
Returns:
|
||||
The value of the tracked variables.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_traceable(self) -> bool:
|
||||
"""
|
||||
Determine if all the tracked variables can be traced from the frame.
|
||||
|
||||
Returns:
|
||||
bool: True if all tracked variables are traceable, False otherwise.
|
||||
"""
|
||||
if self.changed:
|
||||
return False
|
||||
for input in self.inputs:
|
||||
if not input.tracker.is_traceable():
|
||||
return False
|
||||
return True
|
||||
|
||||
def need_guard(self) -> bool:
|
||||
return self.is_traceable()
|
||||
|
||||
|
||||
class DummyTracker(Tracker):
|
||||
"""
|
||||
DummyTracker is a subclass of Tracker that specifically tracks variables cannot be reproduced from the frame.
|
||||
It is mostly generated by complex operations (instructions).
|
||||
|
||||
Args:
|
||||
inputs (list[VariableBase]): The input variables associated with the generated variables.
|
||||
"""
|
||||
|
||||
def __init__(self, inputs: Sequence[VariableBase]):
|
||||
super().__init__(inputs)
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
raise InnerError("DummyTracker has no instructions")
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
raise InnerError("DummyTracker can't trace value from frame")
|
||||
|
||||
def is_traceable(self):
|
||||
return False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"DummyTracker(num_inputs={len(self.inputs)})"
|
||||
|
||||
def need_guard(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class SymbolicOperationTracker(Tracker):
|
||||
"""
|
||||
SymbolicOperationTracker is a subclass of Tracker that specifically tracks variables cannot be reproduced from the frame.
|
||||
It is mostly generated by complex operations of symbolic variables.
|
||||
|
||||
Args:
|
||||
inputs (list[VariableBase]): The input variables associated with the generated variables.
|
||||
"""
|
||||
|
||||
def __init__(self, inputs: Sequence[VariableBase], op: UnaryOp | BinaryOp):
|
||||
super().__init__(inputs)
|
||||
self.op = op
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
raise InnerError("SymbolicOperationTracker has no instructions")
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
raise InnerError(
|
||||
"SymbolicOperationTracker can't trace value from frame"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"SymbolicOperationTracker(num_inputs={len(self.inputs)})"
|
||||
|
||||
def is_traceable(self):
|
||||
return False
|
||||
|
||||
|
||||
class DanglingTracker(Tracker):
|
||||
"""
|
||||
DanglingTracker is a subclass of Tracker that specifically tracks variables that are not in the frame.
|
||||
Variables whose tracker is DanglingTracker should not be placed on the stack, except for NullVariable.
|
||||
DanglingTracker is often used in conjunction with BuiltinVariable to reuse the dispatch mechanism.
|
||||
|
||||
Examples:
|
||||
>>> import operator
|
||||
>>> from sot.opcode_translator.executor.variables import (
|
||||
... BuiltinVariable,
|
||||
... ConstantVariable,
|
||||
... )
|
||||
>>> a = ConstantVariable.wrap_literal(1, None)
|
||||
>>> b = ConstantVariable.wrap_literal(2, None)
|
||||
>>> c = BuiltinVariable(operator.add, None, DanglingTracker())(a, b)
|
||||
>>> c.value
|
||||
3
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__([])
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
raise InnerError("DanglingTracker has no instructions")
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
raise InnerError("DanglingTracker can't trace value from frame")
|
||||
|
||||
def is_traceable(self):
|
||||
return False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "DanglingTracker()"
|
||||
|
||||
|
||||
class LocalTracker(Tracker):
|
||||
"""
|
||||
LocalTracker is a subclass of Tracker that specifically tracks variables from f_locals of frame.
|
||||
|
||||
Args:
|
||||
name (str): The name of the variable in f_locals to be tracked.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__([])
|
||||
self.name = name
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen) -> None:
|
||||
codegen.gen_load_fast(self.name)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
return paddle.framework.core.LocalVarExprNode(self.name)
|
||||
|
||||
def trace_value_from_frame(self) -> StringifiedExpression:
|
||||
return StringifiedExpression(f"frame.f_locals['{self.name}']", [], {})
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"LocalTracker(name={self.name})"
|
||||
|
||||
|
||||
class CellTracker(LocalTracker):
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
codegen.gen_load_deref(self.name)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
return paddle.framework.core.LocalVarExprNode(self.name)
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
return StringifiedExpression(f"frame.f_locals['{self.name}']", [], {})
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"CellTracker(name={self.name})"
|
||||
|
||||
|
||||
class GlobalTracker(Tracker):
|
||||
"""
|
||||
GlobalTracker is a subclass of Tracker that specifically tracks variables from f_globals of frame.
|
||||
|
||||
Args:
|
||||
name (str): The name of the variable in f_globals to be tracked.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__([])
|
||||
self.name = name
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen) -> None:
|
||||
codegen.gen_load_global(self.name, push_null=False)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
return paddle.framework.core.GlobalVarExprNode(self.name)
|
||||
|
||||
def trace_value_from_frame(self) -> StringifiedExpression:
|
||||
return StringifiedExpression(f"frame.f_globals['{self.name}']", [], {})
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"GlobalTracker(name={self.name})"
|
||||
|
||||
|
||||
class BuiltinTracker(Tracker):
|
||||
"""
|
||||
BuiltinTracker is a subclass of Tracker that specifically tracks variables from f_builtins of frame.
|
||||
|
||||
Args:
|
||||
name (str): The name of the variable in f_builtins to be tracked.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__([])
|
||||
self.name = name
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen) -> None:
|
||||
codegen.gen_load_global(self.name, push_null=False)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
return paddle.framework.core.ConstantExprNode(
|
||||
getattr(builtins, self.name)
|
||||
)
|
||||
|
||||
def trace_value_from_frame(self) -> StringifiedExpression:
|
||||
return StringifiedExpression(
|
||||
f"builtins.__dict__['{self.name}']", [], {"builtins": builtins}
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"BuiltinTracker(name={self.name})"
|
||||
|
||||
|
||||
class ConstTracker(Tracker):
|
||||
"""
|
||||
ConstTracker is a subclass of Tracker that specifically tracks a constant value.
|
||||
|
||||
Args:
|
||||
value (Any): The value of the constant.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
super().__init__([])
|
||||
self.value = value
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
codegen.gen_load_const(self.value)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
return paddle.framework.core.ConstantExprNode(self.value)
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
value_str, value_free_vars = stringify_pyobject(self.value)
|
||||
return StringifiedExpression(
|
||||
value_str, [], union_free_vars(value_free_vars)
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ConstTracker(value={self.value})"
|
||||
|
||||
def need_guard(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class GetAttrTracker(Tracker):
|
||||
"""
|
||||
GetAttrTracker is a subclass of Tracker that specifically tracks the attribute access of an variable.
|
||||
|
||||
Args:
|
||||
obj (VariableBase): The object whose attribute is to be tracked.
|
||||
attr (str): The attribute to be tracked.
|
||||
"""
|
||||
|
||||
def __init__(self, obj: VariableBase, attr: str, changed: bool = False):
|
||||
super().__init__([obj], changed)
|
||||
self.obj = obj
|
||||
self.attr = attr
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
self.obj.tracker.gen_instructions(codegen)
|
||||
codegen.gen_load_attr(self.attr)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
obj_tracer = self.obj.tracker.guard_tree_expr_node()
|
||||
return paddle.framework.core.AttributeExprNode(
|
||||
obj_tracer,
|
||||
self.attr,
|
||||
)
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
obj_tracer = self.obj.tracker.trace_value_from_frame()
|
||||
if self.attr.isidentifier():
|
||||
expr = f"{{}}.{self.attr}"
|
||||
else:
|
||||
expr = f"getattr({{}}, '{self.attr}')"
|
||||
return StringifiedExpression(
|
||||
expr,
|
||||
[obj_tracer],
|
||||
union_free_vars(obj_tracer.free_vars),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"GetAttrTracker(attr={self.attr})"
|
||||
|
||||
def need_guard(self) -> bool:
|
||||
return self.is_traceable() and self.obj.tracker.need_guard()
|
||||
|
||||
|
||||
class GetItemTracker(Tracker):
|
||||
"""
|
||||
GetItemTracker is a subclass of Tracker that specifically tracks item access of a container variable.
|
||||
|
||||
It generates instructions and traces the item value from the frame.
|
||||
|
||||
Args:
|
||||
container_var (VariableBase): The container object whose item is to be tracked.
|
||||
key: The key/index of the item to be tracked.
|
||||
"""
|
||||
|
||||
def __init__(self, container_var: VariableBase, key: object, changed=False):
|
||||
super().__init__([container_var], changed)
|
||||
self.container = container_var
|
||||
self.key = key
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
self.container.tracker.gen_instructions(codegen)
|
||||
if isinstance(self.key, slice):
|
||||
codegen.gen_load_const(self.key.start)
|
||||
codegen.gen_load_const(self.key.stop)
|
||||
codegen.gen_load_const(self.key.step)
|
||||
codegen.gen_build_slice(3)
|
||||
else:
|
||||
codegen.gen_load_const(self.key)
|
||||
codegen.gen_subscribe()
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
container_tracer = self.container.tracker.guard_tree_expr_node()
|
||||
return paddle.framework.core.ItemExprNode(
|
||||
container_tracer,
|
||||
paddle.framework.core.ConstantExprNode(self.key),
|
||||
)
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
container_tracer = self.container.tracker.trace_value_from_frame()
|
||||
key_string, key_free_vars = stringify_pyobject(self.key)
|
||||
return StringifiedExpression(
|
||||
f"{{}}[{key_string}]",
|
||||
[container_tracer],
|
||||
union_free_vars(container_tracer.free_vars, key_free_vars),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"GetItemTracker(key={self.key!r})"
|
||||
|
||||
def need_guard(self) -> bool:
|
||||
return self.is_traceable() and self.container.tracker.need_guard()
|
||||
|
||||
|
||||
class GetIterTracker(Tracker):
|
||||
"""
|
||||
GetIterTracker is a subclass of Tracker that specifically tracks iteration of a variable.
|
||||
|
||||
It generates instructions and traces the iterator from the frame.
|
||||
|
||||
Args:
|
||||
iter_source (VariableBase): The source variable to be iterated.
|
||||
"""
|
||||
|
||||
def __init__(self, iter_source: VariableBase):
|
||||
super().__init__([iter_source])
|
||||
self.iter_source = iter_source
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
self.iter_source.tracker.gen_instructions(codegen)
|
||||
codegen.add_instr("GET_ITER")
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
# TODO(zrr1999): implement IterExprNode
|
||||
raise NotImplementedError("IterExprNode is not implemented")
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
iter_source_tracer = self.iter_source.tracker.trace_value_from_frame()
|
||||
return StringifiedExpression(
|
||||
"iter({})",
|
||||
[iter_source_tracer],
|
||||
union_free_vars(iter_source_tracer.free_vars),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "GetIterTracker()"
|
||||
|
||||
|
||||
class CreateLayerTracker(Tracker):
|
||||
def __init__(self, layer_class, args, kwargs):
|
||||
super().__init__([layer_class, *list(args), *list(kwargs.values())])
|
||||
self.layer_class = layer_class
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
if sys.version_info >= (3, 11):
|
||||
codegen.gen_push_null()
|
||||
|
||||
self.layer_class.reconstruct(codegen)
|
||||
for variable in self.args:
|
||||
variable.reconstruct(codegen)
|
||||
|
||||
if len(self.kwargs) == 0:
|
||||
codegen.gen_call_function(argc=len(self.args))
|
||||
else:
|
||||
codegen.gen_build_tuple(len(self.args))
|
||||
for k, v in self.kwargs.items():
|
||||
codegen.gen_load_const(k)
|
||||
v.reconstruct(codegen)
|
||||
codegen.gen_build_map(len(self.kwargs))
|
||||
codegen.gen_call_function_ex(has_kwargs=True)
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
# TODO(zrr1999): implement LayerExprNode.guard_tree_expr_node
|
||||
raise NotImplementedError("LayerExprNode is not implemented")
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
class_tracer = self.layer_class.tracker.trace_value_from_frame()
|
||||
arg_tracers = [
|
||||
arg.tracker.trace_value_from_frame() for arg in self.args
|
||||
]
|
||||
kwarg_tracers_dict = {
|
||||
k: v.tracker.trace_value_from_frame()
|
||||
for k, v in self.kwargs.items()
|
||||
}
|
||||
kwarg_tracers = list(kwarg_tracers_dict.values())
|
||||
|
||||
expr = "{}("
|
||||
expr += ", ".join(["{}"] * len(arg_tracers))
|
||||
if len(arg_tracers) and len(kwarg_tracers) > 0:
|
||||
expr += ", "
|
||||
expr += ", ".join(f"{k}={{}}" for k in kwarg_tracers_dict.keys())
|
||||
expr += ")"
|
||||
|
||||
return StringifiedExpression(
|
||||
expr,
|
||||
[class_tracer, *arg_tracers, *kwarg_tracers],
|
||||
union_free_vars(
|
||||
*(
|
||||
tracer.free_vars
|
||||
for tracer in chain(
|
||||
[class_tracer], arg_tracers, kwarg_tracers
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"CreateLayerTracker(Layer={self.layer_class}, args={self.args}, kwargs={self.kwargs})"
|
||||
|
||||
|
||||
class FunctionClosureTracker(Tracker):
|
||||
"""
|
||||
A tracker class that represents a function closure variable.
|
||||
|
||||
Args:
|
||||
fn: The FunctionVariable object.
|
||||
idx: The index of the closure variable.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fn: FunctionVariable, idx: int):
|
||||
super().__init__([fn])
|
||||
self.fn = fn
|
||||
self.idx = idx
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
"""
|
||||
Generate bytecode instructions to trace the value of the function closure variable.
|
||||
|
||||
Args:
|
||||
codegen: The PyCodeGen object used to generate bytecode.
|
||||
|
||||
"""
|
||||
self.fn.tracker.gen_instructions(codegen)
|
||||
codegen.gen_load_attr("__closure__")
|
||||
codegen.gen_load_const(self.idx)
|
||||
codegen.gen_subscribe()
|
||||
codegen.gen_load_attr("cell_contents")
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
fn_tracer = self.fn.tracker.guard_tree_expr_node()
|
||||
return paddle.framework.core.AttributeExprNode(
|
||||
paddle.framework.core.ItemExprNode(
|
||||
paddle.framework.core.AttributeExprNode(
|
||||
fn_tracer,
|
||||
"__closure__",
|
||||
),
|
||||
paddle.framework.core.ConstantExprNode(self.idx),
|
||||
),
|
||||
"cell_contents",
|
||||
)
|
||||
|
||||
def trace_value_from_frame(self):
|
||||
"""
|
||||
Trace the value of the function closure variable from the frame.
|
||||
|
||||
Returns:
|
||||
The traced value of the function closure variable.
|
||||
|
||||
"""
|
||||
fn_tracer = self.fn.tracker.trace_value_from_frame()
|
||||
return StringifiedExpression(
|
||||
f"{{}}.__closure__[{self.idx}].cell_contents",
|
||||
[fn_tracer],
|
||||
union_free_vars(fn_tracer.free_vars),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"FunctionClosureTracker(fn={self.fn}, idx={self.idx})"
|
||||
|
||||
|
||||
class FunctionGlobalTracker(Tracker):
|
||||
"""
|
||||
A tracker class that represents a function global variable.
|
||||
|
||||
Args:
|
||||
fn: FunctionVariable object.
|
||||
name: The name of the global variable.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fn: FunctionVariable, name: str):
|
||||
super().__init__([fn])
|
||||
self.fn = fn
|
||||
self.name = name
|
||||
|
||||
def gen_instructions(self, codegen: PyCodeGen):
|
||||
"""
|
||||
Generate bytecode instructions in order to put the variables at the top of the stack.
|
||||
|
||||
Args:
|
||||
codegen: The PyCodeGen object used to generate bytecode.
|
||||
|
||||
"""
|
||||
self.fn.tracker.gen_instructions(codegen)
|
||||
codegen.gen_load_attr("__globals__")
|
||||
codegen.gen_load_const(self.name)
|
||||
codegen.gen_subscribe()
|
||||
|
||||
def guard_tree_expr_node(self) -> paddle.framework.core.ExprNodeBase:
|
||||
fn_tracer = self.fn.tracker.guard_tree_expr_node()
|
||||
return paddle.framework.core.ItemExprNode(
|
||||
paddle.framework.core.AttributeExprNode(
|
||||
fn_tracer,
|
||||
"__globals__",
|
||||
),
|
||||
paddle.framework.core.ConstantExprNode(self.name),
|
||||
)
|
||||
|
||||
def trace_value_from_frame(self) -> StringifiedExpression:
|
||||
"""
|
||||
Trace the value of the function global variable from the frame.
|
||||
|
||||
Returns:
|
||||
StringifiedExpression: The traced value of the function global variable.
|
||||
|
||||
"""
|
||||
fn_tracer = self.fn.tracker.trace_value_from_frame()
|
||||
return StringifiedExpression(
|
||||
f"{{}}.__globals__['{self.name}']",
|
||||
[fn_tracer],
|
||||
union_free_vars(fn_tracer.free_vars),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"FunctionGlobalTracker(fn={self.fn}, name={self.name})"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,217 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
ValidateValueFunc = Callable[[Any], None]
|
||||
|
||||
|
||||
StackDataT = TypeVar("StackDataT")
|
||||
|
||||
|
||||
class VariableStack(Generic[StackDataT]):
|
||||
"""
|
||||
A stack class for storing variables.
|
||||
|
||||
Examples:
|
||||
>>> var1, var2, var3, var4 = range(1, 5)
|
||||
>>> stack = VariableStack()
|
||||
>>> stack.push(var1)
|
||||
>>> stack.push(var3)
|
||||
>>> stack.insert(1, var2)
|
||||
>>> stack
|
||||
[1, 2, 3]
|
||||
>>> stack.pop()
|
||||
3
|
||||
>>> stack.pop_n(2)
|
||||
[1, 2]
|
||||
>>> stack.push(var1)
|
||||
>>> stack.push(var2)
|
||||
>>> stack.push(var3)
|
||||
>>> stack
|
||||
[1, 2, 3]
|
||||
>>> stack.top
|
||||
3
|
||||
>>> stack.peek[1]
|
||||
3
|
||||
>>> stack.peek[:1]
|
||||
[3]
|
||||
>>> stack.peek[:2]
|
||||
[2, 3]
|
||||
>>> stack.peek[1] = var4
|
||||
>>> stack
|
||||
[1, 2, 4]
|
||||
|
||||
"""
|
||||
|
||||
class VariablePeeker:
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> StackDataT: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> list[StackDataT]: ...
|
||||
|
||||
@overload
|
||||
def __call__(self, index: int = 1) -> StackDataT: ...
|
||||
|
||||
@overload
|
||||
def __call__(self, index: slice) -> list[StackDataT]: ...
|
||||
|
||||
def __init__(
|
||||
self, data: list[StackDataT], validate_value_func: ValidateValueFunc
|
||||
):
|
||||
self._data = data
|
||||
self.validate_value_func = validate_value_func
|
||||
|
||||
def __getitem__(
|
||||
self, index: int | slice
|
||||
) -> StackDataT | list[StackDataT]:
|
||||
if isinstance(index, int):
|
||||
assert 0 < index <= len(self._data)
|
||||
return self._data[-index]
|
||||
if isinstance(index, slice):
|
||||
assert index.start is None and index.step is None, (
|
||||
"slice which has start or step not supported"
|
||||
)
|
||||
assert 0 < index.stop <= len(self._data)
|
||||
return self._data[-index.stop :]
|
||||
raise NotImplementedError(f"index type {type(index)} not supported")
|
||||
|
||||
def __setitem__(self, index: int, value: Any):
|
||||
assert isinstance(index, int), (
|
||||
f"index type {type(index)} not supported"
|
||||
)
|
||||
assert 0 < index <= len(self._data), (
|
||||
f"index should be in [1, {len(self._data)}], but get {index}"
|
||||
)
|
||||
self.validate_value_func(value)
|
||||
self._data[-index] = value
|
||||
|
||||
def __call__(
|
||||
self, index: int | slice = 1
|
||||
) -> StackDataT | list[StackDataT]:
|
||||
return self[index]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: list[StackDataT] | None = None,
|
||||
*,
|
||||
validate_value_func: ValidateValueFunc | None = None,
|
||||
):
|
||||
if data is None:
|
||||
data = []
|
||||
else:
|
||||
data = data.copy()
|
||||
self.validate_value_func = (
|
||||
(lambda _: None)
|
||||
if validate_value_func is None
|
||||
else validate_value_func
|
||||
)
|
||||
self._data = data
|
||||
self._peeker = VariableStack.VariablePeeker(
|
||||
self._data, self.validate_value_func
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
return VariableStack(
|
||||
self._data, validate_value_func=self.validate_value_func
|
||||
)
|
||||
|
||||
def push(self, val: StackDataT):
|
||||
"""
|
||||
Pushes a variable onto the stack.
|
||||
|
||||
Args:
|
||||
val: The variable to be pushed.
|
||||
|
||||
"""
|
||||
self.validate_value_func(val)
|
||||
self._data.append(val)
|
||||
|
||||
def insert(self, index: int, val: StackDataT):
|
||||
"""
|
||||
Inserts a variable onto the stack.
|
||||
|
||||
Args:
|
||||
index: The index at which the variable is to be inserted, the top of the stack is at index 0.
|
||||
val: The variable to be inserted.
|
||||
|
||||
"""
|
||||
assert 0 <= index <= len(self), (
|
||||
f"index should be in [0, {len(self)}], but get {index}"
|
||||
)
|
||||
self.validate_value_func(val)
|
||||
self._data.insert(len(self) - index, val)
|
||||
|
||||
def pop(self) -> StackDataT:
|
||||
"""
|
||||
Pops the top value from the stack.
|
||||
|
||||
Returns:
|
||||
The popped value.
|
||||
|
||||
"""
|
||||
assert len(self) > 0, "stack is empty"
|
||||
return self._data.pop()
|
||||
|
||||
def pop_n(self, n: int) -> list[StackDataT]:
|
||||
"""
|
||||
Pops the top n values from the stack.
|
||||
|
||||
Args:
|
||||
n: The number of values to pop.
|
||||
|
||||
Returns:
|
||||
A list of the popped values.
|
||||
|
||||
"""
|
||||
assert len(self) >= n >= 0, (
|
||||
f"n should be in [0, {len(self)}], but get {n}"
|
||||
)
|
||||
if n == 0:
|
||||
return []
|
||||
retval = self._data[-n:]
|
||||
self._data[-n:] = []
|
||||
return retval
|
||||
|
||||
@property
|
||||
def peek(self) -> VariablePeeker:
|
||||
return self._peeker
|
||||
|
||||
@property
|
||||
def top(self) -> StackDataT:
|
||||
assert len(self) > 0, "stack is empty"
|
||||
return self.peek[1]
|
||||
|
||||
@top.setter
|
||||
def top(self, value):
|
||||
assert len(self) > 0, "stack is empty"
|
||||
self.peek[1] = value
|
||||
|
||||
def __contains__(self, value):
|
||||
return value in self._data
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._data)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._data)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self._data)
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from .base import ( # noqa: F401
|
||||
VariableBase,
|
||||
VariableFactory,
|
||||
find_traceable_vars,
|
||||
map_variables,
|
||||
)
|
||||
from .basic import ( # noqa: F401
|
||||
CellVariable,
|
||||
ConstantVariable,
|
||||
DataClassInstanceVariable,
|
||||
DataVariable,
|
||||
DygraphTracerVariable,
|
||||
EnumVariable,
|
||||
ExceptionVariable,
|
||||
FunctionGlobalVariable,
|
||||
GlobalVariable,
|
||||
InterpolationVariable,
|
||||
ModuleVariable,
|
||||
NullVariable,
|
||||
NumPyArrayVariable,
|
||||
NumPyNumberVariable,
|
||||
NumPyVariable,
|
||||
ObjectVariable,
|
||||
ParameterVariable,
|
||||
PlaceVariable,
|
||||
SliceVariable,
|
||||
SuperVariable,
|
||||
SymbolicVariable,
|
||||
TemplateVariable,
|
||||
TensorVariable,
|
||||
)
|
||||
from .callable import ( # noqa: F401
|
||||
BuiltinVariable,
|
||||
CallableVariable,
|
||||
ClassVariable,
|
||||
ContainerLayerVariable,
|
||||
DataClassVariable,
|
||||
FunctionVariable,
|
||||
LayerVariable,
|
||||
MethodVariable,
|
||||
NumPyApiVariable,
|
||||
PaddleApiVariable,
|
||||
PaddleLayerVariable,
|
||||
PartialVariable,
|
||||
UserCodeVariable,
|
||||
UserDefinedFunctionVariable,
|
||||
UserDefinedGeneratorFunctionVariable,
|
||||
UserDefinedLayerVariable,
|
||||
)
|
||||
from .container import ( # noqa: F401
|
||||
ContainerVariable,
|
||||
DictVariable,
|
||||
ListVariable,
|
||||
RangeVariable,
|
||||
SizeVariable,
|
||||
TupleVariable,
|
||||
)
|
||||
from .iter import ( # noqa: F401
|
||||
EnumerateVariable,
|
||||
GeneratorVariable,
|
||||
IterVariable,
|
||||
MapVariable,
|
||||
SequenceIterVariable,
|
||||
UserDefinedIterVariable,
|
||||
ZipVariable,
|
||||
)
|
||||
@@ -0,0 +1,731 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import operator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import fields
|
||||
from functools import cached_property
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import paddle
|
||||
from paddle.jit.dy2static.utils import (
|
||||
dataclass_from_dict,
|
||||
)
|
||||
|
||||
from ....profiler import event_register
|
||||
from ....utils import (
|
||||
NameGenerator,
|
||||
get_unbound_method,
|
||||
log,
|
||||
)
|
||||
from ....utils.exceptions import FallbackError, HasNoAttributeError
|
||||
from ..dispatcher import Dispatcher
|
||||
from ..guard import (
|
||||
FasterStringifiedExpression,
|
||||
StringifiedExpression,
|
||||
check_faster_guard,
|
||||
check_guard,
|
||||
union_free_vars,
|
||||
)
|
||||
from ..mutable_data import MutableDictLikeData
|
||||
from ..tracker import (
|
||||
BuiltinTracker,
|
||||
ConstTracker,
|
||||
DummyTracker,
|
||||
GetAttrTracker,
|
||||
GetItemTracker,
|
||||
GetIterTracker,
|
||||
GlobalTracker,
|
||||
LocalTracker,
|
||||
Tracker,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import TypeAlias
|
||||
|
||||
from ..function_graph import FunctionGraph
|
||||
from ..pycode_generator import PyCodeGen
|
||||
|
||||
# Each variable object should implement a method called `from_value`,
|
||||
# which should adhere to the FromValueFunc signature.
|
||||
FromValueFunc: TypeAlias = Callable[
|
||||
[Any, FunctionGraph, Tracker], "VariableBase | None"
|
||||
]
|
||||
|
||||
|
||||
@event_register("find_traceable_vars")
|
||||
def find_traceable_vars(
|
||||
root_vars: list[VariableBase],
|
||||
) -> list[VariableBase]:
|
||||
"""
|
||||
This function is used to find all traceable variables in the given list of variables.
|
||||
|
||||
Args:
|
||||
root_vars (list[VariableBase]): A list of root variables from which the ordering starts.
|
||||
|
||||
Returns:
|
||||
list[VariableBase]: A list of variables that are traceable.
|
||||
"""
|
||||
results: list[VariableBase] = []
|
||||
visited: set[VariableBase] = set()
|
||||
queue: Queue[VariableBase] = Queue()
|
||||
|
||||
for root in root_vars:
|
||||
queue.put(root)
|
||||
|
||||
while not queue.empty():
|
||||
var = queue.get()
|
||||
if var in visited:
|
||||
continue
|
||||
|
||||
visited.add(var)
|
||||
if var.tracker.need_guard():
|
||||
results.append(var)
|
||||
continue
|
||||
|
||||
# Pruning traceable variable, if the variable is traceable, we don't need to
|
||||
# trace its inputs.
|
||||
inputs = var.get_inputs()
|
||||
|
||||
for var in inputs:
|
||||
if var not in visited and var not in queue.queue:
|
||||
queue.put(var)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def map_variables(
|
||||
map_func,
|
||||
variables: list[VariableBase],
|
||||
*,
|
||||
restore_variable=False,
|
||||
) -> list[VariableBase]:
|
||||
"""
|
||||
This function maps the given map_func to the given list of variables in a recursive manner.
|
||||
Args:
|
||||
map_func (Callable[[VariableBase], Any]): The function to be mapped to each variable.
|
||||
variables (list[VariableBase]): A list of variables to which the map_func is to be applied.
|
||||
|
||||
Returns:
|
||||
tuple: The result of applying the map_func to the variables.
|
||||
"""
|
||||
from .basic import DataClassInstanceVariable, SliceVariable
|
||||
from .container import ContainerVariable
|
||||
|
||||
def _map_container_variable(variable: VariableBase | object):
|
||||
if not isinstance(variable, ContainerVariable):
|
||||
return variable
|
||||
new_container = paddle.utils.map_structure(
|
||||
_map_variable, variable.get_wrapped_items()
|
||||
)
|
||||
if not restore_variable:
|
||||
return new_container
|
||||
return VariableFactory.from_value(
|
||||
new_container,
|
||||
variable.graph,
|
||||
DummyTracker(paddle.utils.flatten(new_container)),
|
||||
)
|
||||
|
||||
def _map_slice_variable(variable: VariableBase | object):
|
||||
if not isinstance(variable, SliceVariable):
|
||||
return variable
|
||||
new_slice = slice(
|
||||
map_func(variable.getattr("start")),
|
||||
map_func(variable.getattr("stop")),
|
||||
map_func(variable.getattr("step")),
|
||||
)
|
||||
if not restore_variable:
|
||||
return new_slice
|
||||
return VariableFactory.from_value(
|
||||
new_slice,
|
||||
variable.graph,
|
||||
DummyTracker([new_slice.start, new_slice.stop, new_slice.step]),
|
||||
)
|
||||
|
||||
def _map_dataclass_variable(variable: VariableBase | object):
|
||||
if not isinstance(variable, DataClassInstanceVariable):
|
||||
return variable
|
||||
new_dataclass = dataclass_from_dict(
|
||||
variable.get_py_type(),
|
||||
{
|
||||
fd.name: _map_variable(variable.getattr(fd.name))
|
||||
for fd in fields(variable.get_py_type())
|
||||
},
|
||||
)
|
||||
if not restore_variable:
|
||||
return new_dataclass
|
||||
return VariableFactory.from_value(
|
||||
new_dataclass,
|
||||
variable.graph,
|
||||
DummyTracker(
|
||||
[
|
||||
variable.getattr(fd.name)
|
||||
for fd in fields(variable.get_py_type())
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def _map_variable(variable: VariableBase | object):
|
||||
variable = _map_container_variable(variable)
|
||||
variable = _map_slice_variable(variable)
|
||||
variable = _map_dataclass_variable(variable)
|
||||
return map_func(variable)
|
||||
|
||||
return paddle.utils.map_structure(_map_variable, variables)
|
||||
|
||||
|
||||
class VariableFactory:
|
||||
"""
|
||||
A factory class for creating variables from arbitrary values.
|
||||
|
||||
This class provides a set of registration and factory methods for creating variables
|
||||
of different types based on the type of the input value.
|
||||
|
||||
"""
|
||||
|
||||
registered_funcs: dict[str, list[str]] = {"default": []}
|
||||
mapping_str_func: dict[str, FromValueFunc] = {}
|
||||
|
||||
@staticmethod
|
||||
def default_from_value(value, graph, tracker):
|
||||
"""
|
||||
A default factory function that creates an ObjectVariable from the given value.
|
||||
|
||||
Args:
|
||||
value: The input value.
|
||||
graph: The FunctionGraph object that this variable is associated with.
|
||||
tracker: The Tracker object that tracks the information of this variable.
|
||||
|
||||
Returns:
|
||||
ObjectVariable: A new ObjectVariable representing the input value.
|
||||
"""
|
||||
from .basic import ObjectVariable
|
||||
|
||||
return ObjectVariable(value, graph, tracker)
|
||||
|
||||
@staticmethod
|
||||
def register_from_value(*, successor: str | None = None):
|
||||
"""
|
||||
A decorator function that registers a function for creating a Variable from a value.
|
||||
|
||||
Args:
|
||||
successor (str | None, optional): The name of the successor function that will be called after this function when creating a Variable. If None, the function is added to a default list of functions.
|
||||
|
||||
Returns:
|
||||
The _register_from_value decorator function, which takes the function to be registered as an argument.
|
||||
"""
|
||||
registered_funcs = VariableFactory.registered_funcs
|
||||
mapping_str_func = VariableFactory.mapping_str_func
|
||||
|
||||
def _register_from_value(func: FromValueFunc):
|
||||
"""
|
||||
Function to register a function for creating a Variable from a value
|
||||
"""
|
||||
# Get the name of the function
|
||||
name = func.__qualname__.split(".")[0]
|
||||
# Map the name of the function to the function
|
||||
mapping_str_func[name] = func
|
||||
if successor is None:
|
||||
registered_funcs["default"].append(
|
||||
name
|
||||
) # If successor is None, add the function to the "default" list
|
||||
elif successor not in registered_funcs.keys():
|
||||
registered_funcs[successor] = [
|
||||
name
|
||||
] # If the successor is not in the registered_funcs dictionary, set the value to a list containing only name
|
||||
else:
|
||||
registered_funcs[successor].append(
|
||||
name
|
||||
) # If the successor is in the registered_funcs dictionary, append name to the existing list of functions for that successor
|
||||
|
||||
log(
|
||||
4, VariableFactory.registered_funcs
|
||||
) # Print the registered_funcs dictionary if the logging level is at least 4
|
||||
return _register_from_value
|
||||
|
||||
@staticmethod
|
||||
def from_value(
|
||||
value: Any,
|
||||
graph: FunctionGraph,
|
||||
tracker: Tracker,
|
||||
) -> VariableBase:
|
||||
"""
|
||||
Create a new variable object from the given value.
|
||||
|
||||
This method searches through the registered from_value functions to find one
|
||||
that can create a variable object from the given value. If no matching function
|
||||
is found, the default_from_value function is used.
|
||||
|
||||
Args:
|
||||
value (Any): The input value.
|
||||
graph (FunctionGraph): The FunctionGraph object that this variable is associated with.
|
||||
tracker (Tracker): The Tracker object that tracks the information of this variable.
|
||||
|
||||
Returns:
|
||||
VariableBase: A new variable object representing the input value.
|
||||
"""
|
||||
registered_funcs = VariableFactory.registered_funcs
|
||||
|
||||
def _find_var(key: str = "default") -> VariableBase | None:
|
||||
for name in registered_funcs[key]:
|
||||
if name in registered_funcs.keys():
|
||||
# If the function name is a key in the registered_funcs dictionary, recursively find a Variable using that function
|
||||
var = _find_var(name)
|
||||
if var is not None:
|
||||
return var
|
||||
# Get the function corresponding to the name from the mapping_str_func dictionary
|
||||
func = VariableFactory.mapping_str_func[name]
|
||||
var = func(
|
||||
value, graph, tracker
|
||||
) # Call the function to create a Variable from the value
|
||||
if var is not None:
|
||||
return var
|
||||
|
||||
var = _find_var()
|
||||
if var is None:
|
||||
var = VariableFactory.default_from_value(
|
||||
value, graph, tracker
|
||||
) # If a Variable could not be found using the registered functions, use the default function to create a new Variable
|
||||
return var
|
||||
|
||||
|
||||
def infer_debug_name_from_tracker(tracker: Tracker) -> str | None:
|
||||
res = None
|
||||
if isinstance(tracker, (LocalTracker, GlobalTracker, BuiltinTracker)):
|
||||
res = f"{tracker.name}"
|
||||
elif isinstance(tracker, ConstTracker):
|
||||
res = f"{tracker.value}"
|
||||
elif isinstance(tracker, GetItemTracker) and tracker.container.debug_name:
|
||||
res = f"{tracker.container.debug_name}[{tracker.key}]"
|
||||
elif isinstance(tracker, GetAttrTracker) and tracker.obj.debug_name:
|
||||
res = f"{tracker.obj.debug_name}.{tracker.attr}"
|
||||
return res
|
||||
|
||||
|
||||
class VariableBase:
|
||||
"""
|
||||
VariableBase is a basic concept and each symbols in VM stack is regarded as
|
||||
an Variable Object in symbolic tracing process.
|
||||
|
||||
There are two key data structures during Python runtime:
|
||||
PyFrameObject, which provides the instance for function logical lock usage,
|
||||
and PyCodeObject, which provides the bytecode for the corresponding function.
|
||||
With these data, the Python virtual machine executes the bytecode sequentially on a stack to complete function logic.
|
||||
|
||||
Args:
|
||||
tracker(Tracker): The Tracker object that tracks the information of this variable.
|
||||
|
||||
Note:
|
||||
We should push an object of a subclass of VariableBase instead of an object of VariableBase onto the VM stack.
|
||||
It serves as an abstract class and should not be instantiated directly.
|
||||
"""
|
||||
|
||||
tracker: Tracker # An attribute to store the Tracker object associated with the variable
|
||||
value: Any
|
||||
name_generator = NameGenerator(
|
||||
"object_"
|
||||
) # A class-level attribute to generate names for new variables
|
||||
mutable_attrs = []
|
||||
|
||||
def __init__(self, graph: FunctionGraph, tracker: Tracker):
|
||||
self.graph = graph
|
||||
self.tracker = tracker
|
||||
self.id = VariableBase.name_generator.next()
|
||||
self.debug_name = infer_debug_name_from_tracker(tracker)
|
||||
|
||||
@property
|
||||
def main_info(self) -> dict[str, Any]:
|
||||
"""
|
||||
Property method to return a dictionary of main information about the variable
|
||||
|
||||
Returns:
|
||||
main_info: Main information of the variable.
|
||||
"""
|
||||
return {}
|
||||
|
||||
@property
|
||||
def debug_info(self) -> dict[str, Any]:
|
||||
"""
|
||||
Property method to return a dictionary of debug information about the variable
|
||||
"""
|
||||
info = {
|
||||
"id": self.id,
|
||||
}
|
||||
if self.debug_name:
|
||||
info["debug_name"] = self.debug_name
|
||||
return info
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
||||
@check_faster_guard
|
||||
def make_faster_guard(self) -> list[paddle.framework.core.GuardNodeBase]:
|
||||
expr_node = self.tracker.guard_tree_expr_node()
|
||||
return [
|
||||
paddle.framework.core.GuardNode(
|
||||
paddle.framework.core.ValueMatchGuard(self.get_py_value()),
|
||||
[expr_node],
|
||||
)
|
||||
]
|
||||
|
||||
@check_guard
|
||||
def make_stringified_guard(self) -> list[StringifiedExpression]:
|
||||
"""
|
||||
Create a StringifiedExpression object that represents a guard expression for this variable.
|
||||
|
||||
Returns:
|
||||
StringifiedExpression: An object that contains the guard expression and the free variables used in the expression.
|
||||
"""
|
||||
|
||||
# Get a ValueTracer object from the Tracker object associated with the variable
|
||||
frame_value_tracer = self.tracker.trace_value_from_frame()
|
||||
return [
|
||||
FasterStringifiedExpression(
|
||||
f"id(type({{0}})) == {id(self.get_py_type())} and {{0}} == {self.get_py_value()!r}",
|
||||
paddle.framework.core.ValueMatchGuard(self.get_py_value()),
|
||||
[frame_value_tracer],
|
||||
union_free_vars(frame_value_tracer.free_vars),
|
||||
)
|
||||
]
|
||||
|
||||
def get_py_value(self, allow_tensor=False) -> Any:
|
||||
"""
|
||||
Abstract method to get the value of the variable
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_py_type(self):
|
||||
"""
|
||||
Method to get the type of the variable's value
|
||||
"""
|
||||
return type(self.get_py_value())
|
||||
|
||||
def is_none(self) -> bool:
|
||||
"""
|
||||
Method to check if the variable's value is None
|
||||
"""
|
||||
return self.get_py_value() is None
|
||||
|
||||
def reconstruct(
|
||||
self,
|
||||
codegen: PyCodeGen,
|
||||
*,
|
||||
use_tracker: bool = True,
|
||||
add_to_global_guarded_vars: bool = True,
|
||||
):
|
||||
if self.tracker.is_traceable() and use_tracker:
|
||||
self.tracker.gen_instructions(codegen)
|
||||
else:
|
||||
if add_to_global_guarded_vars:
|
||||
self.graph.add_global_guarded_variable(self)
|
||||
self._reconstruct(codegen)
|
||||
|
||||
def _reconstruct(self, codegen: PyCodeGen) -> None:
|
||||
"""
|
||||
Abstract method to construct an opcode and append it into codegen.instructions
|
||||
"""
|
||||
raise FallbackError(
|
||||
f'{self.__class__.__name__} does not implement "_reconstruct" method'
|
||||
)
|
||||
|
||||
def flatten_inner_vars(self) -> list[VariableBase]:
|
||||
"""
|
||||
Recursively flatten the items in this container variable to a list of Variable objects.
|
||||
|
||||
Returns:
|
||||
list[VariableBase]: Flattened items of a container variable.
|
||||
"""
|
||||
return [self]
|
||||
|
||||
def get_inputs(self) -> list[VariableBase]:
|
||||
"""
|
||||
This method is used to get the inputs for the current variable.
|
||||
|
||||
Returns:
|
||||
list[VariableBase]: Inputs for the current variable.
|
||||
"""
|
||||
return self.tracker.inputs
|
||||
|
||||
def get_traceable_inputs(self) -> list[VariableBase]:
|
||||
"""
|
||||
This method is used to get the traceable inputs for the current variable.
|
||||
|
||||
Returns:
|
||||
list[VariableBase]: Traceable inputs for the current variable.
|
||||
"""
|
||||
return list(
|
||||
filter(lambda x: x.tracker.is_traceable(), self.tracker.inputs)
|
||||
)
|
||||
|
||||
def call_function(self, /, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@cached_property
|
||||
def attr_proxy(self):
|
||||
return self.graph.side_effects.get_proxy(
|
||||
MutableDictLikeData, self.get_py_value(), self.attr_proxy_getter
|
||||
)
|
||||
|
||||
def attr_proxy_getter(self, proxy: MutableDictLikeData, name: str):
|
||||
if not hasattr(proxy.original_data, name): # can't true.
|
||||
return MutableDictLikeData.Empty()
|
||||
|
||||
attr = getattr(proxy.original_data, name)
|
||||
if inspect.ismethod(attr) or (
|
||||
hasattr(attr, "__self__")
|
||||
and inspect.ismethoddescriptor(
|
||||
getattr(attr.__self__.__class__, name, None)
|
||||
)
|
||||
):
|
||||
from .callable import MethodVariable
|
||||
|
||||
fn = None
|
||||
instance = self
|
||||
if inspect.ismethoddescriptor(
|
||||
getattr(attr.__self__.__class__, name, None)
|
||||
):
|
||||
class_var = VariableFactory.from_value(
|
||||
self.get_py_type(),
|
||||
self.graph,
|
||||
GetAttrTracker(self, "__class__"),
|
||||
)
|
||||
fn = VariableFactory.from_value(
|
||||
getattr(attr.__self__.__class__, name),
|
||||
self.graph,
|
||||
GetAttrTracker(class_var, name),
|
||||
)
|
||||
if not hasattr(self.get_py_type(), name):
|
||||
instance = None
|
||||
return MethodVariable.wrap_method(
|
||||
value=attr,
|
||||
instance=instance,
|
||||
fn=fn,
|
||||
graph=self.graph,
|
||||
tracker=GetAttrTracker(self, name),
|
||||
)
|
||||
|
||||
return VariableFactory.from_value(
|
||||
attr, self.graph, tracker=GetAttrTracker(self, name)
|
||||
)
|
||||
|
||||
def hasattr(self, name: str):
|
||||
from .basic import ConstantVariable
|
||||
|
||||
try:
|
||||
self.getattr(name)
|
||||
return ConstantVariable(
|
||||
True, graph=self.graph, tracker=DummyTracker([self])
|
||||
)
|
||||
except HasNoAttributeError:
|
||||
# NOTE(SigureMo): Only the HasNoAttributeError is raised, we can
|
||||
# ensure that the attribute does not exist. Otherwise, we should
|
||||
# raise the error.
|
||||
return ConstantVariable(
|
||||
False, graph=self.graph, tracker=DummyTracker([self])
|
||||
)
|
||||
|
||||
def getattr(self, name: str, default=None):
|
||||
result = self.attr_proxy.get(name)
|
||||
if isinstance(result, MutableDictLikeData.Empty):
|
||||
if default is not None:
|
||||
assert isinstance(default, VariableBase)
|
||||
return default
|
||||
raise HasNoAttributeError(
|
||||
f"{self.__class__.__name__} {self} has no attribute {name}"
|
||||
)
|
||||
return result
|
||||
|
||||
def setattr(self, key: str, value):
|
||||
from .basic import ConstantVariable
|
||||
|
||||
self.attr_proxy.set(key, value)
|
||||
self.graph.side_effects.record_proxy_variable(self)
|
||||
return ConstantVariable.wrap_literal(None, self.graph)
|
||||
|
||||
def delattr(self, key: str):
|
||||
from .basic import ConstantVariable
|
||||
|
||||
self.attr_proxy.delete(key)
|
||||
self.graph.side_effects.record_proxy_variable(self)
|
||||
return ConstantVariable.wrap_literal(None, self.graph)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return self.setitem(key, value)
|
||||
|
||||
def setitem(self, key, value):
|
||||
raise FallbackError(f"{self} is not support setitem.")
|
||||
|
||||
def __repr__(self):
|
||||
info = self.main_info | self.debug_info
|
||||
info_str = ", ".join([f"{value}" for value in info.values()])
|
||||
return f"{self.__class__.__name__}({info_str})"
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return Dispatcher.call(operator.getitem, self, idx)
|
||||
|
||||
def getitem(self, item):
|
||||
class_var = VariableFactory.from_value(
|
||||
self.get_py_value().__class__,
|
||||
self.graph,
|
||||
GetAttrTracker(self, '__class__'),
|
||||
)
|
||||
fn_var = VariableFactory.from_value(
|
||||
get_unbound_method(self.get_py_value(), '__getitem__'),
|
||||
self.graph,
|
||||
GetAttrTracker(class_var, '__getitem__'),
|
||||
)
|
||||
self.graph.add_global_guarded_variable(item)
|
||||
item = item.get_py_value()
|
||||
output = fn_var(self, item)
|
||||
return output
|
||||
|
||||
def __call__(self, /, *args, **kwargs):
|
||||
"""
|
||||
Call the object represented by this variable with the given arguments.
|
||||
|
||||
Args:
|
||||
*args: Positional arguments to pass to the object's __call__ method.
|
||||
**kwargs: Keyword arguments to pass to the object's __call__ method.
|
||||
|
||||
Returns:
|
||||
VariableBase: A new variable representing the result of calling the object's __call__ method.
|
||||
"""
|
||||
from .callable import BuiltinVariable, UserDefinedFunctionVariable
|
||||
|
||||
class_var = VariableFactory.from_value(
|
||||
self.get_py_value().__class__,
|
||||
self.graph,
|
||||
GetAttrTracker(self, '__class__'),
|
||||
)
|
||||
assert class_var is not None
|
||||
# if __call__ is a method, we should add self to arguments.
|
||||
if inspect.ismethod(self.get_py_value().__call__):
|
||||
args = (self, *args)
|
||||
unbound_method = get_unbound_method(self.get_py_value(), '__call__')
|
||||
if hasattr(unbound_method, "__code__"):
|
||||
fn_var = UserDefinedFunctionVariable(
|
||||
unbound_method,
|
||||
self.graph,
|
||||
GetAttrTracker(class_var, '__call__'),
|
||||
)
|
||||
else:
|
||||
fn_var = BuiltinVariable(
|
||||
self.value,
|
||||
self.graph,
|
||||
GetAttrTracker(class_var, '__call__'),
|
||||
)
|
||||
output = fn_var(*args, **kwargs)
|
||||
return output
|
||||
|
||||
def get_iter(self):
|
||||
from . import (
|
||||
BuiltinVariable,
|
||||
ConstantVariable,
|
||||
SequenceIterVariable,
|
||||
UserDefinedFunctionVariable,
|
||||
UserDefinedIterVariable,
|
||||
)
|
||||
|
||||
if not hasattr(self.value, "__iter__"):
|
||||
return UserDefinedIterVariable(
|
||||
self, self.graph, GetIterTracker(self)
|
||||
)
|
||||
iter_name_var = ConstantVariable.wrap_literal("__iter__", self.graph)
|
||||
iter_method = BuiltinVariable(
|
||||
getattr, graph=self.graph, tracker=DummyTracker([self])
|
||||
)(self, iter_name_var)
|
||||
# If the target object is a builtin object like list_iterator, the iter_method's fn will be a ObjectVariable instead of UserDefinedFunctionVariable.
|
||||
if not isinstance(iter_method.fn, UserDefinedFunctionVariable):
|
||||
return UserDefinedIterVariable(
|
||||
self, self.graph, GetIterTracker(self)
|
||||
)
|
||||
iter_result = iter_method()
|
||||
|
||||
if not isinstance(iter_result, SequenceIterVariable):
|
||||
return UserDefinedIterVariable(
|
||||
self, self.graph, GetIterTracker(self)
|
||||
)
|
||||
|
||||
return iter_result
|
||||
|
||||
@VariableFactory.register_from_value()
|
||||
def from_value(
|
||||
value: Any,
|
||||
graph: FunctionGraph | None,
|
||||
tracker: Tracker,
|
||||
) -> VariableBase | None:
|
||||
"""
|
||||
Create a new variable from a given value, or return None if the value cannot be converted to a variable.
|
||||
Args:
|
||||
value (Any): The value to create a variable from.
|
||||
graph (FunctionGraph | None): The graph in which the variable will be used.
|
||||
tracker (Tracker): The variable tracker to put the new variable in if created.
|
||||
|
||||
Returns:
|
||||
VariableBase | None: A new variable if one can be created from the given value, or None if the value cannot be converted to a variable.
|
||||
"""
|
||||
if isinstance(value, VariableBase):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def signature_clear_guard(fn, name):
|
||||
if not hasattr(fn, name):
|
||||
yield
|
||||
else:
|
||||
saved_attr = getattr(fn, name)
|
||||
delattr(fn, name)
|
||||
yield
|
||||
setattr(fn, name, saved_attr)
|
||||
|
||||
|
||||
def fn_bind_inputs(
|
||||
fn: Callable[..., Any],
|
||||
graph: FunctionGraph,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
):
|
||||
# temparay clear the fn.__signature__ to avoid signature check error
|
||||
with (
|
||||
signature_clear_guard(fn, "__signature__"),
|
||||
signature_clear_guard(fn, "__wrapped__"),
|
||||
):
|
||||
sig = inspect.signature(fn)
|
||||
bound_args = sig.bind(*args, **kwargs)
|
||||
bound_args.apply_defaults()
|
||||
parameters = {}
|
||||
for name, value in bound_args.arguments.items():
|
||||
assert name in sig.parameters
|
||||
# Convert varargs and kwargs to Variable
|
||||
if sig.parameters[name].kind == inspect.Parameter.VAR_POSITIONAL:
|
||||
tracker = DummyTracker(value)
|
||||
elif sig.parameters[name].kind == inspect.Parameter.VAR_KEYWORD:
|
||||
tracker = DummyTracker(list(value.values()))
|
||||
# Convert default args to Variable
|
||||
elif not isinstance(value, VariableBase):
|
||||
tracker = ConstTracker(value)
|
||||
else:
|
||||
tracker = value.tracker
|
||||
value = VariableFactory.from_value(value, graph, tracker)
|
||||
parameters[name] = value
|
||||
return parameters
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,460 @@
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import types
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from paddle._typing import unreached
|
||||
|
||||
from ....profiler import EventGuard
|
||||
from ....utils import do_until_stop_iteration
|
||||
from ....utils.exceptions import (
|
||||
BreakGraphError,
|
||||
BreakGraphInlineCallBreak,
|
||||
FallbackError,
|
||||
FallbackInlineCallBreak,
|
||||
OtherInlineCallBreak,
|
||||
SotCapturedExceptionFactory,
|
||||
SotCapturedStopIteration,
|
||||
SotErrorBase,
|
||||
UnsupportedOperationBreak,
|
||||
)
|
||||
from ..guard import check_faster_guard
|
||||
from ..tracker import ConstTracker, DanglingTracker, DummyTracker
|
||||
from .base import (
|
||||
VariableBase,
|
||||
VariableFactory,
|
||||
)
|
||||
from .basic import ConstantVariable
|
||||
from .callable import BuiltinVariable
|
||||
from .container import TupleVariable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
import paddle
|
||||
|
||||
from ..function_graph import FunctionGraph
|
||||
from ..pycode_generator import PyCodeGen
|
||||
from ..tracker import Tracker
|
||||
from ..virtual_frame import VirtualFrame
|
||||
|
||||
|
||||
class IterVariable(VariableBase):
|
||||
"""
|
||||
This Variable (include subclasses) should be generated only when simulate GET_ITER opcode
|
||||
"""
|
||||
|
||||
def __init__(self, graph: FunctionGraph, tracker: Tracker):
|
||||
super().__init__(graph, tracker)
|
||||
|
||||
def next(self):
|
||||
raise NotImplementedError(f"Can not simulate `next` for {type(self)}")
|
||||
|
||||
def to_list(self):
|
||||
raise NotImplementedError(
|
||||
f"Can not simulate `to_list` for {type(self)}"
|
||||
)
|
||||
|
||||
def send(self, value: VariableBase):
|
||||
return self.next()
|
||||
|
||||
def get_iter(self):
|
||||
return self
|
||||
|
||||
|
||||
class SequenceIterVariable(IterVariable):
|
||||
"""
|
||||
The basic SequenceIterVariable wraps iterators which can be simulated by call getitem
|
||||
Currently includes: List | Tuple | Dict (keys) | Range | Tensor | nn.LayerList
|
||||
|
||||
these interfaces is needed:
|
||||
- next
|
||||
- to_list
|
||||
- has_side_effect
|
||||
- _reconstruct
|
||||
"""
|
||||
|
||||
mutable_attrs = ["idx"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
held: VariableBase | list[VariableBase],
|
||||
graph: FunctionGraph,
|
||||
tracker: Tracker,
|
||||
):
|
||||
if not isinstance(held, list):
|
||||
held = [held]
|
||||
super().__init__(graph, tracker)
|
||||
self.holds = held
|
||||
self.idx = 0
|
||||
self.graph.side_effects.record_mutable_variable(self)
|
||||
|
||||
@check_faster_guard
|
||||
def make_faster_guard(self) -> list[paddle.framework.core.GuardNodeBase]:
|
||||
return [
|
||||
guard for held in self.holds for guard in held.make_faster_guard()
|
||||
]
|
||||
|
||||
def make_stringified_guard(self):
|
||||
return [
|
||||
guard
|
||||
for held in self.holds
|
||||
for guard in held.make_stringified_guard()
|
||||
]
|
||||
|
||||
def next(self):
|
||||
held = self.holds[0]
|
||||
if self.idx < len(held):
|
||||
val = held[self.idx]
|
||||
self.idx += 1
|
||||
return val
|
||||
else:
|
||||
raise SotCapturedExceptionFactory.create(StopIteration())
|
||||
|
||||
def to_list(self) -> list:
|
||||
if self.has_side_effect():
|
||||
raise FallbackError("Can not convert an used iterator into list")
|
||||
held = self.holds[0]
|
||||
self.idx = len(held)
|
||||
retval = []
|
||||
for i in range(len(held)):
|
||||
retval.append(held[i])
|
||||
return retval
|
||||
|
||||
def has_side_effect(self) -> bool:
|
||||
return self.idx != 0
|
||||
|
||||
def _reconstruct(self, codegen: PyCodeGen):
|
||||
if self.has_side_effect():
|
||||
super()._reconstruct(codegen)
|
||||
else:
|
||||
self.holds[0].reconstruct(codegen)
|
||||
codegen.gen_get_iter()
|
||||
|
||||
@property
|
||||
def main_info(self) -> dict[str, Any]:
|
||||
return {
|
||||
"idx": self.idx,
|
||||
}
|
||||
|
||||
def flatten_inner_vars(self) -> list[VariableBase]:
|
||||
held = self.holds
|
||||
return [
|
||||
inner_var for obj in held for inner_var in obj.flatten_inner_vars()
|
||||
]
|
||||
|
||||
|
||||
class EnumerateVariable(SequenceIterVariable):
|
||||
"""
|
||||
EnumerateVariable holds a SequenceIterVariable and return additional index
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, val_iterator: IterVariable, graph: FunctionGraph, tracker: Tracker
|
||||
):
|
||||
super().__init__(val_iterator, graph, tracker)
|
||||
|
||||
def next(self):
|
||||
val = self.holds[0].next()
|
||||
idx_var = ConstantVariable(self.idx, self.graph, ConstTracker(self.idx))
|
||||
self.idx += 1
|
||||
return TupleVariable(
|
||||
(idx_var, val), self.graph, DummyTracker([idx_var, val])
|
||||
)
|
||||
|
||||
def to_list(self):
|
||||
values = self.holds[0].to_list()
|
||||
idx = [
|
||||
ConstantVariable(i, self.graph, ConstTracker(i))
|
||||
for i in range(len(values))
|
||||
]
|
||||
return list(zip(idx, values))
|
||||
|
||||
def has_side_effect(self) -> bool:
|
||||
return self.holds[0].has_side_effect()
|
||||
|
||||
def _reconstruct(self, codegen: PyCodeGen):
|
||||
if self.has_side_effect():
|
||||
super()._reconstruct(codegen)
|
||||
else:
|
||||
codegen.gen_load_global("enumerate", push_null=True)
|
||||
self.holds[0].reconstruct(codegen)
|
||||
codegen.gen_call_function(1)
|
||||
|
||||
@staticmethod
|
||||
def from_iterator(value, graph: FunctionGraph | None, tracker: Tracker):
|
||||
iter_variable = value.get_iter()
|
||||
if isinstance(iter_variable, UserDefinedIterVariable):
|
||||
return UserDefinedIterVariable(value, graph, tracker)
|
||||
else:
|
||||
return EnumerateVariable(iter_variable, graph, tracker)
|
||||
|
||||
|
||||
class ZipVariable(SequenceIterVariable):
|
||||
"""
|
||||
ZipVariable holds a list of SequenceIterVariable
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, iters: list[IterVariable], graph: FunctionGraph, tracker: Tracker
|
||||
):
|
||||
super().__init__(iters, graph, tracker)
|
||||
|
||||
def next(self):
|
||||
# can not use <listcomp> here, because it will raise a RuntimeError("StopIteration")
|
||||
# but we want a StopIteration Exception
|
||||
values = []
|
||||
for iter_var in self.holds:
|
||||
next_var = iter_var.next()
|
||||
values.append(next_var)
|
||||
|
||||
return VariableFactory.from_value(
|
||||
tuple(values), self.graph, DummyTracker(values)
|
||||
)
|
||||
|
||||
def to_list(self):
|
||||
lists = [iter_vars.to_list() for iter_vars in self.holds]
|
||||
min_len = min(len(l) for l in lists)
|
||||
result = []
|
||||
for i in range(min_len):
|
||||
result.append(
|
||||
VariableFactory.from_value(
|
||||
tuple(l[i] for l in lists),
|
||||
self.graph,
|
||||
DummyTracker(list(self.holds)),
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def has_side_effect(self) -> bool:
|
||||
return any(iter_var.has_side_effect() for iter_var in self.holds)
|
||||
|
||||
def _reconstruct(self, codegen: PyCodeGen):
|
||||
if self.has_side_effect():
|
||||
super()._reconstruct(codegen)
|
||||
else:
|
||||
codegen.gen_load_global("zip", push_null=True)
|
||||
for iter_var in self.holds:
|
||||
iter_var.reconstruct(codegen)
|
||||
codegen.gen_call_function(len(self.holds))
|
||||
|
||||
@staticmethod
|
||||
def from_iterator(
|
||||
value: Sequence[VariableBase],
|
||||
graph: FunctionGraph | None,
|
||||
tracker: Tracker,
|
||||
):
|
||||
assert isinstance(value, (list, tuple))
|
||||
zip_targets = []
|
||||
|
||||
for variable in value:
|
||||
iter_variable = variable.get_iter()
|
||||
if isinstance(iter_variable, UserDefinedIterVariable):
|
||||
return UserDefinedIterVariable(value, graph, tracker)
|
||||
zip_targets.append(iter_variable)
|
||||
|
||||
return ZipVariable(zip_targets, graph, tracker)
|
||||
|
||||
|
||||
class MapVariable(SequenceIterVariable):
|
||||
"""
|
||||
MapVariable holds a SequenceIterVariable and return a Iterable Variable after map function
|
||||
"""
|
||||
|
||||
def __init__(self, fn, iters: list[IterVariable], graph, tracker):
|
||||
super().__init__(iters, graph, tracker)
|
||||
self.fn = fn
|
||||
|
||||
def next(self):
|
||||
return self.fn(*[iter_var.next() for iter_var in self.holds])
|
||||
|
||||
def to_list(self) -> list:
|
||||
lists = [iter_var.to_list() for iter_var in self.holds]
|
||||
min_len = min(len(l) for l in lists)
|
||||
result = []
|
||||
for i in range(min_len):
|
||||
result.append(self.fn(*(l[i] for l in lists)))
|
||||
return result
|
||||
|
||||
def has_side_effect(self) -> bool:
|
||||
return any(iter_var.has_side_effect() for iter_var in self.holds)
|
||||
|
||||
def _reconstruct(self, codegen: PyCodeGen):
|
||||
if self.has_side_effect():
|
||||
super()._reconstruct(codegen)
|
||||
else:
|
||||
codegen.gen_load_global("map", push_null=True)
|
||||
self.fn.reconstruct(codegen)
|
||||
for iter_var in self.holds:
|
||||
iter_var.reconstruct(codegen)
|
||||
codegen.gen_call_function(len(self.holds) + 1)
|
||||
|
||||
@staticmethod
|
||||
def from_iterator(
|
||||
fn,
|
||||
value: Sequence[VariableBase],
|
||||
graph: FunctionGraph | None,
|
||||
tracker: Tracker,
|
||||
):
|
||||
map_targets = []
|
||||
|
||||
for variable in value:
|
||||
iter_variable = variable.get_iter()
|
||||
if isinstance(iter_variable, UserDefinedIterVariable):
|
||||
return UserDefinedIterVariable(value, graph, tracker)
|
||||
map_targets.append(iter_variable)
|
||||
|
||||
return MapVariable(fn, map_targets, graph, tracker)
|
||||
|
||||
|
||||
class GeneratorVariable(IterVariable):
|
||||
def __init__(
|
||||
self,
|
||||
code_var: VariableBase,
|
||||
vframe: VirtualFrame,
|
||||
graph: FunctionGraph,
|
||||
tracker: Tracker,
|
||||
):
|
||||
self.code_var = code_var
|
||||
self.vframe = vframe
|
||||
self.shared_stack = []
|
||||
super().__init__(graph, tracker)
|
||||
|
||||
def send(self, /, value: VariableBase):
|
||||
from ..opcode_inline_executor import OpcodeInlineGeneratorExecutor
|
||||
|
||||
checkpoint = self.graph.save_memo()
|
||||
frame_state = self.vframe.get_state()
|
||||
try:
|
||||
inline_gen_executor = OpcodeInlineGeneratorExecutor(
|
||||
self.vframe, self.code_var, self.graph
|
||||
)
|
||||
self.vframe.stack.push(value)
|
||||
with EventGuard(
|
||||
f"Inline Gen Call: {inline_gen_executor.vframe.code.co_name}, file {inline_gen_executor.vframe.code.co_filename}, line {int(inline_gen_executor.vframe.code.co_firstlineno)}"
|
||||
):
|
||||
output: VariableBase = inline_gen_executor.inline_call()
|
||||
if inline_gen_executor.stop_state == "Return":
|
||||
raise SotCapturedExceptionFactory.create(StopIteration())
|
||||
except SotCapturedStopIteration:
|
||||
raise
|
||||
except SotErrorBase as error:
|
||||
self.graph.restore_memo(checkpoint)
|
||||
self.vframe.restore_state(frame_state)
|
||||
filename = self.code_var.value.co_filename
|
||||
lineno = self.code_var.value.co_firstlineno
|
||||
code_name = self.code_var.value.co_name
|
||||
location_info = f'File "{filename}", line {lineno}, in {code_name}'
|
||||
|
||||
exception_class = OtherInlineCallBreak
|
||||
if isinstance(error, BreakGraphError):
|
||||
exception_class = BreakGraphInlineCallBreak
|
||||
elif isinstance(error, FallbackError):
|
||||
exception_class = FallbackInlineCallBreak
|
||||
|
||||
raise BreakGraphError(
|
||||
exception_class(
|
||||
f"{location_info} encountered breakgraph error caused by\n {error}"
|
||||
)
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
def getattr(self, name: str, default=None):
|
||||
from ..dispatch_functions import generator_send
|
||||
|
||||
known_generator_attrs = {"send"}
|
||||
if name not in known_generator_attrs:
|
||||
raise BreakGraphError(
|
||||
UnsupportedOperationBreak(
|
||||
reason_str=f"Get attribute {name} from generator is not supported."
|
||||
)
|
||||
)
|
||||
if name == "send":
|
||||
return BuiltinVariable(
|
||||
generator_send, self.graph, DanglingTracker()
|
||||
).bind_dangling_fn(self, "send")
|
||||
unreached()
|
||||
|
||||
def get_py_value(self, allow_tensor=False):
|
||||
raise BreakGraphError(
|
||||
UnsupportedOperationBreak(
|
||||
reason_str="Get real value from generator is not supported."
|
||||
)
|
||||
)
|
||||
|
||||
def get_py_type(self):
|
||||
return types.GeneratorType
|
||||
|
||||
def next(self):
|
||||
return self.send(ConstantVariable.wrap_literal(None, self.graph))
|
||||
|
||||
def to_list(self):
|
||||
return do_until_stop_iteration(lambda: self.next())
|
||||
|
||||
@property
|
||||
def main_info(self) -> dict[str, Any]:
|
||||
return {
|
||||
"co_name": self.code_var.value.co_name,
|
||||
}
|
||||
|
||||
# @VariableFactory.register_from_value()
|
||||
# def from_value(value: Any, graph: FunctionGraph, tracker: Tracker):
|
||||
# if inspect.isgenerator(value):
|
||||
# return GeneratorVariable()
|
||||
# return None
|
||||
|
||||
|
||||
# what UserDefinedIterVariable holds doesn't matter, because use user defined iterator will trigger break graph
|
||||
class UserDefinedIterVariable(IterVariable):
|
||||
def __init__(
|
||||
self,
|
||||
held: VariableBase | list[VariableBase],
|
||||
graph: FunctionGraph,
|
||||
tracker: Tracker,
|
||||
):
|
||||
if not isinstance(held, list):
|
||||
held = [held]
|
||||
self.holds = held
|
||||
super().__init__(graph, tracker)
|
||||
|
||||
def to_list(self):
|
||||
raise BreakGraphError(
|
||||
UnsupportedOperationBreak(
|
||||
reason_str="Break graph when iterating user defined iterator"
|
||||
)
|
||||
)
|
||||
|
||||
def next(self):
|
||||
raise BreakGraphError(
|
||||
UnsupportedOperationBreak(
|
||||
reason_str="Break graph when iterating user defined iterator"
|
||||
)
|
||||
)
|
||||
|
||||
@check_faster_guard
|
||||
def make_faster_guard(self) -> list[paddle.framework.core.GuardNodeBase]:
|
||||
return [
|
||||
guard for held in self.holds for guard in held.make_faster_guard()
|
||||
]
|
||||
|
||||
def make_stringified_guard(self):
|
||||
return [
|
||||
guard
|
||||
for held in self.holds
|
||||
for guard in held.make_stringified_guard()
|
||||
]
|
||||
@@ -0,0 +1,238 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from ...utils import log
|
||||
from .tracker import (
|
||||
BuiltinTracker,
|
||||
CellTracker,
|
||||
ConstTracker,
|
||||
DanglingTracker,
|
||||
FunctionClosureTracker,
|
||||
LocalTracker,
|
||||
)
|
||||
from .variable_stack import VariableStack
|
||||
from .variables.base import VariableBase, VariableFactory, fn_bind_inputs
|
||||
from .variables.basic import (
|
||||
CellVariable,
|
||||
FunctionGlobalVariable,
|
||||
GlobalVariable,
|
||||
NullVariable,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import types
|
||||
from typing import TypeAlias
|
||||
|
||||
from ..instruction_utils import Instruction
|
||||
from .function_graph import FunctionGraph
|
||||
from .variables.callable import FunctionVariable
|
||||
|
||||
# The type to represent the (*args, **kwargs) pack in the call.
|
||||
CallArgsPack: TypeAlias = tuple[tuple[Any, ...], dict[str, Any]]
|
||||
|
||||
|
||||
def validate_value(value):
|
||||
assert isinstance(value, VariableBase), (
|
||||
f"value: {value}, type should be VariableBase(or derived), but get {type(value)}"
|
||||
)
|
||||
assert not isinstance(value.tracker, DanglingTracker) or isinstance(
|
||||
value, (NullVariable, CellVariable)
|
||||
), f"dangling variable {value} should not be pushed into stack."
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlockStackItem:
|
||||
# `PyTryBlock` in CPython source code
|
||||
type: str
|
||||
inst: Instruction
|
||||
handler: Instruction
|
||||
level: int
|
||||
|
||||
|
||||
class VirtualFrameState(NamedTuple):
|
||||
locals: dict[str, VariableBase]
|
||||
builtins: dict[str, VariableBase]
|
||||
cells: dict[str, VariableBase]
|
||||
lasti: int
|
||||
stack_data: list[VariableBase]
|
||||
block_stack: list[BlockStackItem]
|
||||
|
||||
|
||||
class VirtualFrame:
|
||||
code: types.CodeType
|
||||
locals: dict[str, Any] # TODO: should we use DictVariable instead of dict?
|
||||
globals: GlobalVariable
|
||||
builtins: dict[str, Any]
|
||||
consts: list[Any]
|
||||
cells: dict[str, Any]
|
||||
lasti: int
|
||||
stack: VariableStack
|
||||
block_stack: list[BlockStackItem]
|
||||
|
||||
def __init__(self, code: types.CodeType):
|
||||
self.code = code
|
||||
self.locals = {}
|
||||
self.globals = None # type: ignore
|
||||
self.builtins = {}
|
||||
self.cells = {}
|
||||
self.lasti = 0
|
||||
self.consts = []
|
||||
self.stack = VariableStack(validate_value_func=validate_value)
|
||||
self.block_stack: list[BlockStackItem] = []
|
||||
|
||||
@staticmethod
|
||||
def from_real_frame(frame: types.FrameType, graph: FunctionGraph):
|
||||
code = frame.f_code
|
||||
locals = frame.f_locals
|
||||
vframe = VirtualFrame(code)
|
||||
|
||||
# convert locals
|
||||
free_or_cell_vars = code.co_cellvars + code.co_freevars
|
||||
for name, value in locals.items():
|
||||
tracker = (
|
||||
CellTracker(name)
|
||||
if name in free_or_cell_vars
|
||||
else LocalTracker(name)
|
||||
)
|
||||
vframe.locals[name] = VariableFactory.from_value(
|
||||
value, graph, tracker
|
||||
)
|
||||
|
||||
for name in free_or_cell_vars:
|
||||
# create a cell for each variable.
|
||||
vframe.cells[name] = CellVariable() # put in cells.
|
||||
if name in vframe.locals:
|
||||
vframe.cells[name].set_value(vframe.locals[name])
|
||||
|
||||
# convert globals
|
||||
vframe.globals = GlobalVariable(
|
||||
frame.f_globals,
|
||||
graph,
|
||||
DanglingTracker(),
|
||||
)
|
||||
|
||||
# convert builtins
|
||||
for name, value in builtins.__dict__.items():
|
||||
vframe.builtins[name] = VariableFactory.from_value(
|
||||
value, graph, BuiltinTracker(name)
|
||||
)
|
||||
# Temporarily use the builtins from the graph to avoid the conversion overhead.
|
||||
graph.builtins = vframe.builtins
|
||||
|
||||
# prepare consts
|
||||
for value in code.co_consts:
|
||||
vframe.consts.append(
|
||||
VariableFactory.from_value(value, graph, ConstTracker(value))
|
||||
)
|
||||
return vframe
|
||||
|
||||
@staticmethod
|
||||
def from_inline_call(
|
||||
code: types.CodeType,
|
||||
fn_var: FunctionVariable,
|
||||
fn_value: types.FunctionType,
|
||||
graph: FunctionGraph,
|
||||
call_args_pack: CallArgsPack,
|
||||
):
|
||||
call_args, call_kwargs = call_args_pack
|
||||
vframe = VirtualFrame(code)
|
||||
vframe.globals = FunctionGlobalVariable(
|
||||
fn_var,
|
||||
fn_value.__globals__,
|
||||
graph,
|
||||
DanglingTracker(),
|
||||
)
|
||||
|
||||
# convert builtins
|
||||
# NOTE(SigureMo): inline call should inherit the builtins from the caller to reduce the conversion overhead.
|
||||
vframe.builtins = graph.builtins
|
||||
|
||||
# prepare consts
|
||||
for value in code.co_consts:
|
||||
vframe.consts.append(
|
||||
VariableFactory.from_value(value, graph, ConstTracker(value))
|
||||
)
|
||||
|
||||
# convert locals
|
||||
vframe.locals.update(
|
||||
fn_bind_inputs(fn_value, graph, *call_args, **call_kwargs)
|
||||
)
|
||||
|
||||
log(
|
||||
5,
|
||||
f"[INLINE CALL] {code.co_name} with locals: ",
|
||||
vframe.locals,
|
||||
)
|
||||
|
||||
# handle implicit variables in comprehensions
|
||||
vframe.handle_comps(fn_value)
|
||||
|
||||
# convert closures
|
||||
closure = fn_var.get_py_value().__closure__
|
||||
for name in code.co_cellvars + code.co_freevars:
|
||||
# create a cell for each variable.
|
||||
vframe.cells[name] = CellVariable() # put in cells.
|
||||
if name in vframe.locals:
|
||||
vframe.cells[name].set_value(vframe.locals[name])
|
||||
|
||||
if closure is None:
|
||||
return vframe
|
||||
assert len(closure) == len(code.co_freevars)
|
||||
for idx, (name, cell) in enumerate(zip(code.co_freevars, closure)):
|
||||
value = cell.cell_contents
|
||||
value = VariableFactory.from_value(
|
||||
value, graph, FunctionClosureTracker(fn_var, idx)
|
||||
)
|
||||
# wrapped by a CellVariable
|
||||
if not isinstance(value, CellVariable):
|
||||
value = CellVariable(value)
|
||||
vframe.cells[name] = value
|
||||
return vframe
|
||||
|
||||
def handle_comps(self, fn_value):
|
||||
is_comp = any(
|
||||
x in fn_value.__name__
|
||||
for x in ['<listcomp>', '<dictcomp>', '<setcomp>', '<genexpr>']
|
||||
)
|
||||
if not is_comp:
|
||||
return
|
||||
pattern = r'implicit\d+'
|
||||
for name in list(self.locals.keys()):
|
||||
if re.match(pattern, name):
|
||||
self.locals[name.replace('implicit', '.')] = self.locals[name]
|
||||
|
||||
def get_state(self):
|
||||
return VirtualFrameState(
|
||||
locals=self.locals.copy(),
|
||||
builtins=self.builtins.copy(),
|
||||
cells=self.cells.copy(),
|
||||
lasti=self.lasti,
|
||||
stack_data=list(self.stack._data),
|
||||
block_stack=self.block_stack.copy(),
|
||||
)
|
||||
|
||||
def restore_state(self, state: VirtualFrameState):
|
||||
self.locals = state.locals
|
||||
self.builtins = state.builtins
|
||||
self.cells = state.cells
|
||||
self.lasti = state.lasti
|
||||
self.stack._data = state.stack_data
|
||||
self.block_stack = state.block_stack
|
||||
Reference in New Issue
Block a user