Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : */
9 :
10 : #include <config_features.h>
11 :
12 : #include "formulagroup.hxx"
13 : #include "formulagroupcl.hxx"
14 : #include "document.hxx"
15 : #include "formulacell.hxx"
16 : #include "tokenarray.hxx"
17 : #include "compiler.hxx"
18 : #include "interpre.hxx"
19 : #include "scmatrix.hxx"
20 : #include "globalnames.hxx"
21 :
22 : #include <formula/vectortoken.hxx>
23 : #include <officecfg/Office/Common.hxx>
24 : #if HAVE_FEATURE_OPENCL
25 : #include <opencl/platforminfo.hxx>
26 : #endif
27 : #include <rtl/bootstrap.hxx>
28 :
29 : #include <cstdio>
30 : #include <unordered_map>
31 : #include <vector>
32 :
33 : #if HAVE_FEATURE_OPENCL
34 : #include <opencl/openclwrapper.hxx>
35 : #endif
36 :
37 : namespace sc {
38 :
39 67 : FormulaGroupEntry::FormulaGroupEntry( ScFormulaCell** pCells, size_t nRow, size_t nLength ) :
40 67 : mpCells(pCells), mnRow(nRow), mnLength(nLength), mbShared(true) {}
41 :
42 225 : FormulaGroupEntry::FormulaGroupEntry( ScFormulaCell* pCell, size_t nRow ) :
43 225 : mpCell(pCell), mnRow(nRow), mnLength(0), mbShared(false) {}
44 :
45 51 : size_t FormulaGroupContext::ColKey::Hash::operator ()( const FormulaGroupContext::ColKey& rKey ) const
46 : {
47 51 : return rKey.mnTab * MAXCOLCOUNT + rKey.mnCol;
48 : }
49 :
50 51 : FormulaGroupContext::ColKey::ColKey( SCTAB nTab, SCCOL nCol ) : mnTab(nTab), mnCol(nCol) {}
51 :
52 14 : bool FormulaGroupContext::ColKey::operator== ( const ColKey& r ) const
53 : {
54 14 : return mnTab == r.mnTab && mnCol == r.mnCol;
55 : }
56 :
57 0 : bool FormulaGroupContext::ColKey::operator!= ( const ColKey& r ) const
58 : {
59 0 : return !operator==(r);
60 : }
61 :
62 17 : FormulaGroupContext::ColArray::ColArray( NumArrayType* pNumArray, StrArrayType* pStrArray ) :
63 17 : mpNumArray(pNumArray), mpStrArray(pStrArray), mnSize(0)
64 : {
65 17 : if (mpNumArray)
66 13 : mnSize = mpNumArray->size();
67 4 : else if (mpStrArray)
68 4 : mnSize = mpStrArray->size();
69 17 : }
70 :
71 22 : FormulaGroupContext::ColArray* FormulaGroupContext::getCachedColArray( SCTAB nTab, SCCOL nCol, size_t nSize )
72 : {
73 22 : ColArraysType::iterator itColArray = maColArrays.find(ColKey(nTab, nCol));
74 22 : if (itColArray == maColArrays.end())
75 : // Not cached for this column.
76 13 : return NULL;
77 :
78 9 : ColArray& rCached = itColArray->second;
79 9 : if (nSize > rCached.mnSize)
80 : // Cached data array is not long enough for the requested range.
81 5 : return NULL;
82 :
83 4 : return &rCached;
84 : }
85 :
86 17 : FormulaGroupContext::ColArray* FormulaGroupContext::setCachedColArray(
87 : SCTAB nTab, SCCOL nCol, NumArrayType* pNumArray, StrArrayType* pStrArray )
88 : {
89 17 : ColArraysType::iterator it = maColArrays.find(ColKey(nTab, nCol));
90 17 : if (it == maColArrays.end())
91 : {
92 : std::pair<ColArraysType::iterator,bool> r =
93 : maColArrays.insert(
94 12 : ColArraysType::value_type(ColKey(nTab, nCol), ColArray(pNumArray, pStrArray)));
95 :
96 12 : if (!r.second)
97 : // Somehow the insertion failed.
98 0 : return NULL;
99 :
100 12 : return &r.first->second;
101 : }
102 :
103 : // Prior array exists for this column. Overwrite it.
104 5 : ColArray& rArray = it->second;
105 5 : rArray = ColArray(pNumArray, pStrArray);
106 5 : return &rArray;
107 : }
108 :
109 6 : void FormulaGroupContext::ensureStrArray( ColArray& rColArray, size_t nArrayLen )
110 : {
111 6 : if (rColArray.mpStrArray)
112 7 : return;
113 :
114 : maStrArrays.push_back(
115 5 : new sc::FormulaGroupContext::StrArrayType(nArrayLen, NULL));
116 5 : rColArray.mpStrArray = &maStrArrays.back();
117 : }
118 :
119 14 : void FormulaGroupContext::ensureNumArray( ColArray& rColArray, size_t nArrayLen )
120 : {
121 14 : if (rColArray.mpNumArray)
122 27 : return;
123 :
124 : double fNan;
125 1 : rtl::math::setNan(&fNan);
126 :
127 : maNumArrays.push_back(
128 1 : new sc::FormulaGroupContext::NumArrayType(nArrayLen, fNan));
129 1 : rColArray.mpNumArray = &maNumArrays.back();
130 : }
131 :
132 8 : FormulaGroupContext::FormulaGroupContext()
133 : {
134 8 : }
135 :
136 8 : FormulaGroupContext::~FormulaGroupContext()
137 : {
138 8 : }
139 :
140 : namespace {
141 :
142 : /**
143 : * Input double array consists of segments of NaN's and normal values.
144 : * Insert only the normal values into the matrix while skipping the NaN's.
145 : */
146 0 : void fillMatrix( ScMatrix& rMat, size_t nCol, const double* pNums, size_t nLen )
147 : {
148 0 : const double* pNum = pNums;
149 0 : const double* pNumEnd = pNum + nLen;
150 0 : const double* pNumHead = NULL;
151 0 : for (; pNum != pNumEnd; ++pNum)
152 : {
153 0 : if (!rtl::math::isNan(*pNum))
154 : {
155 0 : if (!pNumHead)
156 : // Store the first non-NaN position.
157 0 : pNumHead = pNum;
158 :
159 0 : continue;
160 : }
161 :
162 0 : if (pNumHead)
163 : {
164 : // Flush this non-NaN segment to the matrix.
165 0 : rMat.PutDouble(pNumHead, pNum - pNumHead, nCol, pNumHead - pNums);
166 0 : pNumHead = NULL;
167 : }
168 : }
169 :
170 0 : if (pNumHead)
171 : {
172 : // Flush last non-NaN segment to the matrix.
173 0 : rMat.PutDouble(pNumHead, pNum - pNumHead, nCol, pNumHead - pNums);
174 : }
175 0 : }
176 :
177 0 : void flushStrSegment(
178 : ScMatrix& rMat, size_t nCol, rtl_uString** pHead, rtl_uString** pCur, rtl_uString** pTop )
179 : {
180 0 : size_t nOffset = pHead - pTop;
181 0 : std::vector<svl::SharedString> aStrs;
182 0 : aStrs.reserve(pCur - pHead);
183 0 : for (; pHead != pCur; ++pHead)
184 0 : aStrs.push_back(svl::SharedString(*pHead, *pHead));
185 :
186 0 : rMat.PutString(&aStrs[0], aStrs.size(), nCol, nOffset);
187 0 : }
188 :
189 0 : void fillMatrix( ScMatrix& rMat, size_t nCol, rtl_uString** pStrs, size_t nLen )
190 : {
191 0 : rtl_uString** p = pStrs;
192 0 : rtl_uString** pEnd = p + nLen;
193 0 : rtl_uString** pHead = NULL;
194 0 : for (; p != pEnd; ++p)
195 : {
196 0 : if (*p)
197 : {
198 0 : if (!pHead)
199 : // Store the first non-empty string position.
200 0 : pHead = p;
201 :
202 0 : continue;
203 : }
204 :
205 0 : if (pHead)
206 : {
207 : // Flush this non-empty segment to the matrix.
208 0 : flushStrSegment(rMat, nCol, pHead, p, pStrs);
209 0 : pHead = NULL;
210 : }
211 : }
212 :
213 0 : if (pHead)
214 : {
215 : // Flush last non-empty segment to the matrix.
216 0 : flushStrSegment(rMat, nCol, pHead, p, pStrs);
217 : }
218 0 : }
219 :
220 0 : void fillMatrix( ScMatrix& rMat, size_t nCol, const double* pNums, rtl_uString** pStrs, size_t nLen )
221 : {
222 0 : if (!pStrs)
223 : {
224 0 : fillMatrix(rMat, nCol, pNums, nLen);
225 0 : return;
226 : }
227 :
228 0 : const double* pNum = pNums;
229 0 : const double* pNumHead = NULL;
230 0 : rtl_uString** pStr = pStrs;
231 0 : rtl_uString** pStrEnd = pStr + nLen;
232 0 : rtl_uString** pStrHead = NULL;
233 :
234 0 : for (; pStr != pStrEnd; ++pStr, ++pNum)
235 : {
236 0 : if (*pStr)
237 : {
238 : // String cell exists.
239 :
240 0 : if (pNumHead)
241 : {
242 : // Flush this numeric segment to the matrix.
243 0 : rMat.PutDouble(pNumHead, pNum - pNumHead, nCol, pNumHead - pNums);
244 0 : pNumHead = NULL;
245 : }
246 :
247 0 : if (!pStrHead)
248 : // Store the first non-empty string position.
249 0 : pStrHead = pStr;
250 :
251 0 : continue;
252 : }
253 :
254 : // No string cell. Check the numeric cell value.
255 :
256 0 : if (pStrHead)
257 : {
258 : // Flush this non-empty string segment to the matrix.
259 0 : flushStrSegment(rMat, nCol, pStrHead, pStr, pStrs);
260 0 : pStrHead = NULL;
261 : }
262 :
263 0 : if (!rtl::math::isNan(*pNum))
264 : {
265 : // Numeric cell exists.
266 0 : if (!pNumHead)
267 : // Store the first non-NaN position.
268 0 : pNumHead = pNum;
269 :
270 0 : continue;
271 : }
272 :
273 : // Empty cell. No action required.
274 : }
275 :
276 0 : if (pStrHead)
277 : {
278 : // Flush the last non-empty segment to the matrix.
279 0 : flushStrSegment(rMat, nCol, pStrHead, pStr, pStrs);
280 : }
281 0 : else if (pNumHead)
282 : {
283 : // Flush the last numeric segment to the matrix.
284 0 : rMat.PutDouble(pNumHead, pNum - pNumHead, nCol, pNumHead - pNums);
285 : }
286 : }
287 :
288 : }
289 :
290 0 : CompiledFormula::CompiledFormula() {}
291 :
292 0 : CompiledFormula::~CompiledFormula() {}
293 :
294 0 : FormulaGroupInterpreterSoftware::FormulaGroupInterpreterSoftware() : FormulaGroupInterpreter()
295 : {
296 0 : }
297 :
298 0 : ScMatrixRef FormulaGroupInterpreterSoftware::inverseMatrix(const ScMatrix& /*rMat*/)
299 : {
300 0 : return ScMatrixRef();
301 : }
302 :
303 0 : CompiledFormula* FormulaGroupInterpreterSoftware::createCompiledFormula(
304 : ScFormulaCellGroup& /*rGroup*/, ScTokenArray& /*rCode*/ )
305 : {
306 0 : return NULL;
307 : }
308 :
309 0 : bool FormulaGroupInterpreterSoftware::interpret(ScDocument& rDoc, const ScAddress& rTopPos,
310 : ScFormulaCellGroupRef& xGroup,
311 : ScTokenArray& rCode)
312 : {
313 : typedef std::unordered_map<const formula::FormulaToken*, formula::FormulaTokenRef> CachedTokensType;
314 :
315 : // Decompose the group into individual cells and calculate them individually.
316 :
317 : // The caller must ensure that the top position is the start position of
318 : // the group.
319 :
320 0 : ScAddress aTmpPos = rTopPos;
321 0 : std::vector<formula::FormulaTokenRef> aResults;
322 0 : aResults.reserve(xGroup->mnLength);
323 0 : CachedTokensType aCachedTokens;
324 :
325 : double fNan;
326 0 : rtl::math::setNan(&fNan);
327 :
328 0 : for (SCROW i = 0; i < xGroup->mnLength; ++i, aTmpPos.IncRow())
329 : {
330 0 : ScTokenArray aCode2;
331 0 : for (const formula::FormulaToken* p = rCode.First(); p; p = rCode.Next())
332 : {
333 0 : CachedTokensType::iterator it = aCachedTokens.find(p);
334 0 : if (it != aCachedTokens.end())
335 : {
336 : // This token is cached. Use the cached one.
337 0 : aCode2.AddToken(*it->second);
338 0 : continue;
339 : }
340 :
341 0 : switch (p->GetType())
342 : {
343 : case formula::svSingleVectorRef:
344 : {
345 0 : const formula::SingleVectorRefToken* p2 = static_cast<const formula::SingleVectorRefToken*>(p);
346 0 : const formula::VectorRefArray& rArray = p2->GetArray();
347 :
348 0 : rtl_uString* pStr = NULL;
349 0 : double fVal = fNan;
350 0 : if (static_cast<size_t>(i) < p2->GetArrayLength())
351 : {
352 0 : if (rArray.mpStringArray)
353 : // See if the cell is of string type.
354 0 : pStr = rArray.mpStringArray[i];
355 :
356 0 : if (!pStr && rArray.mpNumericArray)
357 0 : fVal = rArray.mpNumericArray[i];
358 : }
359 :
360 0 : if (pStr)
361 : {
362 : // This is a string cell.
363 0 : svl::SharedStringPool& rPool = rDoc.GetSharedStringPool();
364 0 : aCode2.AddString(rPool.intern(OUString(pStr)));
365 : }
366 0 : else if (rtl::math::isNan(fVal))
367 : // Value of NaN represents an empty cell.
368 0 : aCode2.AddToken(ScEmptyCellToken(false, false));
369 : else
370 : // Numeric cell.
371 0 : aCode2.AddDouble(fVal);
372 : }
373 0 : break;
374 : case formula::svDoubleVectorRef:
375 : {
376 0 : const formula::DoubleVectorRefToken* p2 = static_cast<const formula::DoubleVectorRefToken*>(p);
377 0 : const std::vector<formula::VectorRefArray>& rArrays = p2->GetArrays();
378 0 : size_t nColSize = rArrays.size();
379 0 : size_t nRowStart = p2->IsStartFixed() ? 0 : i;
380 0 : size_t nRowEnd = p2->GetRefRowSize() - 1;
381 0 : if (!p2->IsEndFixed())
382 0 : nRowEnd += i;
383 0 : size_t nRowSize = nRowEnd - nRowStart + 1;
384 0 : ScMatrixRef pMat(new ScMatrix(nColSize, nRowSize));
385 :
386 0 : size_t nDataRowEnd = p2->GetArrayLength() - 1;
387 0 : if (nRowStart > nDataRowEnd)
388 : // Referenced rows are all empty.
389 0 : nRowSize = 0;
390 0 : else if (nRowEnd > nDataRowEnd)
391 : // Data array is shorter than the row size of the reference. Truncate it to the data.
392 0 : nRowSize -= nRowEnd - nDataRowEnd;
393 :
394 0 : for (size_t nCol = 0; nCol < nColSize; ++nCol)
395 : {
396 0 : const formula::VectorRefArray& rArray = rArrays[nCol];
397 0 : if (rArray.mpStringArray)
398 : {
399 0 : if (rArray.mpNumericArray)
400 : {
401 : // Mixture of string and numeric values.
402 0 : const double* pNums = rArray.mpNumericArray;
403 0 : pNums += nRowStart;
404 0 : rtl_uString** pStrs = rArray.mpStringArray;
405 0 : pStrs += nRowStart;
406 0 : fillMatrix(*pMat, nCol, pNums, pStrs, nRowSize);
407 : }
408 : else
409 : {
410 : // String cells only.
411 0 : rtl_uString** pStrs = rArray.mpStringArray;
412 0 : pStrs += nRowStart;
413 0 : fillMatrix(*pMat, nCol, pStrs, nRowSize);
414 : }
415 : }
416 0 : else if (rArray.mpNumericArray)
417 : {
418 : // Numeric cells only.
419 0 : const double* pNums = rArray.mpNumericArray;
420 0 : pNums += nRowStart;
421 0 : fillMatrix(*pMat, nCol, pNums, nRowSize);
422 : }
423 : }
424 :
425 0 : if (p2->IsStartFixed() && p2->IsEndFixed())
426 : {
427 : // Cached the converted token for absolute range referene.
428 : ScComplexRefData aRef;
429 0 : ScRange aRefRange = rTopPos;
430 0 : aRefRange.aEnd.SetRow(rTopPos.Row() + nRowEnd);
431 0 : aRef.InitRange(aRefRange);
432 0 : formula::FormulaTokenRef xTok(new ScMatrixRangeToken(pMat, aRef));
433 0 : aCachedTokens.insert(CachedTokensType::value_type(p, xTok));
434 0 : aCode2.AddToken(*xTok);
435 : }
436 : else
437 : {
438 0 : ScMatrixToken aTok(pMat);
439 0 : aCode2.AddToken(aTok);
440 0 : }
441 : }
442 0 : break;
443 : default:
444 0 : aCode2.AddToken(*p);
445 : }
446 : }
447 :
448 0 : ScFormulaCell* pDest = rDoc.GetFormulaCell(aTmpPos);
449 0 : if (!pDest)
450 0 : return false;
451 :
452 0 : ScCompiler aComp(&rDoc, aTmpPos, aCode2);
453 0 : aComp.CompileTokenArray();
454 0 : ScInterpreter aInterpreter(pDest, &rDoc, aTmpPos, aCode2);
455 0 : aInterpreter.Interpret();
456 0 : aResults.push_back(aInterpreter.GetResultToken());
457 0 : } // for loop end (xGroup->mnLength)
458 :
459 0 : if (!aResults.empty())
460 0 : rDoc.SetFormulaResults(rTopPos, &aResults[0], aResults.size());
461 :
462 0 : return true;
463 : }
464 :
465 : FormulaGroupInterpreter *FormulaGroupInterpreter::msInstance = NULL;
466 :
467 0 : void FormulaGroupInterpreter::MergeCalcConfig(const ScDocument& rDoc)
468 : {
469 0 : maCalcConfig = ScInterpreter::GetGlobalConfig();
470 0 : maCalcConfig.MergeDocumentSpecific(rDoc.GetCalcConfig());
471 0 : }
472 :
473 : /// load and/or configure the correct formula group interpreter
474 1 : FormulaGroupInterpreter *FormulaGroupInterpreter::getStatic()
475 : {
476 1 : if ( !msInstance )
477 : {
478 : #if HAVE_FEATURE_OPENCL
479 1 : const ScCalcConfig& rConfig = ScInterpreter::GetGlobalConfig();
480 1 : if (officecfg::Office::Common::Misc::UseOpenCL::get())
481 1 : switchOpenCLDevice(rConfig.maOpenCLDevice, rConfig.mbOpenCLAutoSelect, false);
482 : #endif
483 1 : static bool bAllowSoftwareInterpreter = (getenv("SC_ALLOW_BROKEN_SOFTWARE_INTERPRETER") != NULL);
484 :
485 1 : if ( !msInstance && bAllowSoftwareInterpreter ) // software fallback
486 : {
487 : SAL_INFO("sc.formulagroup", "Create S/W interpreter");
488 0 : msInstance = new sc::FormulaGroupInterpreterSoftware();
489 : }
490 : }
491 :
492 1 : return msInstance;
493 : }
494 :
495 : #if HAVE_FEATURE_OPENCL
496 0 : void FormulaGroupInterpreter::fillOpenCLInfo(std::vector<OpenCLPlatformInfo>& rPlatforms)
497 : {
498 : const std::vector<OpenCLPlatformInfo>& rPlatformsFromWrapper =
499 0 : ::opencl::fillOpenCLInfo();
500 :
501 0 : rPlatforms.assign(rPlatformsFromWrapper.begin(), rPlatformsFromWrapper.end());
502 0 : }
503 :
504 224 : bool FormulaGroupInterpreter::switchOpenCLDevice(const OUString& rDeviceId, bool bAutoSelect, bool bForceEvaluation)
505 : {
506 224 : bool bOpenCLEnabled = officecfg::Office::Common::Misc::UseOpenCL::get();
507 224 : static bool bAllowSoftwareInterpreter = (getenv("SC_ALLOW_BROKEN_SOFTWARE_INTERPRETER") != NULL);
508 224 : if (!bOpenCLEnabled || (bAllowSoftwareInterpreter && rDeviceId == OPENCL_SOFTWARE_DEVICE_CONFIG_NAME))
509 : {
510 0 : if(msInstance)
511 : {
512 : // if we already have a software interpreter don't delete it
513 0 : if(dynamic_cast<sc::FormulaGroupInterpreterSoftware*>(msInstance))
514 0 : return true;
515 :
516 0 : delete msInstance;
517 : }
518 :
519 0 : msInstance = new sc::FormulaGroupInterpreterSoftware();
520 0 : return true;
521 : }
522 224 : bool bSuccess = ::opencl::switchOpenCLDevice(&rDeviceId, bAutoSelect, bForceEvaluation);
523 224 : if(!bSuccess)
524 224 : return false;
525 :
526 0 : delete msInstance;
527 0 : msInstance = NULL;
528 :
529 0 : if ( officecfg::Office::Common::Misc::UseOpenCL::get() )
530 : {
531 0 : msInstance = new sc::opencl::FormulaGroupInterpreterOpenCL();
532 0 : return msInstance != NULL;
533 : }
534 :
535 0 : return false;
536 : }
537 :
538 0 : void FormulaGroupInterpreter::getOpenCLDeviceInfo(sal_Int32& rDeviceId, sal_Int32& rPlatformId)
539 : {
540 0 : rDeviceId = -1;
541 0 : rPlatformId = -1;
542 0 : bool bOpenCLEnabled = officecfg::Office::Common::Misc::UseOpenCL::get();
543 0 : if(!bOpenCLEnabled)
544 0 : return;
545 :
546 0 : size_t aDeviceId = static_cast<size_t>(-1);
547 0 : size_t aPlatformId = static_cast<size_t>(-1);
548 :
549 0 : ::opencl::getOpenCLDeviceInfo(aDeviceId, aPlatformId);
550 0 : rDeviceId = aDeviceId;
551 0 : rPlatformId = aPlatformId;
552 : }
553 :
554 223 : void FormulaGroupInterpreter::enableOpenCL_UnitTestsOnly()
555 : {
556 223 : std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
557 223 : officecfg::Office::Common::Misc::UseOpenCL::set(true, batch);
558 223 : batch->commit();
559 :
560 446 : ScCalcConfig aConfig = ScInterpreter::GetGlobalConfig();
561 :
562 223 : aConfig.mbOpenCLSubsetOnly = false;
563 223 : aConfig.mnOpenCLMinimumFormulaGroupSize = 2;
564 :
565 446 : ScInterpreter::SetGlobalConfig(aConfig);
566 223 : }
567 :
568 : #endif
569 :
570 156 : } // namespace sc
571 :
572 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|