Changeset 192816 in webkit


Ignore:
Timestamp:
Nov 30, 2015 1:05:25 PM (8 years ago)
Author:
fpizlo@apple.com
Message:

B3 should be be clever about choosing which child to reuse for result in two-operand commutative operations
https://bugs.webkit.org/show_bug.cgi?id=151321

Reviewed by Geoffrey Garen.

When lowering a commutative operation to a two-operand instruction, you have a choice of which
child value to move into the result tmp. For example we might have:

@x = Add(@y, @z)

Assuming no three-operand add is available, we could either lower it to this:

Move %y, %x
Add %z, %x

or to this:

Move %z, %x
Add %y, %x

Which is better depends on the likelihood of coalescing with %x. If it's more likely that %y will
coalesce with %x, then we want to use the first form. Otherwise, we should use the second form.

This implements two heuristics for selecting the right form, and makes those heuristics reusable
within the B3->Air lowering by abstracting it as preferRightForResult(). For non-commutative
operations we must use the first form, so the first form is the default. The heuristics are:

  • If the right child has only one user, then use the second form instead. This is profitable because that means that @z dies at the Add, so using the second form means that the Move will be coalesced away.
  • If one of the children is a Phi that this operation (the Add in this case) flows into via some Upsilon - possibly transitively through other Phis - then use the form that cases a Move on that child. This overrides everything else, and is meant to optimize variables that accumulate in a loop.

This required adding a reusable PhiChildren analysis, so I wrote one. It has an API that is mostly
based on iterators, and a higher-level API for looking at transitive children that is based on
functors.

I was originally implementing this for completeness, but when looking at how it interacted with
imaging-gaussian-blur, I realized the need for some heuristic for the loop-accumulator case. This
helps a lot on that benchmark. This widens the overall lead that B3 has on imaging-gaussian-blur, but
steady-state runs that exclude compile latency still show a slight deficit. That will most likely get
fixed by https://bugs.webkit.org/show_bug.cgi?id=151174.

No new tests because the commutativity appears to be covered by existing tests, and anyway, there are
no correctness implications to commuting a commutative operation.

  • CMakeLists.txt:
  • JavaScriptCore.xcodeproj/project.pbxproj:
  • b3/B3LowerToAir.cpp:

(JSC::B3::Air::LowerToAir::LowerToAir):
(JSC::B3::Air::LowerToAir::canBeInternal):
(JSC::B3::Air::LowerToAir::appendUnOp):
(JSC::B3::Air::LowerToAir::preferRightForResult):
(JSC::B3::Air::LowerToAir::appendBinOp):
(JSC::B3::Air::LowerToAir::lower):

  • b3/B3PhiChildren.cpp: Added.

(JSC::B3::PhiChildren::PhiChildren):
(JSC::B3::PhiChildren::~PhiChildren):

  • b3/B3PhiChildren.h: Added.

(JSC::B3::PhiChildren::ValueCollection::ValueCollection):
(JSC::B3::PhiChildren::ValueCollection::size):
(JSC::B3::PhiChildren::ValueCollection::at):
(JSC::B3::PhiChildren::ValueCollection::operator[]):
(JSC::B3::PhiChildren::ValueCollection::contains):
(JSC::B3::PhiChildren::ValueCollection::iterator::iterator):
(JSC::B3::PhiChildren::ValueCollection::iterator::operator*):
(JSC::B3::PhiChildren::ValueCollection::iterator::operator++):
(JSC::B3::PhiChildren::ValueCollection::iterator::operator==):
(JSC::B3::PhiChildren::ValueCollection::iterator::operator!=):
(JSC::B3::PhiChildren::ValueCollection::begin):
(JSC::B3::PhiChildren::ValueCollection::end):
(JSC::B3::PhiChildren::UpsilonCollection::UpsilonCollection):
(JSC::B3::PhiChildren::UpsilonCollection::size):
(JSC::B3::PhiChildren::UpsilonCollection::at):
(JSC::B3::PhiChildren::UpsilonCollection::operator[]):
(JSC::B3::PhiChildren::UpsilonCollection::contains):
(JSC::B3::PhiChildren::UpsilonCollection::begin):
(JSC::B3::PhiChildren::UpsilonCollection::end):
(JSC::B3::PhiChildren::UpsilonCollection::values):
(JSC::B3::PhiChildren::UpsilonCollection::forAllTransitiveIncomingValues):
(JSC::B3::PhiChildren::UpsilonCollection::transitivelyUses):
(JSC::B3::PhiChildren::at):
(JSC::B3::PhiChildren::operator[]):

  • b3/B3Procedure.cpp:

(JSC::B3::Procedure::Procedure):

  • b3/B3Procedure.h:
  • b3/B3UseCounts.cpp:

(JSC::B3::UseCounts::UseCounts):

  • b3/B3UseCounts.h:

(JSC::B3::UseCounts::numUses):
(JSC::B3::UseCounts::numUsingInstructions):
(JSC::B3::UseCounts::operator[]): Deleted.

Location:
trunk/Source/JavaScriptCore
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/CMakeLists.txt

    r192776 r192816  
    122122    b3/B3PatchpointValue.cpp
    123123    b3/B3PhaseScope.cpp
     124    b3/B3PhiChildren.cpp
    124125    b3/B3Procedure.cpp
    125126    b3/B3ReduceStrength.cpp
  • trunk/Source/JavaScriptCore/ChangeLog

    r192815 r192816  
     12015-11-30  Filip Pizlo  <fpizlo@apple.com>
     2
     3        B3 should be be clever about choosing which child to reuse for result in two-operand commutative operations
     4        https://bugs.webkit.org/show_bug.cgi?id=151321
     5
     6        Reviewed by Geoffrey Garen.
     7
     8        When lowering a commutative operation to a two-operand instruction, you have a choice of which
     9        child value to move into the result tmp. For example we might have:
     10
     11            @x = Add(@y, @z)
     12
     13        Assuming no three-operand add is available, we could either lower it to this:
     14
     15            Move %y, %x
     16            Add %z, %x
     17
     18        or to this:
     19
     20            Move %z, %x
     21            Add %y, %x
     22
     23        Which is better depends on the likelihood of coalescing with %x. If it's more likely that %y will
     24        coalesce with %x, then we want to use the first form. Otherwise, we should use the second form.
     25
     26        This implements two heuristics for selecting the right form, and makes those heuristics reusable
     27        within the B3->Air lowering by abstracting it as preferRightForResult(). For non-commutative
     28        operations we must use the first form, so the first form is the default. The heuristics are:
     29
     30        - If the right child has only one user, then use the second form instead. This is profitable because
     31          that means that @z dies at the Add, so using the second form means that the Move will be coalesced
     32          away.
     33
     34        - If one of the children is a Phi that this operation (the Add in this case) flows into via some
     35          Upsilon - possibly transitively through other Phis - then use the form that cases a Move on that
     36          child. This overrides everything else, and is meant to optimize variables that accumulate in a
     37          loop.
     38
     39        This required adding a reusable PhiChildren analysis, so I wrote one. It has an API that is mostly
     40        based on iterators, and a higher-level API for looking at transitive children that is based on
     41        functors.
     42
     43        I was originally implementing this for completeness, but when looking at how it interacted with
     44        imaging-gaussian-blur, I realized the need for some heuristic for the loop-accumulator case. This
     45        helps a lot on that benchmark. This widens the overall lead that B3 has on imaging-gaussian-blur, but
     46        steady-state runs that exclude compile latency still show a slight deficit. That will most likely get
     47        fixed by https://bugs.webkit.org/show_bug.cgi?id=151174.
     48
     49        No new tests because the commutativity appears to be covered by existing tests, and anyway, there are
     50        no correctness implications to commuting a commutative operation.
     51
     52        * CMakeLists.txt:
     53        * JavaScriptCore.xcodeproj/project.pbxproj:
     54        * b3/B3LowerToAir.cpp:
     55        (JSC::B3::Air::LowerToAir::LowerToAir):
     56        (JSC::B3::Air::LowerToAir::canBeInternal):
     57        (JSC::B3::Air::LowerToAir::appendUnOp):
     58        (JSC::B3::Air::LowerToAir::preferRightForResult):
     59        (JSC::B3::Air::LowerToAir::appendBinOp):
     60        (JSC::B3::Air::LowerToAir::lower):
     61        * b3/B3PhiChildren.cpp: Added.
     62        (JSC::B3::PhiChildren::PhiChildren):
     63        (JSC::B3::PhiChildren::~PhiChildren):
     64        * b3/B3PhiChildren.h: Added.
     65        (JSC::B3::PhiChildren::ValueCollection::ValueCollection):
     66        (JSC::B3::PhiChildren::ValueCollection::size):
     67        (JSC::B3::PhiChildren::ValueCollection::at):
     68        (JSC::B3::PhiChildren::ValueCollection::operator[]):
     69        (JSC::B3::PhiChildren::ValueCollection::contains):
     70        (JSC::B3::PhiChildren::ValueCollection::iterator::iterator):
     71        (JSC::B3::PhiChildren::ValueCollection::iterator::operator*):
     72        (JSC::B3::PhiChildren::ValueCollection::iterator::operator++):
     73        (JSC::B3::PhiChildren::ValueCollection::iterator::operator==):
     74        (JSC::B3::PhiChildren::ValueCollection::iterator::operator!=):
     75        (JSC::B3::PhiChildren::ValueCollection::begin):
     76        (JSC::B3::PhiChildren::ValueCollection::end):
     77        (JSC::B3::PhiChildren::UpsilonCollection::UpsilonCollection):
     78        (JSC::B3::PhiChildren::UpsilonCollection::size):
     79        (JSC::B3::PhiChildren::UpsilonCollection::at):
     80        (JSC::B3::PhiChildren::UpsilonCollection::operator[]):
     81        (JSC::B3::PhiChildren::UpsilonCollection::contains):
     82        (JSC::B3::PhiChildren::UpsilonCollection::begin):
     83        (JSC::B3::PhiChildren::UpsilonCollection::end):
     84        (JSC::B3::PhiChildren::UpsilonCollection::values):
     85        (JSC::B3::PhiChildren::UpsilonCollection::forAllTransitiveIncomingValues):
     86        (JSC::B3::PhiChildren::UpsilonCollection::transitivelyUses):
     87        (JSC::B3::PhiChildren::at):
     88        (JSC::B3::PhiChildren::operator[]):
     89        * b3/B3Procedure.cpp:
     90        (JSC::B3::Procedure::Procedure):
     91        * b3/B3Procedure.h:
     92        * b3/B3UseCounts.cpp:
     93        (JSC::B3::UseCounts::UseCounts):
     94        * b3/B3UseCounts.h:
     95        (JSC::B3::UseCounts::numUses):
     96        (JSC::B3::UseCounts::numUsingInstructions):
     97        (JSC::B3::UseCounts::operator[]): Deleted.
     98
    1992015-11-30  Filip Pizlo  <fpizlo@apple.com>
    2100
  • trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

    r192812 r192816  
    307307                0F34B14916D42010001CDA5A /* DFGUseKind.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F34B14716D4200E001CDA5A /* DFGUseKind.cpp */; };
    308308                0F34B14A16D42013001CDA5A /* DFGUseKind.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F34B14816D4200E001CDA5A /* DFGUseKind.h */; };
     309                0F37308C1C0BD29100052BFA /* B3PhiChildren.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F37308A1C0BD29100052BFA /* B3PhiChildren.cpp */; };
     310                0F37308D1C0BD29100052BFA /* B3PhiChildren.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F37308B1C0BD29100052BFA /* B3PhiChildren.h */; };
    309311                0F37308F1C0CD68500052BFA /* DisallowMacroScratchRegisterUsage.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F37308E1C0CD68500052BFA /* DisallowMacroScratchRegisterUsage.h */; };
    310312                0F3730911C0CD70C00052BFA /* AllowMacroScratchRegisterUsage.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F3730901C0CD70C00052BFA /* AllowMacroScratchRegisterUsage.h */; };
     
    23702372                0F34B14716D4200E001CDA5A /* DFGUseKind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DFGUseKind.cpp; path = dfg/DFGUseKind.cpp; sourceTree = "<group>"; };
    23712373                0F34B14816D4200E001CDA5A /* DFGUseKind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DFGUseKind.h; path = dfg/DFGUseKind.h; sourceTree = "<group>"; };
     2374                0F37308A1C0BD29100052BFA /* B3PhiChildren.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = B3PhiChildren.cpp; path = b3/B3PhiChildren.cpp; sourceTree = "<group>"; };
     2375                0F37308B1C0BD29100052BFA /* B3PhiChildren.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = B3PhiChildren.h; path = b3/B3PhiChildren.h; sourceTree = "<group>"; };
    23722376                0F37308E1C0CD68500052BFA /* DisallowMacroScratchRegisterUsage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisallowMacroScratchRegisterUsage.h; sourceTree = "<group>"; };
    23732377                0F3730901C0CD70C00052BFA /* AllowMacroScratchRegisterUsage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AllowMacroScratchRegisterUsage.h; sourceTree = "<group>"; };
     
    45494553                                0FEC84DF1BDACDAC0080FF74 /* B3PhaseScope.cpp */,
    45504554                                0FEC84E01BDACDAC0080FF74 /* B3PhaseScope.h */,
     4555                                0F37308A1C0BD29100052BFA /* B3PhiChildren.cpp */,
     4556                                0F37308B1C0BD29100052BFA /* B3PhiChildren.h */,
    45514557                                0FEC84E11BDACDAC0080FF74 /* B3Procedure.cpp */,
    45524558                                0FEC84E21BDACDAC0080FF74 /* B3Procedure.h */,
     
    69006906                                0F9D36951AE9CC33000D4DFB /* DFGCleanUpPhase.h in Headers */,
    69016907                                A77A424017A0BBFD00A8DB81 /* DFGClobberize.h in Headers */,
     6908                                0F37308D1C0BD29100052BFA /* B3PhiChildren.h in Headers */,
    69026909                                A77A424217A0BBFD00A8DB81 /* DFGClobberSet.h in Headers */,
    69036910                                0F3C1F1B1B868E7900ABB08B /* DFGClobbersExitState.h in Headers */,
     
    88358842                                142D6F1113539A4100B02E86 /* MarkStack.cpp in Sources */,
    88368843                                4340A4841A9051AF00D73CCA /* MathCommon.cpp in Sources */,
     8844                                0F37308C1C0BD29100052BFA /* B3PhiChildren.cpp in Sources */,
    88378845                                14469DDF107EC7E700650446 /* MathObject.cpp in Sources */,
    88388846                                90213E3D123A40C200D422F3 /* MemoryStatistics.cpp in Sources */,
  • trunk/Source/JavaScriptCore/b3/B3LowerToAir.cpp

    r192699 r192816  
    4545#include "B3PatchpointValue.h"
    4646#include "B3PhaseScope.h"
     47#include "B3PhiChildren.h"
    4748#include "B3Procedure.h"
    4849#include "B3StackSlotValue.h"
     
    6667        , m_blockToBlock(procedure.size())
    6768        , m_useCounts(procedure)
     69        , m_phiChildren(procedure)
    6870        , m_procedure(procedure)
    6971        , m_code(procedure.code())
     
    318320            return false;
    319321       
    320         // We require internals to have only one use - us.
    321         if (m_useCounts[value] != 1)
     322        // We require internals to have only one use - us. It's not clear if this should be numUses() or
     323        // numUsingInstructions(). Ideally, it would be numUsingInstructions(), except that it's not clear
     324        // if we'd actually do the right thing when matching over such a DAG pattern. For now, it simply
     325        // doesn't matter because we don't implement patterns that would trigger this.
     326        if (m_useCounts.numUses(value) != 1)
    322327            return false;
    323328
     
    526531    }
    527532
     533    // Call this method when doing two-operand lowering of a commutative operation. You have a choice of
     534    // which incoming Value is moved into the result. This will select which one is likely to be most
     535    // profitable to use as the result. Doing the right thing can have big performance consequences in tight
     536    // kernels.
     537    bool preferRightForResult(Value* left, Value* right)
     538    {
     539        // The default is to move left into result, because that's required for non-commutative instructions.
     540        // The value that we want to move into result position is the one that dies here. So, if we're
     541        // compiling a commutative operation and we know that actually right is the one that dies right here,
     542        // then we can flip things around to help coalescing, which then kills the move instruction.
     543        //
     544        // But it's more complicated:
     545        // - Used-once is a bad estimate of whether the variable dies here.
     546        // - A child might be a candidate for coalescing with this value.
     547        //
     548        // Currently, we have machinery in place to recognize super obvious forms of the latter issue.
     549
     550        bool result = m_useCounts.numUsingInstructions(right) == 1;
     551       
     552        // We recognize when a child is a Phi that has this value as one of its children. We're very
     553        // conservative about this; for example we don't even consider transitive Phi children.
     554        bool leftIsPhiWithThis = m_phiChildren[left].transitivelyUses(m_value);
     555        bool rightIsPhiWithThis = m_phiChildren[right].transitivelyUses(m_value);
     556       
     557        if (leftIsPhiWithThis != rightIsPhiWithThis)
     558            result = rightIsPhiWithThis;
     559
     560        return result;
     561    }
     562
    528563    template<
    529564        Air::Opcode opcode32, Air::Opcode opcode64, Air::Opcode opcodeDouble,
     
    599634        }
    600635
    601         // FIXME: If we're going to use a two-operand instruction, and the operand is commutative, we
    602         // should coalesce the result with the operand that is killed.
    603         // https://bugs.webkit.org/show_bug.cgi?id=151321
     636        if (commutativity == Commutative && preferRightForResult(left, right)) {
     637            append(relaxedMoveForType(m_value->type()), tmp(right), result);
     638            append(opcode, tmp(left), result);
     639            return;
     640        }
    604641       
    605642        append(relaxedMoveForType(m_value->type()), tmp(left), result);
     
    16591696
    16601697        case CheckAdd:
    1661         case CheckSub: {
     1698        case CheckSub:
     1699        case CheckMul: {
    16621700            CheckValue* checkValue = m_value->as<CheckValue>();
    16631701
     
    16851723            }
    16861724
    1687             // FIXME: Use commutativity of CheckAdd to increase the likelihood of coalescing.
    1688             // https://bugs.webkit.org/show_bug.cgi?id=151321
    1689 
    1690             append(Move, tmp(left), result);
    1691            
    16921725            Air::Opcode opcode = Air::Oops;
     1726            Commutativity commutativity = NotCommutative;
     1727            Arg::Role stackmapRole = Arg::Use;
    16931728            switch (m_value->opcode()) {
    16941729            case CheckAdd:
    16951730                opcode = opcodeForType(BranchAdd32, BranchAdd64, Air::Oops, m_value->type());
     1731                commutativity = Commutative;
    16961732                break;
    16971733            case CheckSub:
    16981734                opcode = opcodeForType(BranchSub32, BranchSub64, Air::Oops, m_value->type());
    16991735                break;
     1736            case CheckMul:
     1737                opcode = opcodeForType(BranchMul32, BranchMul64, Air::Oops, checkValue->type());
     1738                stackmapRole = Arg::LateUse;
     1739                break;
    17001740            default:
    17011741                RELEASE_ASSERT_NOT_REACHED();
     
    17081748            // https://bugs.webkit.org/show_bug.cgi?id=151228
    17091749
    1710             Arg source;
    1711             if (imm(right) && isValidForm(opcode, Arg::ResCond, Arg::Imm, Arg::Tmp))
    1712                 source = imm(right);
    1713             else
    1714                 source = tmp(right);
     1750            Vector<Arg, 2> sources;
     1751            if (imm(right) && isValidForm(opcode, Arg::ResCond, Arg::Tmp, Arg::Imm, Arg::Tmp)) {
     1752                sources.append(tmp(left));
     1753                sources.append(imm(right));
     1754            } else if (imm(right) && isValidForm(opcode, Arg::ResCond, Arg::Imm, Arg::Tmp)) {
     1755                sources.append(imm(right));
     1756                append(Move, tmp(left), result);
     1757            } else if (commutativity == Commutative && preferRightForResult(left, right)) {
     1758                sources.append(tmp(left));
     1759                append(Move, tmp(right), result);
     1760            } else {
     1761                sources.append(tmp(right));
     1762                append(Move, tmp(left), result);
     1763            }
    17151764
    17161765            // There is a really hilarious case that arises when we do BranchAdd32(%x, %x). We won't emit
     
    17371786            // LateUse here to take care of add-to-self.
    17381787           
    1739             CheckSpecial* special = ensureCheckSpecial(opcode, 3);
     1788            CheckSpecial* special = ensureCheckSpecial(opcode, 2 + sources.size(), stackmapRole);
    17401789           
    17411790            Inst inst(Patch, checkValue, Arg::special(special));
     
    17431792            inst.args.append(Arg::resCond(MacroAssembler::Overflow));
    17441793
    1745             inst.args.append(source);
     1794            inst.args.appendVector(sources);
    17461795            inst.args.append(result);
    17471796
    17481797            fillStackmap(inst, checkValue, 2);
    17491798
    1750             m_insts.last().append(WTF::move(inst));
    1751             return;
    1752         }
    1753 
    1754         case CheckMul: {
    1755             CheckValue* checkValue = m_value->as<CheckValue>();
    1756 
    1757             Value* left = checkValue->child(0);
    1758             Value* right = checkValue->child(1);
    1759 
    1760             Tmp result = tmp(m_value);
    1761 
    1762             Air::Opcode opcode =
    1763                 opcodeForType(BranchMul32, BranchMul64, Air::Oops, checkValue->type());
    1764             CheckSpecial* special = ensureCheckSpecial(opcode, 3, Arg::LateUse);
    1765 
    1766             // FIXME: Handle immediates.
    1767             // https://bugs.webkit.org/show_bug.cgi?id=151230
    1768 
    1769             append(Move, tmp(left), result);
    1770 
    1771             Inst inst(Patch, checkValue, Arg::special(special));
    1772             inst.args.append(Arg::resCond(MacroAssembler::Overflow));
    1773             inst.args.append(tmp(right));
    1774             inst.args.append(result);
    1775 
    1776             fillStackmap(inst, checkValue, 2);
    1777            
    17781799            m_insts.last().append(WTF::move(inst));
    17791800            return;
     
    18591880
    18601881    UseCounts m_useCounts;
     1882    PhiChildren m_phiChildren;
    18611883
    18621884    Vector<Vector<Inst, 4>> m_insts;
  • trunk/Source/JavaScriptCore/b3/B3UseCounts.cpp

    r191716 r192816  
    3636    : m_counts(procedure.values().size())
    3737{
    38     for (Value* value : procedure.values())
    39         ASSERT_UNUSED(value, !m_counts[value]);
     38    Vector<Value*, 64> children;
    4039    for (Value* value : procedure.values()) {
    41         for (Value* child : value->children())
    42             m_counts[child]++;
     40        children.resize(0);
     41        for (Value* child : value->children()) {
     42            m_counts[child].numUses++;
     43            children.append(child);
     44        }
     45        std::sort(children.begin(), children.end());
     46        Value* last = nullptr;
     47        for (Value* child : children) {
     48            if (child == last)
     49                continue;
     50
     51            m_counts[child].numUsingInstructions++;
     52            last = child;
     53        }
    4354    }
    4455}
  • trunk/Source/JavaScriptCore/b3/B3UseCounts.h

    r191705 r192816  
    4141    ~UseCounts();
    4242
    43     unsigned operator[](Value* value) const
    44     {
    45         return m_counts[value];
    46     }
     43    unsigned numUses(Value* value) const { return m_counts[value].numUses; }
     44    unsigned numUsingInstructions(Value* value) const { return m_counts[value].numUsingInstructions; }
     45   
    4746private:
    48     IndexMap<Value, unsigned> m_counts;
     47    struct Counts {
     48        unsigned numUses { 0 };
     49        unsigned numUsingInstructions { 0 };
     50    };
     51   
     52    IndexMap<Value, Counts> m_counts;
    4953};
    5054
Note: See TracChangeset for help on using the changeset viewer.