source: icGREP/icgrep-devel/icgrep/kernels/kernel.cpp @ 5706

Last change on this file since 5706 was 5706, checked in by nmedfort, 20 months ago

First stage of MultiBlockKernel? and pipeline restructuring

File size: 66.7 KB
Line 
1/*
2 *  Copyright (c) 2016-7 International Characters.
3 *  This software is licensed to the public under the Open Software License 3.0.
4 */
5
6#include "kernel.h"
7#include <toolchain/toolchain.h>
8#include <kernels/streamset.h>
9#include <llvm/IR/Constants.h>
10#include <llvm/IR/Function.h>
11#include <llvm/IR/Instructions.h>
12#include <llvm/IR/MDBuilder.h>
13#include <llvm/IR/Module.h>
14#include <llvm/Support/raw_ostream.h>
15#include <llvm/Bitcode/ReaderWriter.h>
16#include <llvm/Transforms/Utils/Local.h>
17#include <kernels/streamset.h>
18#include <sstream>
19#include <kernels/kernel_builder.h>
20#include <boost/math/common_factor_rt.hpp>
21#include <llvm/Support/Debug.h>
22
23using namespace llvm;
24using namespace parabix;
25using namespace boost::math;
26
27namespace kernel {
28
29const std::string Kernel::DO_BLOCK_SUFFIX = "_DoBlock";
30const std::string Kernel::FINAL_BLOCK_SUFFIX = "_FinalBlock";
31const std::string Kernel::MULTI_BLOCK_SUFFIX = "_MultiBlock";
32const std::string Kernel::LOGICAL_SEGMENT_NO_SCALAR = "logicalSegNo";
33const std::string Kernel::PROCESSED_ITEM_COUNT_SUFFIX = "_processedItemCount";
34const std::string Kernel::CONSUMED_ITEM_COUNT_SUFFIX = "_consumedItemCount";
35const std::string Kernel::PRODUCED_ITEM_COUNT_SUFFIX = "_producedItemCount";
36const std::string Kernel::TERMINATION_SIGNAL = "terminationSignal";
37const std::string Kernel::BUFFER_PTR_SUFFIX = "_bufferPtr";
38const std::string Kernel::CONSUMER_SUFFIX = "_consumerLocks";
39const std::string Kernel::CYCLECOUNT_SCALAR = "CPUcycles";
40
41/** ------------------------------------------------------------------------------------------------------------- *
42 * @brief addScalar
43 ** ------------------------------------------------------------------------------------------------------------- */
44unsigned Kernel::addScalar(Type * const type, const std::string & name) {
45    if (LLVM_UNLIKELY(mKernelStateType != nullptr)) {
46        report_fatal_error("Cannot add field " + name + " to " + getName() + " after kernel state finalized");
47    }
48    if (LLVM_UNLIKELY(mKernelMap.count(name))) {
49        report_fatal_error(getName() + " already contains scalar field " + name);
50    }
51    const auto index = mKernelFields.size();
52    mKernelMap.emplace(name, index);
53    mKernelFields.push_back(type);
54    return index;
55}
56
57
58/** ------------------------------------------------------------------------------------------------------------- *
59 * @brief addUnnamedScalar
60 ** ------------------------------------------------------------------------------------------------------------- */
61unsigned Kernel::addUnnamedScalar(Type * const type) {
62    if (LLVM_UNLIKELY(mKernelStateType != nullptr)) {
63        report_fatal_error("Cannot add unnamed field  to " + getName() + " after kernel state finalized");
64    }
65    const auto index = mKernelFields.size();
66    mKernelFields.push_back(type);
67    return index;
68}
69
70
71/** ------------------------------------------------------------------------------------------------------------- *
72 * @brief prepareStreamSetNameMap
73 ** ------------------------------------------------------------------------------------------------------------- */
74void Kernel::prepareStreamSetNameMap() {
75    for (unsigned i = 0; i < mStreamSetInputs.size(); i++) {
76        mStreamMap.emplace(mStreamSetInputs[i].getName(), std::make_pair(Port::Input, i));
77    }
78    for (unsigned i = 0; i < mStreamSetOutputs.size(); i++) {
79        mStreamMap.emplace(mStreamSetOutputs[i].getName(), std::make_pair(Port::Output, i));
80    }
81}
82
83
84/** ------------------------------------------------------------------------------------------------------------- *
85 * @brief bindPorts
86 ** ------------------------------------------------------------------------------------------------------------- */
87void Kernel::bindPorts(const StreamSetBuffers & inputs, const StreamSetBuffers & outputs) {
88    assert (mModule == nullptr);
89    assert (mStreamSetInputBuffers.empty());
90    assert (mStreamSetOutputBuffers.empty());
91
92    if (LLVM_UNLIKELY(mStreamSetInputs.size() != inputs.size())) {
93        report_fatal_error(getName() + ": expected " + std::to_string(mStreamSetInputs.size()) +
94                           " input stream sets but was given "
95                           + std::to_string(inputs.size()));
96    }
97
98    for (unsigned i = 0; i < inputs.size(); ++i) {
99        StreamSetBuffer * const buf = inputs[i];
100        if (LLVM_UNLIKELY(buf == nullptr)) {
101            report_fatal_error(getName() + ": input stream set " + std::to_string(i)
102                               + " cannot be null");
103        }
104        buf->addConsumer(this);
105    }
106
107    if (LLVM_UNLIKELY(mStreamSetOutputs.size() != outputs.size())) {
108        report_fatal_error(getName() + ": expected " + std::to_string(mStreamSetOutputs.size())
109                           + " output stream sets but was given "
110                           + std::to_string(outputs.size()));
111    }
112
113    for (unsigned i = 0; i < outputs.size(); ++i) {
114        StreamSetBuffer * const buf = outputs[i];
115        if (LLVM_UNLIKELY(buf == nullptr)) {
116            report_fatal_error(getName() + ": output stream set " + std::to_string(i) + " cannot be null");
117        }
118        if (LLVM_LIKELY(buf->getProducer() == nullptr)) {
119            buf->setProducer(this);
120        } else {
121            report_fatal_error(getName() + ": output stream set " + std::to_string(i)
122                               + " is already produced by kernel " + buf->getProducer()->getName());
123        }
124    }
125
126    mStreamSetInputBuffers.assign(inputs.begin(), inputs.end());
127    mStreamSetOutputBuffers.assign(outputs.begin(), outputs.end());
128}
129
130
131/** ------------------------------------------------------------------------------------------------------------- *
132 * @brief getCacheName
133 ** ------------------------------------------------------------------------------------------------------------- */
134std::string Kernel::getCacheName(const std::unique_ptr<KernelBuilder> & idb) const {
135    std::stringstream cacheName;
136    cacheName << getName() << '_' << idb->getBuilderUniqueName();
137    for (const StreamSetBuffer * b: mStreamSetInputBuffers) {
138        cacheName <<  ':' <<  b->getUniqueID();
139    }
140    for (const StreamSetBuffer * b: mStreamSetOutputBuffers) {
141        cacheName <<  ':' <<  b->getUniqueID();
142    }
143    return cacheName.str();
144}
145
146
147/** ------------------------------------------------------------------------------------------------------------- *
148 * @brief setModule
149 ** ------------------------------------------------------------------------------------------------------------- */
150Module * Kernel::setModule(Module * const module) {
151    assert (mModule == nullptr || mModule == module);
152    assert (module != nullptr);
153    mModule = module;
154    return mModule;
155}
156
157
158/** ------------------------------------------------------------------------------------------------------------- *
159 * @brief makeModule
160 ** ------------------------------------------------------------------------------------------------------------- */
161Module * Kernel::makeModule(const std::unique_ptr<kernel::KernelBuilder> & idb) {
162    return setModule(new Module(getCacheName(idb), idb->getContext()));
163}
164
165
166/** ------------------------------------------------------------------------------------------------------------- *
167 * @brief prepareKernel
168 ** ------------------------------------------------------------------------------------------------------------- */
169void Kernel::prepareKernel(const std::unique_ptr<KernelBuilder> & idb) {
170    assert ("KernelBuilder does not have a valid IDISA Builder" && idb);
171    if (LLVM_UNLIKELY(mKernelStateType != nullptr)) {
172        report_fatal_error(getName() + ": cannot prepare kernel after kernel state finalized");
173    }
174    addBaseKernelProperties(idb);
175    addInternalKernelProperties(idb);
176    // NOTE: StructType::create always creates a new type even if an identical one exists.
177    if (LLVM_UNLIKELY(mModule == nullptr)) {
178        setModule(new Module(getCacheName(idb), idb->getContext()));
179    }
180    mKernelStateType = mModule->getTypeByName(getName());
181    if (LLVM_LIKELY(mKernelStateType == nullptr)) {
182        mKernelStateType = StructType::create(idb->getContext(), mKernelFields, getName());
183        assert (mKernelStateType);
184    }   
185}
186
187
188/** ------------------------------------------------------------------------------------------------------------- *
189 * @brief prepareCachedKernel
190 ** ------------------------------------------------------------------------------------------------------------- */
191void Kernel::prepareCachedKernel(const std::unique_ptr<KernelBuilder> & idb) {
192    assert ("KernelBuilder does not have a valid IDISA Builder" && idb);
193    if (LLVM_UNLIKELY(mKernelStateType != nullptr)) {
194        report_fatal_error(getName() + ": cannot prepare kernel after kernel state finalized");
195    }
196    assert (getModule());
197    addBaseKernelProperties(idb);
198    mKernelStateType = getModule()->getTypeByName(getName());
199    if (LLVM_UNLIKELY(mKernelStateType == nullptr)) {
200        report_fatal_error("Kernel definition for " + getName() + " could not be found in the cache object");
201    }   
202}
203
204/** ------------------------------------------------------------------------------------------------------------- *
205 * @brief getItemsPerStride
206 ** ------------------------------------------------------------------------------------------------------------- */
207std::pair<unsigned, unsigned> Kernel::getStreamRate(const Port p, const unsigned i) const {
208    const ProcessingRate & rate = (p == Port::Input) ? mStreamSetInputs[i].getRate() : mStreamSetOutputs[i].getRate();
209    unsigned min = 0, max = 0;
210    if (rate.isFixed()) {
211        min = max = rate.getRate();
212    } else if (rate.isBounded()) {
213        min = rate.getLowerBound();
214        max = rate.getUpperBound();
215    } else if (rate.isUnknown()) {
216        min = rate.getLowerBound();
217        max = 0;
218    } else if (rate.isExactlyRelative()) {
219        for (unsigned j = 0; j < mStreamSetInputs.size(); ++j) {
220            if (mStreamSetInputs[j].getName() == rate.getReference()) {
221                std::tie(min, max) = getStreamRate(Port::Input, j);
222                min = (min * rate.getNumerator()) / rate.getDenominator();
223                assert (max == 0 || (max * rate.getNumerator()) % rate.getDenominator() == 0);
224                max = (max * rate.getNumerator()) / rate.getDenominator();
225                return std::make_pair(min, max);
226            }
227        }
228        for (unsigned j = 0; j < mStreamSetOutputs.size(); ++j) {
229            if (mStreamSetOutputs[j].getName() == rate.getReference()) {
230                assert (p == Port::Output);
231                std::tie(min, max) = getStreamRate(Port::Output, j);
232                min = (min * rate.getNumerator()) / rate.getDenominator();
233                assert (max == 0 || (max * rate.getNumerator()) % rate.getDenominator() == 0);
234                max = (max * rate.getNumerator()) / rate.getDenominator();
235                return std::make_pair(min, max);
236            }
237        }
238        llvm_unreachable("Reference rate must be associated with an input or output!");
239    }
240    return std::make_pair(min, max);
241}
242
243/** ------------------------------------------------------------------------------------------------------------- *
244 * @brief addBaseKernelProperties
245 ** ------------------------------------------------------------------------------------------------------------- */
246void Kernel::addBaseKernelProperties(const std::unique_ptr<KernelBuilder> & idb) {
247   
248    const unsigned inputSetCount = mStreamSetInputs.size();
249    const unsigned outputSetCount = mStreamSetOutputs.size();
250   
251    assert (inputSetCount == mStreamSetInputBuffers.size());
252    assert (outputSetCount == mStreamSetOutputBuffers.size());
253
254    if (mStride == 0) {
255        // Set the default kernel stride.
256        mStride = idb->getBitBlockWidth();
257    }
258
259    IntegerType * const sizeTy = idb->getSizeTy();
260
261    for (unsigned i = 0; i < inputSetCount; i++) {
262        const Binding & b = mStreamSetInputs[i];
263        //const ProcessingRate & rate = b.getRate();
264        //if (rate.isBounded() || rate.isUnknown()) {
265            addScalar(sizeTy, b.getName() + PROCESSED_ITEM_COUNT_SUFFIX);
266        //}
267    }
268
269    for (unsigned i = 0; i < outputSetCount; i++) {
270        const Binding & b = mStreamSetOutputs[i];
271        //const ProcessingRate & rate = b.getRate();
272        //if (rate.isBounded() || rate.isUnknown()) {
273            addScalar(sizeTy, b.getName() + PRODUCED_ITEM_COUNT_SUFFIX);
274        //}
275    }
276
277    for (unsigned i = 0; i < inputSetCount; i++) {
278        mScalarInputs.emplace_back(mStreamSetInputBuffers[i]->getStreamSetHandle()->getType(), mStreamSetInputs[i].getName() + BUFFER_PTR_SUFFIX);
279    }
280    for (unsigned i = 0; i < outputSetCount; i++) {
281        mScalarInputs.emplace_back(mStreamSetOutputBuffers[i]->getStreamSetHandle()->getType(), mStreamSetOutputs[i].getName() + BUFFER_PTR_SUFFIX);
282    }
283    for (const auto & binding : mScalarInputs) {
284        addScalar(binding.getType(), binding.getName());
285    }
286    for (const auto & binding : mScalarOutputs) {
287        addScalar(binding.getType(), binding.getName());
288    }
289    if (mStreamMap.empty()) {
290        prepareStreamSetNameMap();
291    }
292    for (const auto & binding : mInternalScalars) {
293        addScalar(binding.getType(), binding.getName());
294    }
295    Type * const consumerSetTy = StructType::get(sizeTy, sizeTy->getPointerTo()->getPointerTo(), nullptr)->getPointerTo();
296    for (unsigned i = 0; i < mStreamSetOutputs.size(); i++) {
297        addScalar(consumerSetTy, mStreamSetOutputs[i].getName() + CONSUMER_SUFFIX);
298    }
299    addScalar(sizeTy, LOGICAL_SEGMENT_NO_SCALAR);
300    addScalar(idb->getInt1Ty(), TERMINATION_SIGNAL);
301    for (unsigned i = 0; i < mStreamSetOutputs.size(); i++) {
302        addScalar(sizeTy, mStreamSetOutputs[i].getName() + CONSUMED_ITEM_COUNT_SUFFIX);
303    }
304    // We compile in a 64-bit CPU cycle counter into every kernel.   It will remain unused
305    // in normal execution, but when codegen::EnableCycleCounter is specified, pipelines
306    // will be able to add instrumentation to cached modules without recompilation.
307    addScalar(idb->getInt64Ty(), CYCLECOUNT_SCALAR);
308
309}
310
311
312/** ------------------------------------------------------------------------------------------------------------- *
313 * @brief makeSignature
314 *
315 * Default kernel signature: generate the IR and emit as byte code.
316 ** ------------------------------------------------------------------------------------------------------------- */
317std::string Kernel::makeSignature(const std::unique_ptr<kernel::KernelBuilder> & idb) {
318    assert ("KernelBuilder does not have a valid IDISA Builder" && idb.get());
319    if (LLVM_UNLIKELY(hasSignature())) {
320        generateKernel(idb);
321        std::string signature;
322        raw_string_ostream OS(signature);
323        WriteBitcodeToFile(getModule(), OS);
324        return signature;
325    } else {
326        return getModule()->getModuleIdentifier();
327    }
328}
329
330
331/** ------------------------------------------------------------------------------------------------------------- *
332 * @brief generateKernel
333 ** ------------------------------------------------------------------------------------------------------------- */
334void Kernel::generateKernel(const std::unique_ptr<kernel::KernelBuilder> & idb) {
335    assert ("KernelBuilder does not have a valid IDISA Builder" && idb.get());
336    // If the module id cannot uniquely identify this kernel, "generateKernelSignature()" will have already
337    // generated the unoptimized IR.
338    if (!mIsGenerated) {
339        const auto m = idb->getModule();
340        const auto ip = idb->saveIP();
341        // const auto saveInstance = getInstance();
342        idb->setModule(mModule);
343        addKernelDeclarations(idb);
344        callGenerateInitializeMethod(idb);
345        callGenerateDoSegmentMethod(idb);
346        callGenerateFinalizeMethod(idb);
347        // setInstance(saveInstance);
348        idb->setModule(m);
349        idb->restoreIP(ip);
350        mIsGenerated = true;
351    }
352}
353
354
355/** ------------------------------------------------------------------------------------------------------------- *
356 * @brief callGenerateInitializeMethod
357 ** ------------------------------------------------------------------------------------------------------------- */
358inline void Kernel::callGenerateInitializeMethod(const std::unique_ptr<kernel::KernelBuilder> & idb) {
359    mCurrentMethod = getInitFunction(idb->getModule());
360    idb->SetInsertPoint(BasicBlock::Create(idb->getContext(), "entry", mCurrentMethod));
361    Function::arg_iterator args = mCurrentMethod->arg_begin();
362    setInstance(&*(args++));
363    idb->CreateStore(ConstantAggregateZero::get(mKernelStateType), getInstance());
364    for (const auto & binding : mScalarInputs) {
365        idb->setScalarField(binding.getName(), &*(args++));
366    }
367    for (const auto & binding : mStreamSetOutputs) {
368        idb->setConsumerLock(binding.getName(), &*(args++));
369    }
370    generateInitializeMethod(idb);
371    idb->CreateRetVoid();
372}
373
374/** ------------------------------------------------------------------------------------------------------------- *
375 * @brief callGenerateDoSegmentMethod
376 ** ------------------------------------------------------------------------------------------------------------- */
377inline void Kernel::callGenerateDoSegmentMethod(const std::unique_ptr<kernel::KernelBuilder> & idb) {
378    mCurrentMethod = getDoSegmentFunction(idb->getModule());
379    idb->SetInsertPoint(BasicBlock::Create(idb->getContext(), "entry", mCurrentMethod));
380    auto args = mCurrentMethod->arg_begin();
381    setInstance(&*(args++));
382    mIsFinal = &*(args++);
383    mAvailablePrincipleItemCount = nullptr;
384//    if (mHasPrincipleItemCount) {
385//        mAvailablePrincipleItemCount = &*(args++);
386//    }
387    const auto n = mStreamSetInputs.size();
388    mAvailableItemCount.resize(n, nullptr);
389    for (unsigned i = 0; i < n; i++) {
390//        const ProcessingRate & rate = mStreamSetInputs[i].getRate();
391//        Value * itemCount = nullptr;
392//        if (rate.isFixed()) {
393//            itemCount = mAvailablePrincipleItemCount;
394//            if (rate.getRate() != 1) {
395//                itemCount = idb->CreateMul(itemCount, ConstantInt::get(itemCount->getType(), rate.getRate()));
396//            }
397//        } else if (rate.isBounded() || rate.isUnknown()) {
398//            itemCount = &*(args++);
399//        } else if (rate.isRelative()) {
400//            for (unsigned j = 0; j < i; ++j) {
401//                if (mStreamSetInputs[j].getName() == rate.getReference()) {
402//                    itemCount = mAvailableItemCount[j];
403//                    break;
404//                }
405//            }
406//            if (LLVM_UNLIKELY(itemCount == nullptr)) {
407//                report_fatal_error(mStreamSetInputs[i].getName() + " is declared before " + rate.getReference());
408//            }
409//            if (rate.getNumerator() != 1) {
410//                itemCount = idb->CreateMul(itemCount, ConstantInt::get(itemCount->getType(), rate.getNumerator()));
411//            }
412//            if (rate.getDenominator() != 1) {
413//                itemCount = idb->CreateUDiv(itemCount, ConstantInt::get(itemCount->getType(), rate.getDenominator()));
414//            }
415//        }
416//        assert (itemCount);
417//        mAvailableItemCount[i] = itemCount;
418
419        assert (args != mCurrentMethod->arg_end());
420        mAvailableItemCount[i] = &*(args++);
421    }
422    assert (args == mCurrentMethod->arg_end());
423
424    generateKernelMethod(idb); // must be overridden by the Kernel subtype
425    mIsFinal = nullptr;
426    mAvailableItemCount.clear();
427    idb->CreateRetVoid();
428}
429
430
431/** ------------------------------------------------------------------------------------------------------------- *
432 * @brief callGenerateFinalizeMethod
433 ** ------------------------------------------------------------------------------------------------------------- */
434inline void Kernel::callGenerateFinalizeMethod(const std::unique_ptr<KernelBuilder> & idb) {
435    mCurrentMethod = getTerminateFunction(idb->getModule());
436    idb->SetInsertPoint(BasicBlock::Create(idb->getContext(), "entry", mCurrentMethod));
437    auto args = mCurrentMethod->arg_begin();
438    setInstance(&*(args++));
439    generateFinalizeMethod(idb); // may be overridden by the Kernel subtype
440    const auto n = mScalarOutputs.size();
441    if (n == 0) {
442        idb->CreateRetVoid();
443    } else {
444        Value * outputs[n];
445        for (unsigned i = 0; i < n; ++i) {
446            outputs[i] = idb->getScalarField(mScalarOutputs[i].getName());
447        }
448        if (n == 1) {
449            idb->CreateRet(outputs[0]);
450        } else {
451            idb->CreateAggregateRet(outputs, n);
452        }
453    }
454}
455
456
457/** ------------------------------------------------------------------------------------------------------------- *
458 * @brief getScalarIndex
459 ** ------------------------------------------------------------------------------------------------------------- */
460unsigned Kernel::getScalarIndex(const std::string & name) const {
461    const auto f = mKernelMap.find(name);
462    if (LLVM_UNLIKELY(f == mKernelMap.end())) {
463        assert (false);
464        report_fatal_error(getName() + " does not contain scalar: " + name);
465    }
466    return f->second;
467}
468
469
470/** ------------------------------------------------------------------------------------------------------------- *
471 * @brief createInstance
472 ** ------------------------------------------------------------------------------------------------------------- */
473Value * Kernel::createInstance(const std::unique_ptr<KernelBuilder> & idb) {
474    assert ("KernelBuilder does not have a valid IDISA Builder" && idb);
475    if (LLVM_UNLIKELY(mKernelStateType == nullptr)) {
476        report_fatal_error("Cannot instantiate " + getName() + " before calling prepareKernel()");
477    }
478    setInstance(idb->CreateCacheAlignedAlloca(mKernelStateType));
479    return getInstance();
480}
481
482
483/** ------------------------------------------------------------------------------------------------------------- *
484 * @brief initializeInstance
485 ** ------------------------------------------------------------------------------------------------------------- */
486void Kernel::initializeInstance(const std::unique_ptr<KernelBuilder> & idb) {
487    assert ("KernelBuilder does not have a valid IDISA Builder" && idb);
488    if (LLVM_UNLIKELY(getInstance() == nullptr)) {
489        report_fatal_error("Cannot initialize " + getName() + " before calling createInstance()");
490    }
491    std::vector<Value *> args;
492    args.reserve(1 + mInitialArguments.size() + mStreamSetInputBuffers.size() + (mStreamSetOutputBuffers.size() * 2));
493    args.push_back(getInstance());
494    for (unsigned i = 0; i < mInitialArguments.size(); ++i) {
495        Value * arg = mInitialArguments[i];
496        if (LLVM_UNLIKELY(arg == nullptr)) {
497            report_fatal_error(getName() + ": initial argument " + std::to_string(i)
498                               + " cannot be null when calling createInstance()");
499        }
500        args.push_back(arg);
501    }
502    for (unsigned i = 0; i < mStreamSetInputBuffers.size(); ++i) {
503        assert (mStreamSetInputBuffers[i]);
504        Value * arg = mStreamSetInputBuffers[i]->getStreamSetHandle();
505        if (LLVM_UNLIKELY(arg == nullptr)) {
506            report_fatal_error(getName() + ": input stream set " + std::to_string(i)
507                               + " was not allocated prior to calling createInstance()");
508        }
509        args.push_back(arg);
510    }
511    assert (mStreamSetInputs.size() == mStreamSetInputBuffers.size());
512    for (unsigned i = 0; i < mStreamSetOutputBuffers.size(); ++i) {
513        assert (mStreamSetOutputBuffers[i]);
514        Value * arg = mStreamSetOutputBuffers[i]->getStreamSetHandle();
515        if (LLVM_UNLIKELY(arg == nullptr)) {
516            report_fatal_error(getName() + ": output stream set " + std::to_string(i)
517                               + " was not allocated prior to calling createInstance()");
518        }
519        args.push_back(arg);
520    }
521    assert (mStreamSetOutputs.size() == mStreamSetOutputBuffers.size());
522    IntegerType * const sizeTy = idb->getSizeTy();
523    PointerType * const sizePtrTy = sizeTy->getPointerTo();
524    PointerType * const sizePtrPtrTy = sizePtrTy->getPointerTo();
525    StructType * const consumerTy = StructType::get(sizeTy, sizePtrPtrTy, nullptr);
526    for (unsigned i = 0; i < mStreamSetOutputBuffers.size(); ++i) {
527        const auto output = mStreamSetOutputBuffers[i];
528        const auto & consumers = output->getConsumers();
529        const auto n = consumers.size();
530        AllocaInst * const outputConsumers = idb->CreateAlloca(consumerTy);
531        Value * const consumerSegNoArray = idb->CreateAlloca(ArrayType::get(sizePtrTy, n));
532        for (unsigned i = 0; i < n; ++i) {
533            Kernel * const consumer = consumers[i];
534            assert ("all instances must be created prior to initialization of any instance" && consumer->getInstance());
535            idb->setKernel(consumer);
536            Value * const segmentNoPtr = idb->getScalarFieldPtr(LOGICAL_SEGMENT_NO_SCALAR);
537            idb->CreateStore(segmentNoPtr, idb->CreateGEP(consumerSegNoArray, { idb->getInt32(0), idb->getInt32(i) }));
538        }
539        idb->setKernel(this);
540        Value * const consumerCountPtr = idb->CreateGEP(outputConsumers, {idb->getInt32(0), idb->getInt32(0)});
541        idb->CreateStore(idb->getSize(n), consumerCountPtr);
542        Value * const consumerSegNoArrayPtr = idb->CreateGEP(outputConsumers, {idb->getInt32(0), idb->getInt32(1)});
543        idb->CreateStore(idb->CreatePointerCast(consumerSegNoArray, sizePtrPtrTy), consumerSegNoArrayPtr);
544        args.push_back(outputConsumers);
545    }
546    idb->CreateCall(getInitFunction(idb->getModule()), args);
547}
548
549/** ------------------------------------------------------------------------------------------------------------- *
550 * @brief finalizeInstance
551 ** ------------------------------------------------------------------------------------------------------------- */
552void Kernel::finalizeInstance(const std::unique_ptr<KernelBuilder> & idb) {
553    assert ("KernelBuilder does not have a valid IDISA Builder" && idb);
554    mOutputScalarResult = idb->CreateCall(getTerminateFunction(idb->getModule()), { getInstance() });
555}
556
557/** ------------------------------------------------------------------------------------------------------------- *
558 * @brief getStreamPort
559 ** ------------------------------------------------------------------------------------------------------------- */
560Kernel::StreamPort Kernel::getStreamPort(const std::string & name) const {
561    const auto f = mStreamMap.find(name);
562    if (LLVM_UNLIKELY(f == mStreamMap.end())) {
563        report_fatal_error(getName() + " does not contain stream set " + name);
564    }
565    return f->second;
566}
567
568/** ------------------------------------------------------------------------------------------------------------- *
569 * @brief generateKernelMethod
570 ** ------------------------------------------------------------------------------------------------------------- */
571void SegmentOrientedKernel::generateKernelMethod(const std::unique_ptr<KernelBuilder> & b) {
572
573    Constant * const log2BlockWidth = b->getSize(std::log2(b->getBitBlockWidth()));
574
575    const auto inputSetCount = mStreamSetInputs.size();
576    mStreamSetInputBufferPtr.resize(inputSetCount);
577    for (unsigned i = 0; i < inputSetCount; ++i) {
578        const auto & name = mStreamSetInputs[i].getName();
579        Value * ic = b->getProcessedItemCount(name);
580        Value * const blockIndex = b->CreateLShr(ic, log2BlockWidth);
581        mStreamSetInputBufferPtr[i] = b->getInputStreamPtr(name, blockIndex);
582    }
583
584    const auto outputSetCount = mStreamSetOutputs.size();
585    mStreamSetOutputBufferPtr.resize(outputSetCount);
586    for (unsigned i = 0; i < outputSetCount; ++i) {
587        const auto & name = mStreamSetOutputs[i].getName();
588        Value * ic = b->getProducedItemCount(name);
589        Value * const blockIndex = b->CreateLShr(ic, log2BlockWidth);
590        mStreamSetOutputBufferPtr[i] = b->getOutputStreamPtr(name, blockIndex);
591    }
592
593    generateDoSegmentMethod(b);
594
595}
596
597/** ------------------------------------------------------------------------------------------------------------- *
598 * @brief generateKernelMethod
599 ** ------------------------------------------------------------------------------------------------------------- */
600void MultiBlockKernel::generateKernelMethod(const std::unique_ptr<KernelBuilder> & kb) {
601
602    const auto inputSetCount = mStreamSetInputs.size();
603    const auto outputSetCount = mStreamSetOutputs.size();
604    const auto totalSetCount = inputSetCount + outputSetCount;
605
606    // Scan through and see if any of our input streams is marked as the principle
607
608    bool hasPrinciple = false;
609    unsigned principleInput = 0;
610
611    for (unsigned i = 0; i < inputSetCount; i++) {
612        for (const auto attr : mStreamSetInputs[i].getAttributes()) {
613            if (attr.isPrinciple()) {
614                hasPrinciple = true;
615                principleInput = i;
616                break;
617            }
618        }
619    }
620
621    // Now we iteratively process these blocks using the doMultiBlock method.
622    // In each iteration, we check how many linearly accessible / writable
623    // items can be processed with our current input / output buffers. If we
624    // cannot support an full stride, we check whether (a) there is enough
625    // input data to process but it is not linearly accessible, in which case
626    // we move the data into temporary buffers or (b) there is not enough data
627    // to process, in which case we abort unless IsFinal was set.
628
629    // Now proceed with creation of the doSegment method.
630    BasicBlock * const doSegmentLoop = kb->CreateBasicBlock("DoSegmentLoop");
631    kb->CreateBr(doSegmentLoop);
632
633    /// DO SEGMENT LOOP
634
635    kb->SetInsertPoint(doSegmentLoop);
636
637    // For each input buffer, determine the processedItemCount, the block pointer for the
638    // buffer block containing the next item, and the number of linearly available items.
639
640    Value * processedItemCount[inputSetCount];
641    Value * baseInputBuffer[inputSetCount];
642    Value * unprocessed[inputSetCount];
643    Value * linearlyAvailable[inputSetCount];
644    Value * readableStrides[inputSetCount];
645
646    Constant * const log2BlockWidth = kb->getSize(std::log2(kb->getBitBlockWidth()));
647
648    Value * numOfStrides = nullptr;
649
650    for (unsigned i = 0; i < inputSetCount; i++) {
651        const auto name = mStreamSetInputs[i].getName();
652        const ProcessingRate & rate = mStreamSetInputs[i].getRate();
653
654        processedItemCount[i] = kb->getProcessedItemCount(name);
655
656        assert (processedItemCount[i]->getType() == mAvailableItemCount[i]->getType());
657
658        Value * const blockIndex = kb->CreateLShr(processedItemCount[i], log2BlockWidth);
659        baseInputBuffer[i] = kb->getInputStreamPtr(name, blockIndex);
660
661        if (codegen::EnableAsserts) {
662            kb->CreateAssert(kb->CreateICmpUGE(mAvailableItemCount[i], processedItemCount[i]),
663                             "Processed item count cannot exceed the available item count");
664        }
665
666        unprocessed[i] = kb->CreateSub(mAvailableItemCount[i], processedItemCount[i]);
667
668        //kb->CallPrintInt(getName() + "_" + name + "_unprocessed", unprocessed[i]);
669
670        // INVESTIGATE: If the input rate of this stream is constant and known a priori, we could
671        // avoid checking whether it is linearly accessible. Should we have an attribute for this?
672
673        linearlyAvailable[i] = kb->getLinearlyAccessibleItems(name, processedItemCount[i], unprocessed[i]);
674
675        //kb->CallPrintInt(getName() + "_" + name + "_linearlyAvailable", linearlyAvailable[i]);
676
677        readableStrides[i] = nullptr;
678
679        if (rate.isFixed() || rate.isBounded()) {
680            Constant * const maxStrideSize = kb->getSize(rate.getUpperBound() * mStride);
681            readableStrides[i] = kb->CreateUDiv(linearlyAvailable[i], maxStrideSize);
682            if (numOfStrides) {
683                numOfStrides = kb->CreateUMin(numOfStrides, readableStrides[i]);
684            } else {
685                numOfStrides = readableStrides[i];
686            }
687        }
688    }
689
690    //kb->CallPrintInt(getName() + "_numOfStrides", numOfStrides);
691
692    // Now determine the linearly writeable blocks, based on available blocks reduced
693    // by limitations of output buffer space.
694
695    Value * producedItemCount[outputSetCount];
696    Value * baseOutputBuffer[outputSetCount];
697    Value * writableStrides[outputSetCount];
698    Value * linearlyWritable[outputSetCount];
699
700    for (unsigned i = 0; i < outputSetCount; i++) {
701        const auto & name = mStreamSetOutputs[i].getName();
702        const ProcessingRate & rate = mStreamSetOutputs[i].getRate();
703        producedItemCount[i] = kb->getProducedItemCount(name);
704
705        //kb->CallPrintInt(getName() + "_" + name + "_producedItemCount", producedItemCount[i]);
706
707        Value * const blockIndex = kb->CreateLShr(producedItemCount[i], log2BlockWidth);
708        baseOutputBuffer[i] = kb->getOutputStreamPtr(name, blockIndex);
709        linearlyWritable[i] = nullptr;
710        writableStrides[i] = nullptr;
711        if (rate.isFixed() || rate.isBounded()) {
712            linearlyWritable[i] = kb->getLinearlyWritableItems(name, producedItemCount[i]);
713
714            //kb->CallPrintInt(getName() + "_" + name + "_linearlyWritable", linearlyWritable[i]);
715
716            Constant * const maxStrideSize = kb->getSize(rate.getUpperBound() * mStride);
717            writableStrides[i] = kb->CreateUDiv(linearlyWritable[i], maxStrideSize);
718            if (numOfStrides) {
719                numOfStrides = kb->CreateUMin(numOfStrides, writableStrides[i]);
720            } else {
721                numOfStrides = writableStrides[i];
722            }
723        }
724    }
725
726    //kb->CallPrintInt(getName() + "_numOfStrides'", numOfStrides);
727
728    for (unsigned i = 0; i < inputSetCount; i++) {
729        const ProcessingRate & rate = mStreamSetInputs[i].getRate();
730        if (rate.isFixed()) {
731            mAvailableItemCount[i] = kb->CreateMul(numOfStrides, kb->getSize(rate.getRate() * mStride));
732        } else {
733            mAvailableItemCount[i] = linearlyAvailable[i];
734        }
735
736        //kb->CallPrintInt(getName() + "_" + mStreamSetInputs[i].getName() + "_avail", mAvailableItemCount[i]);
737    }
738
739    // Define and allocate the temporary buffer area.
740    Type * tempBuffers[totalSetCount];
741    for (unsigned i = 0; i < inputSetCount; ++i) {
742        Type * bufType = baseInputBuffer[i]->getType()->getPointerElementType();
743        assert (baseInputBuffer[i]->getType()->getPointerAddressSpace() == 0);
744        const ProcessingRate & rate = mStreamSetInputs[i].getRate();
745        unsigned count = 0;
746        if (rate.isFixed()) {
747            count = rate.getRate();
748        } else if (rate.isBounded()) {
749            count = rate.getUpperBound() + 2;
750        }
751        tempBuffers[i] = ArrayType::get(bufType, count);
752    }
753    for (unsigned i = 0; i < outputSetCount; i++) {
754        Type * const bufType = baseOutputBuffer[i]->getType()->getPointerElementType();
755        assert (baseOutputBuffer[i]->getType()->getPointerAddressSpace() == 0);
756        const ProcessingRate & rate = mStreamSetOutputs[i].getRate();
757        unsigned count = 0;
758        if (rate.isFixed()) {
759            count = rate.getRate();
760        } else if (rate.isBounded()) {
761            count = rate.getUpperBound() + 2;
762        }
763        tempBuffers[i + inputSetCount] = ArrayType::get(bufType, count);
764    }
765
766    Type * const tempParameterStructType = StructType::create(kb->getContext(), ArrayRef<Type *>(tempBuffers, totalSetCount));
767
768    Value * const tempBufferArea = kb->CreateCacheAlignedAlloca(tempParameterStructType);
769
770    BasicBlock * const temporaryBufferCheck = kb->CreateBasicBlock("temporaryBufferCheck");
771    BasicBlock * const doMultiBlock = kb->CreateBasicBlock("doMultiBlock");
772    BasicBlock * const copyToTemporaryBuffers = kb->CreateBasicBlock("copyToTemporaryBuffers");
773    BasicBlock * const segmentDone = kb->CreateBasicBlock("segmentDone");
774
775    Value * const hasFullStride = numOfStrides ? kb->CreateICmpNE(numOfStrides, kb->getSize(0)) : kb->getTrue();
776    kb->CreateCondBr(hasFullStride, doMultiBlock, temporaryBufferCheck);
777
778    // We use temporary buffers in 3 different cases that preclude full stride processing.
779
780    //  (a) One or more input buffers does not have a sufficient number of input items linearly available.
781    //  (b) One or more output buffers does not have sufficient linearly available buffer space.
782    //  (c) We have processed all the full strides of input and only the final block remains.
783
784    kb->SetInsertPoint(temporaryBufferCheck);
785
786    // Even if we copy the input data into a linear arrays, is there enough data to perform this stride?
787    // If not, proceed only if this is our final block.
788    Value * hasFullFragmentedStride = nullptr;
789    for (unsigned i = 0; i < inputSetCount; i++) {
790        const ProcessingRate & r = mStreamSetInputs[i].getRate();
791        if (r.isBounded() || (r.isUnknown() && r.getLowerBound() > 0)) {
792            const auto l = r.isBounded() ? r.getUpperBound() : r.getLowerBound();
793            Constant * const strideSize = kb->getSize(l * mStride);
794            Value * enoughAvail = kb->CreateICmpUGE(unprocessed[i], strideSize);
795            if (hasFullFragmentedStride) {
796                hasFullFragmentedStride = kb->CreateAnd(hasFullFragmentedStride, enoughAvail);
797            } else {
798                hasFullFragmentedStride = enoughAvail;
799            }
800        }
801    }
802
803    Value * hasFragmentedOrFinalStride = nullptr;
804    if (hasFullFragmentedStride) {
805        hasFragmentedOrFinalStride = kb->CreateOr(hasFullFragmentedStride, mIsFinal);
806        // Although this might be the final segment, we may have a full fragmented stride to process prior
807        // to the actual final stride.
808        mIsFinal = kb->CreateAnd(mIsFinal, kb->CreateNot(hasFullFragmentedStride));
809    } else {
810        hasFragmentedOrFinalStride = mIsFinal;
811    }
812    kb->CreateCondBr(hasFragmentedOrFinalStride, copyToTemporaryBuffers, segmentDone);
813
814    /// COPY TO TEMPORARY BUFFERS
815    kb->SetInsertPoint(copyToTemporaryBuffers);
816
817    kb->CreateAlignedStore(Constant::getNullValue(tempParameterStructType), tempBufferArea, kb->getCacheAlignment());
818
819    // For each input and output buffer, copy over necessary data starting from the last block boundary.
820
821    Value * temporaryInputBuffer[inputSetCount];
822    Value * temporaryAvailable[inputSetCount];
823
824    for (unsigned i = 0; i < inputSetCount; i++) {
825        temporaryInputBuffer[i] = baseInputBuffer[i];
826        if (readableStrides[i]) {
827            const auto name = mStreamSetInputs[i].getName();
828            const ProcessingRate & rate = mStreamSetInputs[i].getRate();
829            assert (rate.getUpperBound() > 0);
830            Constant * const maxStrideSize = kb->getSize(rate.getUpperBound() * mStride);
831            temporaryAvailable[i] = kb->CreateUMin(unprocessed[i], maxStrideSize);
832
833            BasicBlock * entry = kb->GetInsertBlock();
834            BasicBlock * copy = kb->CreateBasicBlock(name + "Copy");
835            BasicBlock * resume = kb->CreateBasicBlock(name + "ResumeCopy");
836            Value * const test = kb->CreateOr(kb->CreateICmpNE(readableStrides[i], kb->getSize(0)), mIsFinal);
837            kb->CreateCondBr(test, resume, copy);
838
839            kb->SetInsertPoint(copy);
840            Value * const tempBufferPtr = kb->CreateGEP(tempBufferArea, {kb->getInt32(0), kb->getInt32(i), kb->getInt32(0)});
841            assert (tempBufferPtr->getType() == baseInputBuffer[i]->getType());
842            Value * const neededItems = linearlyAvailable[i];
843            Value * const bytesCopied = kb->copy(name, tempBufferPtr, baseInputBuffer[i], neededItems);
844            Value * const nextInputPtr = kb->getRawInputPointer(name, kb->getSize(0));
845            Value * const remaining = kb->CreateSub(temporaryAvailable[i], neededItems);
846            Value * nextBufPtr = kb->CreatePointerCast(tempBufferPtr, kb->getInt8PtrTy());
847            nextBufPtr = kb->CreateGEP(nextBufPtr, bytesCopied);
848            kb->copy(name, nextBufPtr, nextInputPtr, remaining);
849
850            kb->CreateBr(resume);
851
852            kb->SetInsertPoint(resume);
853            PHINode * bufferPtr = kb->CreatePHI(baseInputBuffer[i]->getType(), 2);
854            bufferPtr->addIncoming(baseInputBuffer[i], entry);
855            bufferPtr->addIncoming(tempBufferPtr, copy);
856            temporaryInputBuffer[i] = bufferPtr;
857        }
858    }
859
860    Value * temporaryOutputBuffer[outputSetCount];
861    for (unsigned i = 0; i < outputSetCount; i++) {
862        temporaryOutputBuffer[i] = baseOutputBuffer[i];
863        if (writableStrides[i]) {
864            const auto name = mStreamSetOutputs[i].getName();
865
866            BasicBlock * const entry = kb->GetInsertBlock();
867            BasicBlock * const copy = kb->CreateBasicBlock(name + "Copy");
868            BasicBlock * const resume = kb->CreateBasicBlock(name + "ResumeCopy");
869
870            Value * const test = kb->CreateOr(kb->CreateICmpNE(writableStrides[i], kb->getSize(0)), mIsFinal);
871            kb->CreateCondBr(test, resume, copy);
872
873            kb->SetInsertPoint(copy);
874            Value * const tempBufferPtr = kb->CreateGEP(tempBufferArea,  {kb->getInt32(0), kb->getInt32(inputSetCount + i), kb->getInt32(0)});
875            assert (tempBufferPtr->getType() == baseOutputBuffer[i]->getType());
876            Value * const itemsToCopy = kb->CreateAnd(producedItemCount[i], kb->getSize(kb->getBitBlockWidth() - 1));
877            kb->copy(name, tempBufferPtr, baseOutputBuffer[i], itemsToCopy);
878            kb->CreateBr(resume);
879
880            kb->SetInsertPoint(resume);
881            PHINode * bufferPtr = kb->CreatePHI(tempBufferPtr->getType(), 2);
882            bufferPtr->addIncoming(baseOutputBuffer[i], entry);
883            bufferPtr->addIncoming(tempBufferPtr, copy);
884            temporaryOutputBuffer[i] = bufferPtr;
885        }
886    }
887
888    kb->CreateBr(doMultiBlock);
889    BasicBlock * const usingTemporaryBuffers = kb->GetInsertBlock();
890    doMultiBlock->moveAfter(usingTemporaryBuffers);
891
892    /// DO MULTI BLOCK
893
894    //  At this point we have verified the availability of one or more blocks of input data and output buffer space for all stream sets.
895    //  Now prepare the doMultiBlock call.
896    kb->SetInsertPoint(doMultiBlock);
897
898    PHINode * const isFinal = kb->CreatePHI(mIsFinal->getType(), 2);
899    isFinal->addIncoming(kb->getFalse(), doSegmentLoop);
900    isFinal->addIncoming(mIsFinal, usingTemporaryBuffers);
901    mIsFinal = isFinal;
902
903    mStreamSetInputBufferPtr.resize(inputSetCount);
904    for (unsigned i = 0; i < inputSetCount; ++i) {
905        assert (baseInputBuffer[i] && temporaryInputBuffer[i]);
906        if (baseInputBuffer[i] != temporaryInputBuffer[i]) {
907            PHINode * const avail = kb->CreatePHI(kb->getSizeTy(), 2);
908            avail->addIncoming(mAvailableItemCount[i], doSegmentLoop);
909            avail->addIncoming(temporaryAvailable[i], usingTemporaryBuffers);
910            mAvailableItemCount[i] = avail;
911            PHINode * const bufferPtr = kb->CreatePHI(baseInputBuffer[i]->getType(), 2);
912            bufferPtr->addIncoming(baseInputBuffer[i], doSegmentLoop);
913            assert (baseInputBuffer[i]->getType() == temporaryInputBuffer[i]->getType());
914            bufferPtr->addIncoming(temporaryInputBuffer[i], usingTemporaryBuffers);
915            temporaryInputBuffer[i] = bufferPtr;
916        }
917        mStreamSetInputBufferPtr[i] = temporaryInputBuffer[i];
918    }
919
920    mStreamSetOutputBufferPtr.resize(outputSetCount);
921    for (unsigned i = 0; i < outputSetCount; ++i) {
922        assert (baseOutputBuffer[i] && temporaryOutputBuffer[i]);
923        if (baseOutputBuffer[i] != temporaryOutputBuffer[i]) {
924            PHINode * const bufferPtr = kb->CreatePHI(baseOutputBuffer[i]->getType(), 2);
925            bufferPtr->addIncoming(baseOutputBuffer[i], doSegmentLoop);
926            assert (baseOutputBuffer[i]->getType() == temporaryOutputBuffer[i]->getType());
927            bufferPtr->addIncoming(temporaryOutputBuffer[i], usingTemporaryBuffers);
928            temporaryOutputBuffer[i] = bufferPtr;
929        }
930        mStreamSetOutputBufferPtr[i] = temporaryOutputBuffer[i];
931    }
932
933    // Now use the generateMultiBlockLogic method of the MultiBlockKernelBuilder subtype to
934    // provide the required multi-block kernel logic.
935    generateMultiBlockLogic(kb, numOfStrides);
936
937    // If we have no fixed rate inputs, we won't know when we're done parsing until we test
938    // whether any input data was processed.
939    bool mayMakeNoProgress = true;
940
941    // Update the processed item count of any Fixed input or output stream. While doing so, also
942    // calculate the LCM of their rates. The LCM is used to calculate the final item counts.
943
944    unsigned rateLCM = 1;
945
946    for (unsigned i = 0; i < inputSetCount; ++i) {
947        const ProcessingRate & rate = mStreamSetInputs[i].getRate();
948        if (rate.isFixed()) {
949            mayMakeNoProgress = false;
950            rateLCM = lcm(rateLCM, rate.getRate());
951            Value * const processed = mAvailableItemCount[i]; // kb->CreateMul(numOfStrides, kb->getSize(mStride * rate.getRate()));
952            Value * const ic = kb->CreateAdd(processedItemCount[i], processed);
953            kb->setProcessedItemCount(mStreamSetInputs[i].getName(), ic);
954        }
955    }
956
957    for (unsigned i = 0; i < outputSetCount; ++i) {
958        const ProcessingRate & rate = mStreamSetOutputs[i].getRate();
959        if (rate.isFixed()) {
960            rateLCM = lcm(rateLCM, rate.getRate());
961            Value * const produced = kb->CreateMul(numOfStrides, kb->getSize(mStride * rate.getRate()));
962            Value * const ic = kb->CreateAdd(producedItemCount[i], produced);
963            kb->setProducedItemCount(mStreamSetOutputs[i].getName(), ic);
964        }
965    }
966
967    BasicBlock * const finalStrideCheck = kb->CreateBasicBlock("finalStrideCheck");
968    BasicBlock * const finalStrideAdjustment = kb->CreateBasicBlock("finalStrideAdjustment");
969    BasicBlock * const standardCopyBack = kb->CreateBasicBlock("standardCopyBack");
970    BasicBlock * const temporaryBufferCopyBack = kb->CreateBasicBlock("temporaryBufferCopyBack");
971
972    kb->CreateLikelyCondBr(hasFullStride, standardCopyBack, finalStrideCheck);
973
974
975    /// FINAL STRIDE CHECK
976    kb->SetInsertPoint(finalStrideCheck);
977    kb->CreateUnlikelyCondBr(mIsFinal, finalStrideAdjustment, temporaryBufferCopyBack);
978
979    /// FINAL STRIDE ADJUSTMENT
980    kb->SetInsertPoint(finalStrideAdjustment);
981
982    // If this is our final stride, adjust the Fixed output item counts. The main loop assumes that
983    // the ITEM COUNT % FIXED RATE = 0 for all Fixed Input and Output streams. We correct that here
984    // to calculate them based on the actual input item counts.
985
986    // NOTE: This appears overly complex to avoid an integer overflow without reducing the maximum
987    // integer size. For each Fixed output stream, this calculates:
988
989    //       CEILING(MIN(Total Available Item Count / Fixed Input Rate) * Fixed Output Rate)
990
991    Value * basePreviouslyProcessedItemCount = nullptr;
992    Value * scaledInverseOfStrideItemCount = nullptr;
993
994    for (unsigned i = 0; i < inputSetCount; ++i) {
995        const ProcessingRate & r = mStreamSetInputs[i].getRate();
996        if (r.isFixed()) {
997            assert (rateLCM % r.getRate() == 0);
998            Value * const a = kb->CreateMul(mAvailableItemCount[i], kb->getSize(rateLCM / r.getRate())); // unprocessed
999            Value * const p = kb->CreateUDiv(processedItemCount[i], kb->getSize(r.getRate()));
1000            if (scaledInverseOfStrideItemCount) {
1001                scaledInverseOfStrideItemCount = kb->CreateUMin(scaledInverseOfStrideItemCount, a);
1002                basePreviouslyProcessedItemCount = kb->CreateUMin(basePreviouslyProcessedItemCount, p);
1003            } else {
1004                scaledInverseOfStrideItemCount = a;
1005                basePreviouslyProcessedItemCount = p;
1006            }
1007        }
1008//        const auto name = mStreamSetInputs[i].getName();
1009//        Value * const processed = kb->CreateAdd(processedItemCount[i], unprocessed[i]);
1010//        kb->setProcessedItemCount(name, processed);
1011    }
1012
1013    for (unsigned i = 0; i < outputSetCount; ++i) {
1014        const auto name = mStreamSetOutputs[i].getName();
1015        const ProcessingRate & r = mStreamSetOutputs[i].getRate();
1016        Value * produced = nullptr;
1017        if (r.isFixed()) {
1018            assert (rateLCM % r.getRate() == 0);
1019            assert (basePreviouslyProcessedItemCount && scaledInverseOfStrideItemCount);
1020            Value * const p = kb->CreateMul(basePreviouslyProcessedItemCount, kb->getSize(r.getRate()));
1021            Value * const ic = kb->CreateUDivCeil(scaledInverseOfStrideItemCount, kb->getSize(rateLCM / r.getRate()));
1022            produced = kb->CreateAdd(p, ic);
1023        } else { // check if we have an attribute; if so, get the current produced count and adjust it
1024            bool noAttributes = true;
1025            for (const Attribute & attr : mStreamSetOutputs[i].getAttributes()) {
1026                if (attr.isAdd() || attr.isRoundUpTo()) {
1027                    noAttributes = false;
1028                    break;
1029                }
1030            }
1031            if (noAttributes) {
1032                continue;
1033            }
1034            produced = kb->getProducedItemCount(name);
1035        }
1036        for (const Attribute & attr : mStreamSetOutputs[i].getAttributes()) {
1037            if (attr.isAdd()) {
1038                produced = kb->CreateAdd(produced, kb->getSize(attr.getAmount()));
1039            } else if (attr.isRoundUpTo()) {
1040                produced = kb->CreateRoundUp(produced, kb->getSize(attr.getAmount()));
1041            }
1042        }
1043        kb->setProducedItemCount(name, produced);
1044    }
1045
1046    kb->CreateBr(temporaryBufferCopyBack);
1047
1048    /// TEMPORARY BUFFER COPY BACK
1049    kb->SetInsertPoint(temporaryBufferCopyBack);
1050
1051    // Copy back data to the actual output buffers.
1052    for (unsigned i = 0; i < outputSetCount; i++) {
1053
1054        if (baseOutputBuffer[i] != temporaryOutputBuffer[i]) {
1055
1056            const auto name = mStreamSetOutputs[i].getName();
1057
1058            BasicBlock * const copy = kb->CreateBasicBlock(name + "CopyBack");
1059            BasicBlock * const resume = kb->CreateBasicBlock(name + "ResumeCopyBack");
1060            Value * const usedTemporary = kb->CreateICmpNE(temporaryOutputBuffer[i], baseOutputBuffer[i]);
1061
1062            // If we used a temporary buffer ...
1063            kb->CreateCondBr(usedTemporary, copy, resume);
1064
1065            kb->SetInsertPoint(copy);
1066            Value * bytesCopied = kb->copy(name, baseOutputBuffer[i], temporaryOutputBuffer[i], linearlyWritable[i]);
1067            Value * nextOutputPtr = kb->getRawOutputPointer(name, kb->getSize(0));
1068            Value * producedCount = kb->getProducedItemCount(name);
1069
1070            Value * remaining = kb->CreateSub(producedCount, linearlyWritable[i]);
1071            Value * nextBufPtr = kb->CreatePointerCast(temporaryOutputBuffer[i], kb->getInt8PtrTy());
1072            nextBufPtr = kb->CreateGEP(nextBufPtr, bytesCopied);
1073
1074            kb->copy(name, nextOutputPtr, nextBufPtr, remaining);
1075            kb->CreateBr(resume);
1076
1077            kb->SetInsertPoint(resume);
1078        }
1079    }
1080
1081    //  We've dealt with the partial block processing and copied information back into the
1082    //  actual buffers.  If this isn't the final block, loop back for more multiblock processing.
1083    BasicBlock * setTermination = nullptr;
1084    if (hasNoTerminateAttribute()) {
1085        kb->CreateCondBr(mIsFinal, segmentDone, standardCopyBack);
1086    } else {
1087        setTermination = kb->CreateBasicBlock("setTermination");
1088        kb->CreateCondBr(mIsFinal, setTermination, standardCopyBack);
1089    }
1090
1091    /// STANDARD COPY BACK
1092    kb->SetInsertPoint(standardCopyBack);
1093
1094    // Do copybacks if necessary.
1095    for (unsigned i = 0; i < outputSetCount; i++) {
1096        if (mStreamSetOutputBuffers[i]->supportsCopyBack()) {
1097            const auto name = mStreamSetOutputs[i].getName();
1098            Value * newProduced = kb->getProducedItemCount(name);
1099            kb->CreateCopyBack(name, producedItemCount[i], newProduced);
1100        }
1101    }
1102
1103    // If it is possible to make no progress, verify we processed some of the input. If we haven't,
1104    // we're finished this segment.
1105    if (mayMakeNoProgress) {
1106        Value * madeProgress = nullptr;
1107        for (unsigned i = 0; i < inputSetCount; ++i) {
1108            Value * const processed = kb->getProcessedItemCount(mStreamSetInputs[i].getName());
1109            Value * const progress = kb->CreateICmpNE(processed, processedItemCount[i]);
1110            if (madeProgress) {
1111                madeProgress = kb->CreateOr(madeProgress, progress);
1112            } else {
1113                madeProgress = progress;
1114            }
1115        }
1116        assert (madeProgress);
1117        kb->CreateCondBr(madeProgress, doSegmentLoop, segmentDone);
1118    } else {
1119        kb->CreateBr(doSegmentLoop);
1120    }
1121
1122    if (hasNoTerminateAttribute()) {
1123        segmentDone->moveAfter(kb->GetInsertBlock());
1124    } else {
1125        /// SET TERMINATION
1126        setTermination->moveAfter(kb->GetInsertBlock());
1127        kb->SetInsertPoint(setTermination);
1128        kb->setTerminationSignal();
1129        kb->CreateBr(segmentDone);
1130        segmentDone->moveAfter(setTermination);
1131    }
1132
1133    kb->SetInsertPoint(segmentDone);
1134
1135}
1136
1137//bool MultiBlockKernel::requiresCopyBack(const ProcessingRate & rate) const {
1138//    if (rate.isBounded() || rate.isUnknown()) {
1139//        return true;
1140//    } else if (rate.isDirectlyRelative()) {
1141//        Port port; unsigned i;
1142//        std::tie(port, i) = getStreamPort(rate.getReference());
1143//        const auto & binding = (port == Port::Input) ? mStreamSetInputs[i] : mStreamSetOutputs[i];
1144//        return requiresCopyBack(binding.getRate());
1145//    }
1146//    return false;
1147//}
1148
1149//  The default doSegment method dispatches to the doBlock routine for
1150//  each block of the given number of blocksToDo, and then updates counts.
1151
1152void BlockOrientedKernel::generateMultiBlockLogic(const std::unique_ptr<KernelBuilder> & idb, llvm::Value * const numOfStrides) {
1153
1154    BasicBlock * const entryBlock = idb->GetInsertBlock();
1155    BasicBlock * const strideLoopCond = idb->CreateBasicBlock(getName() + "_strideLoopCond");
1156    mStrideLoopBody = idb->CreateBasicBlock(getName() + "_strideLoopBody");
1157    BasicBlock * const stridesDone = idb->CreateBasicBlock(getName() + "_stridesDone");
1158    BasicBlock * const doFinalBlock = idb->CreateBasicBlock(getName() + "_doFinalBlock");
1159    BasicBlock * const segmentDone = idb->CreateBasicBlock(getName() + "_segmentDone");
1160
1161    Value * baseTarget = nullptr;
1162    if (idb->supportsIndirectBr()) {
1163        baseTarget = idb->CreateSelect(mIsFinal, BlockAddress::get(doFinalBlock), BlockAddress::get(segmentDone));
1164    }
1165
1166    Constant * const log2BlockSize = idb->getSize(std::log2(idb->getBitBlockWidth()));
1167
1168    const auto inputSetCount = mStreamSetInputs.size();
1169    Value * baseProcessedIndex[inputSetCount];
1170    for (unsigned i = 0; i < inputSetCount; ++i) {
1171        const ProcessingRate & rate = mStreamSetInputs[i].getRate();
1172        if (rate.isFixed()) {
1173            baseProcessedIndex[i] = nullptr;
1174        } else {
1175            Value * ic = idb->getProcessedItemCount(mStreamSetInputs[i].getName());
1176            ic = idb->CreateLShr(ic, log2BlockSize);
1177            baseProcessedIndex[i] = ic;
1178        }
1179    }
1180
1181    const auto outputSetCount = mStreamSetOutputs.size();
1182    Value * baseProducedIndex[outputSetCount];
1183    for (unsigned i = 0; i < outputSetCount; ++i) {
1184        const ProcessingRate & rate = mStreamSetOutputs[i].getRate();
1185        if (rate.isFixed()) {
1186            baseProducedIndex[i] = nullptr;
1187        } else {
1188            Value * ic = idb->getProducedItemCount(mStreamSetOutputs[i].getName());
1189            ic = idb->CreateLShr(ic, log2BlockSize);
1190            baseProducedIndex[i] = ic;
1191        }
1192    }
1193
1194    Value * const numOfBlocksToProcess = idb->CreateMul(numOfStrides, idb->getSize(mStride / idb->getBitBlockWidth()));
1195
1196    idb->CreateBr(strideLoopCond);
1197
1198    /// BLOCK COND
1199
1200    idb->SetInsertPoint(strideLoopCond);
1201
1202    PHINode * branchTarget = nullptr;
1203    if (baseTarget) {
1204        branchTarget = idb->CreatePHI(baseTarget->getType(), 2, "branchTarget");
1205        branchTarget->addIncoming(baseTarget, entryBlock);
1206    }
1207
1208    PHINode * const blockIndex = idb->CreatePHI(idb->getSizeTy(), 2, "index");
1209    blockIndex->addIncoming(idb->getSize(0), entryBlock);
1210
1211    for (unsigned i = 0; i < inputSetCount; ++i) {
1212        Value * offset = blockIndex;
1213        if (baseProcessedIndex[i]) {
1214            offset = idb->getProcessedItemCount(mStreamSetInputs[i].getName());
1215            offset = idb->CreateLShr(offset, log2BlockSize);
1216            offset = idb->CreateSub(offset, baseProcessedIndex[i]);
1217        }
1218        mStreamSetInputBufferPtr[i] = idb->CreateGEP(mStreamSetInputBufferPtr[i], offset);
1219    }
1220
1221    for (unsigned i = 0; i < outputSetCount; ++i) {
1222        Value * offset = blockIndex;
1223        if (baseProducedIndex[i]) {
1224            offset = idb->getProducedItemCount(mStreamSetOutputs[i].getName());
1225            offset = idb->CreateLShr(offset, log2BlockSize);
1226            offset = idb->CreateSub(offset, baseProducedIndex[i]);
1227        }
1228        mStreamSetOutputBufferPtr[i] = idb->CreateGEP(mStreamSetOutputBufferPtr[i], offset);
1229    }
1230
1231    Value * const notDone = idb->CreateICmpULT(blockIndex, numOfBlocksToProcess);
1232    idb->CreateLikelyCondBr(notDone, mStrideLoopBody, stridesDone);
1233
1234    /// BLOCK BODY
1235
1236    idb->SetInsertPoint(mStrideLoopBody);
1237
1238    if (idb->supportsIndirectBr()) {
1239        mStrideLoopTarget = idb->CreatePHI(baseTarget->getType(), 2, "strideTarget");
1240        mStrideLoopTarget->addIncoming(branchTarget, strideLoopCond);
1241    }
1242
1243    /// GENERATE DO BLOCK METHOD
1244
1245    writeDoBlockMethod(idb);
1246
1247    BasicBlock * const bodyEnd = idb->GetInsertBlock();
1248    blockIndex->addIncoming(idb->CreateAdd(blockIndex, idb->getSize(1)), bodyEnd);
1249    if (branchTarget) {
1250        branchTarget->addIncoming(mStrideLoopTarget, bodyEnd);
1251    }
1252    idb->CreateBr(strideLoopCond);
1253
1254    stridesDone->moveAfter(bodyEnd);
1255
1256    /// STRIDE DONE
1257
1258    idb->SetInsertPoint(stridesDone);
1259
1260    // Now conditionally perform the final block processing depending on the doFinal parameter.
1261    if (branchTarget) {
1262        mStrideLoopBranch = idb->CreateIndirectBr(branchTarget, 3);
1263        mStrideLoopBranch->addDestination(doFinalBlock);
1264        mStrideLoopBranch->addDestination(segmentDone);
1265    } else {
1266        idb->CreateUnlikelyCondBr(mIsFinal, doFinalBlock, segmentDone);
1267    }
1268
1269    doFinalBlock->moveAfter(stridesDone);
1270
1271    idb->SetInsertPoint(doFinalBlock);
1272
1273    Value * remainingItems = nullptr;
1274    for (unsigned i = 0; i < inputSetCount; ++i) {
1275        const ProcessingRate & r = mStreamSetInputs[i].getRate();
1276        if (r.isFixed()) {
1277            Value * ic = idb->CreateUDiv(mAvailableItemCount[i], idb->getSize(r.getRate()));
1278            if (remainingItems) {
1279                remainingItems = idb->CreateUMax(remainingItems, ic);
1280            } else {
1281                remainingItems = ic;
1282            }
1283        }
1284    }
1285
1286    writeFinalBlockMethod(idb, remainingItems);
1287
1288    idb->CreateBr(segmentDone);
1289
1290    segmentDone->moveAfter(idb->GetInsertBlock());
1291
1292    idb->SetInsertPoint(segmentDone);
1293
1294    // Update the branch prediction metadata to indicate that the likely target will be segmentDone
1295    if (branchTarget) {
1296        MDBuilder mdb(idb->getContext());
1297        const auto destinations = mStrideLoopBranch->getNumDestinations();
1298        uint32_t weights[destinations];
1299        for (unsigned i = 0; i < destinations; ++i) {
1300            weights[i] = (mStrideLoopBranch->getDestination(i) == segmentDone) ? 100 : 1;
1301        }
1302        ArrayRef<uint32_t> bw(weights, destinations);
1303        mStrideLoopBranch->setMetadata(LLVMContext::MD_prof, mdb.createBranchWeights(bw));
1304    }
1305
1306}
1307
1308inline void BlockOrientedKernel::writeDoBlockMethod(const std::unique_ptr<KernelBuilder> & idb) {
1309
1310    Value * const self = getInstance();
1311    Function * const cp = mCurrentMethod;
1312    auto ip = idb->saveIP();
1313    std::vector<Value *> availableItemCount(0);
1314
1315    /// Check if the do block method is called and create the function if necessary
1316    if (!idb->supportsIndirectBr()) {
1317
1318        std::vector<Type *> params;
1319        params.reserve(1 + mAvailableItemCount.size());
1320        params.push_back(self->getType());
1321        for (Value * avail : mAvailableItemCount) {
1322            params.push_back(avail->getType());
1323        }
1324
1325        FunctionType * const type = FunctionType::get(idb->getVoidTy(), params, false);
1326        mCurrentMethod = Function::Create(type, GlobalValue::InternalLinkage, getName() + DO_BLOCK_SUFFIX, idb->getModule());
1327        mCurrentMethod->setCallingConv(CallingConv::C);
1328        mCurrentMethod->setDoesNotThrow();
1329        mCurrentMethod->setDoesNotCapture(1);
1330        auto args = mCurrentMethod->arg_begin();
1331        args->setName("self");
1332        setInstance(&*args);
1333        availableItemCount.reserve(mAvailableItemCount.size());
1334        while (++args != mCurrentMethod->arg_end()) {
1335            availableItemCount.push_back(&*args);
1336        }
1337        assert (availableItemCount.size() == mAvailableItemCount.size());
1338        mAvailableItemCount.swap(availableItemCount);
1339        idb->SetInsertPoint(BasicBlock::Create(idb->getContext(), "entry", mCurrentMethod));
1340    }
1341
1342    generateDoBlockMethod(idb); // must be implemented by the BlockOrientedKernelBuilder subtype
1343
1344    if (!idb->supportsIndirectBr()) {
1345        // Restore the DoSegment function state then call the DoBlock method
1346        idb->CreateRetVoid();
1347        mDoBlockMethod = mCurrentMethod;
1348        idb->restoreIP(ip);
1349        setInstance(self);
1350        mCurrentMethod = cp;
1351        mAvailableItemCount.swap(availableItemCount);
1352        CreateDoBlockMethodCall(idb);
1353    }
1354
1355}
1356
1357inline void BlockOrientedKernel::writeFinalBlockMethod(const std::unique_ptr<KernelBuilder> & idb, Value * remainingItems) {
1358
1359    Value * const self = getInstance();
1360    Function * const cp = mCurrentMethod;
1361    Value * const remainingItemCount = remainingItems;
1362    auto ip = idb->saveIP();
1363    std::vector<Value *> availableItemCount(0);
1364
1365    if (!idb->supportsIndirectBr()) {
1366        std::vector<Type *> params;
1367        params.reserve(2 + mAvailableItemCount.size());
1368        params.push_back(self->getType());
1369        params.push_back(idb->getSizeTy());
1370        for (Value * avail : mAvailableItemCount) {
1371            params.push_back(avail->getType());
1372        }
1373        FunctionType * const type = FunctionType::get(idb->getVoidTy(), params, false);
1374        mCurrentMethod = Function::Create(type, GlobalValue::InternalLinkage, getName() + FINAL_BLOCK_SUFFIX, idb->getModule());
1375        mCurrentMethod->setCallingConv(CallingConv::C);
1376        mCurrentMethod->setDoesNotThrow();
1377        mCurrentMethod->setDoesNotCapture(1);
1378        auto args = mCurrentMethod->arg_begin();
1379        args->setName("self");
1380        setInstance(&*args);
1381        remainingItems = &*(++args);
1382        remainingItems->setName("remainingItems");
1383        availableItemCount.reserve(mAvailableItemCount.size());
1384        while (++args != mCurrentMethod->arg_end()) {
1385            availableItemCount.push_back(&*args);
1386        }
1387        assert (availableItemCount.size() == mAvailableItemCount.size());
1388        mAvailableItemCount.swap(availableItemCount);
1389        idb->SetInsertPoint(BasicBlock::Create(idb->getContext(), "entry", mCurrentMethod));
1390    }
1391
1392    generateFinalBlockMethod(idb, remainingItems); // may be implemented by the BlockOrientedKernel subtype
1393
1394    if (!idb->supportsIndirectBr()) {
1395        idb->CreateRetVoid();
1396        idb->restoreIP(ip);
1397        setInstance(self);
1398        mAvailableItemCount.swap(availableItemCount);
1399        // Restore the DoSegment function state then call the DoFinal method
1400        std::vector<Value *> args;
1401        args.reserve(2 + mAvailableItemCount.size());
1402        args.push_back(self);
1403        args.push_back(remainingItemCount);
1404        for (Value * avail : mAvailableItemCount) {
1405            args.push_back(avail);
1406        }
1407        idb->CreateCall(mCurrentMethod, args);
1408        mCurrentMethod = cp;
1409    }
1410
1411}
1412
1413//  The default finalBlock method simply dispatches to the doBlock routine.
1414void BlockOrientedKernel::generateFinalBlockMethod(const std::unique_ptr<KernelBuilder> & idb, Value * /* remainingItems */) {
1415    CreateDoBlockMethodCall(idb);
1416}
1417
1418void BlockOrientedKernel::CreateDoBlockMethodCall(const std::unique_ptr<KernelBuilder> & idb) {
1419    if (idb->supportsIndirectBr()) {
1420        BasicBlock * bb = idb->CreateBasicBlock("resume");
1421        mStrideLoopBranch->addDestination(bb);
1422        mStrideLoopTarget->addIncoming(BlockAddress::get(bb), idb->GetInsertBlock());
1423        idb->CreateBr(mStrideLoopBody);
1424        bb->moveAfter(idb->GetInsertBlock());
1425        idb->SetInsertPoint(bb);
1426    } else {
1427        std::vector<Value *> args;
1428        args.reserve(1 + mAvailableItemCount.size());
1429        args.push_back(getInstance());
1430        for (Value * avail : mAvailableItemCount) {
1431            args.push_back(avail);
1432        }
1433        idb->CreateCall(mDoBlockMethod, args);
1434    }
1435}
1436
1437static inline std::string annotateKernelNameWithDebugFlags(std::string && name) {
1438    if (codegen::EnableAsserts) {
1439        name += "_EA";
1440    }
1441    name += "_O" + std::to_string((int)codegen::OptLevel);
1442    return name;
1443}
1444
1445// CONSTRUCTOR
1446Kernel::Kernel(std::string && kernelName,
1447               std::vector<Binding> && stream_inputs,
1448               std::vector<Binding> && stream_outputs,
1449               std::vector<Binding> && scalar_parameters,
1450               std::vector<Binding> && scalar_outputs,
1451               std::vector<Binding> && internal_scalars)
1452: KernelInterface(annotateKernelNameWithDebugFlags(std::move(kernelName))
1453                  , std::move(stream_inputs), std::move(stream_outputs)
1454                  , std::move(scalar_parameters), std::move(scalar_outputs)
1455                  , std::move(internal_scalars))
1456, mCurrentMethod(nullptr)
1457, mAvailablePrincipleItemCount(nullptr)
1458, mNoTerminateAttribute(false)
1459, mIsGenerated(false)
1460, mStride(0)
1461, mIsFinal(nullptr)
1462, mOutputScalarResult(nullptr) {
1463
1464}
1465
1466Kernel::~Kernel() {
1467
1468}
1469
1470// CONSTRUCTOR
1471BlockOrientedKernel::BlockOrientedKernel(std::string && kernelName,
1472                                         std::vector<Binding> && stream_inputs,
1473                                         std::vector<Binding> && stream_outputs,
1474                                         std::vector<Binding> && scalar_parameters,
1475                                         std::vector<Binding> && scalar_outputs,
1476                                         std::vector<Binding> && internal_scalars)
1477: MultiBlockKernel(std::move(kernelName), std::move(stream_inputs), std::move(stream_outputs), std::move(scalar_parameters), std::move(scalar_outputs), std::move(internal_scalars))
1478, mDoBlockMethod(nullptr)
1479, mStrideLoopBody(nullptr)
1480, mStrideLoopBranch(nullptr)
1481, mStrideLoopTarget(nullptr) {
1482
1483}
1484
1485// MULTI-BLOCK KERNEL CONSTRUCTOR
1486MultiBlockKernel::MultiBlockKernel(std::string && kernelName,
1487                                   std::vector<Binding> && stream_inputs,
1488                                   std::vector<Binding> && stream_outputs,
1489                                   std::vector<Binding> && scalar_parameters,
1490                                   std::vector<Binding> && scalar_outputs,
1491                                   std::vector<Binding> && internal_scalars)
1492: Kernel(std::move(kernelName), std::move(stream_inputs), std::move(stream_outputs), std::move(scalar_parameters), std::move(scalar_outputs), std::move(internal_scalars)) {
1493
1494}
1495
1496// CONSTRUCTOR
1497SegmentOrientedKernel::SegmentOrientedKernel(std::string && kernelName,
1498                                             std::vector<Binding> && stream_inputs,
1499                                             std::vector<Binding> && stream_outputs,
1500                                             std::vector<Binding> && scalar_parameters,
1501                                             std::vector<Binding> && scalar_outputs,
1502                                             std::vector<Binding> && internal_scalars)
1503: Kernel(std::move(kernelName), std::move(stream_inputs), std::move(stream_outputs), std::move(scalar_parameters), std::move(scalar_outputs), std::move(internal_scalars)) {
1504
1505}
1506
1507
1508}
Note: See TracBrowser for help on using the repository browser.