<commit_before>/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <stack>
#include <string.h>
#include <osl/process.h>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/salobj.h>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/atkbridge.hxx>
#include <unx/gtk/gtkprn.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <headless/svpvd.hxx>
#include <headless/svpbmp.hxx>
#include <vcl/apptypes.hxx>
#include <generic/genpspgraphics.h>
#include <rtl/strbuf.hxx>
#include <rtl/uri.hxx>

#include <vcl/settings.hxx>

#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>

#include "gtkprintwrapper.hxx"

extern "C"
{
    #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
    static void GdkThreadsEnter()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsEnter();
    }
    static void GdkThreadsLeave()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsLeave();
    }

    VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
    {
        SAL_INFO(
            "vcl.gtk",
            "create vcl plugin instance with gtk version " << gtk_major_version
                << " " << gtk_minor_version << " " << gtk_micro_version);
        if( gtk_major_version < 2 || // very unlikely sanity check
            ( gtk_major_version == 2 && gtk_minor_version < 4 ) )
        {
            g_warning("require a newer gtk than %d.%d for gdk_threads_set_lock_functions", (int) gtk_major_version, gtk_minor_version);
            return NULL;
        }

        /* #i92121# workaround deadlocks in the X11 implementation
        */
        static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
        /* #i90094#
           from now on we know that an X connection will be
           established, so protect X against itself
        */
        if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
            XInitThreads();

#if GTK_CHECK_VERSION(3,0,0)
        const gchar* pVersion = gtk_check_version( 3, 2, 0 );
#else
        const gchar* pVersion = gtk_check_version( 2, 2, 0 );
#endif
        if( pVersion )
        {
            SAL_WARN("vcl.gtk", "gtk version conflict: " << pVersion);
            return NULL;
        }

        GtkYieldMutex *pYieldMutex;

        // init gdk thread protection
        if ( !g_thread_supported() )
            g_thread_init( NULL );

        gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
        SAL_INFO("vcl.gtk", "Hooked gdk threads locks");

        pYieldMutex = new GtkYieldMutex();

        gdk_threads_init();

        GtkInstance* pInstance = new GtkInstance( pYieldMutex );
        SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);

        // Create SalData, this does not leak
        new GtkData( pInstance );

        return pInstance;
    }
}

#if GTK_CHECK_VERSION(3,0,0)
static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
{
    VclInputFlags nType = VclInputFlags::NONE;
    switch( pEvent->type )
    {
    case GDK_MOTION_NOTIFY:
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
    case GDK_SCROLL:
        nType = VclInputFlags::MOUSE;
        break;
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
        nType = VclInputFlags::KEYBOARD;
        break;
    case GDK_EXPOSE:
        nType = VclInputFlags::PAINT;
        break;
    default:
        nType = VclInputFlags::OTHER;
        break;
    }
    return nType;
}
#endif

GtkInstance::GtkInstance( SalYieldMutex* pMutex )
#if GTK_CHECK_VERSION(3,0,0)
    : SvpSalInstance( pMutex )
#else
    : X11SalInstance( pMutex )
#endif
    , bNeedsInit(true)
{
}

//We want to defer initializing gtk until we are after uno has been
//bootstrapped so we can ask the config what the UI language is so that we can
//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
//UI in a LTR locale
void GtkInstance::AfterAppInit()
{
    OUString aLocaleString(Application::GetSettings().GetUILanguageTag().getGlibcLocaleString(".UTF-8"));
    if (!aLocaleString.isEmpty())
    {
        OUString envVar("LANGUAGE");
        osl_setEnvironment(envVar.pData, aLocaleString.pData);
    }
    EnsureInit();
}

void GtkInstance::EnsureInit()
{
    if (!bNeedsInit)
        return;
    // initialize SalData
    GtkData *pSalData = GetGtkSalData();
    pSalData->Init();
    GtkData::initNWF();

    InitAtkBridge();

    bNeedsInit = false;
}

GtkInstance::~GtkInstance()
{
    while( !m_aTimers.empty() )
        delete *m_aTimers.begin();
    DeInitAtkBridge();
}

SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, sal_uLong nStyle )
{
    EnsureInit();
    return new GtkSalFrame( pParent, nStyle );
}

SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, sal_uLong )
{
    EnsureInit();
    return new GtkSalFrame( pParentData );
}

SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
{
    EnsureInit();
#if !GTK_CHECK_VERSION(3,0,0)
    // there is no method to set a visual for a GtkWidget
    // so we need the X11SalObject in that case
    if( pWindowData )
        return X11SalObject::CreateObject( pParent, pWindowData, bShow );
#else
    (void)pWindowData;
    //FIXME: Missing CreateObject functionality ...
#endif

    return new GtkSalObject( static_cast<GtkSalFrame*>(pParent), bShow );
}

extern "C"
{
    typedef void*(* getDefaultFnc)();
    typedef void(* addItemFnc)(void *, const char *);
}

void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService)
{
    EnsureInit();
    OString sGtkURL;
    rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
    if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
        sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
    else
    {
        //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
        //Decode %XX components
        OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
        //Convert back to system locale encoding
        OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
        //Encode to an escaped ASCII-encoded URI
        gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), NULL, NULL);
        sGtkURL = OString(g_uri);
        g_free(g_uri);
    }
    GtkRecentManager *manager = gtk_recent_manager_get_default ();
    gtk_recent_manager_add_item (manager, sGtkURL.getStr());
    (void)rMimeType;
    (void)rDocumentService;
}

SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
    ImplJobSetup* pSetupData )
{
    EnsureInit();
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
    mbPrinterInit = true;
    // create and initialize SalInfoPrinter
    PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
    configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
    return pPrinter;
#else
    return Superclass_t::CreateInfoPrinter( pQueueInfo, pSetupData );
#endif
}

SalPrinter* GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
    EnsureInit();
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
    mbPrinterInit = true;
    return new GtkSalPrinter( pInfoPrinter );
#else
    return Superclass_t::CreatePrinter( pInfoPrinter );
#endif
}

/*
 * These methods always occur in pairs
 * A ThreadsEnter is followed by a ThreadsLeave
 * We need to queue up the recursive lock count
 * for each pair, so we can accurately restore
 * it later.
 */
void GtkYieldMutex::ThreadsEnter()
{
    acquire();
    if( !aYieldStack.empty() )
    { /* Previously called ThreadsLeave() */
        sal_uLong nCount = aYieldStack.front();
        aYieldStack.pop_front();
        while( nCount-- > 1 )
            acquire();
    }
}

void GtkYieldMutex::ThreadsLeave()
{
    aYieldStack.push_front( mnCount );

    SAL_WARN_IF(
        mnThreadId && mnThreadId != osl::Thread::getCurrentIdentifier(),
        "vcl.gtk", "other thread " << mnThreadId << " owns the mutex");

    while( mnCount > 1 )
        release();
    release();
}

SalVirtualDevice* GtkInstance::CreateVirtualDevice( SalGraphics *pG,
                                                    long &nDX, long &nDY,
                                                    sal_uInt16 nBitCount,
                                                    const SystemGraphicsData *pGd )
{
    EnsureInit();
#if GTK_CHECK_VERSION(3,0,0)
    (void)pG; (void) pGd;
    SvpSalVirtualDevice* pNew = new SvpSalVirtualDevice( nBitCount );
    pNew->SetSize( nDX, nDY );
    return pNew;
#else
    GtkSalGraphics *pGtkSalGraphics = dynamic_cast<GtkSalGraphics*>(pG);
    assert(pGtkSalGraphics);
    return CreateX11VirtualDevice(pG, nDX, nDY, nBitCount, pGd,
            new GtkSalGraphics(pGtkSalGraphics->GetGtkFrame(), pGtkSalGraphics->GetGtkWidget()));
#endif
}

SalBitmap* GtkInstance::CreateSalBitmap()
{
    EnsureInit();
#if GTK_CHECK_VERSION(3,0,0)
    return new SvpSalBitmap();
#else
    return X11SalInstance::CreateSalBitmap();
#endif
}

#ifdef ENABLE_GMENU_INTEGRATION

SalMenu* GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
{
    EnsureInit();
    GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
    pSalMenu->SetMenu( pVCLMenu );
    return pSalMenu;
}

void GtkInstance::DestroyMenu( SalMenu* pMenu )
{
    EnsureInit();
    delete pMenu;
}

SalMenuItem* GtkInstance::CreateMenuItem( const SalItemParams* pItemData )
{
    EnsureInit();
    return new GtkSalMenuItem( pItemData );
}

void GtkInstance::DestroyMenuItem( SalMenuItem* pItem )
{
    EnsureInit();
    delete pItem;
}

#else // not ENABLE_GMENU_INTEGRATION

SalMenu*     GtkInstance::CreateMenu( bool, Menu* )          { return NULL; }
void         GtkInstance::DestroyMenu( SalMenu* )                {}
SalMenuItem* GtkInstance::CreateMenuItem( const SalItemParams* ) { return NULL; }
void         GtkInstance::DestroyMenuItem( SalMenuItem* )        {}

#endif

SalTimer* GtkInstance::CreateSalTimer()
{
    EnsureInit();
    GtkSalTimer *pTimer = new GtkSalTimer();
    m_aTimers.push_back( pTimer );
    return pTimer;
}

void GtkInstance::RemoveTimer (SalTimer *pTimer)
{
    EnsureInit();
    std::vector<GtkSalTimer *>::iterator it;
    it = std::find( m_aTimers.begin(), m_aTimers.end(), pTimer );
    if( it != m_aTimers.end() )
        m_aTimers.erase( it );
}

void GtkInstance::Yield( bool bWait, bool bHandleAllCurrentEvents )
{
    EnsureInit();
    GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
}

bool GtkInstance::IsTimerExpired()
{
    EnsureInit();
    for( std::vector<GtkSalTimer *>::iterator it = m_aTimers.begin();
         it != m_aTimers.end(); ++it )
        if( (*it)->Expired() )
            return true;

    return false;
}

bool GtkInstance::AnyInput( VclInputFlags nType )
{
    EnsureInit();
    if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
        return true;
#if !GTK_CHECK_VERSION(3,0,0)
    bool bRet = X11SalInstance::AnyInput(nType);
#else
    if (!gdk_events_pending())
        return false;

    if (nType == VCL_INPUT_ANY)
        return true;

    bool bRet = false;
    std::stack<GdkEvent*> aEvents;
    GdkEvent *pEvent = NULL;
    while ((pEvent = gdk_event_get()))
    {
        aEvents.push(pEvent);
        VclInputFlags nEventType = categorizeEvent(pEvent);
        if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
        {
            bRet = true;
            break;
        }
    }

    while (!aEvents.empty())
    {
        pEvent = aEvents.top();
        gdk_event_put(pEvent);
        gdk_event_free(pEvent);
        aEvents.pop();
    }
#endif
    return bRet;
}

GenPspGraphics *GtkInstance::CreatePrintGraphics()
{
    EnsureInit();
    return new GenPspGraphics();
}

std::shared_ptr<vcl::unx::GtkPrintWrapper>
GtkInstance::getPrintWrapper() const
{
    if (!m_xPrintWrapper)
        m_xPrintWrapper.reset(new vcl::unx::GtkPrintWrapper);
    return m_xPrintWrapper;
}

