6.8 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:
codesection: the actual body of the code. It gets called with argumentsOStream& os, const dictionary& dictand the C++ code can do adict.lookupto find current dictionary values.- optional
codeIncludesection: any #include statements to include OpenFOAM files. - optional 'codeOptions' section: any extra compilation flags to be added to
EXE_INCinMake/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
// For paraFoam's sake re-load in OpenFOAM library with exported symbols
libs ("libOpenFOAM.so");
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
#codeStreamentry reads the dictionary following it, extracts thecode,codeInclude,codeOptionssections (these are just strings) and calculates the SHA1 checksum of the contents. - it copies a template file
(etc/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/liband 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
#codeStreamsection. - 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
fixedValueFvPatchField.
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.
When postprocessing using paraFoam it requires one to add the used libraries to the libs entry so in the system/controlDict:
libs ("libOpenFOAM.so" "libfiniteVolume.so");
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
codestring 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
writeEntryas above to handle this and also e.g. binary streams (codeStreaminherits the stream type from the dictionary) - the
code,codeInclude,codeOptionsentries are just like any other dictionary string entry so there has to be a ';' after the string - the
#codeStreamentry (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:
foamFormatConvertchangeDictionaryDictfoamUpgradeCyclics
These applications will usually switch off all '#' processing which just preserves the entries as strings (including all formatting).
Other
- paraFoam: paraview does not export symbols on loaded libraries (more specific : it does not add 'RTLD_GLOBAL' to the dlopen flags) so one will have to add the used libraries (libOpenFOAM, libfiniteVolume, lib..) to the 'libs' entry in system/controlDict to prevent getting an error of the form –> FOAM FATAL IO ERROR: Failed loading library "libcodeStream_3cd388ceb070a2f8b0ae61782adbc21c5687ce6f.so" This will force re-loading these libraries, this time exporting the symbols so the generated library can be loaded.
- 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.