1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#ifndef DFGRegisterBank_h
27#define DFGRegisterBank_h
28
29#if ENABLE(DFG_JIT)
30
31#include <dfg/DFGJITCompiler.h>
32
33namespace JSC { namespace DFG {
34
35// === RegisterBank ===
36//
37// This class is used to implement the GPR and FPR register banks.
38// All registers have two pieces of state associated with them:
39// a lock count (used to indicate this register is already in use
40// in code generation of the current node, and cannot be spilled or
41// allocated as a temporary), and VirtualRegister 'name', recording
42// which value (if any) a machine register currently holds.
43// Either or both of these pieces of information may be valid for a
44// given register. A register may be:
45//
46//  - unlocked, and unnamed: Available for allocation.
47//  - locked, but unnamed:   Already allocated as a temporary or
48//                           result for the current node.
49//  - unlocked, but named:   Contains the result of a prior operation,
50//                           not yet in use for this node,
51//  - locked, but named:     Contains the result of a prior operation,
52//                           already allocated as a operand to the
53//                           current operation.
54//
55// For every named register we also record a hint value indicating
56// the order in which registers should be selected to be spilled;
57// registers that can be more cheaply spilled and/or filled should
58// be selected first.
59//
60// Locking register is a strong retention mechanism; a locked register
61// will never be reallocated (this is used to ensure the operands to
62// the current node are in registers). Naming, conversely, in a weak
63// retention mechanism - allocating a register may force a named value
64// to be spilled.
65//
66// All named values must be given a hint that is greater than Min and
67// less than Max.
68template<typename RegID, size_t NUM_REGS, typename SpillHint, SpillHint SpillHintMin, SpillHint SpillHintMax>
69class RegisterBank {
70public:
71    RegisterBank()
72        : m_lastAllocated(NUM_REGS - 1)
73    {
74    }
75
76    // Allocate a register - this function finds an unlocked register,
77    // locks it, and returns it. If any named registers exist, one
78    // of these should be selected to be allocated. If all unlocked
79    // registers are named, then one of the named registers will need
80    // to be spilled. In this case the register selected to be spilled
81    // will be one of the registers that has the lowest 'spillOrder'
82    // cost associated with it.
83    //
84    // This method select the register to be allocated, and calls the
85    // private 'allocateInternal' method to update internal data
86    // structures accordingly.
87    RegID allocate(VirtualRegister &spillMe)
88    {
89        uint32_t currentLowest = NUM_REGS;
90        SpillHint currentSpillOrder = SpillHintMax;
91
92        // Scan through all register, starting at the last allocated & looping around.
93        ASSERT(m_lastAllocated < NUM_REGS);
94
95        // This loop is broken into two halves, looping from the last allocated
96        // register (the register returned last time this method was called) to
97        // the maximum register value, then from 0 to the last allocated.
98        // This implements a simple round-robin like approach to try to reduce
99        // thrash, and minimize time spent scanning locked registers in allocation.
100        // If a unlocked and unnamed register is found return it immediately.
101        // Otherwise, find the first unlocked register with the lowest spillOrder.
102        for (uint32_t i = m_lastAllocated + 1; i < NUM_REGS; ++i) {
103            // (1) If the current register is locked, it is not a candidate.
104            if (m_data[i].lockCount)
105                continue;
106            // (2) If the current register's spill order is 0, pick this! – unassigned registers have spill order 0.
107            SpillHint spillOrder = m_data[i].spillOrder;
108            if (!spillOrder)
109                return allocateInternal(i, spillMe);
110            // If this register is better (has a lower spill order value) than any prior
111            // candidate, then record it.
112            if (spillOrder < currentSpillOrder) {
113                currentSpillOrder = spillOrder;
114                currentLowest = i;
115            }
116        }
117        // Loop over the remaining entries.
118        for (uint32_t i = 0; i <= m_lastAllocated; ++i) {
119            if (m_data[i].lockCount)
120                continue;
121            SpillHint spillOrder = m_data[i].spillOrder;
122            if (!spillOrder)
123                return allocateInternal(i, spillMe);
124            if (spillOrder < currentSpillOrder) {
125                currentSpillOrder = spillOrder;
126                currentLowest = i;
127            }
128        }
129
130        // Deadlock check - this could only occur is all registers are locked!
131        ASSERT(currentLowest != NUM_REGS && currentSpillOrder != SpillHintMax);
132        // There were no available registers; currentLowest will need to be spilled.
133        return allocateInternal(currentLowest, spillMe);
134    }
135
136    // retain/release - these methods are used to associate/disassociate names
137    // with values in registers. retain should only be called on locked registers.
138    void retain(RegID reg, VirtualRegister name, SpillHint spillOrder)
139    {
140        // 'reg' must be a valid, locked register.
141        ASSERT(reg < NUM_REGS);
142        ASSERT(m_data[reg].lockCount);
143        // 'reg' should not currently be named, the new name must be valid.
144        ASSERT(m_data[reg].name == InvalidVirtualRegister);
145        ASSERT(name != InvalidVirtualRegister);
146        // 'reg' should not currently have a spillOrder, the new spill order must be valid.
147        ASSERT(spillOrder && spillOrder < SpillHintMax);
148        ASSERT(m_data[reg].spillOrder == SpillHintMin);
149
150        m_data[reg].name = name;
151        m_data[reg].spillOrder = spillOrder;
152    }
153    void release(RegID reg)
154    {
155        // 'reg' must be a valid register.
156        ASSERT(reg < NUM_REGS);
157        // 'reg' should currently be named.
158        ASSERT(m_data[reg].name != InvalidVirtualRegister);
159        // 'reg' should currently have a valid spill order.
160        ASSERT(m_data[reg].spillOrder > SpillHintMin && m_data[reg].spillOrder < SpillHintMax);
161
162        m_data[reg].name = InvalidVirtualRegister;
163        m_data[reg].spillOrder = SpillHintMin;
164    }
165
166    // lock/unlock register, ensures that they are not spilled.
167    void lock(RegID reg)
168    {
169        ASSERT(reg < NUM_REGS);
170        ++m_data[reg].lockCount;
171        ASSERT(m_data[reg].lockCount);
172    }
173    void unlock(RegID reg)
174    {
175        ASSERT(reg < NUM_REGS);
176        ASSERT(m_data[reg].lockCount);
177        --m_data[reg].lockCount;
178    }
179    bool isLocked(RegID reg)
180    {
181        ASSERT(reg < NUM_REGS);
182        return m_data[reg].lockCount;
183    }
184
185    // Get the name (VirtualRegister) associated with the
186    // given register (or InvalidVirtualRegister for none).
187    VirtualRegister name(RegID reg)
188    {
189        ASSERT(reg < NUM_REGS);
190        return m_data[reg].name;
191    }
192
193#ifndef NDEBUG
194    void dump()
195    {
196        // For each register, print the VirtualRegister 'name'.
197        for (uint32_t i =0; i < NUM_REGS; ++i) {
198            if (m_data[i].name != InvalidVirtualRegister)
199                fprintf(stderr, "[%02d]", m_data[i].name);
200            else
201                fprintf(stderr, "[--]");
202        }
203        fprintf(stderr, "\n");
204    }
205#endif
206
207private:
208    // Used by 'allocate', above, to update inforamtion in the map.
209    RegID allocateInternal(uint32_t i, VirtualRegister &spillMe)
210    {
211        // 'i' must be a valid, unlocked register.
212        ASSERT(i < NUM_REGS && !m_data[i].lockCount);
213
214        // Return the VirtualRegister of the named value currently stored in
215        // the register being returned - or InvalidVirtualRegister if none.
216        spillMe = m_data[i].name;
217
218        // Clear any name/spillOrder currently associated with the register,
219        m_data[i] = MapEntry();
220        m_data[i].lockCount = 1;
221        // Mark the register as locked (with a lock count of 1).
222        m_lastAllocated = i;
223        return (RegID)i;
224    }
225
226    // === MapEntry ===
227    //
228    // This structure provides information for an individual machine register
229    // being managed by the RegisterBank. For each register we track a lock
230    // count, name and spillOrder hint.
231    struct MapEntry {
232        MapEntry()
233            : name(InvalidVirtualRegister)
234            , spillOrder(SpillHintMin)
235            , lockCount(0)
236        {
237        }
238
239        VirtualRegister name;
240        SpillHint spillOrder;
241        uint32_t lockCount;
242    };
243
244    // Holds the current status of all registers.
245    MapEntry m_data[NUM_REGS];
246    // Used to to implement a simple round-robin like allocation scheme.
247    uint32_t m_lastAllocated;
248};
249
250} } // namespace JSC::DFG
251
252#endif
253#endif
254