#if GTK_CHECK_VERSION(3,0,0)
#include "../../../headless/svpinst.cxx"
#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
<commit_msg>don't need to include svpinst.cxx anymore<commit_after>/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <stack>
#include <string.h>
#include <osl/process.h>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/salobj.h>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/atkbridge.hxx>
#include <unx/gtk/gtkprn.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <headless/svpvd.hxx>
#include <headless/svpbmp.hxx>
#include <vcl/apptypes.hxx>
#include <generic/genpspgraphics.h>
#include <rtl/strbuf.hxx>
#include <rtl/uri.hxx>

#include <vcl/settings.hxx>

#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>

#include "gtkprintwrapper.hxx"

extern "C"
{
    #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
    static void GdkThreadsEnter()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsEnter();
    }
    static void GdkThreadsLeave()
    {
        GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
        pYieldMutex->ThreadsLeave();
    }

    VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
    {
        SAL_INFO(
            "vcl.gtk",
            "create vcl plugin instance with gtk version " << gtk_major_version
                << " " << gtk_minor_version << " " << gtk_micro_version);
        if( gtk_major_version < 2 || // very unlikely sanity check
            ( gtk_major_version == 2 && gtk_minor_version < 4 ) )
        {
            g_warning("require a newer gtk than %d.%d for gdk_threads_set_lock_functions", (int) gtk_major_version, gtk_minor_version);
            return NULL;
        }

        /* #i92121# workaround deadlocks in the X11 implementation
        */
        static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
        /* #i90094#
           from now on we know that an X connection will be
           established, so protect X against itself
        */
        if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
            XInitThreads();

#if GTK_CHECK_VERSION(3,0,0)
        const gchar* pVersion = gtk_check_version( 3, 2, 0 );
#else
        const gchar* pVersion = gtk_check_version( 2, 2, 0 );
#endif
        if( pVersion )
        {
            SAL_WARN("vcl.gtk", "gtk version conflict: " << pVersion);
            return NULL;
        }

        GtkYieldMutex *pYieldMutex;

        // init gdk thread protection
        if ( !g_thread_supported() )
            g_thread_init( NULL );

        gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
        SAL_INFO("vcl.gtk", "Hooked gdk threads locks");

        pYieldMutex = new GtkYieldMutex();

        gdk_threads_init();

        GtkInstance* pInstance = new GtkInstance( pYieldMutex );
        SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);

        // Create SalData, this does not leak
        new GtkData( pInstance );

        return pInstance;
    }
}

#if GTK_CHECK_VERSION(3,0,0)
static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
{
    VclInputFlags nType = VclInputFlags::NONE;
    switch( pEvent->type )
    {
    case GDK_MOTION_NOTIFY:
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
    case GDK_SCROLL:
        nType = VclInputFlags::MOUSE;
        break;
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
        nType = VclInputFlags::KEYBOARD;
        break;
    case GDK_EXPOSE:
        nType = VclInputFlags::PAINT;
        break;
    default:
        nType = VclInputFlags::OTHER;
        break;
    }
    return nType;
}
#endif

GtkInstance::GtkInstance( SalYieldMutex* pMutex )
#if GTK_CHECK_VERSION(3,0,0)
    : SvpSalInstance( pMutex )
#else
    : X11SalInstance( pMutex )
#endif
    , bNeedsInit(true)
{
}

//We want to defer initializing gtk until we are after uno has been
//bootstrapped so we can ask the config what the UI language is so that we can
//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
//UI in a LTR locale
void GtkInstance::AfterAppInit()
{
    OUString aLocaleString(Application::GetSettings().GetUILanguageTag().getGlibcLocaleString(".UTF-8"));
    if (!aLocaleString.isEmpty())
    {
        OUString envVar("LANGUAGE");
        osl_setEnvironment(envVar.pData, aLocaleString.pData);
    }
    EnsureInit();
}

void GtkInstance::EnsureInit()
{
    if (!bNeedsInit)
        return;
    // initialize SalData
    GtkData *pSalData = GetGtkSalData();
    pSalData->Init();
    GtkData::initNWF();

    InitAtkBridge();

    bNeedsInit = false;
}

GtkInstance::~GtkInstance()
{
    while( !m_aTimers.empty() )
        delete *m_aTimers.begin();
    DeInitAtkBridge();
}

SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, sal_uLong nStyle )
{
    EnsureInit();
    return new GtkSalFrame( pParent, nStyle );
}

SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, sal_uLong )
{
    EnsureInit();
    return new GtkSalFrame( pParentData );
}

SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
{
    EnsureInit();
#if !GTK_CHECK_VERSION(3,0,0)
    // there is no method to set a visual for a GtkWidget
    // so we need the X11SalObject in that case
    if( pWindowData )
        return X11SalObject::CreateObject( pParent, pWindowData, bShow );
#else
    (void)pWindowData;
    //FIXME: Missing CreateObject functionality ...
#endif

    return new GtkSalObject( static_cast<GtkSalFrame*>(pParent), bShow );
}

extern "C"
{
    typedef void*(* getDefaultFnc)();
    typedef void(* addItemFnc)(void *, const char *);
}

void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& rMimeType, const OUString& rDocumentService)
{
    EnsureInit();
    OString sGtkURL;
    rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
    if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
        sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
    else
    {
        //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
        //Decode %XX components
        OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
        //Convert back to system locale encoding
        OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
        //Encode to an escaped ASCII-encoded URI
        gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), NULL, NULL);
        sGtkURL = OString(g_uri);
        g_free(g_uri);
    }
    GtkRecentManager *manager = gtk_recent_manager_get_default ();
    gtk_recent_manager_add_item (manager, sGtkURL.getStr());
    (void)rMimeType;
    (void)rDocumentService;
}

SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
    ImplJobSetup* pSetupData )
{
    EnsureInit();
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
    mbPrinterInit = true;
    // create and initialize SalInfoPrinter
    PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
    configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
    return pPrinter;
#else
    return Superclass_t::CreateInfoPrinter( pQueueInfo, pSetupData );
#endif
}

SalPrinter* GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
    EnsureInit();
#if defined ENABLE_GTK_PRINT || GTK_CHECK_VERSION(3,0,0)
    mbPrinterInit = true;
    return new GtkSalPrinter( pInfoPrinter );
#else
    return Superclass_t::CreatePrinter( pInfoPrinter );
#endif
}

/*
 * These methods always occur in pairs
 * A ThreadsEnter is followed by a ThreadsLeave
 * We need to queue up the recursive lock count
 * for each pair, so we can accurately restore
 * it later.
 */
void GtkYieldMutex::ThreadsEnter()
{
    acquire();
    if( !aYieldStack.empty() )
    { /* Previously called ThreadsLeave() */
        sal_uLong nCount = aYieldStack.front();
        aYieldStack.pop_front();
        while( nCount-- > 1 )
            acquire();
    }
}

void GtkYieldMutex::ThreadsLeave()
{
    aYieldStack.push_front( mnCount );

    SAL_WARN_IF(
        mnThreadId && mnThreadId != osl::Thread::getCurrentIdentifier(),
        "vcl.gtk", "other thread " << mnThreadId << " owns the mutex");

    while( mnCount > 1 )
        release();
    release();
}

SalVirtualDevice* GtkInstance::CreateVirtualDevice( SalGraphics *pG,
                                                    long &nDX, long &nDY,
                                                    sal_uInt16 nBitCount,
                                                    const SystemGraphicsData *pGd )
{
    EnsureInit();
#if GTK_CHECK_VERSION(3,0,0)
    (void)pG; (void) pGd;
    SvpSalVirtualDevice* pNew = new SvpSalVirtualDevice( nBitCount );
    pNew->SetSize( nDX, nDY );
    return pNew;
#else
    GtkSalGraphics *pGtkSalGraphics = dynamic_cast<GtkSalGraphics*>(pG);
    assert(pGtkSalGraphics);
    return CreateX11VirtualDevice(pG, nDX, nDY, nBitCount, pGd,
            new GtkSalGraphics(pGtkSalGraphics->GetGtkFrame(), pGtkSalGraphics->GetGtkWidget()));
#endif
}

SalBitmap* GtkInstance::CreateSalBitmap()
{
    EnsureInit();
#if GTK_CHECK_VERSION(3,0,0)
    return new SvpSalBitmap();
#else
    return X11SalInstance::CreateSalBitmap();
#endif
}

#ifdef ENABLE_GMENU_INTEGRATION

SalMenu* GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
{
    EnsureInit();
    GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
    pSalMenu->SetMenu( pVCLMenu );
    return pSalMenu;
}

void GtkInstance::DestroyMenu( SalMenu* pMenu )
{
    EnsureInit();
    delete pMenu;
}

SalMenuItem* GtkInstance::CreateMenuItem( const SalItemParams* pItemData )
{
    EnsureInit();
    return new GtkSalMenuItem( pItemData );
}

void GtkInstance::DestroyMenuItem( SalMenuItem* pItem )
{
    EnsureInit();
    delete pItem;
}

#else // not ENABLE_GMENU_INTEGRATION

SalMenu*     GtkInstance::CreateMenu( bool, Menu* )          { return NULL; }
void         GtkInstance::DestroyMenu( SalMenu* )                {}
SalMenuItem* GtkInstance::CreateMenuItem( const SalItemParams* ) { return NULL; }
void         GtkInstance::DestroyMenuItem( SalMenuItem* )        {}

#endif

SalTimer* GtkInstance::CreateSalTimer()
{
    EnsureInit();
    GtkSalTimer *pTimer = new GtkSalTimer();
    m_aTimers.push_back( pTimer );
    return pTimer;
}

void GtkInstance::RemoveTimer (SalTimer *pTimer)
{
    EnsureInit();
    std::vector<GtkSalTimer *>::iterator it;
    it = std::find( m_aTimers.begin(), m_aTimers.end(), pTimer );
    if( it != m_aTimers.end() )
        m_aTimers.erase( it );
}

void GtkInstance::Yield( bool bWait, bool bHandleAllCurrentEvents )
{
    EnsureInit();
    GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
}

bool GtkInstance::IsTimerExpired()
{
    EnsureInit();
    for( std::vector<GtkSalTimer *>::iterator it = m_aTimers.begin();
         it != m_aTimers.end(); ++it )
        if( (*it)->Expired() )
            return true;

    return false;
}

bool GtkInstance::AnyInput( VclInputFlags nType )
{
    EnsureInit();
    if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
        return true;
#if !GTK_CHECK_VERSION(3,0,0)
    bool bRet = X11SalInstance::AnyInput(nType);
#else
    if (!gdk_events_pending())
        return false;

    if (nType == VCL_INPUT_ANY)
        return true;

    bool bRet = false;
    std::stack<GdkEvent*> aEvents;
    GdkEvent *pEvent = NULL;
    while ((pEvent = gdk_event_get()))
    {
        aEvents.push(pEvent);
        VclInputFlags nEventType = categorizeEvent(pEvent);
        if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
        {
            bRet = true;
            break;
        }
    }

    while (!aEvents.empty())
    {
        pEvent = aEvents.top();
        gdk_event_put(pEvent);
        gdk_event_free(pEvent);
        aEvents.pop();
    }
#endif
    return bRet;
}

GenPspGraphics *GtkInstance::CreatePrintGraphics()
{
    EnsureInit();
    return new GenPspGraphics();
}

