• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Background: declarative builders API
2
3The main purpose of the declarative builders API is to provide an intuitive way
4of constructing MLIR programmatically. In the majority of cases, the IR we wish
5to construct exhibits structured control-flow. The Declarative builders in the
6`EDSC` library (Embedded Domain Specific Constructs) provide an API to make MLIR
7construction and manipulation very idiomatic, for the structured control-flow
8case, in C++.
9
10## ScopedContext
11
12`mlir::edsc::ScopedContext` provides an implicit thread-local context,
13supporting a simple declarative API with globally accessible builders. These
14declarative builders are available within the lifetime of a `ScopedContext`.
15
16## Intrinsics
17
18`mlir::ValueBuilder` is a generic wrapper for the `mlir::OpBuilder::create`
19method that operates on `Value` objects and return a single Value. For
20instructions that return no values or that return multiple values, the
21`mlir::edsc::OperationBuilder` can be used. Named intrinsics are provided as
22syntactic sugar to further reduce boilerplate.
23
24```c++
25using load = ValueBuilder<LoadOp>;
26using store = OperationBuilder<StoreOp>;
27```
28
29## LoopBuilder and AffineLoopNestBuilder
30
31`mlir::edsc::AffineLoopNestBuilder` provides an interface to allow writing
32concise and structured loop nests.
33
34```c++
35  ScopedContext scope(f.get());
36  Value i, j, lb(f->getArgument(0)), ub(f->getArgument(1));
37  Value f7(std_constant_float(llvm::APFloat(7.0f), f32Type)),
38           f13(std_constant_float(llvm::APFloat(13.0f), f32Type)),
39           i7(constant_int(7, 32)),
40           i13(constant_int(13, 32));
41  AffineLoopNestBuilder(&i, lb, ub, 3)([&]{
42      lb * index_type(3) + ub;
43      lb + index_type(3);
44      AffineLoopNestBuilder(&j, lb, ub, 2)([&]{
45          ceilDiv(index_type(31) * floorDiv(i + j * index_type(3), index_type(32)),
46                  index_type(32));
47          ((f7 + f13) / f7) % f13 - f7 * f13;
48          ((i7 + i13) / i7) % i13 - i7 * i13;
49      });
50  });
51```
52
53## IndexedValue
54
55`mlir::edsc::IndexedValue` provides an index notation around load and store
56operations on abstract data types by overloading the C++ assignment and
57parenthesis operators. The relevant loads and stores are emitted as appropriate.
58
59## Putting it all together
60
61With declarative builders, it becomes fairly concise to build rank and
62type-agnostic custom operations even though MLIR does not yet have generic
63types. Here is what a definition of a general pointwise add looks in
64Tablegen with declarative builders.
65
66```c++
67def AddOp : Op<"x.add">,
68    Arguments<(ins Tensor:$A, Tensor:$B)>,
69    Results<(outs Tensor: $C)> {
70  code referenceImplementation = [{
71    SmallVector<Value, 4> ivs(view_A.rank());
72    IndexedValue A(arg_A), B(arg_B), C(arg_C);
73    AffineLoopNestBuilder(
74      ivs, view_A.getLbs(), view_A.getUbs(), view_A.getSteps())([&]{
75        C(ivs) = A(ivs) + B(ivs)
76      });
77  }];
78}
79```
80
81Depending on the function signature on which this emitter is called, the
82generated IR resembles the following, for a 4-D memref of `vector<4xi8>`:
83
84```
85// CHECK-LABEL: func @t1(%lhs: memref<3x4x5x6xvector<4xi8>>, %rhs: memref<3x4x5x6xvector<4xi8>>, %result: memref<3x4x5x6xvector<4xi8>>) -> () {
86//       CHECK: affine.for {{.*}} = 0 to 3 {
87//       CHECK:   affine.for {{.*}} = 0 to 4 {
88//       CHECK:     affine.for {{.*}} = 0 to 5 {
89//       CHECK:       affine.for {{.*}}= 0 to 6 {
90//       CHECK:         {{.*}} = load %arg1[{{.*}}] : memref<3x4x5x6xvector<4xi8>>
91//       CHECK:         {{.*}} = load %arg0[{{.*}}] : memref<3x4x5x6xvector<4xi8>>
92//       CHECK:         {{.*}} = addi {{.*}} : vector<4xi8>
93//       CHECK:         store {{.*}}, %arg2[{{.*}}] : memref<3x4x5x6xvector<4xi8>>
94```
95
96or the following, for a 0-D `memref<f32>`:
97
98```
99// CHECK-LABEL: func @t3(%lhs: memref<f32>, %rhs: memref<f32>, %result: memref<f32>) -> () {
100//       CHECK: {{.*}} = load %arg1[] : memref<f32>
101//       CHECK: {{.*}} = load %arg0[] : memref<f32>
102//       CHECK: {{.*}} = addf {{.*}}, {{.*}} : f32
103//       CHECK: store {{.*}}, %arg2[] : memref<f32>
104```
105
106Similar APIs are provided to emit the lower-level `scf.for` op with
107`LoopNestBuilder`. See the `builder-api-test.cpp` test for more usage examples.
108
109Since the implementation of declarative builders is in C++, it is also available
110to program the IR with an embedded-DSL flavor directly integrated in MLIR.
111