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