6.0 KiB
dynamicCode
: Dynamic code compilation
- Dictionary preprocessing directive:
#codeStream
- Implementation
- Boundary condition:
codedFixedValue
- Security
- Field manipulation
- Pitfalls
- Exceptions
- Other
#
Dictionary preprocessing directive: #codeStream
This is a dictionary preprocessing directive ('functionEntry
') which
provides a snippet of OpenFOAM C++ code which gets compiled and executed to
provide the actual dictionary entry. The snippet gets provided as three
sections of C++ code which just gets inserted into a template:
code
section: the actual body of the code. It gets called with argumentsOStream& os, const dictionary& dict
and the C++ code can do adict.lookup
to find current dictionary values.- optional
codeInclude
section: any #include statements to include OpenFOAM files. - optional 'codeOptions' section: any extra compilation flags to be added to
EXE_INC
inMake/options
To ease inputting mulit-line code there is the #{ #}
syntax. Anything in
between these two delimiters becomes a string with all newlines, quotes etc
preserved.
Example: Look up dictionary entries and do some calculation
startTime 0;
endTime 100;
..
writeInterval #codeStream
{
code
#{
scalar start = readScalar(dict["startTime"]);
scalar end = readScalar(dict["endTime"]);
label nDumps = 5;
os << ((end-start)/nDumps);
#};
};
Implementation
- the
#codeStream
entry reads the dictionary following it, extracts thecode
,codeInclude
,codeOptions
sections (these are just strings) and calculates the SHA1 checksum of the contents. - it copies a template file
(~OpenFOAM/codeTemplates/dynamicCode/codeStreamTemplate.C)
or($FOAM_CODE_TEMPLATES/codeStreamTemplate.C)
, substituting all occurences ofcode
,codeInclude
,codeOptions
. - it writes library source files to
dynamicCode/<SHA1>
and compiles it usingwmake libso
. - the resulting library is generated under
dynamicCode/platforms/$WM_OPTIONS/lib
and is loaded (dlopen
,dlsym
) and the function executed - the function will have written its output into the Ostream which then gets
used to construct the entry to replace the whole
#codeStream
section. - using the SHA1 means that same code will only be compiled and loaded once.
Boundary condition: codedFixedValue
This uses the code from codeStream to have an in-line specialised
fixedValueFvPatchScalarField
. For now only for scalars:
outlet
{
type codedFixedValue;
value uniform 0;
redirectType fixedValue10;
code
#{
operator==(min(10, 0.1*this->db().time().value()));
#};
}
It by default always includes fvCFD.H
and adds the finiteVolume
library to
the include search path.
A special form is where the code is not supplied in-line but instead comes
from the codeDict
dictionary in the system
directory. It should contain
a fixedValue10
entry:
fixedValue10
{
code
#{
operator==(min(10, 0.1*this->db().time().value()));
#};
}
The advantage of using this indirect way is that it supports runTimeModifiable so any change of the code will be picked up next iteration.
Security
Allowing the case to execute C++ code does introduce security risks. A
third-party case might have a #codeStream{#code system("rm -rf .");};
hidden
somewhere in a dictionary. #codeStream
is therefore not enabled by default
you have to enable it by setting in the system-wide controlDict
InfoSwitches
{
// Allow case-supplied c++ code (#codeStream, codedFixedValue)
allowSystemOperations 1;
}
Field manipulation
Fields are read in as IOdictionary
so can be upcast to provide access to the
mesh:
internalField #codeStream
{
codeInclude
#{
#include "fvCFD.H"
#};
code
#{
const IOdictionary& d = dynamicCast<const IOdictionary>(dict);
const fvMesh& mesh = refCast<const fvMesh>(d.db());
scalarField fld(mesh.nCells(), 12.34);
fld.writeEntry("", os);
#};
codeOptions
#{
-I$(LIB_SRC)/finiteVolume/lnInclude
#};
};
Note: above field initialisation has the problem that the boundary conditions are not evaluated so e.g. processor boundaries will not hold the opposite cell value.
Pitfalls
The syntax of #codeStream
can be quite hard to get right. These are some
common pitfalls:
- the
code
string has to be a valid set of C++ expressions so has to end in a ';' - the C++ code upon execution has to print a valid dictionary entry. In above example it
prints 'uniform 12.34;'. Note the ';' at the end. It is advised to use the
writeEntry
as above to handle this and also e.g. binary streams (codeStream
inherits the stream type from the dictionary) - the
code
,codeInclude
,codeOptions
entries are just like any other dictionary string entry so there has to be a ';' after the string - the
#codeStream
entry (itself a dictionary) has to end in a ';'
Exceptions
There are unfortunately some exceptions to above field massaging.
Following applications read
the field as a dictionary, not as an IOdictionary
:
foamFormatConvert
changeDictionaryDict
foamUpgradeCyclics
These applications will usually switch off all '#' processing which just preserves the entries as strings (including all formatting).
Other
- parallel running not tested a lot. What about distributed data
(i.e. non-
NFS
) parallel? - paraview has been patched so it will pass in RTLD_GLOBAL when loading the OpenFOAM reader module. This is necessary for above dictionary processing to work.