Creation of handles for the evaluation of expressions in a context where the expression type is not available.
More...
template<typename T_Real>
struct codi::StatementEvaluatorInterface< T_Real >
Creation of handles for the evaluation of expressions in a context where the expression type is not available.
For primal value taping, the expression templates create a problem. The expression type and value is generated in the code where the statement is evaluated. The type and value of the expression is required again to evaluate the reverse or forward mode of the elemental function described by the expression (see AD theory and mathematical definitions). The problem is that types cannot be stored in C++. Also, the value of the expression is context specific and this context is not available during the tape interpretation either. The generation of handles solves this problem by providing the tape with functions for the evaluation of the expressions. Since these functions can be instantiated with the type of the expression, inside of the handles, the expression type is available again. For more details on the topic please see SAG2018Expression.
The creation of the handles is tightly coupled with the implementing tape. The tape defines how the data for the expression is loaded and which functions are evaluated on the expression in order to perform the necessary operations for the tape. However, how the handles are generated, how the data for the handles is stored (e.g. which kinds of function pointers) and how they are evaluated is neither tape specific nor is one way optimal for every use case.
The process of evaluating a handle can be separated into multiple steps:
- 1. Load statement specific data
- 2. Load expression specific data
- 3. Call expression specific function
The call to a handle may take place either between step 1 and 2 or 2 and 3. The advantage between step 1 and 2 is that it is very simple to implement, the disadvantage is that the expression specific data load is handled after the function pointer call. The compiler can therefore not optimize these data loading calls. If the call of the handle is between step 2 and 3, then the compiler can optimize the generalized data handling and only the specifics for the expression are hidden behind the function pointer call. However, in order to perform step 2, the tape needs some expression specific data which has to be extracted from the handle. Therefore, the implementation is more involved.
The first approach (handle call between step 1 and 2) is defined by the StatementEvaluatorTapeInterface. The second approach (handle call between step 2 and 3) is defined by the StatementEvaluatorInnerTapeInterface.
In general, the tape implements the interfaces mentioned above. However, special cases like preaccumulation support requires a different generator. Therefore, createHandle
has two template arguments, one for the tape and one for the generator.
In general, implementations of this interface need to store functions pointers to the statementEvaluate*
functions of the StatementEvaluatorTapeInterface or function pointers to the statementEvaluate*Inner
of the StatementEvaluatorInnerTapeInterface.
A usual call flow for the first approach is (see also the code for ReverseStatementEvaluator):
auto handle = StatementEvaluatorInterface::createHandle<Tape, Tape, Expr>();
tapeData.pushHandle(handle);
auto handle = tapeData.popHandle(handle);
static void callReverse(Handle const &h, Args &&... args)
- Template Parameters
-