hostsupport/hostopenvg/src/src/sfFunctionCache.h
author Matt Plumtree <matt.plumtree@nokia.com>
Wed, 06 Oct 2010 17:59:01 +0100
branchbug235_bringup_0
changeset 53 c2ef9095503a
parent 24 holdingarea/vg/2D_OpenVG_1_1_SF/ri/src/sfFunctionCache.h@a3f46bb01be2
permissions -rw-r--r--
Copy code from the holdingarea into the target locations. Some initial rework of CMakeLists.txt files, but not yet tested.

/* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and /or associated documentation files
 * (the "Materials "), to deal in the Materials without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Materials,
 * and to permit persons to whom the Materials are furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Materials.
 *
 * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR
 * THE USE OR OTHER DEALINGS IN THE MATERIALS.
 */

#ifndef __SFFUNCTIONCACHE_H
#define __SFFUNCTIONCACHE_H

// (LRU) Cache for compiled pixelpipe functions. Never takes ownership of
// any of the objects.
// \todo LRU might not be the best strategy or the best strategy might
// depend on the use-case -> create more of these.

#include "riArray.h"

#if defined(__unix__)
#   include <pthread.h>
#else
#   include <windows.h>
#endif

#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/Module.h"

namespace llvm {
    class Function;
}

namespace OpenVGRI {

template<class HashClass> class FunctionCache 
{
private:
    enum { IMPLEMENTATION_MAX_CACHE_ENTRIES = 1024 };
    //enum { MAX_GLOBAL_TIME = 10000};
    enum { MAX_GLOBAL_TIME = RI_UINT32_MAX };

    struct CacheEntry 
    {
        CacheEntry() : refCount(1) {}
        CacheEntry(HashClass aHash, ::llvm::Function* aFunc, ::llvm::GlobalVariable* aConst, RIuint32 theUT) : refCount(1) {hash = aHash; llvmFunction = aFunc; llvmConstant = aConst; ut = theUT;}
        bool operator==(const CacheEntry& rhs) const { return hash == rhs.hash; }
        bool operator<(const CacheEntry& rhs) const { return ut < rhs.ut; } // Sort by time of usage.

        HashClass           hash;
        ::llvm::Function*   llvmFunction;
        ::llvm::GlobalVariable*   llvmConstant;
        RIuint32            ut;
        RIint32             refCount;
    };

public:
    typedef CacheEntry* EntryHandle;

public:
    FunctionCache(int nMaxEntries) :
        m_time(0)
    {
        // Limit so that if the cache is too large, you must optimize the implementation.
        // Also note that the optimized pixel pipes are most likely small, so it would 
        // be better to have a fast cache and a lot of entries!
        // \note A simple optimization is to sort the usage time sort order and remove the last
        // item in the array (instead of the first).
        RI_ASSERT(nMaxEntries > 0 && nMaxEntries < IMPLEMENTATION_MAX_CACHE_ENTRIES); 
        m_nMaxEntries = nMaxEntries;
        m_entries.reserve(nMaxEntries);
    }

    ~FunctionCache() 
    {
        for (int i = 0; i < m_entries.size(); i++)
        {
            clearEntry(m_entries[i]);
        }
    }
    
    // This info is needed for the module to remove functions and deallocate executable
    // functions:
    void setLLVMInterface(::llvm::ExecutionEngine* ee, ::llvm::Module* module)
    {
        m_executionEngine = ee;
        m_module = module;
    }

    // \todo If we never need the entry index, the locking can be
    // simplified a lot.
    // Must lock the cache during this operation!
    EntryHandle findCachedItemByHash(const HashClass& hash)
    {
        acquireMutex();
        int i = findCachedItemIndexByHash(hash, true);
        if (i == -1)
        {
            releaseMutex();
            return NULL;
        }
        EntryHandle handle = &m_entries[i];
        releaseMutex();
        
        return handle;
    }

    /**
     * \brief   Caches a function. Sets the reference count to 1
     * \return  EntryHandle != NULL on success.
     * \todo    The cache must be locked during the operation.
     */
    EntryHandle cacheFunction(HashClass hash, ::llvm::Function* function, ::llvm::GlobalVariable* constant)
    {
        acquireMutex();
        RI_ASSERT(findCachedItemIndexByHash(hash) == -1);

        if (m_entries.size() == m_nMaxEntries)
        {
            if (!removeLRU())
            {
                releaseMutex();
                return NULL;
            }
        }

        m_entries.push_back(CacheEntry(hash, function, constant, m_time));
        
        RI_ASSERT(m_entries.size() > 0);
        EntryHandle ret = &m_entries[m_entries.size()-1];
        incrementGlobalTime();

        releaseMutex();
        return ret;
    }

    ::llvm::Function* getFunction(EntryHandle handle)
    {
        return handle->llvmFunction;
    }

    // \note Does not remove the function from cache!
    void releaseEntry(EntryHandle handle)
    {
        RI_ASSERT(handle->refCount > 0);
        handle->refCount--;
    }

private:
    void incrementGlobalTime()
    {
        m_time++;
        if (m_time == MAX_GLOBAL_TIME)
            rebaseUsageTime();
    }

    void incrementAccessTime(CacheEntry &entry)
    {
        entry.ut = m_time;
        incrementGlobalTime();
    }

    int findCachedItemIndexByHash(const HashClass& hash, bool reserve = false)
    {
        // \note Could just overload operator== from entry and use the Array.find function.
        for (int i = 0; i < m_entries.size(); i++)
        {
            if (m_entries[i].hash == hash)
            {
                if (reserve)
                {
                    incrementAccessTime(m_entries[i]);
                    m_entries[i].refCount++;
                }
                return i;
            }
        }
        return -1;
    }

    void clearEntry(CacheEntry& entry)
    {
        m_executionEngine->freeMachineCodeForFunction(entry.llvmFunction);
        entry.llvmFunction->eraseFromParent();
        //entry.llvmConstant->eraseFromParent();
    }

    /**
     * \return  true if LRU item was successfully removed, false otherwise.
     * \note    Could try other pipes, but it is unlikely that the cache gets filled
     *          so soon that the blit for the least recently used blit has not been
     *          released.
     * \todo    Implement drop of other cache-entries?
     */
    bool removeLRU() 
    {
        // \note This is pretty inefficient for many cache size:
        // After first LRU removal, the cache is almost sorted anyway, so
        // more efficient solution should be implemented.
        //
        m_entries.sort();

        if (m_entries[0].refCount > 0)
            return false;

        clearEntry(m_entries[0]);
        m_entries.remove(m_entries[0]);

        return true;
    }

    void rebaseUsageTime()
    {
        RIuint32 i;
        m_entries.sort();
        RI_ASSERT(m_entries.size() > 0);
        for(i = 0; i < (RIuint32)m_entries.size(); i++)
        {
            m_entries[i].ut = i;
        };
        m_time = i;
    }

    static void acquireMutex();
    static void releaseMutex();

private:
    ::llvm::Module              *m_module;
    ::llvm::ExecutionEngine     *m_executionEngine;

    RIuint32            m_time;
    Array <CacheEntry>  m_entries;
    int                 m_nMaxEntries;

    static bool             s_mutexInitialized;
#if defined(__unix__)
    static pthread_mutex_t  s_mutex;
#else
    static CRITICAL_SECTION s_mutex;
#endif
};

template<class HashClass>
bool FunctionCache<HashClass>::s_mutexInitialized = false;

#if defined(__unix__)
template<class HashClass>
pthread_mutex_t FunctionCache<HashClass>::s_mutex;
#else
template<class HashClass>
CRITICAL_SECTION FunctionCache<HashClass>::s_mutex;
#endif

template<class HashClass>
void FunctionCache<HashClass>::acquireMutex()
{
    if (!s_mutexInitialized)
    {
#if defined(__unix__)
        int ret;
        pthread_mutexattr_t attr;
        ret = pthread_mutexattr_init(&attr);	//initially not locked
        RI_ASSERT(!ret);	//check that there aren't any errors
        ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);	//count the number of recursive locks
        RI_ASSERT(!ret);	//check that there aren't any errors
        ret = pthread_mutex_init(&s_mutex, &attr);
        pthread_mutexattr_destroy(&attr);
        RI_ASSERT(!ret);	//check that there aren't more errors
#else
        ::InitializeCriticalSection(&s_mutex);
#endif
        s_mutexInitialized = true;
    }
#if defined(__unix__)
    int ret = pthread_mutex_lock(&s_mutex);
    RI_ASSERT(!ret);
#else
    ::EnterCriticalSection(&s_mutex);
#endif
}

template<class HashClass>
void FunctionCache<HashClass>::releaseMutex()
{
    RI_ASSERT(s_mutexInitialized);
#if defined(__unix__)
    int ret = pthread_mutex_unlock(&s_mutex);
    RI_ASSERT(!ret);
#else
    ::LeaveCriticalSection(&s_mutex);
#endif
}

}


#endif