1 /*---------------------------------------------------------------------------*
2   Project:  Horizon
3   File:     osl_BuddyHeap.h
4 
5   Copyright (C)2009 Nintendo Co., Ltd.  All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law.  They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Rev: 15690 $
14  *---------------------------------------------------------------------------*/
15 
16 #ifndef NN_NET_OSL_BUDDYHEAP_H_
17 #define NN_NET_OSL_BUDDYHEAP_H_
18 
19 #include <nn/types.h>
20 #include <nn/os/os_MemoryBlock.h>
21 #include <nn/fnd/fnd_HeapBase.h>
22 #include <nn/fnd/fnd_Allocator.h>
23 
24 namespace nn {
25 namespace net {
26 namespace osl {
27 
28 template<size_t PageSize, s32 MaxOrder>
29 class BuddyHeapBase : public nn::fnd::HeapBase
30 {
31 protected:
32     template <s32 align, typename T>
DivUp(T x)33     static T DivUp(T x)
34     {
35         return static_cast<T>( (x + (align - 1)) / align );
36     }
37     template<size_t alignment>
RoundDown(uptr addr)38     static uptr RoundDown(uptr addr)
39     {
40         return (addr / alignment) * alignment;
41     }
42 
43     template<size_t alignment>
RoundUp(uptr addr)44     static uptr RoundUp(uptr addr)
45     {
46         return RoundDown<alignment>(addr + alignment - 1);
47     }
48 
49 public:
50     static const size_t ALIGN = 8;
51 
GetNumMaxPages(void)52     static size_t GetNumMaxPages(void)
53     {
54         return 1 << (MAX_ORDER - 1);
55     }
56 
GetMaxSize(void)57     static size_t GetMaxSize(void)
58     {
59         return GetNumMaxPages() * PAGE_SIZE;
60     }
61 
Initialize(uptr addr,size_t numPages)62     void Initialize(uptr addr, size_t numPages)
63     {
64         NN_POINTER_ASSERT(addr);
65         NN_ASSERT((addr % ALIGN) == 0);
66 
67         m_HeapStart = addr;
68         m_HeapSize  = PAGE_SIZE * numPages;
69 
70         NN_LOG_DEBUG("start %p\n", m_HeapStart);
71         NN_LOG_DEBUG("size  %p\n", m_HeapSize);
72 
73         const size_t numMaxPages    = GetNumMaxPages();
74         NN_LOG_DEBUG("numMaxPages %d\n", numMaxPages);
75         const size_t maxSize        = GetMaxSize();
76         NN_LOG_DEBUG("maxSize %d\n", maxSize);
77         const size_t numMaxOrders   = m_HeapSize / maxSize;
78         NN_LOG_DEBUG("numMaxOrders %d\n", numMaxOrders);
79 
80         NN_ASSERT(numMaxOrders > 0);
81 
82         // 管理領域の仮初期化
83         m_pPages = reinterpret_cast<Page*>(m_HeapStart);
84 
85         for(int i = 0; i < numPages; i++)
86         {
87             m_pPages[i].pNext  = NULL;
88         }
89 
90         for( int i = 0; i < MAX_ORDER; ++i )
91         {
92             m_FreeArea[i].Initialize();
93         }
94         for(int i = 0; i < numMaxOrders; i++)
95         {
96             m_FreeArea[MAX_ORDER - 1].PushBack(&(m_pPages[i * numMaxPages]));
97         }
98 
99         // 管理領域を再配置(普通は同じところに配置されるはず)
100         const size_t manageAreaSize = sizeof(Page) * numPages;
101 
102         if(m_pPages != reinterpret_cast<Page*>(AllocateByOrder(GetNearestHigherOrder(DivUp<PAGE_SIZE>(manageAreaSize)))))
103         {
104             NN_LOG_WARN("failed to allocate page managing area.");
105         }
106     }
Finalize(void)107     void Finalize(void){}
108 
AllocateByOrder(s32 order)109     void* AllocateByOrder(s32 order)
110     {
111         NN_ASSERT(order >= 0);
112 
113         NN_LOG_DEBUG("demands order %d\n", order);
114 
115 
116         Page* pPage = GetFreePage(order);
117         NN_LOG_DEBUG("pPage = %08x\n", pPage);
118 
119         if(pPage)
120         {
121             uptr addr = GetAddressFromPage(*pPage);
122 
123             NN_LOG_DEBUG("returns order %d, address 0x%08X\n", order, addr);
124 
125             return reinterpret_cast<void*>(addr);
126         }
127         else
128         {
129             return NULL;
130         }
131     }
132 
133     // Free の引数が違うので HeapBase 準拠は無理
Free(void * p,s32 order)134     void Free(void* p, s32 order)
135     {
136         if(!p)
137         {
138             return;
139         }
140 
141         NN_ASSERT((reinterpret_cast<uptr>(p) - m_HeapStart) % PAGE_SIZE == 0);
142 
143         Page* pPage = GetPageFromAddress(reinterpret_cast<uptr>(p));
144 
145         // ページのインデックスがオーダーで割り切れるか。
146         NN_ASSERT(GetIndexFromPage(*pPage) % (1 << order) == 0);
147 
148         NN_LOG_DEBUG("addr = 0x%08x, order = %d, pPage = 0x%08x\n", p, order, pPage);
149 
150         JointBuddies(pPage, order);
151     }
152 
FreeV(void *)153     virtual void FreeV(void*) { NN_ASSERT(false); }
154 
155     /*!
156         @brief このヒープが利用しているメモリ領域の先頭アドレスを返します。
157     */
GetStartAddress()158     virtual void* GetStartAddress() const { return reinterpret_cast<void*>(m_HeapStart); }
159 
160     /*!
161         @brief このヒープが利用しているメモリ領域のサイズを返します。
162     */
GetTotalSize()163     virtual size_t GetTotalSize() const { return m_HeapSize; }
164 
165     /*!
166         @brief デバッグ用にヒープの内容をダンプします。
167     */
Dump()168     virtual void Dump() const{};
HasAddress(const void * addr)169     virtual bool HasAddress(const void* addr) const
170     {
171         return m_HeapStart <= reinterpret_cast<uptr>(addr)
172            && reinterpret_cast<uptr>(addr) < m_HeapStart + m_HeapSize;
173     }
174 
GetOrder(size_t size)175     static s32 GetOrder(size_t size)
176     {
177         return GetNearestHigherOrder(DivUp<PAGE_SIZE>(size));
178     }
179 
180     static const size_t PAGE_SIZE = PageSize;
181     static const s32    MAX_ORDER = MaxOrder;
182 
183 private:
184     struct Page
185     {
186         Page* pNext;
187     };
188 
189     class PageList
190     {
191     private:
192         Page* m_pFirstPage;
193         Page* m_pLastPage;
194 
195     public:
Initialize()196         void Initialize()
197         {
198             m_pFirstPage = NULL;
199             m_pLastPage = NULL;
200         }
201 
PopFront()202         Page* PopFront()
203         {
204             NN_ASSERT(m_pFirstPage);
205 
206             Page* pPage = m_pFirstPage;
207 
208             m_pFirstPage = pPage->pNext;
209             pPage->pNext = NULL;
210 
211             if(pPage == m_pLastPage)
212             {
213                 m_pLastPage = NULL;
214             }
215 
216             return pPage;
217         }
218 
PushBack(Page * pPage)219         void PushBack(Page* pPage)
220         {
221             NN_ASSERT(!pPage->pNext);
222 
223             if(!m_pFirstPage)
224             {
225                 m_pFirstPage = pPage;
226             }
227             else
228             {
229                 m_pLastPage->pNext = pPage;
230             }
231 
232             m_pLastPage = pPage;
233         }
234 
Remove(Page * pPage)235         bool Remove(Page* pPage)
236         {
237             if(!m_pFirstPage)
238             {
239                 return false;
240             }
241 
242             Page* pPrevPage = NULL;
243             Page* page = m_pFirstPage;
244 
245             for(;;)
246             {
247                 if(page == pPage)
248                 {
249                     if(page == m_pFirstPage)
250                     {
251                         m_pFirstPage = page->pNext;
252                     }
253                     else if(page == m_pLastPage)
254                     {
255                         m_pLastPage = pPrevPage;
256                         m_pLastPage->pNext = NULL;
257                     }
258                     else
259                     {
260                         pPrevPage->pNext = page->pNext;
261                     }
262 
263                     page->pNext = NULL;
264 
265                     return true;
266                 }
267 
268                 if(page->pNext == NULL)
269                 {
270                     return false;
271                 }
272 
273                 pPrevPage = page;
274                 page = page->pNext;
275             }
276         }
277 
IsEmpty()278         bool IsEmpty() const
279         {
280             if(m_pFirstPage)
281             {
282                 return false;
283             }
284             else
285             {
286                 return true;
287             }
288         }
289     };
290 
291     PageList m_FreeArea[MAX_ORDER];
292 
293     uptr    m_HeapStart;
294     size_t  m_HeapSize;
295 
GetFreePage(s32 order)296     Page* GetFreePage(s32 order)
297     {
298         for(int i = order; i < MAX_ORDER; i++)
299         {
300             if(!(m_FreeArea[i].IsEmpty()))
301             {
302                 Page* pPage = m_FreeArea[i].PopFront();
303 
304                 NN_ASSERT(pPage);
305 
306                 DivideBuddies(pPage, order, i);
307 
308                 return pPage;
309             }
310         }
311 
312         NN_LOG_WARN("No free pages order %d\n", order);
313         return NULL;
314     }
315 
DivideBuddies(Page * pPage,s32 demandedOrder,s32 freeOrder)316     void  DivideBuddies(Page* pPage, s32 demandedOrder, s32 freeOrder)
317     {
318         for(int i = freeOrder; demandedOrder < i; i--)
319         {
320             Page* pDividedPage = &pPage[(1 << (i - 1))];;
321 
322             NN_LOG_DEBUG("divides buddy address 0x%08X and 0x%08X, order%d\n", GetAddressFromPage(*pPage), GetAddressFromPage(*pDividedPage), i);
323             m_FreeArea[i - 1].PushBack(pDividedPage);
324         }
325     }
326 
JointBuddies(Page * pPage,s32 order)327     void  JointBuddies(Page* pPage, s32 order)
328     {
329         // 最上位のオーダーの 1つ前まで
330         while(order < MAX_ORDER - 1)
331         {
332             Page* pBuddyPage = GetBuddy(pPage, order);
333 
334             // フリーリストにバディがいるか
335             if(m_FreeArea[order].Remove(pBuddyPage))
336             {
337                 // いたら、フリーリストから削除して(=ジョイントした)1つ上のオーダーでジョイントを試みる。
338 
339                 // ページが1つ上のオーダーでアラインしていなかったら、アドレスを Buddy に。
340                 if(!IsAlignedToOrder(pPage, order + 1))
341                 {
342                     pPage = pBuddyPage;
343                 }
344 
345                 NN_LOG_DEBUG("joints buddy address 0x%08X and 0x%08X, order %d", GetAddressFromPage(*pPage), GetAddressFromPage(*GetBuddy(pPage, order)), order);
346 
347 
348                 order++;
349             }
350             else
351             {
352                 // いなかったら、ジョイントできなかったので、ページをフリーリストに追加する。
353                 break;
354             }
355         }
356 
357         // Buddy が利用中だったか、最上位オーダーまでジョイントした
358         NN_LOG_DEBUG("adding a free page, address 0x%08X, order %d", GetAddressFromPage(*pPage), order );
359         m_FreeArea[order].PushBack(pPage);
360     }
361 
GetAddressFromPage(const Page & page)362     inline uptr GetAddressFromPage(const Page& page) const
363     {
364         return m_HeapStart + GetIndexFromPage(page) * PAGE_SIZE;
365     }
366 
GetPageFromAddress(uptr addr)367     inline Page* GetPageFromAddress(uptr addr)
368     {
369         return &m_pPages[(addr - m_HeapStart) / PAGE_SIZE];
370     }
371 
GetIndexFromPage(const Page & page)372     inline s32 GetIndexFromPage(const Page& page) const
373     {
374         return &page - m_pPages;
375     }
376 
GetBuddy(Page * pPage,s32 order)377     inline Page* GetBuddy(Page* pPage, s32 order)
378     {
379         // ページのインデックスが上のオーダーで割り切れるなら、右隣が Buddy
380         if(IsAlignedToOrder(pPage, order + 1))
381         {
382             return pPage + GetNumPagesByOrder(order);
383         }
384         else // でなければ、左隣が Buddy
385         {
386             return pPage - GetNumPagesByOrder(order);
387         }
388     }
389 
IsAlignedToOrder(Page * pPage,s32 order)390     inline bool IsAlignedToOrder(Page* pPage, s32 order) const
391     {
392         return GetIndexFromPage(*pPage) % GetNumPagesByOrder(order) == 0;
393     }
394 
GetNumPagesByOrder(s32 order)395     inline int GetNumPagesByOrder(s32 order) const
396     {
397         return (1 << order);
398     }
399 
GetNearestHigherOrder(size_t numPages)400     static s32 GetNearestHigherOrder(size_t numPages)
401     {
402         for(int order = 0; order <= MAX_ORDER; order++)
403         {
404             if( numPages <= (1 << order) ) {
405                 return order;
406             }
407         }
408 
409         NN_LOG_WARN("No nearest higher order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER);
410         return -1;
411     }
412 
GetNearestLowerOrder(size_t numPages)413     static s32 GetNearestLowerOrder(size_t numPages)
414     {
415         for(int order = 0; order <= MAX_ORDER; order++)
416         {
417             if( numPages < (1 << order) ) {
418                 return order - 1;
419             }
420         }
421 
422         NN_LOG_WARN("No nearest lower order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER);
423         return -1;
424     }
425 
426     Page* m_pPages;
427 };
428 
429 template <size_t PageSize, s32 MaxOrder, class LockPolicy>
430 class BuddyHeapTemplate : public BuddyHeapBase<PageSize, MaxOrder>, private LockPolicy::LockObject
431 {
432 private:
433     typedef BuddyHeapBase<PageSize, MaxOrder> Base;
434     typedef typename LockPolicy::LockObject LockObject;
435     typedef typename LockPolicy::ScopedLock ScopedLock;
436 
437 public:
BuddyHeapTemplate()438     BuddyHeapTemplate() {}
439 
440     template <class MemoryBlockT>
BuddyHeapTemplate(const MemoryBlockT & block)441     explicit BuddyHeapTemplate(const MemoryBlockT& block)
442     {
443         Initialize(block.GetAddress(), block.GetSize());
444     }
445 
Initialize(uptr addr,size_t size)446     void Initialize(uptr addr, size_t size)
447     {
448         uptr addrAligned = Base::RoundUp<Base::ALIGN>(addr);
449         size -= addrAligned - addr;
450 
451         Base::Initialize(addrAligned, size / Base::PAGE_SIZE);
452         LockObject::Initialize();
453     }
Initialize(const nn::os::MemoryBlockBase & block)454     void Initialize(const nn::os::MemoryBlockBase& block)
455     {
456         Initialize(block.GetAddress(), block.GetSize());
457     }
458 
Finalize()459     void Finalize()
460     {
461         LockObject::Finalize();
462         Base::Finalize();
463     }
464 
AllocateByOrder(s32 order)465     void* AllocateByOrder(s32 order)
466     {
467         ScopedLock lk(*this);
468         return Base::AllocateByOrder(order);
469     }
470 
471     /*!
472         @brief 拡張ヒープから確保したメモリブロックを解放します。
473 
474         @param[in] p    解放するメモリブロックの先頭アドレスを指定します。
475 
476         @return 無し。
477      */
Free(void * p,s32 order)478     void Free(void* p, s32 order)
479     {
480         ScopedLock lk(*this);
481         Base::Free(p, order);
482     }
483 
484     class Allocator;
485 };
486 
487 template <size_t PageSize, s32 MaxOrder, class LockPolicy>
488 class BuddyHeapTemplate<PageSize, MaxOrder, LockPolicy>::Allocator : public nn::fnd::IAllocator
489 {
490 private:
491     typedef BuddyHeapTemplate<PageSize, MaxOrder, LockPolicy> BuddyHeap;
492     static const size_t ALIGN = BuddyHeap::ALIGN;
493 
494 public:
Allocator(BuddyHeap & heap)495     Allocator(BuddyHeap& heap) : m_Heap(0) { Initialize(heap); }
496 
497     /*!
498         @brief 初期化をしないコンストラクタです。
499      */
Allocator()500     Allocator() : m_Heap(0) {}
501 
Initialize(BuddyHeap & heap)502     void Initialize(BuddyHeap& heap)
503     {
504         NN_TASSERT_(!this->m_Heap);
505         this->m_Heap = &heap;
506     }
507 
508     /*!
509         @brief アロケータにセットされているヒープを返します。
510         @return アロケータにセットされているヒープ
511      */
GetHeap()512     BuddyHeap* GetHeap() { return m_Heap; }
513 
514     /*!
515         @brief アロケータにセットされている拡張ヒープを返します。
516         @return アロケータにセットされている拡張ヒープ
517      */
GetHeap()518     const BuddyHeap* GetHeap() const { return m_Heap; }
519 
520     /*!
521         @brief 指定したサイズとアラインメントでメモリ領域を確保します。
522 
523         @param[in] size 確保するメモリのサイズ
524         @param[in] alignment 確保するメモリのアラインメント
525 
526         @return 確保したメモリ領域の先頭へのポインタ
527     */
528     virtual void* Allocate(size_t size, s32 alignment = nn::fnd::HeapBase::DEFAULT_ALIGNMENT)
529     {
530         NN_ASSERT(alignment <= ALIGN);
531         if (alignment > ALIGN)
532         {
533             return NULL;
534         }
535 
536         s32 order = m_Heap->GetOrder(size + ALIGN);
537         void* p = m_Heap->Allocate(order);
538         if (!p)
539         {
540             return NULL;
541         }
542         NN_LOG_DEBUG("Alloc size %d, order %d\n", size + ALIGN, order);
543         *reinterpret_cast<s32*>(p) = order;
544         return reinterpret_cast<void*>(reinterpret_cast<uptr>(p) + ALIGN);
545     }
546 
547     /*!
548         @brief メモリ領域を解放します。
549         @param[in] p 確保されているメモリ領域の先頭へのポインタ
550     */
Free(void * p)551     virtual void Free(void* p)
552     {
553         p = reinterpret_cast<void*>(reinterpret_cast<uptr>(p) - ALIGN);
554         s32 order = *reinterpret_cast<s32*>(p);
555         m_Heap->Free(p, order);
556     }
557 
558 private:
559     BuddyHeap* m_Heap;
560 };
561 
562 }
563 }
564 }
565 
566 #endif /* NN_NET_OSL_BUDDYHEAP_H_ */
567