150
|
1 # Background: declarative builders API
|
|
2
|
|
3 The main purpose of the declarative builders API is to provide an intuitive way
|
|
4 of constructing MLIR programmatically. In the majority of cases, the IR we wish
|
173
|
5 to construct exhibits structured control-flow. The Declarative builders in the
|
|
6 `EDSC` library (Embedded Domain Specific Constructs) provide an API to make MLIR
|
|
7 construction and manipulation very idiomatic, for the structured control-flow
|
|
8 case, in C++.
|
150
|
9
|
|
10 ## ScopedContext
|
|
11
|
|
12 `mlir::edsc::ScopedContext` provides an implicit thread-local context,
|
|
13 supporting a simple declarative API with globally accessible builders. These
|
|
14 declarative builders are available within the lifetime of a `ScopedContext`.
|
|
15
|
|
16 ## Intrinsics
|
|
17
|
173
|
18 `mlir::ValueBuilder` is a generic wrapper for the `mlir::OpBuilder::create`
|
|
19 method that operates on `Value` objects and return a single Value. For
|
|
20 instructions that return no values or that return multiple values, the
|
|
21 `mlir::edsc::OperationBuilder` can be used. Named intrinsics are provided as
|
150
|
22 syntactic sugar to further reduce boilerplate.
|
|
23
|
|
24 ```c++
|
|
25 using load = ValueBuilder<LoadOp>;
|
173
|
26 using store = OperationBuilder<StoreOp>;
|
150
|
27 ```
|
|
28
|
|
29 ## LoopBuilder and AffineLoopNestBuilder
|
|
30
|
|
31 `mlir::edsc::AffineLoopNestBuilder` provides an interface to allow writing
|
|
32 concise and structured loop nests.
|
|
33
|
|
34 ```c++
|
|
35 ScopedContext scope(f.get());
|
173
|
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));
|
150
|
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
|
|
56 operations on abstract data types by overloading the C++ assignment and
|
|
57 parenthesis operators. The relevant loads and stores are emitted as appropriate.
|
|
58
|
|
59 ## Putting it all together
|
|
60
|
|
61 With declarative builders, it becomes fairly concise to build rank and
|
|
62 type-agnostic custom operations even though MLIR does not yet have generic
|
|
63 types. Here is what a definition of a general pointwise add looks in
|
|
64 Tablegen with declarative builders.
|
|
65
|
|
66 ```c++
|
|
67 def AddOp : Op<"x.add">,
|
|
68 Arguments<(ins Tensor:$A, Tensor:$B)>,
|
|
69 Results<(outs Tensor: $C)> {
|
|
70 code referenceImplementation = [{
|
173
|
71 SmallVector<Value, 4> ivs(view_A.rank());
|
150
|
72 IndexedValue A(arg_A), B(arg_B), C(arg_C);
|
173
|
73 AffineLoopNestBuilder(
|
|
74 ivs, view_A.getLbs(), view_A.getUbs(), view_A.getSteps())([&]{
|
150
|
75 C(ivs) = A(ivs) + B(ivs)
|
|
76 });
|
|
77 }];
|
|
78 }
|
|
79 ```
|
|
80
|
|
81 Depending on the function signature on which this emitter is called, the
|
|
82 generated 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
|
|
96 or 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
|
173
|
106 Similar APIs are provided to emit the lower-level `scf.for` op with
|
150
|
107 `LoopNestBuilder`. See the `builder-api-test.cpp` test for more usage examples.
|
|
108
|
|
109 Since the implementation of declarative builders is in C++, it is also available
|
173
|
110 to program the IR with an embedded-DSL flavor directly integrated in MLIR.
|