std::shared_ptr<vcl::unx::GtkPrintWrapper>
GtkInstance::getPrintWrapper() const
{
    if (!m_xPrintWrapper)
        m_xPrintWrapper.reset(new vcl::unx::GtkPrintWrapper);
    return m_xPrintWrapper;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
<|endoftext|>


<commit_before>/*
 * Copyright 2011 Simon A. F. Lund <safl@safl.dk>
 *
 * This file is part of cphVB.
 *
 * cphVB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * cphVB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with cphVB. If not, see <http://www.gnu.org/licenses/>.
 */
#include <cphvb.h>
#include "cphvb_ve_score.h"

static cphvb_com *myself = NULL;
static cphvb_userfunc_impl reduce_impl = NULL;
static cphvb_intp reduce_impl_id = 0;
static cphvb_userfunc_impl random_impl = NULL;
static cphvb_intp random_impl_id = 0;

cphvb_error cphvb_ve_score_init(cphvb_com *self)
{
    myself = self;
    return CPHVB_SUCCESS;
}

inline cphvb_error execute_range( cphvb_instruction* list, cphvb_intp start, cphvb_intp count) {

    cphvb_intp i, bundle_size;
    cphvb_instruction* inst[CPHVB_MAX_NO_INST];

    if (count > 1) {

        cphvb_pprint_instr_list( &list[start], count, "POTENTIAL" );
        for(i=0; i < count; i++)
        {
            inst[i] = &list[start+i];
        }
        bundle_size = cphvb_inst_bundle( inst, count );
        cphvb_pprint_instr_list( *inst, bundle_size, "BUNDLE" );

        for(i=0; i < bundle_size; i++)          // TODO: Make nice loop here
        {
            cphvb_compute_apply( inst[i] );
            inst[i]->status = CPHVB_INST_DONE;
        }

        for(i=bundle_size; i < count; i++)
        {
            cphvb_compute_apply( inst[i] );
            inst[i]->status = CPHVB_INST_DONE;
        }

    } else {

        for(i=start; i < (start+count); i++)
        {
            cphvb_compute_apply( &list[i] );
            list[i].status = CPHVB_INST_DONE;
        }

    }

    return CPHVB_SUCCESS;

}

cphvb_error cphvb_ve_score_execute( cphvb_intp instruction_count, cphvb_instruction* instruction_list )
{
    cphvb_intp count, nops, i, kernel_size;
    cphvb_instruction* inst;
    cphvb_error ret = CPHVB_SUCCESS;

    kernel_size = 0;
    for(count=0; count < instruction_count; count++)
    {
        inst = &instruction_list[count];

        if(inst->status == CPHVB_INST_DONE)         // SKIP instruction
        {
            execute_range( instruction_list, count-kernel_size, kernel_size );
            kernel_size = 0;

            continue;
        }

        nops = cphvb_operands(inst->opcode);        // Allocate memory for operands
        for(i=0; i<nops; i++)
        {
            if (!cphvb_is_constant(inst->operand[i]))
            {
                if (cphvb_data_malloc(inst->operand[i]) != CPHVB_SUCCESS)
                {
                    return CPHVB_OUT_OF_MEMORY;     // EXIT
                }
            }

        }

        switch(inst->opcode)                        // Dispatch instruction
        {
            case CPHVB_NONE:                        // NOOP.
            case CPHVB_DISCARD:
            case CPHVB_SYNC:
                execute_range( instruction_list, count-kernel_size, kernel_size );
                kernel_size = 0;

                inst->status = CPHVB_INST_DONE;
                break;

            case CPHVB_USERFUNC:                    // External libraries are executed

                execute_range( instruction_list, count-kernel_size, kernel_size );
                kernel_size = 0;

                if(inst->userfunc->id == reduce_impl_id)
                {
                    ret = reduce_impl(inst->userfunc, NULL);
                    inst->status = (ret == CPHVB_SUCCESS) ? CPHVB_INST_DONE : CPHVB_INST_UNDONE;
                }
                else if(inst->userfunc->id == random_impl_id)
                {
                    ret = random_impl(inst->userfunc, NULL);
                    inst->status = (ret == CPHVB_SUCCESS) ? CPHVB_INST_DONE : CPHVB_INST_UNDONE;
                }
                else                                // Unsupported userfunc
                {
                    ret = CPHVB_TYPE_NOT_SUPPORTED;
                }

                break;

            default:                                // Built-in operations
                kernel_size++;
        }

    }
                                                // All instructions succeeded.
    execute_range( instruction_list, count-kernel_size, kernel_size );
    kernel_size = 0;

    return ret;                                 // EXIT

}

cphvb_error cphvb_ve_score_shutdown( void )
{
    return CPHVB_SUCCESS;
}

cphvb_error cphvb_ve_score_reg_func(char *lib, char *fun, cphvb_intp *id) {

    if(strcmp("cphvb_reduce", fun) && reduce_impl == NULL)
    {
        cphvb_com_get_func(myself, lib, fun, &reduce_impl);
        reduce_impl_id = *id;
    }
    else if(strcmp("cphvb_random", fun) && random_impl == NULL)
    {
        cphvb_com_get_func(myself, lib, fun, &random_impl);
        random_impl_id = *id;
    }
    return CPHVB_SUCCESS;
}

cphvb_error cphvb_reduce( cphvb_userfunc *arg, void* ve_arg)
{
    return cphvb_compute_reduce( arg, ve_arg );
}

cphvb_error cphvb_random( cphvb_userfunc *arg, void* ve_arg)
{
    return cphvb_compute_random( arg, ve_arg );
}
<commit_msg>Commented out verbose debug-printing.<commit_after>/*
 * Copyright 2011 Simon A. F. Lund <safl@safl.dk>
 *
 * This file is part of cphVB.
 *
 * cphVB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * cphVB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with cphVB. If not, see <http://www.gnu.org/licenses/>.
 */
#include <cphvb.h>
#include "cphvb_ve_score.h"

static cphvb_com *myself = NULL;
static cphvb_userfunc_impl reduce_impl = NULL;
static cphvb_intp reduce_impl_id = 0;
static cphvb_userfunc_impl random_impl = NULL;
static cphvb_intp random_impl_id = 0;

cphvb_error cphvb_ve_score_init(cphvb_com *self)
{
    myself = self;
    return CPHVB_SUCCESS;
}

inline cphvb_error execute_range( cphvb_instruction* list, cphvb_intp start, cphvb_intp count) {

    cphvb_intp i, bundle_size;
    cphvb_instruction* inst[CPHVB_MAX_NO_INST];

    if (count > 1) {

        //cphvb_pprint_instr_list( &list[start], count, "POTENTIAL" );
        for(i=0; i < count; i++)
        {
            inst[i] = &list[start+i];
        }
        bundle_size = cphvb_inst_bundle( inst, count );
        //cphvb_pprint_instr_list( *inst, bundle_size, "BUNDLE" );

        for(i=0; i < bundle_size; i++)          // TODO: Make nice loop here
        {
            cphvb_compute_apply( inst[i] );
            inst[i]->status = CPHVB_INST_DONE;
        }

        for(i=bundle_size; i < count; i++)
        {
            cphvb_compute_apply( inst[i] );
            inst[i]->status = CPHVB_INST_DONE;
        }

    } else {

        for(i=start; i < (start+count); i++)
        {
            cphvb_compute_apply( &list[i] );
            list[i].status = CPHVB_INST_DONE;
        }

    }

    return CPHVB_SUCCESS;

}

cphvb_error cphvb_ve_score_execute( cphvb_intp instruction_count, cphvb_instruction* instruction_list )
{
    cphvb_intp count, nops, i, kernel_size;
    cphvb_instruction* inst;
    cphvb_error ret = CPHVB_SUCCESS;

    kernel_size = 0;
    for(count=0; count < instruction_count; count++)
    {
        inst = &instruction_list[count];

        if(inst->status == CPHVB_INST_DONE)         // SKIP instruction
        {
            execute_range( instruction_list, count-kernel_size, kernel_size );
            kernel_size = 0;

            continue;
        }

        nops = cphvb_operands(inst->opcode);        // Allocate memory for operands
        for(i=0; i<nops; i++)
        {
            if (!cphvb_is_constant(inst->operand[i]))
            {
                if (cphvb_data_malloc(inst->operand[i]) != CPHVB_SUCCESS)
                {
                    return CPHVB_OUT_OF_MEMORY;     // EXIT
                }
            }

        }

        switch(inst->opcode)                        // Dispatch instruction
        {
            case CPHVB_NONE:                        // NOOP.
            case CPHVB_DISCARD:
            case CPHVB_SYNC:
                execute_range( instruction_list, count-kernel_size, kernel_size );
                kernel_size = 0;

                inst->status = CPHVB_INST_DONE;
                break;

            case CPHVB_USERFUNC:                    // External libraries are executed

                execute_range( instruction_list, count-kernel_size, kernel_size );
                kernel_size = 0;

                if(inst->userfunc->id == reduce_impl_id)
                {
                    ret = reduce_impl(inst->userfunc, NULL);
                    inst->status = (ret == CPHVB_SUCCESS) ? CPHVB_INST_DONE : CPHVB_INST_UNDONE;
                }
                else if(inst->userfunc->id == random_impl_id)
                {
                    ret = random_impl(inst->userfunc, NULL);
                    inst->status = (ret == CPHVB_SUCCESS) ? CPHVB_INST_DONE : CPHVB_INST_UNDONE;
                }
                else                                // Unsupported userfunc
                {
                    ret = CPHVB_TYPE_NOT_SUPPORTED;
                }

                break;

            default:                                // Built-in operations
                kernel_size++;
        }

    }
                                                // All instructions succeeded.
    execute_range( instruction_list, count-kernel_size, kernel_size );
    kernel_size = 0;

    return ret;                                 // EXIT

}

cphvb_error cphvb_ve_score_shutdown( void )
{
    return CPHVB_SUCCESS;
}

cphvb_error cphvb_ve_score_reg_func(char *lib, char *fun, cphvb_intp *id) {

    if(strcmp("cphvb_reduce", fun) && reduce_impl == NULL)
    {
        cphvb_com_get_func(myself, lib, fun, &reduce_impl);
        reduce_impl_id = *id;
    }
    else if(strcmp("cphvb_random", fun) && random_impl == NULL)
    {
        cphvb_com_get_func(myself, lib, fun, &random_impl);
        random_impl_id = *id;
    }
    return CPHVB_SUCCESS;
}

cphvb_error cphvb_reduce( cphvb_userfunc *arg, void* ve_arg)
{
    return cphvb_compute_reduce( arg, ve_arg );
}

cphvb_error cphvb_random( cphvb_userfunc *arg, void* ve_arg)
{
    return cphvb_compute_random( arg, ve_arg );
}
<|endoftext|>


<commit_before>/*
 * diagonalize.cc
 */

#include "llvm/Pass.h"
#include "llvm/Function.h"
#include "llvm/Constants.h"
#include "llvm/Instructions.h"
#include "llvm/Analysis/LoopPass.h"
#include "llvm/ADT/ValueMap.h"
#include "llvm/Support/PatternMatch.h"

#include <Eigen/Core>
#include <Eigen/Eigenvalues>

using namespace llvm;
using namespace PatternMatch;
using namespace Eigen;

#define OP_IN_RANGE(_op, _start, _end) \
    (_op >= Instruction:: _start && _op <= Instruction:: _end)

namespace
{

struct ADPass : public LoopPass
{
private:
    Loop* loop;
    DominatorTree* DT;
    BasicBlock* exit_block;
    std::vector<BasicBlock*> blocks;

    /* Keep track of the loop range. */
    ICmpInst* icmp;
    Value* iter_final;
    PHINode* iter_var;

    /* Index and store state variables. */
    ValueMap<PHINode*, size_t> phis;

    /* Track linear combinations of variables. */
    typedef ValueMap<PHINode*, double> Coefficients;

public:
    static char ID;

    ADPass() : LoopPass(ID) {}

    virtual void getAnalysisUsage(AnalysisUsage& AU) const {
        AU.addRequired<DominatorTree>();
    }

    virtual bool runOnLoop(Loop* L, LPPassManager&) {
        loop = L;
        blocks = loop->getBlocks();
        DT = &getAnalysis<DominatorTree>();
        phis.clear();

        if (!loop->isLCSSAForm(*DT)
            || loop->getSubLoops().size()
            || !(exit_block = loop->getUniqueExitBlock())
            || !loopFilter())
        {
            return false;
        }

        /* Extract the loop iterator if possible. */
        if (!extractIterator(icmp)) {
            return false;
        } else {
            phis.erase(iter_var);
        }

        size_t phi_index = 0;
        for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
            phis[kv->first] = phi_index++;
        }

        /* Find the initial state and all linear dependence relations. */
        size_t nr_phis = phis.size();
        MatrixXd InitialState(nr_phis, 1);
        MatrixXd TransformationMatrix(nr_phis, nr_phis);
        TransformationMatrix << MatrixXd::Zero(nr_phis, nr_phis);
        for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
            PHINode* PN = kv->first;
            size_t phi_label = phis[PN];
            InitialState(phi_label, 0) = ToDouble(PN->getIncomingValue(0));

            Coefficients coeffs;
            if (!trackUpdates(PN->getIncomingValue(1), coeffs)) {
                return false;
            }
            for (auto ckv = coeffs.begin(); ckv != coeffs.end(); ++ckv) {
                size_t target_phi = phis[ckv->first];
                TransformationMatrix(phi_label, target_phi) = ckv->second;
            }
        }

        /* Diagonalize the transformation matrix. */
        EigenSolver<MatrixXd> EigSolver(TransformationMatrix);
        MatrixXcd P = EigSolver.eigenvectors();
        MatrixXcd D = EigSolver.eigenvalues().asDiagonal();
        MatrixXcd Pinv = P.inverse();
        if (!checkSystem(TransformationMatrix, InitialState, P, D, Pinv)) {
            return false;
        }

        /* Emit instructions to compute the closed form in a new block. */
        LLVMContext& ctx = blocks.front()->getContext();
        Function* parentFunc = exit_block->getParent();
        Module* mod = parentFunc->getParent();
        BasicBlock* dgen = BasicBlock::Create(ctx, "dgen", parentFunc,
            exit_block);
        Type* numTy = Type::getDoubleTy(ctx);
        std::vector<Type*> powProto(2, numTy);
        FunctionType* powType = FunctionType::get(numTy, powProto, false);
        Function* powf = Function::Create(powType,
            GlobalValue::ExternalLinkage, "llvm.pow.f64", mod);
        powf->setCallingConv(CallingConv::C);

        /* P(D^n) = r * λ^n : ∀(r) ∈ row(P) */
        Value** PDn = new Value*[nr_phis * nr_phis];
        Value* exponent = CastInst::Create(Instruction::UIToFP, iter_final,
            numTy, "expt", dgen);
        for (size_t j = 0; j < nr_phis; ++j) {
            /* λ[j]^n */
            Value* exptargs[] = {
                ToConstantFP(ctx, std::real(D(j, j))), exponent
            };
            Value* eigvexpt = CallInst::Create(powf,
                ArrayRef<Value*>(exptargs, 2), "eigvexpt", dgen);

            /* PDn[i][j] = P[i][j] * λ[j]^n */
            for (size_t i = 0; i < nr_phis; ++i) {
                size_t index = i * nr_phis + j;
                PDn[index] = BinaryOperator::Create(Instruction::FMul,
                    ToConstantFP(ctx, std::real(P(i, j))), eigvexpt, "pdn",
                    dgen);
            }
        }

        /* xf = P(D^n) * Pinv * x0 */
        Value** soln = new Value*[nr_phis];
        Value* zero = ToConstantFP(ctx, 0.0);
        for (size_t i = 0; i < nr_phis; ++i) {
            soln[i] = zero;
            for (size_t j = 0; j < nr_phis; ++j) {
                /* dotp = <a, b> : a ∈ row(P(D^n)), b ∈ col(Pinv) */
                Value* dotp = zero;
                for (size_t k = 0; k < nr_phis; ++k) {
                    Value* ik_kj = BinaryOperator::Create(Instruction::FMul,
                        PDn[i * nr_phis + k],
                        ToConstantFP(ctx, std::real(Pinv(k, j))),
                        "ik_kj", dgen);
                    dotp = BinaryOperator::Create(Instruction::FAdd,
                        ik_kj, dotp, "dotp", dgen);
                }

                /* xf[i] = ∑ P(D^n)Pinv[i][j] * x0[j] */
                Value* xj_prod = BinaryOperator::Create(Instruction::FMul,
                    dotp, ToConstantFP(ctx, InitialState(j)), "pdpxj",
                    dgen);
                soln[i] = BinaryOperator::Create(Instruction::FAdd,
                    xj_prod, soln[i], "xf", dgen);
            }
        }

        delete[] PDn;

        /* Point outgoing edges from the loop preheader to dgen. */
        BranchInst::Create(exit_block, dgen);
        BasicBlock* preheader = loop->getLoopPreheader();
        TerminatorInst* TI = preheader->getTerminator();
        BranchInst* br = dyn_cast<BranchInst>(TI);
        br->setSuccessor(0, dgen);

        /* Replace dependencies on state variables outside of the loop. */
        for (auto II = exit_block->begin(); II != exit_block->end(); ++II) {
            Instruction* instr = II;
            if (isa<PHINode>(instr)) {
                PHINode* exitPhi = cast<PHINode>(instr);
                int incomingEdge = exitPhi->getBasicBlockIndex(blocks.back());
                if (incomingEdge != -1) {
                    Value* exitV = exitPhi->getIncomingValue(incomingEdge);
                    for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
                        PHINode* loopPhi = kv->first;
                        if (loopPhi->getIncomingValue(1) != exitV) {
                            continue;
                        }
                        size_t phiIdx = phis[loopPhi];
                        exitPhi->setIncomingValue(incomingEdge, soln[phiIdx]);
                        exitPhi->setIncomingBlock(incomingEdge, dgen);
                    }
                }
            }
        }

        /* Call eraseFromParent() on every BasicBlock in the loop. */
        for (auto it = blocks.begin(); it != blocks.end(); ++it) {
            BasicBlock* BB = *it;
            BB->eraseFromParent();
        }

        delete[] soln;

        return true;
    }

