blob: 8d0da740508decb2d8621aa5a976371e507cd24d [file] [log] [blame]
/* -*- 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/.
*/
#include <config_features.h>
#include <formulagroup.hxx>
#include <formulagroupcl.hxx>
#include <document.hxx>
#include <formulacell.hxx>
#include <tokenarray.hxx>
#include <compiler.hxx>
#include <interpre.hxx>
#include <scmatrix.hxx>
#include <globalnames.hxx>
#include <comphelper/threadpool.hxx>
#include <tools/cpuid.hxx>
#include <formula/vectortoken.hxx>
#include <officecfg/Office/Common.hxx>
#include <officecfg/Office/Calc.hxx>
#if HAVE_FEATURE_OPENCL
#include <opencl/platforminfo.hxx>
#endif
#include <o3tl/make_unique.hxx>
#include <rtl/bootstrap.hxx>
#include <sal/alloca.h>
#include <algorithm>
#include <cstdio>
#include <unordered_map>
#include <vector>
#if HAVE_FEATURE_OPENCL
# include <opencl/openclwrapper.hxx>
#endif
namespace sc {
FormulaGroupEntry::FormulaGroupEntry( ScFormulaCell** pCells, size_t nRow, size_t nLength ) :
mpCells(pCells), mnRow(nRow), mnLength(nLength), mbShared(true) {}
FormulaGroupEntry::FormulaGroupEntry( ScFormulaCell* pCell, size_t nRow ) :
mpCell(pCell), mnRow(nRow), mnLength(0), mbShared(false) {}
size_t FormulaGroupContext::ColKey::Hash::operator ()( const FormulaGroupContext::ColKey& rKey ) const
{
return rKey.mnTab * MAXCOLCOUNT + rKey.mnCol;
}
FormulaGroupContext::ColKey::ColKey( SCTAB nTab, SCCOL nCol ) : mnTab(nTab), mnCol(nCol) {}
bool FormulaGroupContext::ColKey::operator== ( const ColKey& r ) const
{
return mnTab == r.mnTab && mnCol == r.mnCol;
}
FormulaGroupContext::ColArray::ColArray( NumArrayType* pNumArray, StrArrayType* pStrArray ) :
mpNumArray(pNumArray), mpStrArray(pStrArray), mnSize(0)
{
if (mpNumArray)
mnSize = mpNumArray->size();
else if (mpStrArray)
mnSize = mpStrArray->size();
}
FormulaGroupContext::ColArray* FormulaGroupContext::getCachedColArray( SCTAB nTab, SCCOL nCol, size_t nSize )
{
ColArraysType::iterator itColArray = maColArrays.find(ColKey(nTab, nCol));
if (itColArray == maColArrays.end())
// Not cached for this column.
return nullptr;
ColArray& rCached = itColArray->second;
if (nSize > rCached.mnSize)
// Cached data array is not long enough for the requested range.
return nullptr;
return &rCached;
}
FormulaGroupContext::ColArray* FormulaGroupContext::setCachedColArray(
SCTAB nTab, SCCOL nCol, NumArrayType* pNumArray, StrArrayType* pStrArray )
{
ColArraysType::iterator it = maColArrays.find(ColKey(nTab, nCol));
if (it == maColArrays.end())
{
std::pair<ColArraysType::iterator,bool> r =
maColArrays.emplace(ColKey(nTab, nCol), ColArray(pNumArray, pStrArray));
if (!r.second)
// Somehow the insertion failed.
return nullptr;
return &r.first->second;
}
// Prior array exists for this column. Overwrite it.
ColArray& rArray = it->second;
rArray = ColArray(pNumArray, pStrArray);
return &rArray;
}
void FormulaGroupContext::ensureStrArray( ColArray& rColArray, size_t nArrayLen )
{
if (rColArray.mpStrArray)
return;
m_StrArrays.push_back(
o3tl::make_unique<sc::FormulaGroupContext::StrArrayType>(nArrayLen, nullptr));
rColArray.mpStrArray = m_StrArrays.back().get();
}
void FormulaGroupContext::ensureNumArray( ColArray& rColArray, size_t nArrayLen )
{
if (rColArray.mpNumArray)
return;
double fNan;
rtl::math::setNan(&fNan);
m_NumArrays.push_back(
o3tl::make_unique<sc::FormulaGroupContext::NumArrayType>(nArrayLen, fNan));
rColArray.mpNumArray = m_NumArrays.back().get();
}
FormulaGroupContext::FormulaGroupContext()
{
}
FormulaGroupContext::~FormulaGroupContext()
{
}
CompiledFormula::CompiledFormula() {}
CompiledFormula::~CompiledFormula() {}
FormulaGroupInterpreterSoftware::FormulaGroupInterpreterSoftware() : FormulaGroupInterpreter()
{
}
ScMatrixRef FormulaGroupInterpreterSoftware::inverseMatrix(const ScMatrix& /*rMat*/)
{
return ScMatrixRef();
}
class SoftwareInterpreterFunc
{
public:
SoftwareInterpreterFunc(ScTokenArray& rCode,
ScAddress aBatchTopPos,
const ScAddress& rTopPos,
ScDocument& rDoc,
SvNumberFormatter* pFormatter,
std::vector<formula::FormulaConstTokenRef>& rRes,
SCROW nIndex,
SCROW nLastIndex) :
mrCode(rCode),
maBatchTopPos(aBatchTopPos),
mrTopPos(rTopPos),
mrDoc(rDoc),
mpFormatter(pFormatter),
mrResults(rRes),
mnIdx(nIndex),
mnLastIdx(nLastIndex)
{
}
void operator() ()
{
double fNan;
rtl::math::setNan(&fNan);
ScTokenArray aCode2;
ScInterpreterContext aContext(mrDoc, mpFormatter);
const formula::FormulaToken* pLastDoubleResultToken = nullptr;
const formula::FormulaToken* pLastStringResultToken = nullptr;
size_t nNumToks = mrCode.GetLen();
bool* pReuseFlags = static_cast<bool*>(alloca(sizeof(bool)*nNumToks));
if (pReuseFlags)
std::fill_n(pReuseFlags, nNumToks, true);
for (SCROW i = mnIdx; i <= mnLastIdx; ++i, maBatchTopPos.IncRow())
{
formula::FormulaTokenArrayPlainIterator aIter(mrCode);
size_t nTokIdx = 0;
for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next(), ++nTokIdx)
{
formula::FormulaToken* pTargetTok = aCode2.TokenAt(nTokIdx);
switch (p->GetType())
{
case formula::svSingleVectorRef:
{
const formula::SingleVectorRefToken* p2 = static_cast<const formula::SingleVectorRefToken*>(p);
const formula::VectorRefArray& rArray = p2->GetArray();
rtl_uString* pStr = nullptr;
double fVal = fNan;
if (static_cast<size_t>(i) < p2->GetArrayLength())
{
if (rArray.mpStringArray)
// See if the cell is of string type.
pStr = rArray.mpStringArray[i];
if (!pStr && rArray.mpNumericArray)
fVal = rArray.mpNumericArray[i];
}
if (pStr)
{
// This is a string cell.
svl::SharedStringPool& rPool = mrDoc.GetSharedStringPool();
if ( !pTargetTok )
aCode2.AddString(rPool.intern(OUString(pStr)));
else
{
bool bReuseToken = pReuseFlags && pReuseFlags[nTokIdx];
if (bReuseToken && pTargetTok == pLastStringResultToken)
// Once pReuseFlags[nTokIdx] is set to false, it will continue to be so.
bReuseToken = pReuseFlags[nTokIdx] = false;
if ( ( pTargetTok->GetType() == formula::svString ) && bReuseToken )
pTargetTok->SetString(rPool.intern(OUString(pStr)));
else
{
formula::FormulaStringToken* pStrTok = new formula::FormulaStringToken(rPool.intern(OUString(pStr)));
aCode2.ReplaceToken(nTokIdx, pStrTok, formula::FormulaTokenArray::CODE_ONLY);
}
}
}
else if (rtl::math::isNan(fVal))
{
// Value of NaN represents an empty cell.
if ( !pTargetTok )
aCode2.AddToken(ScEmptyCellToken(false, false));
else if ( pTargetTok->GetType() != formula::svEmptyCell )
{
ScEmptyCellToken* pEmptyTok = new ScEmptyCellToken(false, false);
aCode2.ReplaceToken(nTokIdx, pEmptyTok, formula::FormulaTokenArray::CODE_ONLY);
}
}
else
{
// Numeric cell.
if ( !pTargetTok )
aCode2.AddDouble(fVal);
else
{
bool bReuseToken = pReuseFlags && pReuseFlags[nTokIdx];
if (bReuseToken && pTargetTok == pLastDoubleResultToken)
// Once pReuseFlags[nTokIdx] is set to false, it will continue to be so.
bReuseToken = pReuseFlags[nTokIdx] = false;
if ( ( pTargetTok->GetType() == formula::svDouble ) && bReuseToken )
pTargetTok->GetDoubleAsReference() = fVal;
else
{
formula::FormulaDoubleToken* pDoubleTok = new formula::FormulaDoubleToken( fVal );
aCode2.ReplaceToken(nTokIdx, pDoubleTok, formula::FormulaTokenArray::CODE_ONLY);
}
}
}
}
break;
case formula::svDoubleVectorRef:
{
const formula::DoubleVectorRefToken* p2 = static_cast<const formula::DoubleVectorRefToken*>(p);
size_t nRowStart = p2->IsStartFixed() ? 0 : i;
size_t nRowEnd = p2->GetRefRowSize() - 1;
if (!p2->IsEndFixed())
nRowEnd += i;
assert(nRowStart <= nRowEnd);
ScMatrixRef pMat(new ScVectorRefMatrix(p2, nRowStart, nRowEnd - nRowStart + 1));
if (p2->IsStartFixed() && p2->IsEndFixed())
{
// Cached the converted token for absolute range reference.
ScComplexRefData aRef;
ScRange aRefRange = mrTopPos;
aRefRange.aEnd.SetRow(mrTopPos.Row() + nRowEnd);
aRef.InitRange(aRefRange);
formula::FormulaTokenRef xTok(new ScMatrixRangeToken(pMat, aRef));
if ( !pTargetTok )
aCode2.AddToken(*xTok);
else
aCode2.ReplaceToken(nTokIdx, xTok.get(), formula::FormulaTokenArray::CODE_ONLY);
}
else
{
if ( !pTargetTok )
{
ScMatrixToken aTok(pMat);
aCode2.AddToken(aTok);
}
else
{
ScMatrixToken* pMatTok = new ScMatrixToken(pMat);
aCode2.ReplaceToken(nTokIdx, pMatTok, formula::FormulaTokenArray::CODE_ONLY);
}
}
}
break;
default:
if ( !pTargetTok )
aCode2.AddToken(*p);
} // end of switch statement
} // end of formula token for loop
ScFormulaCell* pDest = mrDoc.GetFormulaCell(maBatchTopPos);
if (!pDest)
return;
ScCompiler aComp(&mrDoc, maBatchTopPos, aCode2);
aComp.CompileTokenArray();
ScInterpreter aInterpreter(pDest, &mrDoc, aContext, maBatchTopPos, aCode2);
aInterpreter.Interpret();
mrResults[i] = aInterpreter.GetResultToken();
const auto* pResultToken = mrResults[i].get();
if (pResultToken)
{
if (pResultToken->GetType() == formula::svDouble)
pLastDoubleResultToken = pResultToken;
else if (pResultToken->GetType() == formula::svString)
pLastStringResultToken = pResultToken;
}
} // Row iteration for loop end
} // operator () end
private:
ScTokenArray& mrCode;
ScAddress maBatchTopPos;
const ScAddress& mrTopPos;
ScDocument& mrDoc;
SvNumberFormatter* mpFormatter;
std::vector<formula::FormulaConstTokenRef>& mrResults;
SCROW mnIdx;
SCROW mnLastIdx;
};
bool FormulaGroupInterpreterSoftware::interpret(ScDocument& rDoc, const ScAddress& rTopPos,
ScFormulaCellGroupRef& xGroup,
ScTokenArray& rCode)
{
// Decompose the group into individual cells and calculate them individually.
// The caller must ensure that the top position is the start position of
// the group.
static bool bHyperThreadingActive = tools::cpuid::hasHyperThreading();
ScAddress aTmpPos = rTopPos;
std::vector<formula::FormulaConstTokenRef> aResults(xGroup->mnLength);
class Executor : public comphelper::ThreadTask
{
public:
Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag,
ScTokenArray& rCode2,
ScAddress aBatchTopPos,
const ScAddress& rTopPos2,
ScDocument& rDoc2,
SvNumberFormatter* pFormatter2,
std::vector<formula::FormulaConstTokenRef>& rRes,
SCROW nIndex,
SCROW nLastIndex) :
comphelper::ThreadTask(rTag),
maSWIFunc(rCode2, aBatchTopPos, rTopPos2, rDoc2, pFormatter2, rRes, nIndex, nLastIndex)
{
}
virtual void doWork() override
{
maSWIFunc();
}
private:
SoftwareInterpreterFunc maSWIFunc;
};
static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION");
bool bUseThreading = !bThreadingProhibited && ScCalcConfig::isThreadingEnabled() && rCode.IsEnabledForThreading();
SvNumberFormatter* pFormatter = rDoc.GetNonThreadedContext().GetFormatTable();
if (bUseThreading)
{
comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());
sal_Int32 nThreadCount = rThreadPool.getWorkerCount();
if ( bHyperThreadingActive && nThreadCount >= 2 )
nThreadCount /= 2;
SCROW nLen = xGroup->mnLength;
SCROW nBatchSize = nLen / nThreadCount;
if (nLen < nThreadCount)
{
nBatchSize = 1;
nThreadCount = nLen;
}
SCROW nRemaining = nLen - nBatchSize * nThreadCount;
SAL_INFO("sc.threaded", "Running " << nThreadCount << " threads");
SCROW nLeft = nLen;
SCROW nStart = 0;
std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();
while (nLeft > 0)
{
SCROW nCount = std::min(nLeft, nBatchSize) + (nRemaining ? 1 : 0);
if ( nRemaining )
--nRemaining;
SCROW nLast = nStart + nCount - 1;
rThreadPool.pushTask(new Executor(aTag, rCode, aTmpPos, rTopPos, rDoc, pFormatter, aResults, nStart, nLast));
aTmpPos.IncRow(nCount);
nLeft -= nCount;
nStart = nLast + 1;
}
SAL_INFO("sc.threaded", "Joining threads");
rThreadPool.waitUntilDone(aTag);
SAL_INFO("sc.threaded", "Done");
}
else
{
SoftwareInterpreterFunc aSWIFunc(rCode, aTmpPos, rTopPos, rDoc, pFormatter, aResults, 0, xGroup->mnLength - 1);
aSWIFunc();
}
for (SCROW i = 0; i < xGroup->mnLength; ++i)
if (!aResults[i].get())
return false;
if (!aResults.empty())
rDoc.SetFormulaResults(rTopPos, &aResults[0], aResults.size());
return true;
}
FormulaGroupInterpreter *FormulaGroupInterpreter::msInstance = nullptr;
void FormulaGroupInterpreter::MergeCalcConfig(const ScDocument& rDoc)
{
maCalcConfig = ScInterpreter::GetGlobalConfig();
maCalcConfig.MergeDocumentSpecific(rDoc.GetCalcConfig());
}
/// load and/or configure the correct formula group interpreter
FormulaGroupInterpreter *FormulaGroupInterpreter::getStatic()
{
if ( !msInstance )
{
#if HAVE_FEATURE_OPENCL
if (ScCalcConfig::isOpenCLEnabled())
{
const ScCalcConfig& rConfig = ScInterpreter::GetGlobalConfig();
switchOpenCLDevice(rConfig.maOpenCLDevice, rConfig.mbOpenCLAutoSelect);
}
#endif
if (!msInstance && ScCalcConfig::isSwInterpreterEnabled()) // software interpreter
{
SAL_INFO("sc.core.formulagroup", "Create S/W interpreter");
msInstance = new sc::FormulaGroupInterpreterSoftware();
}
}
return msInstance;
}
#if HAVE_FEATURE_OPENCL
void FormulaGroupInterpreter::fillOpenCLInfo(std::vector<OpenCLPlatformInfo>& rPlatforms)
{
const std::vector<OpenCLPlatformInfo>& rPlatformsFromWrapper =
openclwrapper::fillOpenCLInfo();
rPlatforms.assign(rPlatformsFromWrapper.begin(), rPlatformsFromWrapper.end());
}
bool FormulaGroupInterpreter::switchOpenCLDevice(const OUString& rDeviceId, bool bAutoSelect, bool bForceEvaluation)
{
bool bOpenCLEnabled = ScCalcConfig::isOpenCLEnabled();
if (!bOpenCLEnabled || (rDeviceId == OPENCL_SOFTWARE_DEVICE_CONFIG_NAME))
{
bool bSwInterpreterEnabled = ScCalcConfig::isSwInterpreterEnabled();
if (msInstance)
{
// if we already have a software interpreter don't delete it
if (bSwInterpreterEnabled && dynamic_cast<sc::FormulaGroupInterpreterSoftware*>(msInstance))
return true;
delete msInstance;
msInstance = nullptr;
}
if (bSwInterpreterEnabled)
{
msInstance = new sc::FormulaGroupInterpreterSoftware();
return true;
}
return false;
}
OUString aSelectedCLDeviceVersionID;
bool bSuccess = openclwrapper::switchOpenCLDevice(&rDeviceId, bAutoSelect, bForceEvaluation, aSelectedCLDeviceVersionID);
if (!bSuccess)
return false;
delete msInstance;
msInstance = new sc::opencl::FormulaGroupInterpreterOpenCL();
return true;
}
void FormulaGroupInterpreter::getOpenCLDeviceInfo(sal_Int32& rDeviceId, sal_Int32& rPlatformId)
{
rDeviceId = -1;
rPlatformId = -1;
bool bOpenCLEnabled = ScCalcConfig::isOpenCLEnabled();
if(!bOpenCLEnabled)
return;
size_t aDeviceId = static_cast<size_t>(-1);
size_t aPlatformId = static_cast<size_t>(-1);
openclwrapper::getOpenCLDeviceInfo(aDeviceId, aPlatformId);
rDeviceId = aDeviceId;
rPlatformId = aPlatformId;
}
void FormulaGroupInterpreter::enableOpenCL_UnitTestsOnly()
{
std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
officecfg::Office::Common::Misc::UseOpenCL::set(true, batch);
batch->commit();
ScCalcConfig aConfig = ScInterpreter::GetGlobalConfig();
aConfig.mbOpenCLSubsetOnly = false;
aConfig.mnOpenCLMinimumFormulaGroupSize = 2;
ScInterpreter::SetGlobalConfig(aConfig);
}
void FormulaGroupInterpreter::disableOpenCL_UnitTestsOnly()
{
std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
officecfg::Office::Common::Misc::UseOpenCL::set(false, batch);
batch->commit();
}
#endif
} // namespace sc
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */