1 /*---------------------------------------------------------------------------*
2   Project:  Horizon
3   File:     fnd_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: 2151 $
14  *---------------------------------------------------------------------------*/
15 
16 #ifndef NN_FND_FND_TORIABUDDYHEAP_H_
17 #define NN_FND_FND_TORIABUDDYHEAP_H_
18 
19 #include <nn/types.h>
20 #include <nn/fnd/fnd_HeapBase.h>
21 #include <nn/fnd/fnd_Allocator.h>
22 
23 namespace nn {
24 namespace fnd {
25 
26 template<size_t PageSize, s32 MaxOrder>
27 class ToriaBuddyHeapBase : public nn::fnd::HeapBase
28 {
29 protected:
30     template <s32 align, typename T>
DivUp(T x)31     static inline T DivUp(T x)
32     {
33         return static_cast<T>( (x + (align - 1)) / align );
34     }
35 
36     template<size_t alignment>
RoundDown(uptr addr)37     static inline uptr RoundDown(uptr addr)
38     {
39         return (addr / alignment) * alignment;
40     }
41 
42     template<size_t alignment>
RoundUp(uptr addr)43     static inline uptr RoundUp(uptr addr)
44     {
45         return RoundDown<alignment>(addr + alignment - 1);
46     }
47 
48 public:
49     static const size_t ALIGN = 4;
50     static const size_t MIN_SIZE = 8;
51 
52     /*!
53         @brief 総ページ数を取得します。
54     */
GetNumMaxPages(void)55     static inline size_t GetNumMaxPages(void)
56     {
57         return 1 << (MAX_ORDER - 1);
58     }
59 
60     /*!
61         @brief 総データサイズをバイト単位で取得します。
62     */
GetMaxSize(void)63     static inline size_t GetMaxSize(void)
64     {
65         return GetNumMaxPages() * PAGE_SIZE;
66     }
67 
68     /*!
69         @brief バディヒープを初期化します。
70 
71         @param[in] addr ヒープの開始アドレス
72         @param[in] numPages ページ数
73     */
Initialize(uptr addr,size_t numPages)74     void Initialize(uptr addr, size_t numPages)
75     {
76         NN_ASSERT((addr % ALIGN) == 0);
77 
78         m_HeapStart = addr;
79         m_HeapSize  = PAGE_SIZE * numPages;
80 
81         //NN_LOG_DEBUG("start %p\n", m_HeapStart);
82         //NN_LOG_DEBUG("size  %p\n", m_HeapSize);
83 
84         const size_t numMaxPages    = GetNumMaxPages();
85         //NN_LOG_DEBUG("numMaxPages %d\n", numMaxPages);
86         const size_t maxSize        = GetMaxSize();
87         //NN_LOG_DEBUG("maxSize %d\n", maxSize);
88         const size_t numMaxOrders   = m_HeapSize / maxSize;
89         //NN_LOG_DEBUG("numMaxOrders %d\n", numMaxOrders);
90 
91         NN_ASSERT(numMaxOrders > 0);
92 
93         // 管理領域の仮初期化
94         // ページごとに1つ管理領域を割り当てします。
95         m_pPages = reinterpret_cast<Page*>(m_HeapStart);
96 
97         for (size_t i = 0; i < numPages; i++)
98         {
99             m_pPages[i].pNext  = NULL;
100         }
101 
102         for (size_t i = 0; i < MAX_ORDER; ++i )
103         {
104             m_FreeArea[i].Initialize();
105         }
106         for (size_t i = 0; i < numMaxOrders; i++)
107         {
108             // 各ページの開始アドレスをフリーリストに登録します。
109             m_FreeArea[MAX_ORDER - 1].PushBack(&(m_pPages[i * numMaxPages]));
110         }
111 
112         // 管理領域を再配置(普通は同じところに配置されるはず)
113         const size_t manageAreaSize = sizeof(Page) * numPages;
114         if(m_pPages != reinterpret_cast<Page*>(AllocateByOrder(GetNearestHigherOrder(DivUp<PAGE_SIZE>(manageAreaSize)))))
115         {
116             NN_LOG_WARN("failed to allocate page managing area.");
117         }
118     }
119 
120     /*!
121         @brief バディヒープを破棄します。
122     */
Finalize(void)123     void Finalize(void){}
124 
125     /*!
126         @brief オーダーを指定してメモリを確保します。
127 
128         @param[in] order サイズ(2のべき乗)
129 
130         @return 確保したメモリへのポインタ
131     */
AllocateByOrder(s32 order)132     void* AllocateByOrder(s32 order)
133     {
134         NN_ASSERT(order >= 0);
135 
136         //NN_LOG_DEBUG("demands order %d\n", order);
137 
138 
139         Page* pPage = GetFreePage(order);
140         //NN_LOG_DEBUG("pPage = %08x\n", pPage);
141 
142         if(pPage)
143         {
144             uptr addr = GetAddressFromPage(*pPage);
145 
146             //NN_LOG_DEBUG("returns order %d, address 0x%08X\n", order, addr);
147 
148             return reinterpret_cast<void*>(addr);
149         }
150         else
151         {
152             return NULL;
153         }
154     }
155 
156     /*!
157         @brief メモリを解放します。
158 
159                (Free の引数が違うので HeapBase 準拠は無理)
160 
161         @param[in] p 解放するメモリへのポインタ
162         @param[in] order オーダー
163     */
Free(void * p,s32 order)164     void Free(void* p, s32 order)
165     {
166         if(!p)
167         {
168             return;
169         }
170 
171         NN_ASSERT((reinterpret_cast<uptr>(p) - m_HeapStart) % PAGE_SIZE == 0);
172 
173         Page* pPage = GetPageFromAddress(reinterpret_cast<uptr>(p));
174 
175         // ページのインデックスがオーダーで割り切れるか。
176         NN_ASSERT((GetIndexFromPage(*pPage) & ((1 << order)-1)) == 0);
177 
178         //NN_LOG_DEBUG("addr = 0x%08x, order = %d, pPage = 0x%08x\n", p, order, pPage);
179 
180         JointBuddies(pPage, order);
181     }
182 
FreeV(void *)183     virtual void FreeV(void*) { NN_ASSERT(false); }
184 
185     /*!
186         @brief このヒープが利用しているメモリ領域の先頭アドレスを返します。
187     */
GetStartAddress()188     virtual void* GetStartAddress() const { return reinterpret_cast<void*>(m_HeapStart); }
189 
190     /*!
191         @brief このヒープが利用しているメモリ領域のサイズを返します。
192     */
GetTotalSize()193     virtual size_t GetTotalSize() const { return m_HeapSize; }
194 
195     /*!
196         @brief デバッグ用にヒープの内容をダンプします。
197     */
Dump()198     virtual void Dump() const{};
199 
200     /*!
201         @brief 指定したアドレスがヒープ領域に含まれているかどうかを判定します。
202     */
HasAddress(const void * addr)203     virtual bool HasAddress(const void* addr) const
204     {
205         return m_HeapStart <= reinterpret_cast<uptr>(addr)
206            && reinterpret_cast<uptr>(addr) < m_HeapStart + m_HeapSize;
207     }
208 
209     /*!
210         @brief バイト単位のサイズからオーダー数を計算します。
211 
212         @param[in] size サイズ(バイト単位)
213 
214         @return オーダー数
215     */
GetOrder(size_t size)216     static inline s32 GetOrder(size_t size)
217     {
218         return GetNearestHigherOrder(DivUp<PAGE_SIZE>(size));
219     }
220 
221     static const size_t PAGE_SIZE = PageSize;
222     static const s32    MAX_ORDER = MaxOrder;
223 
224 private:
225     // ページエントリーです。
226     struct Page
227     {
228         Page* pNext;
229     };
230 
231     // 同一サイズのメモリブロック毎にリンクリストを構成します。
232     class PageList
233     {
234     private:
235         Page* m_pFirstPage;    // ページリストの先頭要素
236         Page* m_pLastPage;     // ページリストの最後尾要素
237 
238     public:
239         // ページリストを初期化します。
Initialize()240         void Initialize()
241         {
242             m_pFirstPage = NULL;
243             m_pLastPage = NULL;
244         }
245 
246         /*
247             @brief ページリストの先頭から1ページ取得します。
248 
249             @return ページリストの先頭要素。リストに何も登録されていなかったら NULL を返します。
250         */
PopFront()251         inline Page* PopFront()
252         {
253             NN_ASSERT(m_pFirstPage);
254 
255             // ページリストの最初の1ページ分を取得します。
256             Page* pPage = m_pFirstPage;
257 
258             // ページリストをひとつ進めます。
259             m_pFirstPage = pPage->pNext;
260             pPage->pNext = NULL;
261 
262             // もしページリストの先頭と末端が一緒の場合は
263             // ページリストの末尾に到達しています。
264             // (ページリストの要素が空になりました)
265             if(pPage == m_pLastPage)
266             {
267                 m_pLastPage = NULL;
268             }
269 
270             return pPage;
271         }
272 
273         /*
274             @brief ページリストの最後尾に 1 ページ追加します。
275 
276             @param[in]  pPage   追加対象のページ
277         */
PushBack(Page * pPage)278         void PushBack(Page* pPage)
279         {
280             NN_ASSERT(!pPage->pNext);
281 
282             // ページリストは空か?
283             if(!m_pFirstPage)
284             {
285                 // 先頭要素として追加します。
286                 m_pFirstPage = pPage;
287             }
288             else
289             {
290                 // リストの末尾に追加します。
291                 m_pLastPage->pNext = pPage;
292             }
293 
294             m_pLastPage = pPage;
295         }
296 
297         /*
298             @brief ページリストから 1 ページ削除します。
299 
300             @param[in] pPage 削除対象のページ
301 
302             @return 正常に削除できたら true、ページが存在しない場合は false
303         */
Remove(Page * pPage)304         bool Remove(Page* pPage)
305         {
306             NN_NULL_TASSERT_(pPage);
307 
308             // ページリストは空か?
309             if(m_pFirstPage == NULL)
310             {
311                 return false;
312             }
313 
314             Page* pPrevPage = NULL;
315             Page* page = m_pFirstPage;
316 
317             for(;;)
318             {
319                 if(page == pPage)
320                 {
321                     // 削除対象のページが見つかった場合
322                     if(page == m_pFirstPage)
323                     {
324                         // 削除対象のページがページリストの先頭だったため
325                         // リストをひとつ進めつつ要素を削除します。
326                         m_pFirstPage = page->pNext;
327                     }
328                     else if(page == m_pLastPage)
329                     {
330                         // 削除対象のページがページリストの末尾だったため
331                         // 末尾の要素をひとつ戻します。
332                         m_pLastPage = pPrevPage;
333                         m_pLastPage->pNext = NULL;
334                     }
335                     else
336                     {
337                         // 先頭~末尾の間にあるページのため
338                         // リンクリストをつなげなおします。
339                         pPrevPage->pNext = page->pNext;
340                     }
341 
342                     page->pNext = NULL;
343 
344                     return true;
345                 }
346 
347                 if(page->pNext == NULL)
348                 {
349                     return false;
350                 }
351 
352                 pPrevPage = page;
353                 page = page->pNext;
354             }
355 
356             // not reached
357         }
358 
359         /*
360             @brief ページリストは空か?
361 
362             @return ページリストに何も登録されていなければ true
363         */
IsEmpty()364         inline bool IsEmpty() const
365         {
366             return m_pFirstPage ? false : true;
367         }
368     };
369 
370     PageList m_FreeArea[MAX_ORDER];
371 
372     uptr    m_HeapStart;
373     size_t  m_HeapSize;
374 
375     /*
376         :private
377 
378         @brief Order(2^order)に対応したサイズのフリーページを取得します。
379 
380         @param[in] order オーダー(2のべき乗)
381 
382         @return 割り当てられたページエントリー
383     */
GetFreePage(s32 order)384     Page* GetFreePage(s32 order)
385     {
386         for(s32 i = order; i < MAX_ORDER; i++)
387         {
388             // 2の order 乗(べき乗)に対応したページのリストを探索します。
389             // もしそのページに空きページが登録さていない場合は、ひとつ上の order+1 乗の
390             // ページを探索します。
391             PageList &freeArea = m_FreeArea[i];
392             if(!(freeArea.IsEmpty()))
393             {
394                 // 空きメモリを見つけました。
395                 Page* pPage = freeArea.PopFront();
396 
397                 NN_NULL_TASSERT_(pPage);
398 
399                 // 要求に対して大きすぎるメモリを割り当てする場合
400                 // 使用しない部分を分割し、空きページリストへ登録しなおします
401                 DivideBuddies(pPage, order, i);
402 
403                 return pPage;
404             }
405         }
406 
407         //NN_LOG_WARN("No free pages order %d\n", order);
408         return NULL;
409     }
410 
411     /*
412         @brief 大きなページから必要な分だけを切り出します。
413 
414         @param[in] pPage 分割対象のページ
415         @param[in] demandedOrder 割り当てを必要としているorder(2のべき乗)
416         @param[in] freeOrder 分割対象のページのOrder(2のべき乗)
417     */
DivideBuddies(Page * pPage,s32 demandedOrder,s32 freeOrder)418     void DivideBuddies(Page* pPage, s32 demandedOrder, s32 freeOrder)
419     {
420         for(s32 i = freeOrder; demandedOrder < i; i--)
421         {
422             Page* pDividedPage = &pPage[(1 << (i - 1))];
423 
424             // 細切れにして空きページリストへ登録します。
425             // 4KB の要求に対して 16KB の大きな空きページが見つかった場合、差し引きして割り当てを必要としない
426             // 12KB(16KB-4KB) を 4KB, 8KB の二つのページリストに分け、フリーリストへ追加します。
427 
428             //NN_LOG_DEBUG("divides buddy address 0x%08X and 0x%08X, order%d\n", GetAddressFromPage(*pPage), GetAddressFromPage(*pDividedPage), i);
429             m_FreeArea[i - 1].PushBack(pDividedPage);
430         }
431     }
432 
433     /*
434         :private
435 
436         @brief 空きページリストへ登録します。あわせてメモリの連結処理を行います。
437 
438         @param[in] pPage 追加対象のページ
439         @param[in] order ページのサイズ
440     */
JointBuddies(Page * pPage,s32 order)441     void JointBuddies(Page* pPage, s32 order)
442     {
443         // 最上位のオーダーの 1つ前まで
444         while(order < MAX_ORDER - 1)
445         {
446             // バディ(相方)となるページを求めます。
447             Page* pBuddyPage = GetBuddy(pPage, order);
448 
449             // フリーリストにバディがいるか
450             if(m_FreeArea[order].Remove(pBuddyPage))
451             {
452                 // いたら、フリーリストから削除して(=ジョイントした)1つ上のオーダーでジョイントを試みる。
453 
454                 // ページが1つ上のオーダーでアラインしていなかったら、アドレスを Buddy に。
455                 if(!IsAlignedToOrder(pPage, order + 1))
456                 {
457                     pPage = pBuddyPage;
458                 }
459 
460                 //NN_LOG_DEBUG("joints buddy address 0x%08X and 0x%08X, order %d", GetAddressFromPage(*pPage), GetAddressFromPage(*GetBuddy(pPage, order)), order);
461 
462 
463                 order++;
464             }
465             else
466             {
467                 // いなかったら、ジョイントできなかったので、ページをフリーリストに追加する。
468                 break;
469             }
470         }
471 
472         // Buddy が利用中だったか、最上位オーダーまでジョイントした
473         //NN_LOG_DEBUG("adding a free page, address 0x%08X, order %d", GetAddressFromPage(*pPage), order );
474         m_FreeArea[order].PushBack(pPage);
475     }
476 
477     /*!
478         @brief ページが示すアドレスを取得します。
479 
480         @param[in] page ページエントリー
481 
482         @return アドレス
483     */
GetAddressFromPage(const Page & page)484     inline uptr GetAddressFromPage(const Page& page) const
485     {
486         return m_HeapStart + GetIndexFromPage(page) * PAGE_SIZE;
487     }
488 
489     /*!
490         @brief 指定したアドレスを含むページエントリーを取得します。
491 
492         @param[in] addr アドレス
493 
494         @return ページエントリー
495     */
GetPageFromAddress(uptr addr)496     inline Page* GetPageFromAddress(uptr addr)
497     {
498         // TODO: 範囲チェック
499         return &m_pPages[(addr - m_HeapStart) / PAGE_SIZE];
500     }
501 
502     /*
503         @brief 指定したページエントリーが先頭から何番目のページかを取得します。
504 
505         @param[in] page ページエントリー
506 
507         @return 先頭から何番目か
508     */
GetIndexFromPage(const Page & page)509     inline s32 GetIndexFromPage(const Page& page) const
510     {
511         return &page - m_pPages;
512     }
513 
GetBuddy(Page * pPage,s32 order)514     inline Page* GetBuddy(Page* pPage, s32 order)
515     {
516         // ページのインデックスが上のオーダーで割り切れるなら、右隣が Buddy
517         if(IsAlignedToOrder(pPage, order + 1))
518         {
519             return pPage + GetNumPagesByOrder(order);
520         }
521         else // でなければ、左隣が Buddy
522         {
523             return pPage - GetNumPagesByOrder(order);
524         }
525     }
526 
527     /*
528         @brief ページエントリーはorderの境界か?
529 
530         @param[in] pPage 探索対象のページエントリー
531         @param[in] order ページの order (2のべき乗)
532 
533         @return ページエントリーが境界にある場合は true
534     */
IsAlignedToOrder(Page * pPage,s32 order)535     inline bool IsAlignedToOrder(Page* pPage, s32 order) const
536     {
537         return (GetIndexFromPage(*pPage) & (GetNumPagesByOrder(order) - 1)) == 0;
538     }
539 
540     /*
541         @brief Orderからページ数をもとめます
542 
543         @param[in] order ページの order (2のべき乗)
544 
545         @return ページ数
546     */
GetNumPagesByOrder(s32 order)547     static inline int GetNumPagesByOrder(s32 order)
548     {
549         return (1 << order);
550     }
551 
GetBytesByOrder(s32 order)552     inline size_t GetBytesByOrder(s32 order) const
553     {
554         return (1 << order);
555     }
556 
GetNearestHigherOrder(size_t numPages)557     static inline s32 GetNearestHigherOrder(size_t numPages)
558     {
559         for(int order = 0; order <= MAX_ORDER; order++)
560         {
561             if( numPages <= (1U << order) ) {
562                 return order;
563             }
564         }
565 
566         //NN_LOG_WARN("No nearest higher order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER);
567         return -1;
568     }
569 
GetNearestLowerOrder(size_t numPages)570     static inline s32 GetNearestLowerOrder(size_t numPages)
571     {
572         for(int order = 0; order <= MAX_ORDER; order++)
573         {
574             if( numPages < (1 << order) ) {
575                 return order - 1;
576             }
577         }
578 
579         //NN_LOG_WARN("No nearest lower order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER);
580         return -1;
581     }
582 
583     Page* m_pPages;
584 };
585 
586 template <size_t PageSize, s32 MaxOrder, class LockPolicy>
587 class ToriaBuddyHeapTemplate : public ToriaBuddyHeapBase<PageSize, MaxOrder>, private LockPolicy::LockObject
588 {
589 private:
590     typedef ToriaBuddyHeapBase<PageSize, MaxOrder> Base;
591     typedef typename LockPolicy::LockObject LockObject;
592     typedef typename LockPolicy::ScopedLock ScopedLock;
593 
594 public:
ToriaBuddyHeapTemplate()595     ToriaBuddyHeapTemplate() {}
596 
597     template <class MemoryBlockT>
ToriaBuddyHeapTemplate(const MemoryBlockT & block)598     explicit ToriaBuddyHeapTemplate(const MemoryBlockT& block)
599     {
600         Initialize(block.GetAddress(), block.GetSize());
601     }
602 
603     /*
604         @brief バディヒープを初期化します。
605 
606         @param[in] addr ヒープに割り当てる領域のアドレス
607         @param[in] size ヒープに割り当てる領域のサイズ
608     */
Initialize(uptr addr,size_t size)609     void Initialize(uptr addr, size_t size)
610     {
611         NN_TASSERT_(size >= Base::MIN_SIZE);
612         Base::Initialize(addr, size / Base::PAGE_SIZE);
613         LockObject::Initialize();
614     }
615 
616     /*
617         @brief バディヒープを破棄します。
618     */
Finalize()619     void Finalize()
620     {
621         LockObject::Finalize();
622         Base::Finalize();
623     }
624 
625     /*!
626         @brief バディヒープからメモリを確保します。
627 
628         @param[in] order サイズ(2のるい乗)
629 
630         @return 確保したメモリへのポインタ
631     */
AllocateByOrder(s32 order)632     inline void* AllocateByOrder(s32 order)
633     {
634         ScopedLock lk(*this);
635         return Base::AllocateByOrder(order);
636     }
637 
638     /*!
639         @brief バディヒープから確保したメモリブロックを解放します。
640 
641         @param[in] p 解放するメモリブロックの先頭アドレスを指定します。
642         @param[in] order サイズ(2のるい乗)
643      */
Free(void * p,s32 order)644     void Free(void* p, s32 order)
645     {
646         ScopedLock lk(*this);
647         Base::Free(p, order);
648     }
649 
650     class Allocator;
651 };
652 
653 template <size_t PageSize, s32 MaxOrder, class LockPolicy>
654 class ToriaBuddyHeapTemplate<PageSize, MaxOrder, LockPolicy>::Allocator /*: public nn::fnd::IAllocator*/
655 {
656 private:
657     typedef ToriaBuddyHeapTemplate<PageSize, MaxOrder, LockPolicy> BuddyHeap;
658 
659 public:
Allocator(BuddyHeap & heap)660     Allocator(BuddyHeap& heap) : m_Heap(0) { Initialize(heap); }
661 
662     /*!
663         @brief 初期化をしないコンストラクタです。
664      */
Allocator()665     Allocator() : m_Heap(0) {}
666 
667     /*!
668         @breif アロケータを初期化します。
669 
670         @param[in] heap バディヒープ
671     */
Initialize(BuddyHeap & heap)672     void Initialize(BuddyHeap& heap)
673     {
674         NN_TASSERT_(!this->m_Heap);
675         this->m_Heap = &heap;
676     }
677 
678     /*!
679         @brief アロケータにセットされているヒープを返します。
680 
681         @return アロケータにセットされているヒープ
682      */
GetHeap()683     inline BuddyHeap* GetHeap() { return m_Heap; }
684 
685     /*!
686         @brief アロケータにセットされている拡張ヒープを返します。
687 
688         @return アロケータにセットされている拡張ヒープ
689      */
GetHeap()690     inline const BuddyHeap* GetHeap() const { return m_Heap; }
691 
692     /*!
693         @brief 指定したサイズとアラインメントでメモリ領域を確保します。
694 
695         @param[in] size 確保するメモリのサイズ
696         @param[in] alignment 確保するメモリのアラインメント
697 
698         @return 確保したメモリ領域の先頭へのポインタ
699     */
Allocate(s32 * pOutOrder,size_t size)700     void* Allocate(s32 *pOutOrder, size_t size)
701     {
702         s32 order = m_Heap->GetOrder(size);
703         void* p = m_Heap->AllocateByOrder(order);
704         if (!p)
705         {
706             return false;
707         }
708         //NN_LOG_DEBUG("Alloc size %d, order %d\n", size, order);
709         *pOutOrder = order;
710         return reinterpret_cast<void*>(reinterpret_cast<uptr>(p));
711     }
712 
713     /*!
714         @brief メモリ領域を解放します。
715 
716         @param[in] p 確保されているメモリ領域の先頭へのポインタ
717     */
Free(void * p,s32 order)718     inline void Free(void* p, s32 order)
719     {
720         m_Heap->Free(p, order);
721     }
722 
GetBytesByOrder(s32 order)723     inline size_t GetBytesByOrder(s32 order) const
724     {
725         return m_Heap->GetBytesByOrder(order);
726     }
727 
728 
729 private:
730     BuddyHeap* m_Heap;     //!< バディヒープ
731 };
732 
733 }}
734 
735 #endif /* NN_FND_FND_TORIABUDDYHEAP_H_ */
736