Meta-Meta-Programming Generating C++ Template Metaprograms with Racket Macros

Meta-Meta-Programming Generating C++ Template Metaprograms with Racket Macros

Meta-Meta-Programming Generating C++ Template Metaprograms with Racket Macros Michael Ballantyne Chris Earl Matthew Might University of Utah University of Utah University of Utah [email protected] [email protected] [email protected] Abstract processor macros scale poorly as our code generation needs Domain specific languages embedded in C++ (EDSLs) often become more complex. use the techniques of template metaprogramming and ex- For the implementation of Nebo, an EDSL we’ve pub- pression templates. However, these techniques can require lished on previously [4, 5], we instead chose to implement verbose code and introduce maintenance and debugging a code generator for C++ in Racket. It allows us to describe challenges. This paper presents a tool written in Racket for the implementation of the interface functions once and sub- generating C++ programs, paying particular attention to the sequently generate the C++ code for many operators. For ex- challenges of metaprogramming. The code generator uses ample, we write the following to generate both the interface Racket’s macros to provide syntax for defining C++ meta- functions and expression template objects for the operators functions that is more concise and offers more opportunity +, *, and others besides: for error checking than that of native C++. (build-binary-operator 'SumOp '+ (add-spaces 'operator '+)) 1. Introduction (build-binary-operator 'ProdOp '* Embedded domain specific languages (EDSLs) in C++ have (add-spaces 'operator '*)) proven to be an effective way to introduce new programming (build-binary-logical-operator 'AndOp '&& abstractions to fields like scientific computing. implement- (add-spaces 'operator '&&)) ing C++ EDSLs with a popular technique known as expres- (build-unary-logical-function 'NotOp '! sion templates [16] requires many similar function defini- (add-spaces 'operator '!)) tions and operator overloads. The code below shows part of (build-extremum-function 'MaxFcn '> 'max) the implementation for the operators + and * from one such Note that our EDSL provides several types of operator, each EDSL. with different syntactic rules. Our code generator allows template<typename LHS, typename RHS> these operators to cleanly share much of their implementa- typename BinExprRetType<SumOp, LHS, RHS>::result tion. operator+(const LHS & lhs, const RHS & rhs) { Given that we use our code generator to eliminate repetion return binExpr<SumOp>(lhs, rhs); in interface functions, it would be natural to also generate } other components of our EDSL implementation for which the C++ code is difficult to understand and maintain. For template<typename LHS, typename RHS> example, to provide syntax checking for our EDSL we use typename BinExprRetType<MultOp, LHS, RHS>::result template metaprogramming [14] to compute functions from operator*(const LHS & lhs, const RHS & rhs) { types to types, known as metafunctions. The C++ imple- return binExpr<MultOp>(lhs, rhs); mentation of one such metafunction is shown in Figure 1. } Racket’s metaprogramming abilities allow us to write the same metafunction through the following code: The details of these implementations are beyond the (define/meta (join-location l1 l2) scope of this paper, but we need to produce this kind of [('SingleValue 'SingleValue) 'SingleValue] function for each operator in our EDSL, and they’re all quite [('SingleValue l) l] similar. In this case, each differs only by the symbol for the [(l 'SingleValue) l] operator (+, *) and the name of the class that implements it [(l l) l]) (SumOp, MultOp). Expression template EDSL implementa- tions often use C preprocessor macros to reduce this dupli- This paper discusses the design and implementation of cation [9, 15]. However, as we discuss in Section 3.4, pre- our code generator. Specifically, our contributions are: template<typename L1, typename L2 > rhs <<= divX( interpX(alpha) * gradX(phi) ) struct JoinLocation; + divY( interpY(alpha) * gradY(phi) ); template< > phi <<= phi + deltaT * rhs; struct JoinLocation<SingleValue, SingleValue > { SingleValue typedef result; phi <<= cond( left, 10.0 ) }; ( right, 0.0 ) ( top || bottom, 5.0 ) template<typename L > ( phi ); struct JoinLocation<SingleValue, L > { L typedef result; Figure 2. Iteration of the solution to the 2D heat equation }; with Nebo template<typename L > cialization of generic code to achieve more general program struct JoinLocation<L, SingleValue > { transformations. [16]. C++ objects can be used to implement L typedef result; the abstract syntax tree of an embedded domain specific lan- }; guage, while functions and overloaded operators that con- struct those tree elements define its grammar and type sys- template<typename L > tem. This technique is referred to as expression templates struct JoinLocation<L, L > { (ET) for reasons we’ll see shortly. L typedef result; }; 2.1 Deforestation As an example, consider pointwise addition of vectors: Figure 1. C++ metafunction std::vector<int> a, b, c, d; d = a + b + c; • A strategy for generating C++ EDSL implementations A straightforward way to implement such syntax in C++ with a Racket code generator (Section 3). Our code gen- would be to overload the + operator to loop over the vectors erator is publicly available at https://github.com/ it receives as arguments and construct a new vector with the michaelballantyne/fulmar. It allows EDSL devel- result. Given more than one instance of such an operator on opers to use Racket as an expressive metaprogramming the right hand side of an assignment, however, this approach language while EDSL users continue to write in C++. We allocates memory proportional to the size of the vectors for show that this approach makes iterative development of each operator call. EDSLs easier (Section 3.3). Instead, each + operator call can construct an object with • A EDSL in Racket that corresponds to C++ metafunc- an eval(int i) method that evaluates the operation at a tions, with concise syntax and integration with our code single index. The object is an instance of a templated class generation approach (Section 4). Syntactic correctness of parameterized by the types of its arguments, which may the definition and use of the metafunctions is checked at be either std::vector or themselves represent parts of a Racket runtime as the C++ implementation of the EDSL computation like the type of a + b above. The loop over is generated. (Section 4.5). The syntax of the EDSL in indices doesn’t happen until the = assignment operator is Racket elucidates the relationship between Scheme-style invoked; it calls the eval method on the right hand side for pattern matching and C++ template metaprogramming each index in turn and updates the vector on the left hand (Section 4.3). side. • Discussion of the tradeoffs of using the Racket-based The templated classes for the objects representing a com- code generator as opposed to preprocessor macros in putation are referred to as expression templates. This par- the context of expression template based C++ EDSLs ticular use of the delayed evaluation they offer corresponds (Section 3.4). to the deforestation optimizations that Haskell compilers use to remove temporaries from composed list functions [8]. Be- cause the C++ compiler lacks such optimizations, C++ pro- 2. Expression Templates grammers pursuing high performance achieve the same ef- C++ provides limited means to transform code at compile fect with expression templates. time through preprocessor macros and the template system. While the template system was originally designed as a 2.2 Accelerator Portability with Nebo generic programming mechanism, C++ programmers have Another application of expression templates allows compi- devised ways to use the object system and compile-time spe- lation of a single code base for multiple architectures, in- cluding accelerators like GPUs and Intel’s Xeon Phi [2]. Our Code generation for our C++ EDSL presents a unique C++ EDSL, Nebo, is of this variety [4]. Figure 2 shows a set of requirements. The purpose of the EDSL is to offer simple use of Nebo. Client code using Nebo is compiled with programmers new abstractions within C++ by transform- variants for both CPU and GPU execution, with the decision ing the expressions they provide at C++ compile time, so of which to use being delayed until runtime. we can only use the code generator to produce the imple- To implement accelerator portability, expression tem- mentation of the language. Users of the language are deliv- plate objects have parallel objects implementing the com- ered C++ header files containing the template metaprograms putation on each device. For the deforestation example the that operate on expressions written the EDSL. Furthermore, EDSL implementation might include SumOpEvalCPU and the EDSL integrates with runtime support code for mem- SumOpEvalGPU classes. The initial expression template ob- ory management and threading maintained by C++ program- ject constructed by operators and representing the abstract mers. The C++ we generate needs to be human readable so operator has methods that construct these parallel trees. those programmers can understand and debug the interaction Once the assignment operation has selected a device on of the EDSL with the code they maintain. which to execute the expression, it calls such a method to Because we’re generating C++ source code, we’re re- obtain the appropriate code variant. sponsible for: Expression templates for accelerator portability form an extension of the technique used for deforestation. Use of an • Source code

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    9 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us