Skip to content

Instantly share code, notes, and snippets.

@SingleAccretion
Last active November 19, 2021 09:52
Show Gist options
  • Select an option

  • Save SingleAccretion/5d18d6de7bdd778543e4ccd4c8825706 to your computer and use it in GitHub Desktop.

Select an option

Save SingleAccretion/5d18d6de7bdd778543e4ccd4c8825706 to your computer and use it in GitHub Desktop.
Guidelines for writing morph optimizations

Background

Among many other things, morph (fgMorphTree, fgMorphSmpOp, fgMorphSmpOpOptional and others) performs simple optimizing tree transformations, like turning MOD(x, 2^k) !=/== 0 into AND(x, 2^k - 1) !=/== 0. Because morphing functions are called during the optimizations phases, they have to be careful when maintaining the IR state.

Rules

  1. Trees that are marked as CSE candidates cannot be deleted from the IR and their type cannot change. Use gtIsActiveCSE_Candidate to check if any particular node can be removed. For non-trivial transforms, a full !optValnumCSE_phase guard may be more appropriate.
  2. Value numbers must be maintained (including when the tree is being retyped). If you are changing a value of a constant, you can update it via the fgUpdateConstTreeValueNumber helper method. SetVNsFromNode method can be used to quickly transfer a VN from one tree to another, for example if the root tree is being replaced with one of its children. For transforms that cannot easily preserve VNs, fgGlobalMorph guard is needed (note: vnStore != nullptr would be a better guard but it is not used). Remember that VNs need to be updated when the set of exceptions the tree produces changes.
  3. Execution order of side effecting trees must be maintained, always. Remember to take the possibility of GTF_REVERSE_OPS being set into account.
  4. Side effect flags must be maintained. The GTF_RELOP_JMP_USED flag must be preserved on relops.
  5. Some trees cannot be modified as some part of the Jit expects them to have a certain shape. GT_MULs on 32 bit marked with the GT_MUL_64RSLT must have the following shape: MUL(CAST(int <- long), CAST(int <- long) | CONST). Relops marked with GTF_RELOP_JMP_USED must not be detached from their parent GT_JTRUE. Constant expressions involving handles cannot always be folded (use op->AsIntConCommon()->ImmedValCanBeFolded(compiler, oper) to check).
  6. Field sequences must be accounted for and maintained. Watch out for the zero-offset cases (call fgAddFieldSeqForZeroOffset as appropriate)!
  7. In global morphing, implicit byrefs are rewritten into indirections, while their addresses are collapsed. Remember to take this into account, especially if your transform looks down IND(ADDR(x)) trees in pre-order! If you simplify it to x, the implicit byrefs morphing will not kick in. Remember to take the possibility of demoted promoted fields into account - they are in many respects very similar to implicit byrefs themselves.

Guidelines

  1. Prefer guarding optimizing transforms with opts.OptimizationEnabled().
  2. Use DEBUG_DESTROY_NODE on removed nodes.
  3. Prefer bashing (SetOper/ChangeOper - don't forget to use PRESERVE_VN when that's appropriate) to creating a new node when possible, as that's beneficial for throughput. Remember that the source oper must have the default node size not less than the target one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment