525 lines
20 KiB
C++
525 lines
20 KiB
C++
/* -*- 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/.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <clew/clew.h>
|
|
#include <formula/token.hxx>
|
|
#include <formula/types.hxx>
|
|
#include <formula/vectortoken.hxx>
|
|
#include <opencl/OpenCLZone.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <vector>
|
|
#include "utils.hxx"
|
|
|
|
struct ScCalcConfig;
|
|
|
|
namespace sc::opencl {
|
|
|
|
// FIXME: The idea that somebody would bother to (now and then? once a year? once a month?) manually
|
|
// edit a source file and change the value of some #defined constant and run some ill-defined
|
|
// "correctness test" is of course ludicrous. Either things are checked in normal unit tests, in
|
|
// every 'make check', or not at all. The below comments are ridiculous.
|
|
|
|
constexpr auto REDUCE_THRESHOLD = 201; // set to 4 for correctness testing. priority 1
|
|
constexpr auto UNROLLING_FACTOR = 16; // set to 4 for correctness testing (if no reduce)
|
|
|
|
|
|
class FormulaTreeNode;
|
|
|
|
/// Exceptions
|
|
|
|
/// Failed in parsing
|
|
class UnhandledToken
|
|
{
|
|
public:
|
|
UnhandledToken( const char* m, std::string fn, int ln );
|
|
|
|
std::string mMessage;
|
|
std::string mFile;
|
|
int mLineNumber;
|
|
};
|
|
|
|
/// Failed in marshaling
|
|
class OpenCLError
|
|
{
|
|
public:
|
|
OpenCLError( std::string function, cl_int error, std::string file, int line );
|
|
|
|
std::string mFunction;
|
|
cl_int mError;
|
|
std::string mFile;
|
|
int mLineNumber;
|
|
};
|
|
|
|
/// Inconsistent state
|
|
class Unhandled
|
|
{
|
|
public:
|
|
Unhandled( std::string fn, int ln );
|
|
|
|
std::string mFile;
|
|
int mLineNumber;
|
|
};
|
|
|
|
class InvalidParameterCount
|
|
{
|
|
public:
|
|
InvalidParameterCount( int parameterCount, std::string file, int ln );
|
|
|
|
int mParameterCount;
|
|
std::string mFile;
|
|
int const mLineNumber;
|
|
};
|
|
|
|
// Helper macros to be used in code emitting OpenCL code for Calc functions.
|
|
// Require the vSubArguments parameter.
|
|
#define CHECK_PARAMETER_COUNT(min, max) \
|
|
do { \
|
|
const int count = vSubArguments.size(); \
|
|
if( count < ( min ) || count > ( max )) \
|
|
throw InvalidParameterCount( count, __FILE__, __LINE__ ); \
|
|
} while( false )
|
|
#define CHECK_PARAMETER_COUNT_MIN(min) \
|
|
do { \
|
|
const int count = vSubArguments.size(); \
|
|
if( count < ( min )) \
|
|
throw InvalidParameterCount( count, __FILE__, __LINE__ ); \
|
|
} while( false )
|
|
#define CHECK_PARAMETER_DOUBLEVECTORREF(arg) \
|
|
do { \
|
|
formula::FormulaToken *token = vSubArguments[arg]->GetFormulaToken(); \
|
|
if (token == nullptr || token->GetType() != formula::svDoubleVectorRef) \
|
|
throw Unhandled(__FILE__, __LINE__); \
|
|
} while( false )
|
|
|
|
typedef std::shared_ptr<FormulaTreeNode> FormulaTreeNodeRef;
|
|
|
|
class FormulaTreeNode
|
|
{
|
|
public:
|
|
explicit FormulaTreeNode( const formula::FormulaToken* ft ) : mpCurrentFormula(ft)
|
|
{
|
|
Children.reserve(8);
|
|
}
|
|
std::vector<FormulaTreeNodeRef> Children;
|
|
formula::FormulaToken* GetFormulaToken() const
|
|
{
|
|
return const_cast<formula::FormulaToken*>(mpCurrentFormula.get());
|
|
}
|
|
|
|
private:
|
|
formula::FormulaConstTokenRef mpCurrentFormula;
|
|
};
|
|
|
|
/// (Partially) abstract base class for an operand
|
|
class DynamicKernelArgument
|
|
{
|
|
public:
|
|
/// delete copy constructor
|
|
DynamicKernelArgument( const DynamicKernelArgument& ) = delete;
|
|
|
|
/// delete copy-assignment operator
|
|
const DynamicKernelArgument& operator=( const DynamicKernelArgument& ) = delete;
|
|
|
|
DynamicKernelArgument( const ScCalcConfig& config, std::string s, FormulaTreeNodeRef ft );
|
|
virtual ~DynamicKernelArgument() {}
|
|
|
|
/// Generate declaration
|
|
virtual void GenDecl( outputstream& ss ) const = 0;
|
|
|
|
/// When declared as input to a sliding window function
|
|
virtual void GenSlidingWindowDecl( outputstream& ss ) const = 0;
|
|
|
|
/// When referenced in a sliding window function
|
|
virtual std::string GenSlidingWindowDeclRef( bool = false ) const = 0;
|
|
|
|
/// Create buffer and pass the buffer to a given kernel
|
|
virtual size_t Marshal( cl_kernel, int, int, cl_program ) = 0;
|
|
|
|
virtual size_t GetWindowSize() const = 0;
|
|
|
|
/// When Mix, it will be called
|
|
virtual std::string GenDoubleSlidingWindowDeclRef( bool = false ) const;
|
|
|
|
/// When Mix, it will be called
|
|
virtual std::string GenStringSlidingWindowDeclRef( bool = false ) const;
|
|
|
|
/// Will generate value saying whether the value is a string.
|
|
virtual std::string GenIsString( bool = false ) const { return "false"; }
|
|
|
|
/// Generate use/references to the argument
|
|
virtual void GenDeclRef( outputstream& ss ) const;
|
|
|
|
virtual void GenSlidingWindowFunction( outputstream& );
|
|
formula::FormulaToken* GetFormulaToken() const;
|
|
virtual std::string DumpOpName() const;
|
|
virtual void DumpInlineFun( std::set<std::string>&, std::set<std::string>& ) const;
|
|
const std::string& GetName() const;
|
|
virtual bool NeedParallelReduction() const;
|
|
/// If there's actually no argument, i.e. it expands to no code.
|
|
virtual bool IsEmpty() const { return false; }
|
|
|
|
static void ClearStringIds();
|
|
|
|
protected:
|
|
const ScCalcConfig& mCalcConfig;
|
|
std::string mSymName;
|
|
FormulaTreeNodeRef mFormulaTree;
|
|
static int GetStringId( const rtl_uString* string );
|
|
};
|
|
|
|
typedef std::shared_ptr<DynamicKernelArgument> DynamicKernelArgumentRef;
|
|
|
|
/// Holds an input (read-only) argument reference to a SingleVectorRef.
|
|
/// or a DoubleVectorRef for non-sliding-window argument of complex functions
|
|
/// like SumOfProduct
|
|
/// In most of the cases the argument is introduced
|
|
/// by a Push operation in the given RPN.
|
|
class VectorRef : public DynamicKernelArgument
|
|
{
|
|
public:
|
|
VectorRef( const ScCalcConfig& config, const std::string& s, const FormulaTreeNodeRef& ft, int index = 0 );
|
|
virtual ~VectorRef() override;
|
|
|
|
/// Generate declaration
|
|
virtual void GenDecl( outputstream& ss ) const override;
|
|
/// When declared as input to a sliding window function
|
|
virtual void GenSlidingWindowDecl( outputstream& ss ) const override;
|
|
|
|
/// When referenced in a sliding window function
|
|
virtual std::string GenSlidingWindowDeclRef( bool = false ) const override;
|
|
|
|
/// Create buffer and pass the buffer to a given kernel
|
|
virtual size_t Marshal( cl_kernel, int, int, cl_program ) override;
|
|
|
|
virtual void GenSlidingWindowFunction( outputstream& ) override;
|
|
virtual size_t GetWindowSize() const override;
|
|
virtual std::string DumpOpName() const override;
|
|
virtual void DumpInlineFun( std::set<std::string>&, std::set<std::string>& ) const override;
|
|
const std::string& GetName() const;
|
|
cl_mem GetCLBuffer() const;
|
|
virtual bool NeedParallelReduction() const override;
|
|
|
|
protected:
|
|
// Used by marshaling
|
|
cl_mem mpClmem;
|
|
// index in multiple double vector refs that have multiple ranges
|
|
const int mnIndex;
|
|
// Makes Marshall convert strings to 0 values.
|
|
bool forceStringsToZero;
|
|
// Used for storing when the data needs to be modified before sending to OpenCL.
|
|
std::vector< double > dataBuffer;
|
|
};
|
|
|
|
// Sets VectorRef::forceStringsToZero.
|
|
class VectorRefStringsToZero : public VectorRef
|
|
{
|
|
public:
|
|
VectorRefStringsToZero( const ScCalcConfig& config, const std::string& s, const FormulaTreeNodeRef& ft, int index = 0 );
|
|
};
|
|
|
|
/// A vector of strings
|
|
class DynamicKernelStringArgument : public VectorRef
|
|
{
|
|
public:
|
|
DynamicKernelStringArgument( const ScCalcConfig& config, const std::string& s,
|
|
const FormulaTreeNodeRef& ft, int index = 0 ) :
|
|
VectorRef(config, s, ft, index) { }
|
|
|
|
virtual void GenSlidingWindowFunction( outputstream& ) override { }
|
|
/// Generate declaration
|
|
virtual void GenDecl( outputstream& ss ) const override
|
|
{
|
|
ss << "__global double *" << mSymName;
|
|
}
|
|
virtual void GenSlidingWindowDecl( outputstream& ss ) const override
|
|
{
|
|
DynamicKernelStringArgument::GenDecl(ss);
|
|
}
|
|
virtual std::string GenIsString( bool = false ) const override;
|
|
virtual size_t Marshal( cl_kernel, int, int, cl_program ) override;
|
|
};
|
|
|
|
/// Arguments that are actually compile-time constants
|
|
class DynamicKernelConstantArgument : public DynamicKernelArgument
|
|
{
|
|
public:
|
|
DynamicKernelConstantArgument( const ScCalcConfig& config, const std::string& s,
|
|
const FormulaTreeNodeRef& ft ) :
|
|
DynamicKernelArgument(config, s, ft) { }
|
|
/// Generate declaration
|
|
virtual void GenDecl( outputstream& ss ) const override
|
|
{
|
|
ss << "double " << mSymName;
|
|
}
|
|
virtual void GenDeclRef( outputstream& ss ) const override
|
|
{
|
|
ss << mSymName;
|
|
}
|
|
virtual void GenSlidingWindowDecl( outputstream& ss ) const override
|
|
{
|
|
GenDecl(ss);
|
|
}
|
|
virtual std::string GenSlidingWindowDeclRef( bool = false ) const override
|
|
{
|
|
if (GetFormulaToken()->GetType() != formula::svDouble)
|
|
throw Unhandled(__FILE__, __LINE__);
|
|
return mSymName;
|
|
}
|
|
virtual size_t GetWindowSize() const override
|
|
{
|
|
return 1;
|
|
}
|
|
virtual double GetDouble() const
|
|
{
|
|
formula::FormulaToken* Tok = GetFormulaToken();
|
|
if (Tok->GetType() != formula::svDouble)
|
|
throw Unhandled(__FILE__, __LINE__);
|
|
return Tok->GetDouble();
|
|
}
|
|
/// Create buffer and pass the buffer to a given kernel
|
|
virtual size_t Marshal( cl_kernel k, int argno, int, cl_program ) override
|
|
{
|
|
OpenCLZone zone;
|
|
double tmp = GetDouble();
|
|
// Pass the scalar result back to the rest of the formula kernel
|
|
SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": double: " << preciseFloat( tmp ));
|
|
cl_int err = clSetKernelArg(k, argno, sizeof(double), static_cast<void*>(&tmp));
|
|
if (CL_SUCCESS != err)
|
|
throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
// Constant 0 argument when a string is forced to zero.
|
|
class DynamicKernelStringToZeroArgument : public DynamicKernelConstantArgument
|
|
{
|
|
public:
|
|
DynamicKernelStringToZeroArgument( const ScCalcConfig& config, const std::string& s,
|
|
const FormulaTreeNodeRef& ft ) :
|
|
DynamicKernelConstantArgument(config, s, ft) { }
|
|
virtual std::string GenSlidingWindowDeclRef( bool = false ) const override
|
|
{
|
|
return mSymName;
|
|
}
|
|
virtual double GetDouble() const override { return 0; }
|
|
};
|
|
|
|
|
|
/// Abstract class for code generation
|
|
class OpBase
|
|
{
|
|
public:
|
|
// FIXME: What exactly is this? It seems to be a starting value for some calculations
|
|
// (1 for OpMul, MAXFLOAT for OpMin), but it's often used pointlessly and sometimes
|
|
// even incorrectly (default value for when the cell is empty).
|
|
virtual std::string GetBottom() { return "";};
|
|
virtual std::string Gen2( const std::string&/*lhs*/,
|
|
const std::string&/*rhs*/ ) const { return "";}
|
|
static std::string Gen( std::vector<std::string>& /*argVector*/ ) { return "";};
|
|
virtual std::string BinFuncName() const { return "";};
|
|
virtual void BinInlineFun( std::set<std::string>&,
|
|
std::set<std::string>& ) { }
|
|
virtual bool takeString() const = 0;
|
|
virtual bool takeNumeric() const = 0;
|
|
// Whether DoubleRef containing more than one column is handled properly.
|
|
virtual bool canHandleMultiVector() const { return false; }
|
|
//Continue process 'Zero' or Not(like OpMul, not continue process when meet
|
|
// 'Zero'
|
|
virtual bool ZeroReturnZero() { return false;}
|
|
// For use with COUNTA() etc, input strings will be converted to 0 in data.
|
|
virtual bool forceStringsToZero() const { return false; }
|
|
virtual ~OpBase() { }
|
|
};
|
|
|
|
class SlidingFunctionBase : public OpBase
|
|
{
|
|
public:
|
|
typedef std::vector<DynamicKernelArgumentRef> SubArguments;
|
|
virtual void GenSlidingWindowFunction( outputstream&,
|
|
const std::string&, SubArguments& ) = 0;
|
|
protected:
|
|
// This enum controls how the generated code will handle empty cells in ranges.
|
|
enum EmptyArgType
|
|
{
|
|
EmptyIsZero, // empty cells become 0.0
|
|
EmptyIsNan, // empty cells become NAN, use isnan() to check in code
|
|
SkipEmpty // empty cells will be skipped
|
|
};
|
|
// This enum controls whether the generated code will also include variable
|
|
// <name>_is_string that will be set depending on the value type.
|
|
enum GenerateArgTypeType
|
|
{
|
|
DoNotGenerateArgType,
|
|
GenerateArgType
|
|
};
|
|
void GenerateFunctionDeclaration( const std::string& sSymName,
|
|
SubArguments& vSubArguments, outputstream& ss );
|
|
// Generate code for "double <name> = <value>;" from vSubArguments, svDoubleVectorRef is not supported.
|
|
void GenerateArg( const char* name, int arg, SubArguments& vSubArguments, outputstream& ss,
|
|
EmptyArgType empty = EmptyIsZero, GenerateArgTypeType generateType = DoNotGenerateArgType );
|
|
// overload, variable will be named "arg<arg>"
|
|
void GenerateArg( int arg, SubArguments& vSubArguments, outputstream& ss,
|
|
EmptyArgType empty = EmptyIsZero, GenerateArgTypeType generateType = DoNotGenerateArgType );
|
|
// generate code for "double <name> = <value>;" from vSubArguments, if it exists,
|
|
// otherwise set to <def>
|
|
void GenerateArgWithDefault( const char* name, int arg, double def, SubArguments& vSubArguments,
|
|
outputstream& ss, EmptyArgType empty = EmptyIsZero );
|
|
// Generate code that will handle all arguments firstArg-lastArg (zero-based, inclusive),
|
|
// including range arguments (svDoubleVectorRef) and each value will be processed by 'code',
|
|
// value will be named "arg".
|
|
static void GenerateRangeArgs( int firstArg, int lastArg, SubArguments& vSubArguments,
|
|
outputstream& ss, EmptyArgType empty, const char* code );
|
|
// overload, handle all arguments
|
|
static void GenerateRangeArgs( SubArguments& vSubArguments, outputstream& ss,
|
|
EmptyArgType empty, const char* code );
|
|
// overload, handle the given argument
|
|
static void GenerateRangeArg( int arg, SubArguments& vSubArguments, outputstream& ss,
|
|
EmptyArgType empty, const char* code );
|
|
// Overload.
|
|
// Both arguments must be svDoubleRef of the same size.
|
|
// If 'firstElementDiff' is set, the loop start will be offset by '+ firstElementDiff'.
|
|
void GenerateRangeArg( int arg1, int arg2, SubArguments& vSubArguments,
|
|
outputstream& ss, EmptyArgType empty, const char* code, const char* firstElementDiff = nullptr );
|
|
// Generate code that will handle the given two arguments in one loop where n-th element of arg1 and arg2
|
|
// will be handled at the same time, named 'arg1' and 'arg2'.
|
|
// Both arguments must be svDoubleRef of the same size.
|
|
// If 'firstElementDiff' is set, the loop start will be offset by '+ firstElementDiff'.
|
|
static void GenerateRangeArgPair( int arg1, int arg2, SubArguments& vSubArguments,
|
|
outputstream& ss, EmptyArgType empty, const char* code, const char* firstElementDiff = nullptr );
|
|
// Generate code for "double <name> = range[<element>]" from vSubArguments.
|
|
// The argument must be svDoubleRef.
|
|
static void GenerateRangeArgElement( const char* name, int arg, const char* element,
|
|
SubArguments& vSubArguments, outputstream& ss, EmptyArgType empty );
|
|
static void GenerateDoubleVectorLoopHeader( outputstream& ss,
|
|
const formula::DoubleVectorRefToken* pDVR, const char* firstElementDiff );
|
|
};
|
|
|
|
class Normal : public SlidingFunctionBase
|
|
{
|
|
public:
|
|
virtual void GenSlidingWindowFunction( outputstream& ss,
|
|
const std::string& sSymName, SubArguments& vSubArguments ) override;
|
|
virtual bool takeString() const override { return false; }
|
|
virtual bool takeNumeric() const override { return true; }
|
|
};
|
|
|
|
class CheckVariables : public Normal
|
|
{
|
|
public:
|
|
static void GenTmpVariables( outputstream& ss, const SubArguments& vSubArguments );
|
|
static void CheckSubArgumentIsNan( outputstream& ss,
|
|
SubArguments& vSubArguments, int argumentNum );
|
|
static void CheckAllSubArgumentIsNan( outputstream& ss,
|
|
SubArguments& vSubArguments );
|
|
// only check isnan
|
|
static void CheckSubArgumentIsNan2( outputstream& ss,
|
|
SubArguments& vSubArguments, int argumentNum, const std::string& p );
|
|
static void UnrollDoubleVector( outputstream& ss,
|
|
const outputstream& unrollstr, const formula::DoubleVectorRefToken* pCurDVR,
|
|
int nCurWindowSize );
|
|
};
|
|
|
|
class OpAverage;
|
|
class OpCount;
|
|
|
|
/// Handling a Double Vector that is used as a sliding window input
|
|
/// to either a sliding window average or sum-of-products
|
|
/// Generate a sequential loop for reductions
|
|
template<class Base>
|
|
class DynamicKernelSlidingArgument : public Base
|
|
{
|
|
public:
|
|
DynamicKernelSlidingArgument(const ScCalcConfig& config, const std::string& s,
|
|
const FormulaTreeNodeRef& ft,
|
|
std::shared_ptr<SlidingFunctionBase> CodeGen, int index);
|
|
// Should only be called by SumIfs. Yikes!
|
|
virtual bool NeedParallelReduction() const;
|
|
virtual void GenSlidingWindowFunction( outputstream& ) { }
|
|
|
|
std::string GenSlidingWindowDeclRef( bool nested = false ) const;
|
|
/// Controls how the elements in the DoubleVectorRef are traversed
|
|
size_t GenReductionLoopHeader( outputstream& ss, bool& needBody );
|
|
|
|
size_t GetArrayLength() const { return mpDVR->GetArrayLength(); }
|
|
|
|
size_t GetWindowSize() const { return mpDVR->GetRefRowSize(); }
|
|
|
|
bool GetStartFixed() const { return bIsStartFixed; }
|
|
|
|
bool GetEndFixed() const { return bIsEndFixed; }
|
|
|
|
protected:
|
|
bool bIsStartFixed, bIsEndFixed;
|
|
const formula::DoubleVectorRefToken* mpDVR;
|
|
// from parent nodes
|
|
std::shared_ptr<SlidingFunctionBase> mpCodeGen;
|
|
};
|
|
|
|
/// Handling a Double Vector that is used as a sliding window input
|
|
/// Performs parallel reduction based on given operator
|
|
template<class Base>
|
|
class ParallelReductionVectorRef : public Base
|
|
{
|
|
public:
|
|
ParallelReductionVectorRef(const ScCalcConfig& config, const std::string& s,
|
|
const FormulaTreeNodeRef& ft,
|
|
std::shared_ptr<SlidingFunctionBase> CodeGen, int index);
|
|
~ParallelReductionVectorRef();
|
|
|
|
/// Emit the definition for the auxiliary reduction kernel
|
|
virtual void GenSlidingWindowFunction( outputstream& ss );
|
|
virtual std::string GenSlidingWindowDeclRef( bool ) const;
|
|
/// Controls how the elements in the DoubleVectorRef are traversed
|
|
size_t GenReductionLoopHeader( outputstream& ss, int nResultSize, bool& needBody );
|
|
virtual size_t Marshal( cl_kernel k, int argno, int w, cl_program mpProgram );
|
|
size_t GetArrayLength() const { return mpDVR->GetArrayLength(); }
|
|
size_t GetWindowSize() const { return mpDVR->GetRefRowSize(); }
|
|
bool GetStartFixed() const { return bIsStartFixed; }
|
|
bool GetEndFixed() const { return bIsEndFixed; }
|
|
|
|
protected:
|
|
bool bIsStartFixed, bIsEndFixed;
|
|
const formula::DoubleVectorRefToken* mpDVR;
|
|
// from parent nodes
|
|
std::shared_ptr<SlidingFunctionBase> mpCodeGen;
|
|
// controls whether to invoke the reduction kernel during marshaling or not
|
|
cl_mem mpClmem2;
|
|
};
|
|
|
|
class Reduction : public SlidingFunctionBase
|
|
{
|
|
int const mnResultSize;
|
|
public:
|
|
explicit Reduction(int nResultSize) : mnResultSize(nResultSize) {}
|
|
|
|
typedef DynamicKernelSlidingArgument<VectorRef> NumericRange;
|
|
typedef DynamicKernelSlidingArgument<VectorRefStringsToZero> NumericRangeStringsToZero;
|
|
typedef DynamicKernelSlidingArgument<DynamicKernelStringArgument> StringRange;
|
|
typedef ParallelReductionVectorRef<VectorRef> ParallelNumericRange;
|
|
|
|
virtual bool HandleNaNArgument( outputstream&, unsigned, SubArguments& ) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual void GenSlidingWindowFunction( outputstream& ss,
|
|
const std::string& sSymName, SubArguments& vSubArguments ) override;
|
|
virtual bool isAverage() const { return false; }
|
|
virtual bool isMinOrMax() const { return false; }
|
|
virtual bool takeString() const override { return false; }
|
|
virtual bool takeNumeric() const override { return true; }
|
|
};
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|