loki.ir.transformer

Visitor classes for transforming the IR

Classes

MaskedTransformer([start, stop, active, ...])

An enriched Transformer that can selectively include or exclude parts of the tree.

NestedMaskedTransformer([start, stop, ...])

A MaskedTransformer that retains parents for children that are included in the produced tree.

NestedTransformer([mapper, ...])

A Transformer that applies replacements in a depth-first fashion.

Transformer([mapper, invalidate_source, ...])

Visitor class to rebuild the tree and replace nodes according to a mapper.

class Transformer(mapper=None, invalidate_source=True, inplace=False, rebuild_scopes=False)

Bases: Visitor

Visitor class to rebuild the tree and replace nodes according to a mapper.

Given a control flow tree \(T\) and a mapper from nodes in \(T\) to a set of new nodes \(L, M : N \rightarrow L\), build a new control flow tree \(T'\) where a node \(n \in N\) is replaced with \(M(n)\).

Important

The mapping is applied before visiting any children of a node.

Removing nodes: In the special case in which \(M(n)\) is None, \(n\) is dropped from \(T'\).

One to many mapping: In the special case in which \(M(n)\) is an iterable of nodes, all nodes in \(M(n)\) are inserted into the tuple containing \(n\).

Warning

Applying a Transformer to an IR tree rebuilds all nodes by default, which means individual nodes from the original IR are no longer found in the new tree. To update references to IR nodes, the attribute Transformer.rebuilt provides a mapping from original to rebuilt nodes. Alternatively, with inplace the mapping can be applied without rebuilding the tree, leaving existing references to individual IR nodes intact (as long as the mapping does not replace or remove them in the tree).

Parameters:
  • mapper (dict) – The mapping \(M : N \rightarrow L\).

  • invalidate_source (bool, optional) – If set to True, this triggers invalidating the source property of all parent nodes of a node \(n\) if \(M(n)\) has source=None.

  • inplace (bool, optional) – If set to True, all updates are performed on existing Node objects, instead of rebuilding them, keeping the original tree intact.

  • rebuild_scopes (bool, optional) – If set to True, this will also rebuild ScopedNode in the IR. This requires updating TypedSymbol.scope properties, which is expensive and thus carried out only when explicitly requested.

rebuilt

After applying the Transformer to an IR, this contains a mapping \(n \rightarrow n'\) for every node of the original tree \(n \in T\) to the rebuilt nodes in the new tree \(n' \in T'\).

Type:

dict

visit_object(o, **kwargs)

Return the object unchanged.

visit_tuple(o, **kwargs)

Visit all elements in a tuple, injecting any one-to-many mappings.

visit_list(o, **kwargs)

Visit all elements in a tuple, injecting any one-to-many mappings.

visit_Node(o, **kwargs)

Handler for Node objects.

It replaces o by mapper[o], if it is in the mapper, otherwise visits all children before rebuilding the node.

visit_ScopedNode(o, **kwargs)

Handler for ScopedNode objects.

It replaces o by mapper[o], if it is in the mapper, otherwise its behaviour differs slightly from the default visit_Node() as it rebuilds the node first, then visits all children and then updates in-place the rebuilt node. This is to make sure upwards-pointing references to this scope (such as ScopedNode.parent properties) can be updated correctly.

Additionally, it passes down the currently active scope in kwargs when recursing to children.

visit(o, *args, **kwargs)

Apply this Transformer to an IR tree.

Parameters:
  • o (Node) – The node to visit.

  • *args – Optional arguments to pass to the visit methods.

  • **kwargs – Optional keyword arguments to pass to the visit methods.

Returns:

The rebuilt control flow tree.

Return type:

Node or tuple

class NestedTransformer(mapper=None, invalidate_source=True, inplace=False, rebuild_scopes=False)

Bases: Transformer

A Transformer that applies replacements in a depth-first fashion.

visit_tuple(o, **kwargs)

Visit all elements in a tuple, injecting any one-to-many mappings.

visit_list(o, **kwargs)

Visit all elements in a tuple, injecting any one-to-many mappings.

visit_Node(o, **kwargs)

Handler for Node objects.

It visits all children before applying the mapper.

visit_ScopedNode(o, **kwargs)

Handler for ScopedNode objects.

Its behaviour differs slightly from the default visit_Node() as it rebuilds the node first, then visits all children and then updates in-place the rebuilt node. This is to make sure upwards-pointing references to this scope (such as ScopedNode.parent properties) can be updated correctly.

Additionally, it passes down the currently active scope in kwargs when recursing to children.

class MaskedTransformer(start=None, stop=None, active=False, require_all_start=False, greedy_stop=False, **kwargs)

Bases: Transformer

An enriched Transformer that can selectively include or exclude parts of the tree.

For that MaskedTransformer is selectively switched on and off while traversing the tree. Nodes are only included in the new tree while it is “switched on”. The transformer is switched on or off when it encounters nodes from start or stop, respectively. This can be used, e.g., to extract everything between two nodes, or to create a copy of the entire tree but without all nodes between two nodes. Multiple such ranges can be defined by providing more than one start and stop node, respectively.

The sets start and stop are to be understood in a Pythonic way, i.e., start nodes will be included in the result and stop excluded.

Important

When recursing down a tree, any InternalNode are only included in the tree if the MaskedTransformer was switched on before visiting that InternalNode. Importantly, this means the node is also not included if the transformer is switched on while traversing the internal node’s body. In such a case, only the body nodes that are included are retained.

Optionally as a variant, switching on can also be delayed until all nodes from start have been encountered by setting require_all_start to True.

Optionally, traversal can be terminated early with greedy_stop. If enabled, the MaskedTransformer will stop completely to traverse the tree as soon as encountering a node from stop.

Note

Enabling require_all_start and greedy_stop at the same time can be useful when you require the minimum number of nodes in-between multiple start and end nodes without knowing in which order they appear.

Note

MaskedTransformer rebuilds also ScopedNode by default (i.e., it calls the parent constructor with rebuild_scopes=True).

Parameters:
  • start ((iterable of) Node, optional) – Encountering a node from start during traversal switches the MaskedTransformer on and includes that node and all subsequently traversed nodes in the produced tree.

  • stop ((iterable of) Node, optional) – Encountering a node from stop during traversal switches the MaskedTransformer off and excludes that node and all subsequently traversed nodes from the produced tree.

  • active (bool, optional) – Switch the MaskedTransformer on at the beginning of the traversal. By default, it is switched on only after encountering a node from start.

  • require_all_start (bool, optional) – Switch the MaskedTransformer on only after encountering all nodes from start. By default, it is switched on after encountering any node from start.

  • greedy_stop (bool, optional) – Stop traversing the tree as soon as any node from stop is encountered. By default, traversal continues but nodes are excluded from the new tree until a node from start is encountered.

  • **kwargs (optional) – Keyword arguments that are passed to the parent class constructor.

visit(o, *args, **kwargs)

Apply this Transformer to an IR tree.

Parameters:
  • o (Node) – The node to visit.

  • *args – Optional arguments to pass to the visit methods.

  • **kwargs – Optional keyword arguments to pass to the visit methods.

Returns:

The rebuilt control flow tree.

Return type:

Node or tuple

visit_object(o, **kwargs)

Return the object unchanged.

visit_Node(o, **kwargs)

Handler for Node objects.

It replaces o by mapper[o], if it is in the mapper, otherwise visits all children before rebuilding the node.

visit_ScopedNode(o, **kwargs)

Handler for ScopedNode objects.

It replaces o by mapper[o], if it is in the mapper, otherwise its behaviour differs slightly from the default visit_Node() as it rebuilds the node first, then visits all children and then updates in-place the rebuilt node. This is to make sure upwards-pointing references to this scope (such as ScopedNode.parent properties) can be updated correctly.

Additionally, it passes down the currently active scope in kwargs when recursing to children.

class NestedMaskedTransformer(start=None, stop=None, active=False, require_all_start=False, greedy_stop=False, **kwargs)

Bases: MaskedTransformer

A MaskedTransformer that retains parents for children that are included in the produced tree.

In contrast to MaskedTransformer, any encountered InternalNode are included in the new tree as long as any of its children are included.

visit_object(o, **kwargs)

Return the object unchanged.

Note that we need to keep them here regardless of the transformer being active because this handler takes care of properties for inactive parents that may still be retained if other children switch on the transformer.

visit_LeafNode(o, **kwargs)

Handler for LeafNode that are included in the tree if the NestedMaskedTransformer is active.

visit_InternalNode(o, **kwargs)

Handler for InternalNode that are included in the tree as long as any body node is included.

visit_Conditional(o, **kwargs)

Handler for Conditional to account for the else_body.

Note

This removes the Conditional if body is empty. In that case, else_body is returned (which can be empty, too).

visit_MultiConditional(o, **kwargs)

Handler for MultiConditional to account for all bodies.

Note

This removes the MultiConditional if all of the bodies are empty. In that case, else_body is returned (which can be empty, too).