1/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkPurgeableMemoryBlock.h"
9
10#include <mach/mach.h>
11
12bool SkPurgeableMemoryBlock::IsSupported() {
13    return true;
14}
15
16#ifdef SK_DEBUG
17bool SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks() {
18    return true;
19}
20
21bool SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks() {
22    // Unused.
23    int state = 0;
24    kern_return_t ret = vm_purgable_control(mach_task_self(), 0, VM_PURGABLE_PURGE_ALL, &state);
25    return ret == KERN_SUCCESS;
26}
27
28bool SkPurgeableMemoryBlock::purge() {
29    return false;
30}
31#endif
32
33static size_t round_to_page_size(size_t size) {
34    const size_t mask = 4096 - 1;
35    return (size + mask) & ~mask;
36}
37
38SkPurgeableMemoryBlock::SkPurgeableMemoryBlock(size_t size)
39    : fAddr(NULL)
40    , fSize(round_to_page_size(size))
41    , fPinned(false) {
42}
43
44SkPurgeableMemoryBlock::~SkPurgeableMemoryBlock() {
45    SkDEBUGCODE(kern_return_t ret =) vm_deallocate(mach_task_self(),
46                                                   reinterpret_cast<vm_address_t>(fAddr),
47                                                   static_cast<vm_size_t>(fSize));
48#ifdef SK_DEBUG
49    if (ret != KERN_SUCCESS) {
50        SkDebugf("SkPurgeableMemoryBlock destructor failed to deallocate.\n");
51    }
52#endif
53}
54
55void* SkPurgeableMemoryBlock::pin(SkPurgeableMemoryBlock::PinResult* pinResult) {
56    SkASSERT(!fPinned);
57    SkASSERT(pinResult != NULL);
58    if (NULL == fAddr) {
59        vm_address_t addr = 0;
60        kern_return_t ret = vm_allocate(mach_task_self(), &addr, static_cast<vm_size_t>(fSize),
61                                        VM_FLAGS_PURGABLE | VM_FLAGS_ANYWHERE);
62        if (KERN_SUCCESS == ret) {
63            fAddr = reinterpret_cast<void*>(addr);
64            *pinResult = kUninitialized_PinResult;
65            fPinned = true;
66        } else {
67            fAddr = NULL;
68        }
69    } else {
70        int state = VM_PURGABLE_NONVOLATILE;
71        kern_return_t ret = vm_purgable_control(mach_task_self(),
72                                                reinterpret_cast<vm_address_t>(fAddr),
73                                                VM_PURGABLE_SET_STATE, &state);
74        if (ret != KERN_SUCCESS) {
75            fAddr = NULL;
76            fPinned = false;
77            return NULL;
78        }
79
80        fPinned = true;
81
82        if (state & VM_PURGABLE_EMPTY) {
83            *pinResult = kUninitialized_PinResult;
84        } else {
85            *pinResult = kRetained_PinResult;
86        }
87    }
88    return fAddr;
89}
90
91void SkPurgeableMemoryBlock::unpin() {
92    SkASSERT(fPinned);
93    int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT;
94    SkDEBUGCODE(kern_return_t ret =) vm_purgable_control(mach_task_self(),
95                                                         reinterpret_cast<vm_address_t>(fAddr),
96                                                         VM_PURGABLE_SET_STATE, &state);
97    fPinned = false;
98
99#ifdef SK_DEBUG
100    if (ret != KERN_SUCCESS) {
101        SkDebugf("SkPurgeableMemoryBlock::unpin() failed.\n");
102    }
103#endif
104}
105