private:
    bool loopFilter() {
        /* Filter away loops with unsupported instructions. */
        icmp = NULL;
        size_t nr_cmps = 0;
        for (auto it = blocks.begin(); it != blocks.end(); ++it) {
            BasicBlock* BB = *it;
            for (auto II = BB->begin(); II != BB->end(); ++II) {
                Instruction* instr = II;
                if (isa<ICmpInst>(instr)) {
                    if (++nr_cmps > 1) {
                        return false;
                    } else {
                        icmp = cast<ICmpInst>(instr);
                    }
                } else if (isa<PHINode>(instr)) {
                    PHINode* PN = cast<PHINode>(instr);
                    if (PN->getNumIncomingValues() != 2
                        || !DT->properlyDominates(PN->getIncomingBlock(0),
                                                  blocks.front()))
                    {
                        return false;
                    }
                    Value* inLhs = PN->getIncomingValue(0);
                    if (!(isa<ConstantInt>(inLhs) || isa<ConstantFP>(inLhs))) {
                        return false;
                    } else {
                        phis[PN] = 0;
                    }
                } else if (isa<BinaryOperator>(instr)) {
                    BinaryOperator* binop = cast<BinaryOperator>(instr);
                    if (!(OP_IN_RANGE(binop->getOpcode(), Add, FDiv))) {
                        return false;
                    }
                } else if (!isa<BranchInst>(instr)) {
                    return false;
                }
            }
        }
        return true;
    }

    bool extractIterator(ICmpInst* icmp) {
        /* In canonical form, we should get an icmp with the predicate
         * reduced to an equality test. */
        Value *cmpLhs, *cmpRhs;
        ICmpInst::Predicate IPred;
        if (match(icmp, m_ICmp(IPred, m_Value(cmpLhs),
                                      m_Value(cmpRhs)))
            && IPred == CmpInst::Predicate::ICMP_EQ)
        {
            /* We should be comparing against the final iterator value, which
             * should not be found in the loop body. */
            if (isa<Instruction>(cmpRhs)) {
                Instruction* instr = cast<Instruction>(cmpRhs);
                BasicBlock* BB = instr->getParent();
                if (!DT->properlyDominates(BB, blocks.front())) {
                    return false;
                }
                if (isa<Constant>(instr->getOperand(0))
                    || isa<Instruction>(instr->getOperand(0)))
                {
                    return false;
                }
                iter_final = instr->getOperand(0);
            } else {
                iter_final = cmpRhs;
            }

            /* Check if the loop increment is behaving as we expect it to. */
            Value *incrLhs, *incrRhs;
            if (match(cmpLhs, m_Add(m_Value(incrLhs), m_Value(incrRhs)))) {
                if (isa<PHINode>(incrLhs)
                    && isa<ConstantInt>(incrRhs)
                    && cast<ConstantInt>(incrRhs)->isOne())
                {
                    iter_var = cast<PHINode>(incrLhs);
                    Value* iter_initial = iter_var->getIncomingValue(0);
                    if (isa<ConstantInt>(iter_initial)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    bool trackUpdates(Value* parent, Coefficients& coeffs) {
        /* Determine the linear combination that produces 'parent'. */
        if (isa<Constant>(parent)) {
            return true;
        } else if (isa<PHINode>(parent)) {
            /* A PHINode should contribute a single copy of itself. */
            PHINode* PN = cast<PHINode>(parent);
            if (!phis.count(PN)) {
                return false;
            } else {
                coeffs[PN] = 1;
            }
        } else if (isa<BinaryOperator>(parent)) {
            BinaryOperator* binop = cast<BinaryOperator>(parent);
            int opcode = binop->getOpcode();
            Value *LHS = binop->getOperand(0), *RHS = binop->getOperand(1);

            Coefficients lhsCoeffs, rhsCoeffs;
            if (!trackUpdates(LHS, lhsCoeffs)
                || !trackUpdates(RHS, rhsCoeffs))
            {
                return false;
            }

            double scalar;
            if (OP_IN_RANGE(opcode, Add, FSub)) {
                /* Add instructions shouldn't operate on scalars. */
                if (isScalar(lhsCoeffs) || isScalar(rhsCoeffs)) {
                    return false;
                }
            } else {
                /* Mul instructions can only have one scalar operand. */
                if (isScalar(lhsCoeffs) ^ isScalar(rhsCoeffs)) {
                    return false;
                }

                /* Div instructions cannot have scalar numerators. */
                if (OP_IN_RANGE(opcode, UDiv, FDiv) && !isScalar(lhsCoeffs)) {
                    return false;
                }

                scalar = ToDouble(isScalar(lhsCoeffs) ? LHS : RHS);
            }

            /* Merge the two sets of coefficients. */
            for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
                PHINode* PN = kv->first;
                double lcoeff = lhsCoeffs.lookup(PN);
                double rcoeff = rhsCoeffs.lookup(PN);

                /* Adding nil entries to 'coeffs' breaks isScalar(X). */
                if (lcoeff == 0.0 && rcoeff == 0.0) {
                    continue;
                }

                if (OP_IN_RANGE(opcode, Add, FAdd)) {
                    coeffs[PN] = lcoeff + rcoeff;
                } else if (OP_IN_RANGE(opcode, Sub, FSub)) {
                    coeffs[PN] = lcoeff - rcoeff;
                } else if (OP_IN_RANGE(opcode, Mul, FMul)) {
                    coeffs[PN] = scalar * (lcoeff + rcoeff);
                } else {
                    coeffs[PN] = (1 / scalar) * (lcoeff + rcoeff);
                }
            }
        } else return false;
        return true;
    }

    int64_t ToInt(Value* V) {
        return cast<ConstantInt>(V)->getValue().getSExtValue();
    }

    double ToDouble(Value* V) {
        if (isa<ConstantInt>(V)) {
            return double(ToInt(V));
        } else if (isa<ConstantFP>(V)) {
            return cast<ConstantFP>(V)->getValueAPF().convertToDouble();
        }
        return 0.0;
    }

    Value* ToConstantFP(LLVMContext& ctx, double n) {
        return ConstantFP::get(ctx, APFloat(n));
    }

    bool isScalar(Coefficients& coeffs) {
        return coeffs.size() == 0;
    }

    bool checkSystem(MatrixXd& A, MatrixXd& x,
                     MatrixXcd& P, MatrixXcd& D, MatrixXcd& Pinv)
    {
        /* Ensure that there are no complex eigenvectors. */
        for (int i = 0; i < P.rows(); ++i) {
            for (int j = 0; j < P.cols(); ++j) {
                if (std::imag(P(i, j)) != 0.0) {
                    return false;
                }
            }
        }

        /* Check that the diagonalization is reasonably accurate. */
        MatrixXd A3x = A*A*A*x;
        MatrixXcd PD3Px = (P*(D*D*D)*Pinv)*x;
        const double epsilon = 100 * std::numeric_limits<double>::epsilon();
        for (int i = 0; i < x.rows(); ++i) {
            if (epsilon <
                std::abs(std::abs(PD3Px(i, 0)) - std::abs(A3x(i, 0))))
            {
                return false;
            }
        }
        return true;
    }
};

char ADPass::ID = 0;

}

static RegisterPass<ADPass> X("auto-diagonalize",
    "Diagonalize linear dynamical systems", false, false);
<commit_msg>[loop-pass-only] errs() statements.<commit_after>/*
 * diagonalize.cc
 */

#include "llvm/Pass.h"
#include "llvm/Function.h"
#include "llvm/Constants.h"
#include "llvm/Instructions.h"
#include "llvm/Analysis/LoopPass.h"
#include "llvm/ADT/ValueMap.h"
#include "llvm/Support/PatternMatch.h"

#include "llvm/Support/raw_ostream.h"

#include <Eigen/Core>
#include <Eigen/Eigenvalues>

using namespace llvm;
using namespace PatternMatch;
using namespace Eigen;

#define OP_IN_RANGE(_op, _start, _end) \
    (_op >= Instruction:: _start && _op <= Instruction:: _end)

namespace
{

struct ADPass : public LoopPass
{
private:
    Loop* loop;
    DominatorTree* DT;
    BasicBlock* exit_block;
    std::vector<BasicBlock*> blocks;

    /* Keep track of the loop range. */
    ICmpInst* icmp;
    Value* iter_final;
    PHINode* iter_var;

    /* Index and store state variables. */
    ValueMap<PHINode*, size_t> phis;

    /* Track linear combinations of variables. */
    typedef ValueMap<PHINode*, double> Coefficients;

public:
    static char ID;

    ADPass() : LoopPass(ID) {}

    virtual void getAnalysisUsage(AnalysisUsage& AU) const {
        AU.addRequired<DominatorTree>();
    }

    virtual bool runOnLoop(Loop* L, LPPassManager&) {
        loop = L;
        blocks = loop->getBlocks();
        DT = &getAnalysis<DominatorTree>();
        phis.clear();

        if (!loop->isLCSSAForm(*DT)
            || loop->getSubLoops().size()
            || !(exit_block = loop->getUniqueExitBlock())
            || !loopFilter())
        {
            errs() << "Loop did not pass filter.\n";
            return false;
        }

        /* Extract the loop iterator if possible. */
        if (!extractIterator(icmp)) {
            errs() << "Could not find loop iterator.\n";
            return false;
        } else {
            phis.erase(iter_var);
        }

        size_t phi_index = 0;
        for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
            phis[kv->first] = phi_index++;
        }

        /* Find the initial state and all linear dependence relations. */
        size_t nr_phis = phis.size();
        MatrixXd InitialState(nr_phis, 1);
        MatrixXd TransformationMatrix(nr_phis, nr_phis);
        TransformationMatrix << MatrixXd::Zero(nr_phis, nr_phis);
        for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
            PHINode* PN = kv->first;
            size_t phi_label = phis[PN];
            InitialState(phi_label, 0) = ToDouble(PN->getIncomingValue(0));

            Coefficients coeffs;
            if (!trackUpdates(PN->getIncomingValue(1), coeffs)) {
                errs() << "Non-linear dependencies found.\n";
                return false;
            }
            for (auto ckv = coeffs.begin(); ckv != coeffs.end(); ++ckv) {
                size_t target_phi = phis[ckv->first];
                TransformationMatrix(phi_label, target_phi) = ckv->second;
            }
        }

        /* Diagonalize the transformation matrix. */
        EigenSolver<MatrixXd> EigSolver(TransformationMatrix);
        MatrixXcd P = EigSolver.eigenvectors();
        MatrixXcd D = EigSolver.eigenvalues().asDiagonal();
        MatrixXcd Pinv = P.inverse();
        if (!checkSystem(TransformationMatrix, InitialState, P, D, Pinv)) {
            errs() << "Inaccurate or faulty diagonalization.\n";
            return false;
        }

        /* Emit instructions to compute the closed form in a new block. */
        LLVMContext& ctx = blocks.front()->getContext();
        Function* parentFunc = exit_block->getParent();
        Module* mod = parentFunc->getParent();
        BasicBlock* dgen = BasicBlock::Create(ctx, "dgen", parentFunc,
            exit_block);
        Type* numTy = Type::getDoubleTy(ctx);
        std::vector<Type*> powProto(2, numTy);
        FunctionType* powType = FunctionType::get(numTy, powProto, false);
        Function* powf = Function::Create(powType,
            GlobalValue::ExternalLinkage, "llvm.pow.f64", mod);
        powf->setCallingConv(CallingConv::C);

        /* P(D^n) = r * λ^n : ∀(r) ∈ row(P) */
        Value** PDn = new Value*[nr_phis * nr_phis];
        Value* exponent = CastInst::Create(Instruction::UIToFP, iter_final,
            numTy, "expt", dgen);
        for (size_t j = 0; j < nr_phis; ++j) {
            /* λ[j]^n */
            Value* exptargs[] = {
                ToConstantFP(ctx, std::real(D(j, j))), exponent
            };
            Value* eigvexpt = CallInst::Create(powf,
                ArrayRef<Value*>(exptargs, 2), "eigvexpt", dgen);

            /* PDn[i][j] = P[i][j] * λ[j]^n */
            for (size_t i = 0; i < nr_phis; ++i) {
                size_t index = i * nr_phis + j;
                PDn[index] = BinaryOperator::Create(Instruction::FMul,
                    ToConstantFP(ctx, std::real(P(i, j))), eigvexpt, "pdn",
                    dgen);
            }
        }

        /* xf = P(D^n) * Pinv * x0 */
        Value** soln = new Value*[nr_phis];
        Value* zero = ToConstantFP(ctx, 0.0);
        for (size_t i = 0; i < nr_phis; ++i) {
            soln[i] = zero;
            for (size_t j = 0; j < nr_phis; ++j) {
                /* dotp = <a, b> : a ∈ row(P(D^n)), b ∈ col(Pinv) */
                Value* dotp = zero;
                for (size_t k = 0; k < nr_phis; ++k) {
                    Value* ik_kj = BinaryOperator::Create(Instruction::FMul,
                        PDn[i * nr_phis + k],
                        ToConstantFP(ctx, std::real(Pinv(k, j))),
                        "ik_kj", dgen);
                    dotp = BinaryOperator::Create(Instruction::FAdd,
                        ik_kj, dotp, "dotp", dgen);
                }

                /* xf[i] = ∑ P(D^n)Pinv[i][j] * x0[j] */
                Value* xj_prod = BinaryOperator::Create(Instruction::FMul,
                    dotp, ToConstantFP(ctx, InitialState(j)), "pdpxj",
                    dgen);
                soln[i] = BinaryOperator::Create(Instruction::FAdd,
                    xj_prod, soln[i], "xf", dgen);
            }
        }

        delete[] PDn;

        /* Point outgoing edges from the loop preheader to dgen. */
        BranchInst::Create(exit_block, dgen);
        BasicBlock* preheader = loop->getLoopPreheader();
        TerminatorInst* TI = preheader->getTerminator();
        BranchInst* br = dyn_cast<BranchInst>(TI);
        br->setSuccessor(0, dgen);

        /* Replace dependencies on state variables outside of the loop. */
        for (auto II = exit_block->begin(); II != exit_block->end(); ++II) {
            Instruction* instr = II;
            if (isa<PHINode>(instr)) {
                PHINode* exitPhi = cast<PHINode>(instr);
                int incomingEdge = exitPhi->getBasicBlockIndex(blocks.back());
                if (incomingEdge != -1) {
                    Value* exitV = exitPhi->getIncomingValue(incomingEdge);
                    for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
                        PHINode* loopPhi = kv->first;
                        if (loopPhi->getIncomingValue(1) != exitV) {
                            continue;
                        }
                        size_t phiIdx = phis[loopPhi];
                        exitPhi->setIncomingValue(incomingEdge, soln[phiIdx]);
                        exitPhi->setIncomingBlock(incomingEdge, dgen);
                    }
                }
            }
        }

        for (auto it = blocks.begin(); it != blocks.end(); ++it) {
            BasicBlock* BB = *it;
            BB->eraseFromParent();
        }

        delete[] soln;

        return true;
    }

private:
    bool loopFilter() {
        /* Filter away loops with unsupported instructions. */
        icmp = NULL;
        size_t nr_cmps = 0;
        for (auto it = blocks.begin(); it != blocks.end(); ++it) {
            BasicBlock* BB = *it;
            for (auto II = BB->begin(); II != BB->end(); ++II) {
                Instruction* instr = II;
                if (isa<ICmpInst>(instr)) {
                    if (++nr_cmps > 1) {
                        return false;
                    } else {
                        icmp = cast<ICmpInst>(instr);
                    }
                } else if (isa<PHINode>(instr)) {
                    PHINode* PN = cast<PHINode>(instr);
                    if (PN->getNumIncomingValues() != 2
                        || !DT->properlyDominates(PN->getIncomingBlock(0),
                                                  blocks.front()))
                    {
                        return false;
                    }
                    Value* inLhs = PN->getIncomingValue(0);
                    if (!(isa<ConstantInt>(inLhs) || isa<ConstantFP>(inLhs))) {
                        return false;
                    } else {
                        phis[PN] = 0;
                    }
                } else if (isa<BinaryOperator>(instr)) {
                    BinaryOperator* binop = cast<BinaryOperator>(instr);
                    if (!(OP_IN_RANGE(binop->getOpcode(), Add, FDiv))) {
                        return false;
                    }
                } else if (!isa<BranchInst>(instr)) {
                    return false;
                }
            }
        }
        return true;
    }

    bool extractIterator(ICmpInst* icmp) {
        /* In canonical form, we should get an icmp with the predicate
         * reduced to an equality test. */
        Value *cmpLhs, *cmpRhs;
        ICmpInst::Predicate IPred;
        if (match(icmp, m_ICmp(IPred, m_Value(cmpLhs),
                                      m_Value(cmpRhs)))
            && IPred == CmpInst::Predicate::ICMP_EQ)
        {
            /* We should be comparing against the final iterator value, which
             * should not be found in the loop body. */
            if (isa<Instruction>(cmpRhs)) {
                Instruction* instr = cast<Instruction>(cmpRhs);
                BasicBlock* BB = instr->getParent();
                if (!DT->properlyDominates(BB, blocks.front())) {
                    return false;
                }
                if (isa<Constant>(instr->getOperand(0))
                    || isa<Instruction>(instr->getOperand(0)))
                {
                    return false;
                }
                iter_final = instr->getOperand(0);
            } else {
                iter_final = cmpRhs;
            }

            /* Check if the loop increment is behaving as we expect it to. */
            Value *incrLhs, *incrRhs;
            if (match(cmpLhs, m_Add(m_Value(incrLhs), m_Value(incrRhs)))) {
                if (isa<PHINode>(incrLhs)
                    && isa<ConstantInt>(incrRhs)
                    && cast<ConstantInt>(incrRhs)->isOne())
                {
                    iter_var = cast<PHINode>(incrLhs);
                    Value* iter_initial = iter_var->getIncomingValue(0);
                    if (isa<ConstantInt>(iter_initial)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    bool trackUpdates(Value* parent, Coefficients& coeffs) {
        /* Determine the linear combination that produces 'parent'. */
        if (isa<Constant>(parent)) {
            return true;
        } else if (isa<PHINode>(parent)) {
            /* A PHINode should contribute a single copy of itself. */
            PHINode* PN = cast<PHINode>(parent);
            if (!phis.count(PN)) {
                errs() << "Encountered unknown PHI: " << *PN << "\n";
                return false;
            } else {
                coeffs[PN] = 1;
            }
        } else if (isa<BinaryOperator>(parent)) {
            BinaryOperator* binop = cast<BinaryOperator>(parent);
            int opcode = binop->getOpcode();
            Value *LHS = binop->getOperand(0), *RHS = binop->getOperand(1);

            Coefficients lhsCoeffs, rhsCoeffs;
            if (!trackUpdates(LHS, lhsCoeffs)
                || !trackUpdates(RHS, rhsCoeffs))
            {
                return false;
            }

            double scalar;
            if (OP_IN_RANGE(opcode, Add, FSub)) {
                /* Add instructions shouldn't operate on scalars. */
                if (isScalar(lhsCoeffs) || isScalar(rhsCoeffs)) {
                    errs() << *binop << " " << *LHS << " " << *RHS << "\n";
                    errs() << "Scalar operand to ADD instruction.\n";
                    return false;
                }
            } else {
                /* Mul instructions can only have one scalar operand. */
                if (isScalar(lhsCoeffs) ^ isScalar(rhsCoeffs)) {
                    errs() << *binop << " " << *LHS << " " << *RHS << "\n";
                    errs() << "Too many or few scalar operands to MUL instruction.\n";
                    return false;
                }

                /* Div instructions cannot have scalar numerators. */
                if (OP_IN_RANGE(opcode, UDiv, FDiv) && !isScalar(lhsCoeffs)) {
                    errs() << *binop << " " << *LHS << " " << *RHS << "\n";
                    errs() << "DIV instruction has a scalar numerator.\n";
                    return false;
                }

                scalar = ToDouble(isScalar(lhsCoeffs) ? LHS : RHS);
            }

            /* Merge the two sets of coefficients. */
            for (auto kv = phis.begin(); kv != phis.end(); ++kv) {
                PHINode* PN = kv->first;
                double lcoeff = lhsCoeffs.lookup(PN);
                double rcoeff = rhsCoeffs.lookup(PN);

                /* Adding nil entries to 'coeffs' breaks isScalar(X). */
                if (lcoeff == 0.0 && rcoeff == 0.0) {
                    continue;
                }

                if (OP_IN_RANGE(opcode, Add, FAdd)) {
                    coeffs[PN] = lcoeff + rcoeff;
                } else if (OP_IN_RANGE(opcode, Sub, FSub)) {
                    coeffs[PN] = lcoeff - rcoeff;
                } else if (OP_IN_RANGE(opcode, Mul, FMul)) {
                    coeffs[PN] = scalar * (lcoeff + rcoeff);
                } else {
                    coeffs[PN] = (1 / scalar) * (lcoeff + rcoeff);
                }
            }
        } else return false;
        return true;
    }

    int64_t ToInt(Value* V) {
        return cast<ConstantInt>(V)->getValue().getSExtValue();
    }

    double ToDouble(Value* V) {
        if (isa<ConstantInt>(V)) {
            return double(ToInt(V));
        } else if (isa<ConstantFP>(V)) {
            return cast<ConstantFP>(V)->getValueAPF().convertToDouble();
        }
        return 0.0;
    }

    Value* ToConstantFP(LLVMContext& ctx, double n) {
        return ConstantFP::get(ctx, APFloat(n));
    }

    bool isScalar(Coefficients& coeffs) {
        return coeffs.size() == 0;
    }

    bool checkSystem(MatrixXd& A, MatrixXd& x,
                     MatrixXcd& P, MatrixXcd& D, MatrixXcd& Pinv)
    {
        /* Ensure that there are no complex eigenvectors. */
        for (int i = 0; i < P.rows(); ++i) {
            for (int j = 0; j < P.cols(); ++j) {
                if (std::imag(P(i, j)) != 0.0) {
                    return false;
                }
            }
        }

        /* Check that the diagonalization is reasonably accurate. */
        MatrixXd A3x = A*A*A*x;
        MatrixXcd PD3Px = (P*(D*D*D)*Pinv)*x;
        const double epsilon = 100 * std::numeric_limits<double>::epsilon();
        for (int i = 0; i < x.rows(); ++i) {
            if (epsilon <
                std::abs(std::abs(PD3Px(i, 0)) - std::abs(A3x(i, 0))))
            {
                return false;
            }
        }
        return true;
    }
};

char ADPass::ID = 0;

}

static RegisterPass<ADPass> X("auto-diagonalize",
    "Diagonalize linear dynamical systems", false, false);
<|endoftext|>


<commit_before>/* <plugins/director/ObjectCache.cpp>
 *
 * This file is part of the x0 web server project and is released under AGPL-3.
 * http://www.xzero.io/
 *
 * (c) 2009-2014 Christian Parpart <trapni@gmail.com>
 */

#include "ObjectCache.h"
#include "RequestNotes.h"
#include "Director.h"
#include <x0/io/BufferRefSource.h>
#include <x0/http/HttpRequest.h>
#include <x0/DebugLogger.h>

/*
 * List of messages headers that must be taken into account when checking for freshness.
 *
 * REQUEST HEADERS
 * - If-Range
 * - If-Match
 * - If-None-Match
 * - If-Modified-Since
 * - If-Unmodified-Since
 *
 * RESPONSE HEADERS
 * - Last-Modified
 * - ETag
 * - Expires
 * - Cache-Control
 * - Pragma (token: "no-cache")
 * - Vary (list of request headers that make a response unique in addition to its cache key, or "*" for literally all request headers)
 */

// TODO thread safety
// TODO test cache validity when output compression is enabled (or other output filters are applied).

using namespace x0;

#define TRACE(rn, n, msg...) do { \
	if ((rn) && (rn)->request != nullptr) { \
		LogMessage m(Severity::debug ## n, msg); \
		m.addTag("director-cache"); \
		rn->request->log(std::move(m)); \
	} else { \
		X0_DEBUG("director-cache", (n), msg); \
	} \
} while (0)

// {{{ ObjectCache::ConcreteObject
ObjectCache::ConcreteObject::ConcreteObject(ObjectCache* store, const std::string& cacheKey) :
    Object(),
	store_(store),
	cacheKey_(cacheKey),
    state_(Spawning),
    requestNotes_(nullptr),
	interests_(),
	requestHeaders_(),
	bufferIndex_()
{
    TRACE(requestNotes_, 2, "ConcreteObject(key: '%s')", cacheKey_.c_str());
}

ObjectCache::ConcreteObject::~ConcreteObject()
{
    TRACE(requestNotes_, 2, "~ConcreteObject()");
}

std::string ObjectCache::ConcreteObject::requestHeader(const std::string& name) const
{
	return "";
}

void ObjectCache::ConcreteObject::postProcess()
{
	TRACE(requestNotes_, 3, "ConcreteObject.postProcess() status: %d", requestNotes_->request->status);

	for (auto header: requestNotes_->request->responseHeaders) {
		TRACE(requestNotes_, 3, "ConcreteObject.postProcess() %s: %s", header.name.c_str(), header.value.c_str());
		if (unlikely(iequals(header.name, "Set-Cookie"))) {
			requestNotes_->request->log(Severity::info, "Caching requested but origin server provides uncacheable response header, Set-Cookie. Do not cache.");
			destroy();
			return;
		}

		if (unlikely(iequals(header.name, "Cache-Control"))) {
			if (iequals(header.value, "no-cache")) {
				TRACE(requestNotes_, 2, "\"Cache-Control: no-cache\" detected. do not record object then.");
				destroy();
				return;
			}
			// TODO we can actually cache it if it's max-age=N is N > 0 for exact N seconds.
		}

        if (unlikely(iequals(header.name, "Pragma"))) {
            if (iequals(header.value, "no-cache")) {
                TRACE(requestNotes_, 2, "\"Pragma: no-cache\" detected. do not record object then.");
                destroy();
                return;
            }
        }

		if (unlikely(iequals(header.name, "X-Director-Cache")))
			continue;

		bool skip = false;
//		for (auto ignore: notes->cacheHeaderIgnores) {
//			if (iequals(header.name, ignore)) {
//				skip = true;
//				break;
//			}
//		}

		if (!skip) {
			backBuffer().headers.push_back(std::make_pair(header.name, header.value));
		}
	}

	addHeaders(requestNotes_->request, false);

	requestNotes_->request->outputFilters.push_back(std::make_shared<Builder>(this));
	requestNotes_->request->onRequestDone.connect<ObjectCache::ConcreteObject, &ObjectCache::ConcreteObject::commit>(this);
	backBuffer().status = requestNotes_->request->status;
}

std::string to_s(ObjectCache::ConcreteObject::State value)
{
	static const char* ss[] = { "Spawning", "Active", "Stale", "Updating" };
	return ss[(size_t)value];
}

void ObjectCache::ConcreteObject::addHeaders(HttpRequest* r, bool hit)
{
	static const char* ss[] = {
		"miss",  // Spawning
		"hit",   // Active
		"stale", // Stale
		"stale-updating", // Updating
	};

	r->responseHeaders.push_back("X-Cache-Lookup", ss[(size_t)state_]);

	char buf[32];
	snprintf(buf, sizeof(buf), "%zu", hit ? frontBuffer().hits : 0);
	r->responseHeaders.push_back("X-Cache-Hits", buf);

	TimeSpan age(r->connection.worker().now() - frontBuffer().ctime);
	snprintf(buf, sizeof(buf), "%zu", (size_t)(hit ? age.totalSeconds() : 0));
	r->responseHeaders.push_back("Age", buf);
}

void ObjectCache::ConcreteObject::append(const x0::BufferRef& ref)
{
	backBuffer().body.push_back(ref);
}

void ObjectCache::ConcreteObject::commit()
{
	TRACE(requestNotes_, 2, "ConcreteObject: commit");
	backBuffer().ctime = requestNotes_->request->connection.worker().now();
	swapBuffers();
	requestNotes_ = nullptr;
	state_ = Active;

	auto pendingRequests = std::move(interests_);
	size_t i = 0;
	(void) i;
	for (auto rn: pendingRequests) {
		TRACE(requestNotes_, 3, "commit: deliver to pending request %zu", ++i);
		rn->request->post([=]() {
			deliver(rn);
		});
	}
}

DateTime ObjectCache::ConcreteObject::ctime() const
{
	return frontBuffer().ctime;
}

ObjectCache::ConcreteObject* ObjectCache::ConcreteObject::select(const RequestNotes* rn) const
{
    return const_cast<ConcreteObject*>(this);
}

bool ObjectCache::ConcreteObject::update(RequestNotes* rn)
{
	TRACE(rn, 3, "ConcreteObject.update() -> %s", to_s(state_).c_str());

	if (state_ != Spawning)
		state_ = Updating;

	if (!requestNotes_) {
		// this is the first interest request, so it must be responsible for updating this object, too.
        requestNotes_ = rn;
		rn->request->onPostProcess.connect<ConcreteObject, &ConcreteObject::postProcess>(this);
		return false;
	} else {
		// we have already some request that's updating this object, so add us to the interest list
		// and wait for the response.
		interests_.push_back(rn); // TODO honor updateLockTimeout
        TRACE(rn, 3, "Concurrent update detected. Enqueuing interest (%zu).", interests_.size());
		return true;
	}
}

void ObjectCache::ConcreteObject::deliver(RequestNotes* rn)
{
	internalDeliver(rn);
}

void ObjectCache::ConcreteObject::internalDeliver(RequestNotes* rn)
{
    HttpRequest* r = rn->request;

	++frontBuffer().hits;

	TRACE(rn, 3, "ConcreteObject.deliver(): hit %zu, state %s", frontBuffer().hits, to_s(state_).c_str());

	r->status = frontBuffer().status;

	for (auto header: frontBuffer().headers)
		r->responseHeaders.push_back(header.first, header.second);

	addHeaders(r, true);

	if (!equals(r->method, "HEAD"))
		r->write<BufferRefSource>(frontBuffer().body);

	r->finish();
}

void ObjectCache::ConcreteObject::expire()
{
	state_ = Stale;
}

void ObjectCache::ConcreteObject::destroy()
{
    // reschedule all pending interested clients on this object as we're going to get destroyed.
    // the reschedule should not include the cache again.
    auto pendingRequests = std::move(interests_);
    for (auto rn: pendingRequests) {
        rn->cacheIgnore = true;
        store_->director_->reschedule(rn);
    }

	if (store_->objects_.erase(cacheKey_)) {
		delete this;
	} else {
        assert(!"Mission impossible engaged on us. Trying to destroy a cached object that is not (anymore?) in the store.");
    }
}
// }}}
// {{{ ObjectCache::VaryingObject
ObjectCache::ConcreteObject* ObjectCache::VaryingObject::select(const RequestNotes* rn) const
{
	for (const auto& object: objects_)
		if (testMatch(rn, object))
			return object;

	return nullptr;
}

bool ObjectCache::VaryingObject::testMatch(const RequestNotes* rn, const ConcreteObject* object) const
{
    HttpRequest* request = rn->request;

	for (const auto& name: requestHeaders_)
		if (request->requestHeader(name) != object->requestHeader(name))
			return false;

	return true;
}
// }}}
// {{{ ObjectCache
ObjectCache::ObjectCache(Director* director) :
    director_(director),
	enabled_(true),
	deliverActive_(true),
	deliverShadow_(true),
	lockOnUpdate_(true),
	updateLockTimeout_(TimeSpan::fromSeconds(10)),
	defaultKey_("%h%r%q"),
	defaultTTL_(TimeSpan::fromSeconds(20)), //TimeSpan::Zero),
	defaultShadowTTL_(TimeSpan::Zero),
	cacheHits_(0),
	cacheShadowHits_(0),
	cacheMisses_(0),
	cachePurges_(0),
	cacheExpiries_(0),
	objects_()
{
}

ObjectCache::~ObjectCache()
{
}

bool ObjectCache::find(const std::string& cacheKey, const std::function<void(ObjectCache::Object*)>& callback)
{
	if (enabled()) {
		ObjectMap::const_accessor accessor;
		if (objects_.find(accessor, cacheKey)) {
			callback(accessor->second);
			return true;
		}
	}
	callback(nullptr);
	return false;
}

bool ObjectCache::acquire(const std::string& cacheKey, const std::function<void(ObjectCache::Object*, bool)>& callback)
{
	if (enabled()) {
		ObjectMap::accessor accessor;
		if (objects_.insert(accessor, cacheKey)) {
			accessor->second = new ConcreteObject(this, cacheKey);
			callback(accessor->second, true);
			return true;
		} else {
			callback(accessor->second, false);
			return false;
		}
	} else {
		callback(nullptr, false);
		return false;
	}
}

bool ObjectCache::purge(const std::string& cacheKey)
{
	bool result = false;
	ObjectMap::accessor accessor;

	if (objects_.find(accessor, cacheKey)) {
		++cachePurges_;
		accessor->second->expire();
		result = true;
	}

	return result;
}

void ObjectCache::expireAll()
{
    for (auto object: objects_) {
        ++cachePurges_;
        object.second->expire();
    }
}

void ObjectCache::purgeAll()
{
    for (auto object: objects_) {
        delete object.second;
    }

    cachePurges_ += objects_.size();
    objects_.clear();
}

bool ObjectCache::deliverActive(RequestNotes* rn)
{
	if (!deliverActive())
		return false;

	bool processed = false;

	acquire(rn->cacheKey, [&](Object* someObject, bool created) {
		if (created) {
			// cache object did not exist and got just created for by this request
			//printf("Director.processCacheObject: object created\n");
			++cacheMisses_;
			processed = someObject->update(rn);
		} else if (someObject) {
            ConcreteObject* object = someObject->select(rn);

			const DateTime now = rn->request->connection.worker().now();
			const DateTime expiry = object->ctime() + rn->cacheTTL;

			if (expiry < now)
				object->expire();

			switch (object->state()) {
				case ConcreteObject::Spawning:
					++cacheHits_;
					processed = object->update(rn);
					break;
				case ConcreteObject::Updating:
					if (lockOnUpdate()) {
						++cacheHits_;
						processed = !object->update(rn);
					} else {
						++cacheShadowHits_;
						processed = true;
						object->deliver(rn);
						//TRACE(rn, 3, "ConcreteObject.deliver(): deliver shadow object");
					}
					break;
				case ConcreteObject::Stale:
					++cacheMisses_;
					processed = object->update(rn);
					break;
				case ConcreteObject::Active:
					processed = true;
					++cacheHits_;
					object->deliver(rn);
					break;
			}
		}
	});

	return processed;
}

bool ObjectCache::deliverShadow(RequestNotes* rn)
{
	if (deliverShadow()) {
		if (find(rn->cacheKey,
				[rn, this](Object* object)
				{
					if (object) {
						++cacheShadowHits_;
						rn->request->responseHeaders.push_back("X-Director-Cache", "shadow");
						object->deliver(rn);
					}
				}
			))
		{
			return true;
		}
	}

	return false;
}

void ObjectCache::writeJSON(x0::JsonWriter& json) const
{
	json.beginObject()
		.name("enabled")(enabled())
		.name("deliver-active")(deliverActive())
		.name("deliver-shadow")(deliverShadow())
        .name("default-ttl")(defaultTTL().totalSeconds())
        .name("default-shadow-ttl")(defaultShadowTTL().totalSeconds())
        .beginObject("stats")
            .name("misses")(cacheMisses())
            .name("hits")(cacheHits())
            .name("shadow-hits")(cacheShadowHits())
            .name("purges")(cachePurges())
        .endObject()
    .endObject();
}
// }}}
// {{{ ObjectCache::Builder
ObjectCache::Builder::Builder(ConcreteObject* object) :
	object_(object)
{
}

Buffer ObjectCache::Builder::process(const BufferRef& chunk)
{
	if (object_) {
		TRACE(object_->requestNotes_, 3, "ObjectCache.Builder.process(): %zu bytes", chunk.size());
		if (!chunk.empty()) {
			object_->backBuffer().body.push_back(chunk);
		}
	}

	return Buffer(chunk);
}
// }}}
<commit_msg>[plugins] director: explicitely set content-length response header on cached results (as we know it in advance)<commit_after>/* <plugins/director/ObjectCache.cpp>
 *
 * This file is part of the x0 web server project and is released under AGPL-3.
 * http://www.xzero.io/
 *
 * (c) 2009-2014 Christian Parpart <trapni@gmail.com>
 */

#include "ObjectCache.h"
#include "RequestNotes.h"
#include "Director.h"
#include <x0/io/BufferRefSource.h>
#include <x0/http/HttpRequest.h>
#include <x0/DebugLogger.h>

/*
 * List of messages headers that must be taken into account when checking for freshness.
 *
 * REQUEST HEADERS
 * - If-Range
 * - If-Match
 * - If-None-Match
 * - If-Modified-Since
 * - If-Unmodified-Since
 *
 * RESPONSE HEADERS
 * - Last-Modified
 * - ETag
 * - Expires
 * - Cache-Control
 * - Pragma (token: "no-cache")
 * - Vary (list of request headers that make a response unique in addition to its cache key, or "*" for literally all request headers)
 */

// TODO thread safety
// TODO test cache validity when output compression is enabled (or other output filters are applied).

using namespace x0;

#define TRACE(rn, n, msg...) do { \
	if ((rn) && (rn)->request != nullptr) { \
		LogMessage m(Severity::debug ## n, msg); \
		m.addTag("director-cache"); \
		rn->request->log(std::move(m)); \
	} else { \
		X0_DEBUG("director-cache", (n), msg); \
	} \
} while (0)

// {{{ ObjectCache::ConcreteObject
ObjectCache::ConcreteObject::ConcreteObject(ObjectCache* store, const std::string& cacheKey) :
    Object(),
	store_(store),
	cacheKey_(cacheKey),
    state_(Spawning),
    requestNotes_(nullptr),
	interests_(),
	requestHeaders_(),
	bufferIndex_()
{
    TRACE(requestNotes_, 2, "ConcreteObject(key: '%s')", cacheKey_.c_str());
}

ObjectCache::ConcreteObject::~ConcreteObject()
{
    TRACE(requestNotes_, 2, "~ConcreteObject()");
}

std::string ObjectCache::ConcreteObject::requestHeader(const std::string& name) const
{
	return "";
}

void ObjectCache::ConcreteObject::postProcess()
{
	TRACE(requestNotes_, 3, "ConcreteObject.postProcess() status: %d", requestNotes_->request->status);

	for (auto header: requestNotes_->request->responseHeaders) {
		TRACE(requestNotes_, 3, "ConcreteObject.postProcess() %s: %s", header.name.c_str(), header.value.c_str());
		if (unlikely(iequals(header.name, "Set-Cookie"))) {
			requestNotes_->request->log(Severity::info, "Caching requested but origin server provides uncacheable response header, Set-Cookie. Do not cache.");
			destroy();
			return;
		}

		if (unlikely(iequals(header.name, "Cache-Control"))) {
			if (iequals(header.value, "no-cache")) {
				TRACE(requestNotes_, 2, "\"Cache-Control: no-cache\" detected. do not record object then.");
				destroy();
				return;
			}
			// TODO we can actually cache it if it's max-age=N is N > 0 for exact N seconds.
		}

        if (unlikely(iequals(header.name, "Pragma"))) {
            if (iequals(header.value, "no-cache")) {
                TRACE(requestNotes_, 2, "\"Pragma: no-cache\" detected. do not record object then.");
                destroy();
                return;
            }
        }

		if (unlikely(iequals(header.name, "X-Director-Cache")))
			continue;

		bool skip = false;
//		for (auto ignore: notes->cacheHeaderIgnores) {
//			if (iequals(header.name, ignore)) {
//				skip = true;
//				break;
//			}
//		}

		if (!skip) {
			backBuffer().headers.push_back(std::make_pair(header.name, header.value));
		}
	}

	addHeaders(requestNotes_->request, false);

	requestNotes_->request->outputFilters.push_back(std::make_shared<Builder>(this));
	requestNotes_->request->onRequestDone.connect<ObjectCache::ConcreteObject, &ObjectCache::ConcreteObject::commit>(this);
	backBuffer().status = requestNotes_->request->status;
}

std::string to_s(ObjectCache::ConcreteObject::State value)
{
	static const char* ss[] = { "Spawning", "Active", "Stale", "Updating" };
	return ss[(size_t)value];
}

void ObjectCache::ConcreteObject::addHeaders(HttpRequest* r, bool hit)
{
	static const char* ss[] = {
		"miss",  // Spawning
		"hit",   // Active
		"stale", // Stale
		"stale-updating", // Updating
	};

	r->responseHeaders.push_back("X-Cache-Lookup", ss[(size_t)state_]);

	char buf[32];
	snprintf(buf, sizeof(buf), "%zu", hit ? frontBuffer().hits : 0);
	r->responseHeaders.push_back("X-Cache-Hits", buf);

	TimeSpan age(r->connection.worker().now() - frontBuffer().ctime);
	snprintf(buf, sizeof(buf), "%zu", (size_t)(hit ? age.totalSeconds() : 0));
	r->responseHeaders.push_back("Age", buf);
}

void ObjectCache::ConcreteObject::append(const x0::BufferRef& ref)
{
	backBuffer().body.push_back(ref);
}

void ObjectCache::ConcreteObject::commit()
{
	TRACE(requestNotes_, 2, "ConcreteObject: commit");
	backBuffer().ctime = requestNotes_->request->connection.worker().now();
	swapBuffers();
	requestNotes_ = nullptr;
	state_ = Active;

	auto pendingRequests = std::move(interests_);
	size_t i = 0;
	(void) i;
	for (auto rn: pendingRequests) {
		TRACE(requestNotes_, 3, "commit: deliver to pending request %zu", ++i);
		rn->request->post([=]() {
			deliver(rn);
		});
	}
}

DateTime ObjectCache::ConcreteObject::ctime() const
{
	return frontBuffer().ctime;
}

ObjectCache::ConcreteObject* ObjectCache::ConcreteObject::select(const RequestNotes* rn) const
{
    return const_cast<ConcreteObject*>(this);
}

bool ObjectCache::ConcreteObject::update(RequestNotes* rn)
{
	TRACE(rn, 3, "ConcreteObject.update() -> %s", to_s(state_).c_str());

	if (state_ != Spawning)
		state_ = Updating;

	if (!requestNotes_) {
		// this is the first interest request, so it must be responsible for updating this object, too.
        requestNotes_ = rn;
		rn->request->onPostProcess.connect<ConcreteObject, &ConcreteObject::postProcess>(this);
		return false;
	} else {
		// we have already some request that's updating this object, so add us to the interest list
		// and wait for the response.
		interests_.push_back(rn); // TODO honor updateLockTimeout
        TRACE(rn, 3, "Concurrent update detected. Enqueuing interest (%zu).", interests_.size());
		return true;
	}
}

void ObjectCache::ConcreteObject::deliver(RequestNotes* rn)
{
	internalDeliver(rn);
}

void ObjectCache::ConcreteObject::internalDeliver(RequestNotes* rn)
{
    HttpRequest* r = rn->request;

	++frontBuffer().hits;

	TRACE(rn, 3, "ConcreteObject.deliver(): hit %zu, state %s", frontBuffer().hits, to_s(state_).c_str());

	r->status = frontBuffer().status;

	for (auto header: frontBuffer().headers)
		r->responseHeaders.push_back(header.first, header.second);

	addHeaders(r, true);

    char slen[16];
    snprintf(slen, sizeof(slen), "%zu", frontBuffer().body.size());
    r->responseHeaders.overwrite("Content-Length", slen);

	if (!equals(r->method, "HEAD"))
		r->write<BufferRefSource>(frontBuffer().body);

	r->finish();
}

void ObjectCache::ConcreteObject::expire()
{
	state_ = Stale;
}

void ObjectCache::ConcreteObject::destroy()
{
    // reschedule all pending interested clients on this object as we're going to get destroyed.
    // the reschedule should not include the cache again.
    auto pendingRequests = std::move(interests_);
    for (auto rn: pendingRequests) {
        rn->cacheIgnore = true;
        store_->director_->reschedule(rn);
    }

	if (store_->objects_.erase(cacheKey_)) {
		delete this;
	} else {
        assert(!"Mission impossible engaged on us. Trying to destroy a cached object that is not (anymore?) in the store.");
    }
}
// }}}
// {{{ ObjectCache::VaryingObject
ObjectCache::ConcreteObject* ObjectCache::VaryingObject::select(const RequestNotes* rn) const
{
	for (const auto& object: objects_)
		if (testMatch(rn, object))
			return object;

	return nullptr;
}

bool ObjectCache::VaryingObject::testMatch(const RequestNotes* rn, const ConcreteObject* object) const
{
    HttpRequest* request = rn->request;

	for (const auto& name: requestHeaders_)
		if (request->requestHeader(name) != object->requestHeader(name))
			return false;

	return true;
}
// }}}
// {{{ ObjectCache
ObjectCache::ObjectCache(Director* director) :
    director_(director),
	enabled_(true),
	deliverActive_(true),
	deliverShadow_(true),
	lockOnUpdate_(true),
	updateLockTimeout_(TimeSpan::fromSeconds(10)),
	defaultKey_("%h%r%q"),
	defaultTTL_(TimeSpan::fromSeconds(20)), //TimeSpan::Zero),
	defaultShadowTTL_(TimeSpan::Zero),
	cacheHits_(0),
	cacheShadowHits_(0),
	cacheMisses_(0),
	cachePurges_(0),
	cacheExpiries_(0),
	objects_()
{
}

ObjectCache::~ObjectCache()
{
}

bool ObjectCache::find(const std::string& cacheKey, const std::function<void(ObjectCache::Object*)>& callback)
{
	if (enabled()) {
		ObjectMap::const_accessor accessor;
		if (objects_.find(accessor, cacheKey)) {
			callback(accessor->second);
			return true;
		}
	}
	callback(nullptr);
	return false;
}

bool ObjectCache::acquire(const std::string& cacheKey, const std::function<void(ObjectCache::Object*, bool)>& callback)
{
	if (enabled()) {
		ObjectMap::accessor accessor;
		if (objects_.insert(accessor, cacheKey)) {
			accessor->second = new ConcreteObject(this, cacheKey);
			callback(accessor->second, true);
			return true;
		} else {
			callback(accessor->second, false);
			return false;
		}
	} else {
		callback(nullptr, false);
		return false;
	}
}

bool ObjectCache::purge(const std::string& cacheKey)
{
	bool result = false;
	ObjectMap::accessor accessor;

	if (objects_.find(accessor, cacheKey)) {
		++cachePurges_;
		accessor->second->expire();
		result = true;
	}

	return result;
}

void ObjectCache::expireAll()
{
    for (auto object: objects_) {
        ++cachePurges_;
        object.second->expire();
    }
}

void ObjectCache::purgeAll()
{
    for (auto object: objects_) {
        delete object.second;
    }

    cachePurges_ += objects_.size();
    objects_.clear();
}

bool ObjectCache::deliverActive(RequestNotes* rn)
{
	if (!deliverActive())
		return false;

	bool processed = false;

	acquire(rn->cacheKey, [&](Object* someObject, bool created) {
		if (created) {
			// cache object did not exist and got just created for by this request
			//printf("Director.processCacheObject: object created\n");
			++cacheMisses_;
			processed = someObject->update(rn);
		} else if (someObject) {
            ConcreteObject* object = someObject->select(rn);

			const DateTime now = rn->request->connection.worker().now();
			const DateTime expiry = object->ctime() + rn->cacheTTL;

			if (expiry < now)
				object->expire();

			switch (object->state()) {
				case ConcreteObject::Spawning:
					++cacheHits_;
					processed = object->update(rn);
					break;
				case ConcreteObject::Updating:
					if (lockOnUpdate()) {
						++cacheHits_;
						processed = !object->update(rn);
					} else {
						++cacheShadowHits_;
						processed = true;
						object->deliver(rn);
						//TRACE(rn, 3, "ConcreteObject.deliver(): deliver shadow object");
					}
					break;
				case ConcreteObject::Stale:
					++cacheMisses_;
					processed = object->update(rn);
					break;
				case ConcreteObject::Active:
					processed = true;
					++cacheHits_;
					object->deliver(rn);
					break;
			}
		}
	});

	return processed;
}

bool ObjectCache::deliverShadow(RequestNotes* rn)
{
	if (deliverShadow()) {
		if (find(rn->cacheKey,
				[rn, this](Object* object)
				{
					if (object) {
						++cacheShadowHits_;
						rn->request->responseHeaders.push_back("X-Director-Cache", "shadow");
						object->deliver(rn);
					}
				}
			))
		{
			return true;
		}
	}

	return false;
}

void ObjectCache::writeJSON(x0::JsonWriter& json) const
{
	json.beginObject()
		.name("enabled")(enabled())
		.name("deliver-active")(deliverActive())
		.name("deliver-shadow")(deliverShadow())
        .name("default-ttl")(defaultTTL().totalSeconds())
        .name("default-shadow-ttl")(defaultShadowTTL().totalSeconds())
        .beginObject("stats")
            .name("misses")(cacheMisses())
            .name("hits")(cacheHits())
            .name("shadow-hits")(cacheShadowHits())
            .name("purges")(cachePurges())
        .endObject()
    .endObject();
}
// }}}
// {{{ ObjectCache::Builder
ObjectCache::Builder::Builder(ConcreteObject* object) :
	object_(object)
{
}

Buffer ObjectCache::Builder::process(const BufferRef& chunk)
{
	if (object_) {
		TRACE(object_->requestNotes_, 3, "ObjectCache.Builder.process(): %zu bytes", chunk.size());
		if (!chunk.empty()) {
			object_->backBuffer().body.push_back(chunk);
		}
	}

	return Buffer(chunk);
}
// }}}
<|endoftext|>