mirror of https://github.com/llvm/torch-mlir
Rename 'macro' nomenclature to 'partial eval'.
parent
dd6a4e638b
commit
e45287d83e
|
@ -17,13 +17,13 @@ from .target import *
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"BuiltinsValueCoder",
|
"BuiltinsValueCoder",
|
||||||
"Environment",
|
"Environment",
|
||||||
"MacroEvalResult",
|
"LiveValueRef",
|
||||||
"MacroEvalType",
|
|
||||||
"MacroResolver",
|
|
||||||
"MacroValueRef",
|
|
||||||
"NameReference",
|
"NameReference",
|
||||||
"NameResolver",
|
"NameResolver",
|
||||||
"ResolveAttrMacroValueRef",
|
"PartialEvalResult",
|
||||||
|
"PartialEvalType",
|
||||||
|
"PartialEvalHook",
|
||||||
|
"ResolveAttrLiveValueRef",
|
||||||
"ValueCoder",
|
"ValueCoder",
|
||||||
"ValueCoderChain",
|
"ValueCoderChain",
|
||||||
]
|
]
|
||||||
|
@ -59,7 +59,7 @@ class NameReference:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def load(self, env: "Environment",
|
def load(self, env: "Environment",
|
||||||
ir_h: ir.DialectHelper) -> "MacroEvalResult":
|
ir_h: ir.DialectHelper) -> "PartialEvalResult":
|
||||||
"""Loads the IR Value associated with the name.
|
"""Loads the IR Value associated with the name.
|
||||||
|
|
||||||
The load may either be direct, returning an existing value or
|
The load may either be direct, returning an existing value or
|
||||||
|
@ -68,9 +68,9 @@ class NameReference:
|
||||||
Args:
|
Args:
|
||||||
ir_h: The dialect helper used to emit code.
|
ir_h: The dialect helper used to emit code.
|
||||||
Returns:
|
Returns:
|
||||||
A macro evaluation result.
|
A partial evaluation result.
|
||||||
"""
|
"""
|
||||||
return MacroEvalResult.not_evaluated()
|
return PartialEvalResult.not_evaluated()
|
||||||
|
|
||||||
def store(self, env: "Environment", value: ir.Value, ir_h: ir.DialectHelper):
|
def store(self, env: "Environment", value: ir.Value, ir_h: ir.DialectHelper):
|
||||||
"""Stores a new value into the name.
|
"""Stores a new value into the name.
|
||||||
|
@ -103,57 +103,64 @@ class NameResolver:
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Macro evaluation
|
# Partial evaluation
|
||||||
# When the compiler is extracting from a running program, it is likely that
|
# When the compiler is extracting from a running program, it is likely that
|
||||||
# evaluations produce live values which can be further partially evaluated
|
# evaluations produce live values which can be further partially evaluated
|
||||||
# at import time, in the context of the running instance (versus emitting
|
# at import time, in the context of the running instance (versus emitting
|
||||||
# program IR to do so). This facility is called macro evaluation and is
|
# program IR to do so). This behavior is controlled through a PartialEvalHook
|
||||||
# a pluggable component on the environment.
|
# on the environment.
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
class MacroEvalType(Enum):
|
class PartialEvalType(Enum):
|
||||||
# The macro could not be evaluated immediately and the operation should
|
# Could not be evaluated immediately and the operation should be
|
||||||
# be code-generated. yields NotImplemented.
|
# code-generated. yields NotImplemented.
|
||||||
NOT_EVALUATED = 0
|
NOT_EVALUATED = 0
|
||||||
|
|
||||||
# The macro yields a LiveValueRef
|
# Yields a LiveValueRef
|
||||||
YIELDS_LIVE_VALUE = 1
|
YIELDS_LIVE_VALUE = 1
|
||||||
|
|
||||||
# The macro yields an IR value
|
# Yields an IR value
|
||||||
YIELDS_IR_VALUE = 2
|
YIELDS_IR_VALUE = 2
|
||||||
|
|
||||||
# Evaluation yielded an error (yields contains exc_info from sys.exc_info()).
|
# Evaluation yielded an error (yields contains exc_info from sys.exc_info()).
|
||||||
ERROR = 3
|
ERROR = 3
|
||||||
|
|
||||||
|
|
||||||
class MacroEvalResult(namedtuple("MacroEvalResult", "type,yields")):
|
class PartialEvalResult(namedtuple("PartialEvalResult", "type,yields")):
|
||||||
"""Encapsulates the result of a macro evaluation."""
|
"""Encapsulates the result of a partial evaluation."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def not_evaluated(cls):
|
def not_evaluated(cls):
|
||||||
return cls(MacroEvalType.NOT_EVALUATED, NotImplemented)
|
return cls(PartialEvalType.NOT_EVALUATED, NotImplemented)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def yields_live_value(cls, live_value):
|
def yields_live_value(cls, live_value):
|
||||||
assert isinstance(live_value, MacroValueRef)
|
assert isinstance(live_value, LiveValueRef)
|
||||||
return cls(MacroEvalType.YIELDS_LIVE_VALUE, live_value)
|
return cls(PartialEvalType.YIELDS_LIVE_VALUE, live_value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def yields_ir_value(cls, ir_value):
|
def yields_ir_value(cls, ir_value):
|
||||||
assert isinstance(ir_value, ir.Value)
|
assert isinstance(ir_value, ir.Value)
|
||||||
return cls(MacroEvalType.YIELDS_IR_VALUE, ir_value)
|
return cls(PartialEvalType.YIELDS_IR_VALUE, ir_value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def error(cls):
|
def error(cls):
|
||||||
return cls(MacroEvalType.ERROR, sys.exc_info())
|
return cls(PartialEvalType.ERROR, sys.exc_info())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def error_message(cls, message):
|
||||||
|
try:
|
||||||
|
raise RuntimeError(message)
|
||||||
|
except RuntimeError:
|
||||||
|
return cls.error()
|
||||||
|
|
||||||
|
|
||||||
class MacroValueRef:
|
class LiveValueRef:
|
||||||
"""Wraps a live value from the containing environment.
|
"""Wraps a live value from the containing environment.
|
||||||
|
|
||||||
Typically, when expressions encounter a live value, a limited number of
|
Typically, when expressions encounter a live value, a limited number of
|
||||||
"macro" expansions can be done against it in place (versus emitting the code
|
partial evaluations can be done against it in place (versus emitting the code
|
||||||
to import it and perform the operation). This default base class will not
|
to import it and perform the operation). This default base class will not
|
||||||
perform any static evaluations.
|
perform any static evaluations.
|
||||||
"""
|
"""
|
||||||
|
@ -165,31 +172,31 @@ class MacroValueRef:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.live_value = live_value
|
self.live_value = live_value
|
||||||
|
|
||||||
def resolve_getattr(self, env: "Environment", attr_name) -> MacroEvalResult:
|
def resolve_getattr(self, env: "Environment", attr_name) -> PartialEvalResult:
|
||||||
"""Gets a named attribute from the live value."""
|
"""Gets a named attribute from the live value."""
|
||||||
return MacroEvalResult.not_evaluated()
|
return PartialEvalResult.not_evaluated()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "MacroValueRef({}, {})".format(self.__class__.__name__,
|
return "MacroValueRef({}, {})".format(self.__class__.__name__,
|
||||||
self.live_value)
|
self.live_value)
|
||||||
|
|
||||||
|
|
||||||
class ResolveAttrMacroValueRef(MacroValueRef):
|
class ResolveAttrLiveValueRef(LiveValueRef):
|
||||||
"""Custom MacroValueRef that will resolve attributes via getattr."""
|
"""Custom MacroValueRef that will resolve attributes via getattr."""
|
||||||
__slots__ = []
|
__slots__ = []
|
||||||
|
|
||||||
def resolve_getattr(self, env: "Environment", attr_name) -> MacroEvalResult:
|
def resolve_getattr(self, env: "Environment", attr_name) -> PartialEvalResult:
|
||||||
logging.debug("RESOLVE_GETATTR '{}' on {}".format(attr_name,
|
logging.debug("RESOLVE_GETATTR '{}' on {}".format(attr_name,
|
||||||
self.live_value))
|
self.live_value))
|
||||||
try:
|
try:
|
||||||
attr_py_value = getattr(self.live_value, attr_name)
|
attr_py_value = getattr(self.live_value, attr_name)
|
||||||
except:
|
except:
|
||||||
return MacroEvalResult.error()
|
return PartialEvalResult.error()
|
||||||
return env.macro_resolver.resolve(attr_py_value)
|
return env.partial_eval_hook.resolve(attr_py_value)
|
||||||
|
|
||||||
|
|
||||||
class MacroResolver:
|
class PartialEvalHook:
|
||||||
"""Owned by an environment and performs system-wide macro resolution."""
|
"""Owned by an environment to customize partial evaluation."""
|
||||||
__slots__ = [
|
__slots__ = [
|
||||||
"_value_map",
|
"_value_map",
|
||||||
]
|
]
|
||||||
|
@ -198,26 +205,26 @@ class MacroResolver:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._value_map = PyValueMap()
|
self._value_map = PyValueMap()
|
||||||
|
|
||||||
def resolve(self, py_value) -> MacroEvalResult:
|
def resolve(self, py_value) -> PartialEvalResult:
|
||||||
"""Performs macro resolution on a python value."""
|
"""Performs partial evaluation on a python value."""
|
||||||
binding = self._value_map.lookup(py_value)
|
binding = self._value_map.lookup(py_value)
|
||||||
if binding is None:
|
if binding is None:
|
||||||
logging.debug("MACRO RESOLVE {}: Passthrough", py_value)
|
logging.debug("PARTIAL EVAL RESOLVE {}: Passthrough", py_value)
|
||||||
return MacroEvalResult.yields_live_value(MacroValueRef(py_value))
|
return PartialEvalResult.yields_live_value(LiveValueRef(py_value))
|
||||||
if isinstance(binding, MacroValueRef):
|
if isinstance(binding, LiveValueRef):
|
||||||
logging.debug("MACRO RESOLVE {}: {}", py_value, binding)
|
logging.debug("PARTIAL EVAL RESOLVE {}: {}", py_value, binding)
|
||||||
return MacroEvalResult.yields_live_value(binding)
|
return PartialEvalResult.yields_live_value(binding)
|
||||||
if isinstance(binding, MacroEvalResult):
|
if isinstance(binding, PartialEvalResult):
|
||||||
return binding
|
return binding
|
||||||
# Attempt to call.
|
# Attempt to call.
|
||||||
try:
|
try:
|
||||||
binding = binding(py_value)
|
binding = binding(py_value)
|
||||||
assert isinstance(binding, MacroEvalResult), (
|
assert isinstance(binding, PartialEvalResult), (
|
||||||
"Expected MacroEvalResult but got {}".format(binding))
|
"Expected PartialEvalResult but got {}".format(binding))
|
||||||
logging.debug("MACRO RESOLVE {}: {}", py_value, binding)
|
logging.debug("PARTIAL EVAL RESOLVE {}: {}", py_value, binding)
|
||||||
return binding
|
return binding
|
||||||
except:
|
except:
|
||||||
return MacroEvalResult.error()
|
return PartialEvalResult.error()
|
||||||
|
|
||||||
def _bind(self,
|
def _bind(self,
|
||||||
binding,
|
binding,
|
||||||
|
@ -236,10 +243,10 @@ class MacroResolver:
|
||||||
"Must specify one of 'for_ref', 'for_type' or 'for_predicate")
|
"Must specify one of 'for_ref', 'for_type' or 'for_predicate")
|
||||||
|
|
||||||
def enable_getattr(self, **kwargs):
|
def enable_getattr(self, **kwargs):
|
||||||
"""Enables macro attribute resolution."""
|
"""Enables partial evaluation of getattr."""
|
||||||
self._bind(
|
self._bind(
|
||||||
lambda pv: MacroEvalResult.yields_live_value(
|
lambda pv: PartialEvalResult.yields_live_value(
|
||||||
ResolveAttrMacroValueRef(pv)), **kwargs)
|
ResolveAttrLiveValueRef(pv)), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -258,7 +265,7 @@ class Environment(NameResolver):
|
||||||
"_name_resolvers",
|
"_name_resolvers",
|
||||||
"target",
|
"target",
|
||||||
"value_coder",
|
"value_coder",
|
||||||
"macro_resolver",
|
"partial_eval_hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
@ -267,13 +274,14 @@ class Environment(NameResolver):
|
||||||
target: Target,
|
target: Target,
|
||||||
name_resolvers=(),
|
name_resolvers=(),
|
||||||
value_coder,
|
value_coder,
|
||||||
macro_resolver=None):
|
partial_eval_hook=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ir_h = ir_h
|
self.ir_h = ir_h
|
||||||
self.target = target
|
self.target = target
|
||||||
self._name_resolvers = name_resolvers
|
self._name_resolvers = name_resolvers
|
||||||
self.value_coder = value_coder
|
self.value_coder = value_coder
|
||||||
self.macro_resolver = macro_resolver if macro_resolver else MacroResolver()
|
self.partial_eval_hook = partial_eval_hook if partial_eval_hook else PartialEvalHook(
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_const_global_function(cls, ir_h: ir.DialectHelper, f, *,
|
def for_const_global_function(cls, ir_h: ir.DialectHelper, f, *,
|
||||||
|
@ -332,12 +340,11 @@ class LocalNameReference(NameReference):
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self._current_value = initial_value
|
self._current_value = initial_value
|
||||||
|
|
||||||
def load(self, env: "Environment") -> MacroEvalResult:
|
def load(self, env: "Environment") -> PartialEvalResult:
|
||||||
if self._current_value is None:
|
if self._current_value is None:
|
||||||
return MacroEvalResult.error(
|
return PartialEvalResult.error_message(
|
||||||
RuntimeError("Attempt to access local '{}' before assignment".format(
|
"Attempt to access local '{}' before assignment".format(self.name))
|
||||||
self.name)))
|
return PartialEvalResult.yields_ir_value(self._current_value)
|
||||||
return MacroEvalResult.yields_ir_value(self._current_value)
|
|
||||||
|
|
||||||
def store(self, env: "Environment", value: ir.Value):
|
def store(self, env: "Environment", value: ir.Value):
|
||||||
self._current_value = value
|
self._current_value = value
|
||||||
|
@ -374,8 +381,8 @@ class ConstNameReference(NameReference):
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self._py_value = py_value
|
self._py_value = py_value
|
||||||
|
|
||||||
def load(self, env: "Environment") -> MacroEvalResult:
|
def load(self, env: "Environment") -> PartialEvalResult:
|
||||||
return env.macro_resolver.resolve(self._py_value)
|
return env.partial_eval_hook.resolve(self._py_value)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<ConstNameReference({}={})>".format(self.name, self._py_value)
|
return "<ConstNameReference({}={})>".format(self.name, self._py_value)
|
||||||
|
|
|
@ -31,7 +31,7 @@ class ImportFrontend:
|
||||||
"_helper",
|
"_helper",
|
||||||
"_target_factory",
|
"_target_factory",
|
||||||
"_value_coder",
|
"_value_coder",
|
||||||
"_macro_resolver",
|
"_partial_eval_hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
@ -39,15 +39,15 @@ class ImportFrontend:
|
||||||
*,
|
*,
|
||||||
target_factory: TargetFactory = GenericTarget64,
|
target_factory: TargetFactory = GenericTarget64,
|
||||||
value_coder: Optional[ValueCoder] = None,
|
value_coder: Optional[ValueCoder] = None,
|
||||||
macro_resolver: Optional[MacroResolver] = None):
|
partial_eval_hook: Optional[PartialEvalHook] = None):
|
||||||
self._ir_context = ir.MLIRContext() if not ir_context else ir_context
|
self._ir_context = ir.MLIRContext() if not ir_context else ir_context
|
||||||
self._ir_module = self._ir_context.new_module()
|
self._ir_module = self._ir_context.new_module()
|
||||||
self._helper = AllDialectHelper(self._ir_context,
|
self._helper = AllDialectHelper(self._ir_context,
|
||||||
ir.OpBuilder(self._ir_context))
|
ir.OpBuilder(self._ir_context))
|
||||||
self._target_factory = target_factory
|
self._target_factory = target_factory
|
||||||
self._value_coder = value_coder if value_coder else BuiltinsValueCoder()
|
self._value_coder = value_coder if value_coder else BuiltinsValueCoder()
|
||||||
self._macro_resolver = (macro_resolver if macro_resolver else
|
self._partial_eval_hook = (partial_eval_hook if partial_eval_hook else
|
||||||
build_default_macro_resolver())
|
build_default_partial_eval_hook())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ir_context(self):
|
def ir_context(self):
|
||||||
|
@ -62,8 +62,8 @@ class ImportFrontend:
|
||||||
return self._helper
|
return self._helper
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def macro_resolver(self):
|
def partial_eval_hook(self):
|
||||||
return self._macro_resolver
|
return self._partial_eval_hook
|
||||||
|
|
||||||
def import_global_function(self, f):
|
def import_global_function(self, f):
|
||||||
"""Imports a global function.
|
"""Imports a global function.
|
||||||
|
@ -121,7 +121,7 @@ class ImportFrontend:
|
||||||
parameter_bindings=zip(f_params.keys(), ir_f.first_block.args),
|
parameter_bindings=zip(f_params.keys(), ir_f.first_block.args),
|
||||||
value_coder=self._value_coder,
|
value_coder=self._value_coder,
|
||||||
target=target,
|
target=target,
|
||||||
macro_resolver=self._macro_resolver)
|
partial_eval_hook=self._partial_eval_hook)
|
||||||
fctx = FunctionContext(ir_c=ir_c,
|
fctx = FunctionContext(ir_c=ir_c,
|
||||||
ir_f=ir_f,
|
ir_f=ir_f,
|
||||||
ir_h=h,
|
ir_h=h,
|
||||||
|
@ -166,8 +166,8 @@ class AllDialectHelper(Numpy.DialectHelper, ScfDialectHelper):
|
||||||
ScfDialectHelper.__init__(self, *args, **kwargs)
|
ScfDialectHelper.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def build_default_macro_resolver() -> MacroResolver:
|
def build_default_partial_eval_hook() -> PartialEvalHook:
|
||||||
mr = MacroResolver()
|
mr = PartialEvalHook()
|
||||||
### Modules
|
### Modules
|
||||||
mr.enable_getattr(for_type=ast.__class__) # The module we use is arbitrary.
|
mr.enable_getattr(for_type=ast.__class__) # The module we use is arbitrary.
|
||||||
|
|
||||||
|
|
|
@ -46,16 +46,16 @@ class FunctionContext:
|
||||||
ir.emit_error(loc, message)
|
ir.emit_error(loc, message)
|
||||||
raise EmittedError(loc, message)
|
raise EmittedError(loc, message)
|
||||||
|
|
||||||
def check_macro_evaluated(self, result: MacroEvalResult):
|
def check_partial_evaluated(self, result: PartialEvalResult):
|
||||||
"""Checks that a macro has evaluated without error."""
|
"""Checks that a PartialEvalResult has evaluated without error."""
|
||||||
if result.type == MacroEvalType.ERROR:
|
if result.type == PartialEvalType.ERROR:
|
||||||
exc_info = result.yields
|
exc_info = result.yields
|
||||||
loc = self.current_loc
|
loc = self.current_loc
|
||||||
message = ("Error while evaluating value from environment:\n" +
|
message = ("Error while evaluating value from environment:\n" +
|
||||||
"".join(traceback.format_exception(*exc_info)))
|
"".join(traceback.format_exception(*exc_info)))
|
||||||
ir.emit_error(loc, message)
|
ir.emit_error(loc, message)
|
||||||
raise EmittedError(loc, message)
|
raise EmittedError(loc, message)
|
||||||
if result.type == MacroEvalType.NOT_EVALUATED:
|
if result.type == PartialEvalType.NOT_EVALUATED:
|
||||||
self.abort("Unable to evaluate expression")
|
self.abort("Unable to evaluate expression")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -82,22 +82,26 @@ class FunctionContext:
|
||||||
self.abort("Cannot code python value as constant: {}".format(py_value))
|
self.abort("Cannot code python value as constant: {}".format(py_value))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def emit_macro_result(self, macro_result: MacroEvalResult) -> ir.Value:
|
def emit_partial_eval_result(self,
|
||||||
"""Emits a macro result either as a direct IR value or a constant."""
|
partial_result: PartialEvalResult) -> ir.Value:
|
||||||
self.check_macro_evaluated(macro_result)
|
"""Emits a partial eval result either as a direct IR value or a constant."""
|
||||||
if macro_result.type == MacroEvalType.YIELDS_IR_VALUE:
|
self.check_partial_evaluated(partial_result)
|
||||||
|
if partial_result.type == PartialEvalType.YIELDS_IR_VALUE:
|
||||||
# Return directly.
|
# Return directly.
|
||||||
return macro_result.yields
|
return partial_result.yields
|
||||||
elif macro_result.type == MacroEvalType.YIELDS_LIVE_VALUE:
|
elif partial_result.type == PartialEvalType.YIELDS_LIVE_VALUE:
|
||||||
# Import constant.
|
# Import constant.
|
||||||
return self.emit_const_value(macro_result.yields.live_value)
|
return self.emit_const_value(partial_result.yields.live_value)
|
||||||
else:
|
else:
|
||||||
self.abort("Unhandled macro result type {}".format(macro_result))
|
self.abort("Unhandled partial eval result type {}".format(partial_result))
|
||||||
|
|
||||||
|
|
||||||
class BaseNodeVisitor(ast.NodeVisitor):
|
class BaseNodeVisitor(ast.NodeVisitor):
|
||||||
"""Base class of a node visitor that aborts on unhandled nodes."""
|
"""Base class of a node visitor that aborts on unhandled nodes."""
|
||||||
IMPORTER_TYPE = "<unknown>"
|
IMPORTER_TYPE = "<unknown>"
|
||||||
|
__slots__ = [
|
||||||
|
"fctx",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, fctx):
|
def __init__(self, fctx):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -119,6 +123,10 @@ class FunctionDefImporter(BaseNodeVisitor):
|
||||||
Handles nodes that are direct children of a FunctionDef.
|
Handles nodes that are direct children of a FunctionDef.
|
||||||
"""
|
"""
|
||||||
IMPORTER_TYPE = "statement"
|
IMPORTER_TYPE = "statement"
|
||||||
|
__slots__ = [
|
||||||
|
"ast_fd",
|
||||||
|
"_last_was_return",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, fctx, ast_fd):
|
def __init__(self, fctx, ast_fd):
|
||||||
super().__init__(fctx)
|
super().__init__(fctx)
|
||||||
|
@ -186,6 +194,9 @@ class ExpressionImporter(BaseNodeVisitor):
|
||||||
IR value that the expression lowers to.
|
IR value that the expression lowers to.
|
||||||
"""
|
"""
|
||||||
IMPORTER_TYPE = "expression"
|
IMPORTER_TYPE = "expression"
|
||||||
|
__slots__ = [
|
||||||
|
"value",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, fctx):
|
def __init__(self, fctx):
|
||||||
super().__init__(fctx)
|
super().__init__(fctx)
|
||||||
|
@ -209,12 +220,13 @@ class ExpressionImporter(BaseNodeVisitor):
|
||||||
self.value = ir_const_value
|
self.value = ir_const_value
|
||||||
|
|
||||||
def visit_Attribute(self, ast_node):
|
def visit_Attribute(self, ast_node):
|
||||||
# Import the attribute's value recursively as a macro if possible.
|
# Import the attribute's value recursively as a partial eval if possible.
|
||||||
macro_importer = MacroImporter(self.fctx)
|
pe_importer = PartialEvalImporter(self.fctx)
|
||||||
macro_importer.visit(ast_node)
|
pe_importer.visit(ast_node)
|
||||||
if macro_importer.macro_result:
|
if pe_importer.partial_eval_result:
|
||||||
self.fctx.check_macro_evaluated(macro_importer.macro_result)
|
self.fctx.check_partial_evaluated(pe_importer.partial_eval_result)
|
||||||
self.value = self.fctx.emit_macro_result(macro_importer.macro_result)
|
self.value = self.fctx.emit_partial_eval_result(
|
||||||
|
pe_importer.partial_eval_result)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.fctx.abort("unhandled attribute access mode: {}".format(
|
self.fctx.abort("unhandled attribute access mode: {}".format(
|
||||||
|
@ -331,9 +343,9 @@ class ExpressionImporter(BaseNodeVisitor):
|
||||||
self.fctx.abort("Unsupported expression name context type %s" %
|
self.fctx.abort("Unsupported expression name context type %s" %
|
||||||
ast_node.ctx.__class__.__name__)
|
ast_node.ctx.__class__.__name__)
|
||||||
name_ref = self.fctx.lookup_name(ast_node.id)
|
name_ref = self.fctx.lookup_name(ast_node.id)
|
||||||
macro_result = name_ref.load(self.fctx.environment)
|
pe_result = name_ref.load(self.fctx.environment)
|
||||||
logging.debug("LOAD {} -> {}", name_ref, macro_result)
|
logging.debug("LOAD {} -> {}", name_ref, pe_result)
|
||||||
self.value = self.fctx.emit_macro_result(macro_result)
|
self.value = self.fctx.emit_partial_eval_result(pe_result)
|
||||||
|
|
||||||
def visit_UnaryOp(self, ast_node):
|
def visit_UnaryOp(self, ast_node):
|
||||||
ir_h = self.fctx.ir_h
|
ir_h = self.fctx.ir_h
|
||||||
|
@ -371,52 +383,55 @@ class ExpressionImporter(BaseNodeVisitor):
|
||||||
self.emit_constant(ast_node.value)
|
self.emit_constant(ast_node.value)
|
||||||
|
|
||||||
|
|
||||||
class MacroImporter(BaseNodeVisitor):
|
class PartialEvalImporter(BaseNodeVisitor):
|
||||||
"""Importer for expressions that can resolve through the environment's macro
|
"""Importer for performing greedy partial evaluation.
|
||||||
system.
|
|
||||||
|
|
||||||
Concretely this is used for Attribute.value and Call resolution.
|
Concretely this is used for Attribute.value and Call resolution.
|
||||||
|
|
||||||
Attribute resolution is not just treated as a normal expression because it
|
Attribute resolution is not just treated as a normal expression because it
|
||||||
is first subject to "macro expansion", allowing the environment's macro
|
is first subject to "partial evaluation", allowing the environment's partial
|
||||||
resolution facility to operate on live python values from the containing
|
eval hook to operate on live python values from the containing
|
||||||
environment versus naively emitting code for attribute resolution from
|
environment versus naively emitting code for attribute resolution for
|
||||||
entities that can/should be considered constants from the hosting context.
|
entities that can/should be considered constants from the hosting context.
|
||||||
This is used, for example, to resolve attributes from modules without
|
This is used, for example, to resolve attributes from modules without
|
||||||
by immediately dereferencing/transforming the intervening chain of attributes.
|
immediately dereferencing/transforming the intervening chain of attributes.
|
||||||
"""
|
"""
|
||||||
IMPORTER_TYPE = "macro"
|
IMPORTER_TYPE = "partial_eval"
|
||||||
|
__slots__ = [
|
||||||
|
"partial_eval_result",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, fctx):
|
def __init__(self, fctx):
|
||||||
super().__init__(fctx)
|
super().__init__(fctx)
|
||||||
self.macro_result = None
|
self.partial_eval_result = None
|
||||||
|
|
||||||
def visit_Attribute(self, ast_node):
|
def visit_Attribute(self, ast_node):
|
||||||
# Sub-evaluate the 'value'.
|
# Sub-evaluate the 'value'.
|
||||||
sub_macro = MacroImporter(self.fctx)
|
sub_eval = PartialEvalImporter(self.fctx)
|
||||||
sub_macro.visit(ast_node.value)
|
sub_eval.visit(ast_node.value)
|
||||||
|
|
||||||
if sub_macro.macro_result:
|
if sub_eval.partial_eval_result:
|
||||||
# Macro sub-evaluation successful.
|
# Partial sub-evaluation successful.
|
||||||
sub_result = sub_macro.macro_result
|
sub_result = sub_eval.partial_eval_result
|
||||||
else:
|
else:
|
||||||
# Need to evaluate it as an expression.
|
# Need to evaluate it as an expression.
|
||||||
sub_expr = ExpressionImporter(self.fctx)
|
sub_expr = ExpressionImporter(self.fctx)
|
||||||
sub_expr.visit(ast_node.value)
|
sub_expr.visit(ast_node.value)
|
||||||
assert sub_expr.value, (
|
assert sub_expr.value, (
|
||||||
"Macro sub expression did not return a value: %r" % (ast_node.value))
|
"Evaluated sub expression did not return a value: %r" %
|
||||||
sub_result = MacroEvalResult.yields_ir_value(sub_expr.value)
|
(ast_node.value))
|
||||||
|
sub_result = PartialEvalResult.yields_ir_value(sub_expr.value)
|
||||||
|
|
||||||
# Attempt to perform a static getattr as a macro if still operating on a
|
# Attempt to perform a static getattr as a partial eval if still operating
|
||||||
# live value.
|
# on a live value.
|
||||||
self.fctx.check_macro_evaluated(sub_result)
|
self.fctx.check_partial_evaluated(sub_result)
|
||||||
if sub_result.type == MacroEvalType.YIELDS_LIVE_VALUE:
|
if sub_result.type == PartialEvalType.YIELDS_LIVE_VALUE:
|
||||||
logging.debug("STATIC getattr '{}' on {}", ast_node.attr, sub_result)
|
logging.debug("STATIC getattr '{}' on {}", ast_node.attr, sub_result)
|
||||||
getattr_result = sub_result.yields.resolve_getattr(
|
getattr_result = sub_result.yields.resolve_getattr(
|
||||||
self.fctx.environment, ast_node.attr)
|
self.fctx.environment, ast_node.attr)
|
||||||
if getattr_result.type != MacroEvalType.NOT_EVALUATED:
|
if getattr_result.type != PartialEvalType.NOT_EVALUATED:
|
||||||
self.fctx.check_macro_evaluated(getattr_result)
|
self.fctx.check_partial_evaluated(getattr_result)
|
||||||
self.macro_result = getattr_result
|
self.partial_eval_result = getattr_result
|
||||||
return
|
return
|
||||||
# If a non-statically evaluable live value, then convert to a constant
|
# If a non-statically evaluable live value, then convert to a constant
|
||||||
# and dynamic dispatch.
|
# and dynamic dispatch.
|
||||||
|
@ -424,7 +439,7 @@ class MacroImporter(BaseNodeVisitor):
|
||||||
else:
|
else:
|
||||||
ir_value = sub_result.yields
|
ir_value = sub_result.yields
|
||||||
|
|
||||||
# Yielding an IR value from a recursive macro evaluation means that the
|
# Yielding an IR value from a recursive partial evaluation means that the
|
||||||
# entire chain needs to be hoisted to IR.
|
# entire chain needs to be hoisted to IR.
|
||||||
# TODO: Implement.
|
# TODO: Implement.
|
||||||
self.fctx.abort("dynamic-emitted getattr not yet supported: %r" %
|
self.fctx.abort("dynamic-emitted getattr not yet supported: %r" %
|
||||||
|
@ -432,9 +447,9 @@ class MacroImporter(BaseNodeVisitor):
|
||||||
|
|
||||||
def visit_Name(self, ast_node):
|
def visit_Name(self, ast_node):
|
||||||
name_ref = self.fctx.lookup_name(ast_node.id)
|
name_ref = self.fctx.lookup_name(ast_node.id)
|
||||||
macro_result = name_ref.load(self.fctx.environment)
|
partial_eval_result = name_ref.load(self.fctx.environment)
|
||||||
logging.debug("LOAD MACRO {} -> {}", name_ref, macro_result)
|
logging.debug("PARTIAL EVAL {} -> {}", name_ref, partial_eval_result)
|
||||||
self.macro_result = macro_result
|
self.partial_eval_result = partial_eval_result
|
||||||
|
|
||||||
|
|
||||||
class EmittedError(Exception):
|
class EmittedError(Exception):
|
||||||
|
|
Loading…
Reference in New Issue