source: icGREP/icgrep-devel/icgrep/kernels/radix64.cpp @ 5985

Last change on this file since 5985 was 5985, checked in by nmedfort, 17 months ago

Restructured MultiBlock? kernel. Removal of Swizzled buffers. Inclusion of PopCount? rates / non-linear access. Modifications to several kernels to better align them with the kernel and pipeline changes.

File size: 19.5 KB
Line 
1/*
2 *  Copyright (c) 2016 International Characters.
3 *  This software is licensed to the public under the Open Software License 3.0.
4 */
5#include "radix64.h"
6#include <kernels/streamset.h>
7#include <kernels/kernel_builder.h>
8
9using namespace llvm;
10
11namespace kernel {
12
13// This kernel produces an expanded input stream by duplicating every third byte.
14// It is implemented using SIMD shufflevector operations.  With 16-byte registers,
15// a single shufflevector operation produces 16 bytes of output data from the
16// 12 bytes of input data.   With 32-byte registers, 32 bytes of output data are
17// produced from 24 bytes of input data.
18//
19// Using aligned SIMD loads, an inner loop processes three registers full of input
20// data (i.e., three BytePacks) to produce four registers full of output.   This is
21// a 3 step process.
22// Step 1:  Load input_pack0, apply the shuffle operation to produce output_pack0.
23//          At this point 3/4 of the data in input_pack0 has been processed.
24// Step 2:  Load input_pack1, apply a shuffle operation to use the remaining
25//          1/4 of input_pack0 and 1/2 of input_pack1 to produce output_pack1.
26//          At this point 1/2 of the data in input_pack1 has been processed.
27// Step 3:  Load input_pack2, apply a shuffle operation to use the remaining 1/2
28//          of input_pack1 and 1/4 of input_pack2 to produce output_pack2.
29//          Then apply a further shuffle opertaion to use the remaining 3/4 of
30//          input_pack2 to produce output_pack3.
31
32// The MultiBlockLogic is based on a natural stride taking 3 packs at a time.
33// In this case, the output produced is exactly 4 packs or 4 blocks, with no pending
34// data maintained in the kernel state.
35//
36// When processing the final partial stride of data, the kernel performs full
37// triple-pack processing for each full or partial triple-pack remaining,
38// relying on the MultiBlockKernel builder to only copy the correct number
39// of bytes to the actual output stream.
40
41//void expand3_4Kernel::generateMultiBlockLogic(const std::unique_ptr<KernelBuilder> &b, Value * const numOfStrides) {
42
43//    BasicBlock * expand2_3entry = b->GetInsertBlock();
44//    BasicBlock * expand_3_4_loop = b->CreateBasicBlock("expand_3_4_loop");
45//    BasicBlock * expand3_4_exit = b->CreateBasicBlock("expand3_4_exit");
46
47//    // Determine the require shufflevector constants.
48//    const unsigned PACK_SIZE = b->getBitBlockWidth()/8;
49
50//    ConstantInt * const ZERO = b->getSize(0);
51//    ConstantInt * const ONE = b->getSize(1);
52//    ConstantInt * const THREE = b->getSize(3);
53//    ConstantInt * const FOUR = b->getSize(4);
54//    ConstantInt * const SEVEN = b->getSize(7);
55
56//    // Construct a list of indexes in  the form
57//    // 0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, ...
58//    unsigned sourceByteIndex = 0;
59//    unsigned expand3_4_index[PACK_SIZE];
60//    for (unsigned i = 0; i < PACK_SIZE; i++) {
61//        expand3_4_index[i] = sourceByteIndex;
62//        if (i % 4 != 2) sourceByteIndex++;
63//    }
64//    unsigned const expand3_4_offset[4] = {PACK_SIZE, 3*PACK_SIZE/4, PACK_SIZE/2, PACK_SIZE/4};
65//    Value * expand_3_4_shuffle[4];
66//    for (unsigned j = 0; j < 4; j++) {
67//        std::vector<Constant *> Idxs;
68//        for (unsigned i = 0; i < PACK_SIZE; i++) {
69//            Idxs.push_back(ConstantInt::get(b->getInt32Ty(), expand3_4_offset[j] + expand3_4_index[i]));
70//        }
71//        expand_3_4_shuffle[j] = ConstantVector::get(Idxs);
72//    }
73
74
75
76//    Constant * triplePackSize = b->getSize(3 * PACK_SIZE); // 3 packs per loop.
77//    UndefValue * undefPack = UndefValue::get(b->fwVectorType(8));
78
79//    Value * const numOfBlocks = b->CreateMul(numOfStrides, b->getSize(8));
80
81//    Value * itemsToDo = mAvailableItemCount[0];
82
83//    // The main loop processes 3 packs of data at a time.
84//    b->CreateBr(expand_3_4_loop);
85
86//    b->SetInsertPoint(expand_3_4_loop);
87//    PHINode * loopItemsRemain = b->CreatePHI(b->getSizeTy(), 2);
88//    PHINode * strideOffset = b->CreatePHI(b->getSizeTy(), 2);
89//    loopItemsRemain->addIncoming(itemsToDo, expand2_3entry);
90//    strideOffset->addIncoming(ZERO, expand2_3entry);
91
92//    Value * const baseInputOffset = b->CreateMul(strideOffset, THREE);
93//    Value * const baseOutputOffset = b->CreateMul(strideOffset, FOUR);
94//    Value * carryOver = undefPack;
95//    for (unsigned i = 0; i < 3; ++i) {
96//        ConstantInt * const index = b->getSize(i);
97//        Value * const inputOffset = b->CreateAdd(baseInputOffset, index);
98//        Value * const inputPackIndex = b->CreateAnd(inputOffset, SEVEN);
99//        Value * const inputBlockOffset = b->CreateLShr(inputOffset, THREE);
100//        Value * const input = b->fwCast(8, b->loadInputStreamPack("sourceStream", ZERO, inputPackIndex, inputBlockOffset));
101//        Value * const expanded = b->CreateShuffleVector(carryOver, input, expand_3_4_shuffle[i]);
102//        Value * const outputOffset = b->CreateAdd(baseOutputOffset, index);
103//        Value * const outputPackIndex = b->CreateAnd(outputOffset, SEVEN);
104//        Value * const outputBlockOffset = b->CreateLShr(outputOffset, THREE);
105//        b->storeOutputStreamPack("expand34Stream", ZERO, outputPackIndex, outputBlockOffset, b->bitCast(expanded));
106//        carryOver = input;
107//    }
108//    Value * expanded = b->CreateShuffleVector(carryOver, undefPack, expand_3_4_shuffle[3]);
109//    Value * outputOffset = b->CreateAdd(baseOutputOffset, THREE);
110//    Value * const outputPackIndex = b->CreateAnd(outputOffset, SEVEN);
111//    Value * const outputBlockOffset = b->CreateLShr(outputOffset, THREE);
112//    b->storeOutputStreamPack("expand34Stream", ZERO, outputPackIndex, outputBlockOffset, b->bitCast(expanded));
113
114//    Value * remainingItems = b->CreateSub(loopItemsRemain, triplePackSize);
115
116//    loopItemsRemain->addIncoming(remainingItems, expand_3_4_loop);
117//    Value * const nextStrideOffset = b->CreateAdd(strideOffset, ONE);
118//    strideOffset->addIncoming(nextStrideOffset, expand_3_4_loop);
119
120//    //Value * continueLoop = b->CreateICmpSGT(remainingItems, ZERO);
121//    Value * continueLoop = b->CreateICmpULT(nextStrideOffset, numOfBlocks);
122//    b->CreateCondBr(continueLoop, expand_3_4_loop, expand3_4_exit);
123
124//    b->SetInsertPoint(expand3_4_exit);
125
126//}
127
128void expand3_4Kernel::generateMultiBlockLogic(const std::unique_ptr<KernelBuilder> &b, Value * const numOfStrides) {
129
130    BasicBlock * expand2_3entry = b->GetInsertBlock();
131    BasicBlock * expand_3_4_loop = b->CreateBasicBlock("expand_3_4_loop");
132    BasicBlock * expand3_4_exit = b->CreateBasicBlock("expand3_4_exit");
133
134    // Determine the require shufflevector constants.
135    const unsigned PACK_SIZE = b->getBitBlockWidth()/8;
136
137    ConstantInt * const ZERO = b->getSize(0);
138    ConstantInt * const ONE = b->getSize(1);
139    ConstantInt * const THREE = b->getSize(3);
140    ConstantInt * const FOUR = b->getSize(4);
141    ConstantInt * const SEVEN = b->getSize(7);
142
143    // Construct a list of indexes in  the form
144    // 0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, ...
145    unsigned sourceByteIndex = 0;
146    unsigned expand3_4_index[PACK_SIZE];
147    for (unsigned i = 0; i < PACK_SIZE; i++) {
148        expand3_4_index[i] = sourceByteIndex;
149        if (i % 4 != 2) sourceByteIndex++;
150    }
151    unsigned const expand3_4_offset[4] = {PACK_SIZE, 3*PACK_SIZE/4, PACK_SIZE/2, PACK_SIZE/4};
152    Value * expand_3_4_shuffle[4];
153    for (unsigned j = 0; j < 4; j++) {
154        std::vector<Constant *> Idxs;
155        for (unsigned i = 0; i < PACK_SIZE; i++) {
156            Idxs.push_back(ConstantInt::get(b->getInt32Ty(), expand3_4_offset[j] + expand3_4_index[i]));
157        }
158        expand_3_4_shuffle[j] = ConstantVector::get(Idxs);
159    }
160
161    UndefValue * undefPack = UndefValue::get(b->fwVectorType(8));
162    Value * const numOfBlocks = b->CreateMul(numOfStrides, b->getSize(8));
163    // The main loop processes 3 packs of data at a time.
164    b->CreateBr(expand_3_4_loop);
165
166    b->SetInsertPoint(expand_3_4_loop);
167    PHINode * strideOffset = b->CreatePHI(b->getSizeTy(), 2);
168    strideOffset->addIncoming(ZERO, expand2_3entry);
169
170    Value * const baseInputOffset = b->CreateMul(strideOffset, THREE);
171    Value * const baseOutputOffset = b->CreateMul(strideOffset, FOUR);
172    Value * carryOver = undefPack;
173    for (unsigned i = 0; i < 3; ++i) {
174        ConstantInt * const index = b->getSize(i);
175        Value * const inputOffset = b->CreateAdd(baseInputOffset, index);
176        Value * const inputPackIndex = b->CreateAnd(inputOffset, SEVEN);
177        Value * const inputBlockOffset = b->CreateLShr(inputOffset, THREE);
178        Value * const input = b->fwCast(8, b->loadInputStreamPack("sourceStream", ZERO, inputPackIndex, inputBlockOffset));
179        Value * const expanded = b->CreateShuffleVector(carryOver, input, expand_3_4_shuffle[i]);
180        Value * const outputOffset = b->CreateAdd(baseOutputOffset, index);
181        Value * const outputPackIndex = b->CreateAnd(outputOffset, SEVEN);
182        Value * const outputBlockOffset = b->CreateLShr(outputOffset, THREE);
183        b->storeOutputStreamPack("expand34Stream", ZERO, outputPackIndex, outputBlockOffset, b->bitCast(expanded));
184        carryOver = input;
185    }
186    Value * expanded = b->CreateShuffleVector(carryOver, undefPack, expand_3_4_shuffle[3]);
187    Value * outputOffset = b->CreateAdd(baseOutputOffset, THREE);
188    Value * const outputPackIndex = b->CreateAnd(outputOffset, SEVEN);
189    Value * const outputBlockOffset = b->CreateLShr(outputOffset, THREE);
190    b->storeOutputStreamPack("expand34Stream", ZERO, outputPackIndex, outputBlockOffset, b->bitCast(expanded));
191
192    Value * const nextStrideOffset = b->CreateAdd(strideOffset, ONE);
193    strideOffset->addIncoming(nextStrideOffset, expand_3_4_loop);
194    Value * continueLoop = b->CreateICmpULT(nextStrideOffset, numOfBlocks);
195    b->CreateCondBr(continueLoop, expand_3_4_loop, expand3_4_exit);
196
197    b->SetInsertPoint(expand3_4_exit);
198}
199
200
201// Radix 64 determination, converting 3 bytes to 4 6-bit values.
202//
203//  00000000|zyxwvuts|rqpmnlkj|hgfedcba    Original
204//           zy                            bits to move 6 positions right
205//             xwvuts                      bits to move 8 positions left
206//                    rqpm                 bits to move 4 positions right
207//                        nlkj             bits to move 10 positions left
208//                             hqfedc      bits to move 2 positions right
209//                                   ba    bits to move 12 positions left
210//    xwvuts|  nlkjzy|  barqpm|  hgfedc    Target
211inline Value * radix64Kernel::processPackData(const std::unique_ptr<KernelBuilder> & iBuilder, llvm::Value * bytepack) const {
212
213    Value * step_right_6 = iBuilder->simd_fill(32, ConstantInt::get(iBuilder->getInt32Ty(), 0x00C00000));
214    Value * right_6_result = iBuilder->simd_srli(32, iBuilder->simd_and(bytepack, step_right_6), 6);
215
216    Value * step_left_8 = iBuilder->simd_fill(32, ConstantInt::get(iBuilder->getInt32Ty(), 0x003F0000));
217    Value * left_8_result = iBuilder->simd_slli(32, iBuilder->simd_and(bytepack, step_left_8), 8);
218    Value * mid = iBuilder->simd_or(right_6_result, left_8_result);
219
220    Value * step_right_4 = iBuilder->simd_fill(32, ConstantInt::get(iBuilder->getInt32Ty(), 0x0000F000));
221    Value * right_4_result = iBuilder->simd_srli(32, iBuilder->simd_and(bytepack, step_right_4), 4);
222    mid = iBuilder->simd_or(mid, right_4_result);
223
224    Value * step_left_10 = iBuilder->simd_fill(32, ConstantInt::get(iBuilder->getInt32Ty(), 0x00000F00));
225    Value * left_10_result = iBuilder->simd_slli(32, iBuilder->simd_and(bytepack, step_left_10), 10);
226    mid = iBuilder->simd_or(mid, left_10_result);
227
228    Value * step_right_2 = iBuilder->simd_fill(32, ConstantInt::get(iBuilder->getInt32Ty(), 0x000000FC));
229    Value * right_2_result = iBuilder->simd_srli(32, iBuilder->simd_and(bytepack, step_right_2), 2);
230    mid = iBuilder->simd_or(mid, right_2_result);
231
232    Value * step_left_12 = iBuilder->simd_fill(32, ConstantInt::get(iBuilder->getInt32Ty(), 0x00000003));
233    Value * left_12_result = iBuilder->simd_slli(32, iBuilder->simd_and(bytepack, step_left_12), 12);
234    mid = iBuilder->simd_or(mid, left_12_result);
235
236    return iBuilder->bitCast(mid);
237}
238
239void radix64Kernel::generateDoBlockMethod(const std::unique_ptr<KernelBuilder> & iBuilder) {
240    for (unsigned i = 0; i < 8; i++) {
241        Value * bytepack = iBuilder->loadInputStreamPack("expandedStream", iBuilder->getInt32(0), iBuilder->getInt32(i));
242        Value * radix64pack = processPackData(iBuilder, bytepack);
243        iBuilder->storeOutputStreamPack("radix64stream", iBuilder->getInt32(0), iBuilder->getInt32(i), radix64pack);
244    }
245}
246
247void radix64Kernel::generateFinalBlockMethod(const std::unique_ptr<KernelBuilder> & iBuilder, Value * remainingBytes) {
248
249    BasicBlock * entry = iBuilder->GetInsertBlock();
250    BasicBlock * radix64_loop = iBuilder->CreateBasicBlock("radix64_loop");
251    BasicBlock * fbExit = iBuilder->CreateBasicBlock("fbExit");
252   
253    const unsigned PACK_SIZE = iBuilder->getStride()/8;
254    Constant * packSize = iBuilder->getSize(PACK_SIZE);
255
256    // Enter the loop only if there is at least one byte remaining to process.
257    iBuilder->CreateCondBr(iBuilder->CreateICmpEQ(remainingBytes, iBuilder->getSize(0)), fbExit, radix64_loop);
258
259    iBuilder->SetInsertPoint(radix64_loop);
260    PHINode * idx = iBuilder->CreatePHI(iBuilder->getInt32Ty(), 2);
261    PHINode * loopRemain = iBuilder->CreatePHI(iBuilder->getSizeTy(), 2);
262    idx->addIncoming(ConstantInt::getNullValue(iBuilder->getInt32Ty()), entry);
263    loopRemain->addIncoming(remainingBytes, entry);
264
265    Value * bytepack = iBuilder->loadInputStreamPack("expandedStream", iBuilder->getInt32(0), idx);
266    Value * radix64pack = processPackData(iBuilder, bytepack);
267    iBuilder->storeOutputStreamPack("radix64stream", iBuilder->getInt32(0), idx, radix64pack);
268
269    Value* nextIdx = iBuilder->CreateAdd(idx, ConstantInt::get(iBuilder->getInt32Ty(), 1));
270    idx->addIncoming(nextIdx, radix64_loop);
271    Value* remainAfterLoop = iBuilder->CreateSub(loopRemain, packSize);
272    loopRemain->addIncoming(remainAfterLoop, radix64_loop);
273
274    Value* continueLoop = iBuilder->CreateICmpSGT(remainAfterLoop, iBuilder->getSize(0));
275
276    iBuilder->CreateCondBr(continueLoop, radix64_loop, fbExit);
277
278    iBuilder->SetInsertPoint(fbExit);
279}
280
281inline llvm::Value* base64Kernel::processPackData(const std::unique_ptr<KernelBuilder> & iBuilder, llvm::Value* bytepack) const {
282    Value * mask_gt_25 = iBuilder->simd_ugt(8, bytepack, iBuilder->simd_fill(8, iBuilder->getInt8(25)));
283    Value * mask_gt_51 = iBuilder->simd_ugt(8, bytepack, iBuilder->simd_fill(8, iBuilder->getInt8(51)));
284    Value * mask_eq_62 = iBuilder->simd_eq(8, bytepack, iBuilder->simd_fill(8, iBuilder->getInt8(62)));
285    Value * mask_eq_63 = iBuilder->simd_eq(8, bytepack, iBuilder->simd_fill(8, iBuilder->getInt8(63)));
286    // Strategy:
287    // 1. add ord('A') = 65 to all radix64 values, this sets the correct values for entries 0 to 25.
288    // 2. add ord('a') - ord('A') - (26 - 0) = 6 to all values >25, this sets the correct values for entries 0 to 51
289    // 3. subtract ord('a') - ord('0') + (52 - 26) = 75 to all values > 51, this sets the correct values for entries 0 to 61
290    // 4. subtract ord('0') - ord('+') + (62 - 52) = 15 for all values = 62
291    // 4. add ord('/') - ord('0') - (63 - 52) = 3 for all values = 63
292    Value * t0_25 = iBuilder->simd_add(8, bytepack, iBuilder->simd_fill(8, iBuilder->getInt8('A')));
293    Value * t0_51 = iBuilder->simd_add(8, t0_25, iBuilder->simd_and(mask_gt_25, iBuilder->simd_fill(8, iBuilder->getInt8(6))));
294    Value * t0_61 = iBuilder->simd_sub(8, t0_51, iBuilder->simd_and(mask_gt_51, iBuilder->simd_fill(8, iBuilder->getInt8(75))));
295    Value * t0_62 = iBuilder->simd_sub(8, t0_61, iBuilder->simd_and(mask_eq_62, iBuilder->simd_fill(8, iBuilder->getInt8(15))));
296    return iBuilder->bitCast(iBuilder->simd_sub(8, t0_62, iBuilder->simd_and(mask_eq_63, iBuilder->simd_fill(8, iBuilder->getInt8(12)))));
297}
298
299void base64Kernel::generateDoBlockMethod(const std::unique_ptr<KernelBuilder> & iBuilder) {
300    for (unsigned i = 0; i < 8; i++) {
301        Value * bytepack = iBuilder->loadInputStreamPack("radix64stream", iBuilder->getInt32(0), iBuilder->getInt32(i));
302        Value * base64pack = processPackData(iBuilder, bytepack);
303        iBuilder->storeOutputStreamPack("base64stream", iBuilder->getInt32(0), iBuilder->getInt32(i), base64pack);
304    }
305}
306
307// Special processing for the base 64 format.   The output must always contain a multiple
308// of 4 bytes.   When the number of radix 64 values is not a multiple of 4
309// number of radix 64 values
310void base64Kernel::generateFinalBlockMethod(const std::unique_ptr<KernelBuilder> & b, Value * remainingBytes) {
311
312    BasicBlock * entry = b->GetInsertBlock();
313    BasicBlock * base64_loop = b->CreateBasicBlock("base64_loop");
314    BasicBlock * loopExit = b->CreateBasicBlock("loopExit");
315    BasicBlock * doPadding = b->CreateBasicBlock("doPadding");
316    BasicBlock * doPadding2 = b->CreateBasicBlock("doPadding2");
317    BasicBlock * fbExit = b->CreateBasicBlock("fbExit");
318
319    Constant * const ZERO = b->getSize(0);
320    Constant * const ONE = b->getSize(1);
321    Constant * const THREE = b->getSize(3);
322    Constant * const PADDING = b->getInt8('=');
323
324    Value * remainMod4 = b->CreateAnd(remainingBytes, THREE);
325    Value * padBytes = b->CreateAnd(b->CreateSub(b->getSize(4), remainMod4), THREE);
326
327    Constant * const PACK_SIZE = b->getSize(b->getStride() / 8);
328
329    // Enter the loop only if there is at least one byte remaining to process.
330    b->CreateCondBr(b->CreateICmpEQ(remainingBytes, ZERO), fbExit, base64_loop);
331
332    b->SetInsertPoint(base64_loop);
333    PHINode * idx = b->CreatePHI(b->getSizeTy(), 2);
334    PHINode * loopRemain = b->CreatePHI(b->getSizeTy(), 2);
335    idx->addIncoming(ZERO, entry);
336    loopRemain->addIncoming(remainingBytes, entry);
337    Value * bytepack = b->loadInputStreamPack("radix64stream", ZERO, idx);
338    Value * base64pack = processPackData(b, bytepack);
339    b->storeOutputStreamPack("base64stream", ZERO, idx, base64pack);
340    idx->addIncoming(b->CreateAdd(idx, ONE), base64_loop);
341    Value * remainAfterLoop = b->CreateSub(loopRemain, PACK_SIZE);
342    loopRemain->addIncoming(remainAfterLoop, base64_loop);
343    Value * continueLoop = b->CreateICmpUGT(loopRemain, PACK_SIZE);
344    b->CreateCondBr(continueLoop, base64_loop, loopExit);
345
346    b->SetInsertPoint(loopExit);
347    b->CreateCondBr(b->CreateICmpEQ(padBytes, ZERO), fbExit, doPadding);
348
349    b->SetInsertPoint(doPadding);
350    Value * i8output_ptr = b->getOutputStreamBlockPtr("base64stream", ZERO);
351    i8output_ptr = b->CreatePointerCast(i8output_ptr, b->getInt8PtrTy());
352    b->CreateStore(PADDING, b->CreateGEP(i8output_ptr, remainingBytes));
353    b->CreateCondBr(b->CreateICmpEQ(remainMod4, THREE), fbExit, doPadding2);
354
355    b->SetInsertPoint(doPadding2);
356    Value * finalPadPos = b->CreateAdd(remainingBytes, ONE);
357    b->CreateStore(PADDING, b->CreateGEP(i8output_ptr, finalPadPos));
358    b->CreateBr(fbExit);
359    b->SetInsertPoint(fbExit);
360}
361
362expand3_4Kernel::expand3_4Kernel(const std::unique_ptr<kernel::KernelBuilder> & iBuilder)
363: MultiBlockKernel("expand3_4",
364            {Binding{iBuilder->getStreamSetTy(1, 8), "sourceStream", FixedRate(3)}},
365            {Binding{iBuilder->getStreamSetTy(1, 8), "expand34Stream", FixedRate(4)}},
366            {}, {}, {}) {
367
368}
369
370radix64Kernel::radix64Kernel(const std::unique_ptr<kernel::KernelBuilder> & iBuilder)
371: BlockOrientedKernel("radix64",
372            {Binding{iBuilder->getStreamSetTy(1, 8), "expandedStream"}},
373            {Binding{iBuilder->getStreamSetTy(1, 8), "radix64stream"}},
374            {}, {}, {}) {
375}
376
377base64Kernel::base64Kernel(const std::unique_ptr<kernel::KernelBuilder> & iBuilder)
378: BlockOrientedKernel("base64",
379            {Binding{iBuilder->getStreamSetTy(1, 8), "radix64stream"}},
380            {Binding{iBuilder->getStreamSetTy(1, 8), "base64stream", FixedRate(1), RoundUpTo(4)}},
381            {}, {}, {}) {
382}
383
384}
Note: See TracBrowser for help on using the repository browser.