source: icGREP/icgrep-devel/icgrep/kernels/kernel.h @ 5399

Last change on this file since 5399 was 5399, checked in by nmedfort, 2 years ago

Bug fixes for object cache. Minor optimization of FunctionTypeBuilder?.

File size: 15.8 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
6#ifndef KERNEL_BUILDER_H
7#define KERNEL_BUILDER_H
8
9#include <string>           // for string
10#include <memory>           // for unique_ptr
11#include "interface.h"      // for KernelInterface
12#include <boost/container/flat_map.hpp>
13#include <IR_Gen/idisa_builder.h>
14
15//namespace llvm { class ConstantInt; }
16#include <llvm/IR/Constants.h>
17namespace llvm { class Function; }
18namespace llvm { class IntegerType; }
19namespace llvm { class LoadInst; }
20namespace llvm { class Type; }
21namespace llvm { class Value; }
22namespace parabix { class StreamSetBuffer; }
23
24namespace kernel {
25   
26class KernelBuilder : public KernelInterface {
27protected:
28    using KernelMap = boost::container::flat_map<std::string, unsigned>;
29    enum class Port { Input, Output };
30    using StreamPort = std::pair<Port, unsigned>;
31    using StreamMap = boost::container::flat_map<std::string, StreamPort>;
32    using StreamSetBuffers = std::vector<parabix::StreamSetBuffer *>;
33public:
34   
35    // Kernel Signatures and Module IDs
36    //
37    // A kernel signature uniquely identifies a kernel and its full functionality.
38    // In the event that a particular kernel instance is to be generated and compiled
39    // to produce object code, and we have a cached kernel object code instance with
40    // the same signature and targetting the same IDISA architecture, then the cached
41    // object code may safely be used to avoid recompilation.
42    //
43    // A kernel signature is a byte string of arbitrary length.
44    //
45    // Kernel developers should take responsibility for designing appropriate signature
46    // mechanisms that are short, inexpensive to compute and guarantee uniqueness
47    // based on the semantics of the kernel. 
48    //
49    // If no other mechanism is available, the default generateKernelSignature() method
50    // uses the full LLVM IR (before optimization) of the kernel instance.
51    //
52    // A kernel Module ID is short string that is used as a name for a particular kernel
53    // instance.  Kernel Module IDs are used to look up and retrieve cached kernel instances
54    // and so should be highly likely to uniquely identify a kernel instance.
55    //
56    // The ideal case is that a kernel Module ID serves as a full kernel signature thus
57    // guaranteeing uniqueness.  In this case, the moduleIDisUnique() method
58    // should return true.
59    //
60   
61    // Can the module ID itself serve as the unique signature?
62    virtual bool moduleIDisSignature() {/* default */  return false;}
63   
64    virtual void generateKernelSignature(std::string & signature);
65   
66    // Create a module stub for the kernel, populated only with its Module ID.     
67    //
68    std::unique_ptr<llvm::Module> createKernelStub(const StreamSetBuffers & inputs, const StreamSetBuffers & outputs);
69   
70    // Create a module for the kernel, including the kernel state type declaration and
71    // the full implementation of all required methods.     
72    //
73    std::unique_ptr<llvm::Module> createKernelModule(const StreamSetBuffers & inputs, const StreamSetBuffers & outputs);
74   
75    void setCallParameters(const StreamSetBuffers & inputs, const StreamSetBuffers & outputs);
76
77    // Generate the Kernel to the current module (iBuilder->getModule()).
78    void generateKernel();
79    void generateKernel(const StreamSetBuffers & inputs, const StreamSetBuffers & outputs);
80   
81    void createInstance() override;
82
83    llvm::Value * getProducedItemCount(llvm::Value * instance, const std::string & name, llvm::Value * doFinal = nullptr) const final;
84
85    void setProducedItemCount(llvm::Value * instance, const std::string & name, llvm::Value * value) const final;
86
87    llvm::Value * getProcessedItemCount(llvm::Value * instance, const std::string & name) const final;
88
89    void setProcessedItemCount(llvm::Value * instance, const std::string & name, llvm::Value * value) const final;
90
91    bool hasNoTerminateAttribute() { return mNoTerminateAttribute;}
92   
93    llvm::Value * getTerminationSignal(llvm::Value * instance) const final;
94
95    void setTerminationSignal(llvm::Value * instance) const final;
96
97    // Get the value of a scalar field for a given instance.
98    llvm::Value * getScalarField(llvm::Value * instance, const std::string & fieldName) const;
99
100    llvm::Value * getScalarField(llvm::Value * instance, llvm::Value * index) const;
101
102    // Set the value of a scalar field for a given instance.
103    void setScalarField(llvm::Value *instance, const std::string & fieldName, llvm::Value * value) const;
104
105    void setScalarField(llvm::Value * instance, llvm::Value * index, llvm::Value * value) const;
106
107    // Synchronization actions for executing a kernel for a particular logical segment.
108    //
109    // Before the segment is processed, acquireLogicalSegmentNo must be used to load
110    // the segment number of the kernel state to ensure that the previous segment is
111    // complete (by checking that the acquired segment number is equal to the desired segment
112    // number).
113    // After all segment processing actions for the kernel are complete, and any necessary
114    // data has been extracted from the kernel for further pipeline processing, the
115    // segment number must be incremented and stored using releaseLogicalSegmentNo.
116    llvm::LoadInst * acquireLogicalSegmentNo(llvm::Value * instance) const;
117
118    void releaseLogicalSegmentNo(llvm::Value * instance, llvm::Value * newFieldVal) const;
119
120    // Get a parameter by name.
121    llvm::Argument * getParameter(llvm::Function * f, const std::string & name) const;
122
123    inline llvm::IntegerType * getSizeTy() const {
124        return getBuilder()->getSizeTy();
125    }
126
127    inline llvm::Type * getStreamTy(const unsigned FieldWidth = 1) {
128        return getBuilder()->getStreamTy(FieldWidth);
129    }
130   
131    inline llvm::Type * getStreamSetTy(const unsigned NumElements = 1, const unsigned FieldWidth = 1) {
132        return getBuilder()->getStreamSetTy(NumElements, FieldWidth);
133    }
134   
135    virtual ~KernelBuilder() = 0;
136   
137    const std::vector<const parabix::StreamSetBuffer *> & getStreamSetInputBuffers() const { return mStreamSetInputBuffers; }
138
139    const std::vector<const parabix::StreamSetBuffer *> & getStreamSetOutputBuffers() const { return mStreamSetOutputBuffers; }
140
141    llvm::Value * createDoSegmentCall(const std::vector<llvm::Value *> & args) const;
142
143    llvm::Value * createGetAccumulatorCall(llvm::Value * self, const std::string & accumName) const;
144
145protected:
146
147    // Constructor
148    KernelBuilder(IDISA::IDISA_Builder * builder,
149                    std::string && kernelName,
150                    std::vector<Binding> && stream_inputs,
151                    std::vector<Binding> && stream_outputs,
152                    std::vector<Binding> && scalar_parameters,
153                    std::vector<Binding> && scalar_outputs,
154                    std::vector<Binding> && internal_scalars);
155
156    //
157    // Kernel builder subtypes define their logic of kernel construction
158    // in terms of 3 virtual methods for
159    // (a) preparing the Kernel state data structure
160    // (b) defining the logic of the doBlock function, and
161    // (c) defining the logic of the finalBlock function.
162    //
163    // Note: the kernel state data structure must only be finalized after
164    // all scalar fields have been added.   If there are no fields to
165    // be added, the default method for preparing kernel state may be used.
166   
167    void setNoTerminateAttribute(const bool noTerminate = true) {
168        mNoTerminateAttribute = noTerminate;
169    }
170
171    void prepareStreamSetNameMap();
172
173    virtual void prepareKernel();
174
175    virtual void generateInitMethod() { }
176   
177    virtual void generateDoSegmentMethod(llvm::Value * doFinal, const std::vector<llvm::Value *> & producerPos) = 0;
178
179    // Add an additional scalar field to the KernelState struct.
180    // Must occur before any call to addKernelDeclarations or createKernelModule.
181    unsigned addScalar(llvm::Type * type, const std::string & name);
182
183    unsigned addUnnamedScalar(llvm::Type * type);
184
185    unsigned getScalarCount() const;
186
187    // Run-time access of Kernel State and parameters of methods for
188    // use in implementing kernels.
189   
190    // Get the index of a named scalar field within the kernel state struct.
191    llvm::ConstantInt * getScalarIndex(const std::string & name) const;
192
193    // Get the value of a scalar field for a given instance.
194    llvm::Value * getScalarField(const std::string & fieldName) const {
195        return getScalarField(getSelf(), fieldName);
196    }
197
198    llvm::Value * getScalarField(llvm::Value * index) const {
199        return getScalarField(getSelf(), index);
200    }
201
202    // Set the value of a scalar field for a given instance.
203    void setScalarField(const std::string & fieldName, llvm::Value * value) const {
204        return setScalarField(getSelf(), fieldName, value);
205    }
206
207    void setScalarField(llvm::Value * index, llvm::Value * value) const {
208        return setScalarField(getSelf(), index, value);
209    }
210
211    llvm::Value * getInputStreamBlockPtr(const std::string & name, llvm::Value * streamIndex) const;
212
213    llvm::Value * loadInputStreamBlock(const std::string & name, llvm::Value * streamIndex) const;
214   
215    llvm::Value * getInputStreamPackPtr(const std::string & name, llvm::Value * streamIndex, llvm::Value * packIndex) const;
216   
217    llvm::Value * loadInputStreamPack(const std::string & name, llvm::Value * streamIndex, llvm::Value * packIndex) const;
218   
219    llvm::Value * getInputStreamSetCount(const std::string & name) const;
220
221    llvm::Value * getOutputStreamBlockPtr(const std::string & name, llvm::Value * streamIndex) const;
222   
223    void storeOutputStreamBlock(const std::string & name, llvm::Value * streamIndex, llvm::Value * toStore) const;
224   
225    llvm::Value * getOutputStreamPackPtr(const std::string & name, llvm::Value * streamIndex, llvm::Value * packIndex) const;
226   
227    void storeOutputStreamPack(const std::string & name, llvm::Value * streamIndex, llvm::Value * packIndex, llvm::Value * toStore) const;
228
229    llvm::Value * getOutputStreamSetCount(const std::string & name) const;
230
231    llvm::Value * getAdjustedInputStreamBlockPtr(llvm::Value * blockAdjustment, const std::string & name, llvm::Value * streamIndex) const;
232
233    llvm::Value * getRawInputPointer(const std::string & name, llvm::Value * streamIndex, llvm::Value * absolutePosition) const;
234
235    llvm::Value * getRawOutputPointer(const std::string & name, llvm::Value * streamIndex, llvm::Value * absolutePosition) const;
236
237    void setBaseAddress(const std::string & name, llvm::Value * addr) const;
238
239    llvm::Value * getBufferedSize(const std::string & name) const;
240
241    void setBufferedSize(const std::string & name, llvm::Value * size) const;
242
243    void reserveBytes(const std::string & name, llvm::Value * requested) const;
244
245    llvm::Value * getScalarFieldPtr(const std::string & name) const {
246        return getScalarFieldPtr(getSelf(), name);
247    }
248
249    llvm::Value * getScalarFieldPtr(llvm::Value * index) const {
250        return getScalarFieldPtr(getSelf(), index);
251    }
252
253    inline llvm::Value * getProducedItemCount(const std::string & name) const {
254        return getProducedItemCount(getSelf(), name);
255    }
256
257    inline void setProducedItemCount(const std::string & name, llvm::Value * value) const {
258        setProducedItemCount(getSelf(), name, value);
259    }
260
261    inline llvm::Value * getProcessedItemCount(const std::string & name) const {
262        return getProcessedItemCount(getSelf(), name);
263    }
264
265    inline void setProcessedItemCount(const std::string & name, llvm::Value * value) const {
266        setProcessedItemCount(getSelf(), name, value);
267    }
268
269    llvm::Value * getTerminationSignal() const {
270        return getTerminationSignal(getSelf());
271    }
272
273    void setTerminationSignal() const {
274        return setTerminationSignal(getSelf());
275    }
276
277    llvm::Value * getSelf() const {
278        return mSelf;
279    }
280
281    llvm::BasicBlock * CreateBasicBlock(std::string && name) const;
282
283    // Stream set helpers.
284
285    llvm::Value * getStreamSetBufferPtr(const std::string & name) const;
286
287    llvm::Value * getScalarFieldPtr(llvm::Value * instance, const std::string & name) const;
288
289    llvm::Value * getScalarFieldPtr(llvm::Value * instance, llvm::Value * index) const;
290
291    StreamPort getStreamPort(const std::string & name) const;
292
293    const parabix::StreamSetBuffer * getInputStreamSetBuffer(const std::string & name) const {
294        const auto port = getStreamPort(name);
295        assert (port.first == Port::Input);
296        assert (port.second < mStreamSetInputBuffers.size());
297        return mStreamSetInputBuffers[port.second];
298    }
299
300    const parabix::StreamSetBuffer * getOutputStreamSetBuffer(const std::string & name) const {
301        const auto port = getStreamPort(name);
302        assert (port.first == Port::Output);
303        assert (port.second < mStreamSetOutputBuffers.size());
304        return mStreamSetOutputBuffers[port.second];
305    }
306
307    void callGenerateInitMethod();
308
309    void callGenerateDoSegmentMethod();
310
311private:
312
313    llvm::Value * computeBlockIndex(const std::vector<Binding> & binding, const std::string & name, llvm::Value * itemCount) const;
314
315protected:
316
317    llvm::Value *                                   mSelf;
318    llvm::Function *                                mCurrentMethod;
319
320    std::vector<llvm::Type *>                       mKernelFields;
321    KernelMap                                       mKernelMap;
322    StreamMap                                       mStreamMap;
323    std::vector<const parabix::StreamSetBuffer *>   mStreamSetInputBuffers;
324    std::vector<const parabix::StreamSetBuffer *>   mStreamSetOutputBuffers;
325    bool                                            mNoTerminateAttribute;
326    bool                                            mIsGenerated;
327
328};
329
330class SegmentOrientedKernel : public KernelBuilder {
331protected:
332
333    SegmentOrientedKernel(IDISA::IDISA_Builder * builder,
334                          std::string && kernelName,
335                          std::vector<Binding> && stream_inputs,
336                          std::vector<Binding> && stream_outputs,
337                          std::vector<Binding> && scalar_parameters,
338                          std::vector<Binding> && scalar_outputs,
339                          std::vector<Binding> && internal_scalars);
340
341    virtual ~SegmentOrientedKernel() { }
342
343};
344
345class BlockOrientedKernel : public KernelBuilder {
346protected:
347
348    void CreateDoBlockMethodCall();
349
350    // Each kernel builder subtype must provide its own logic for generating
351    // doBlock calls.
352    virtual void generateDoBlockMethod() = 0;
353
354    // Each kernel builder subtypre must also specify the logic for processing the
355    // final block of stream data, if there is any special processing required
356    // beyond simply calling the doBlock function.   In the case that the final block
357    // processing may be trivially implemented by dispatching to the doBlock method
358    // without additional preparation, the default generateFinalBlockMethod need
359    // not be overridden.
360
361    virtual void generateFinalBlockMethod(llvm::Value * remainingItems);
362
363    void generateDoSegmentMethod(llvm::Value * doFinal, const std::vector<llvm::Value *> & producerPos) override final;
364
365    BlockOrientedKernel(IDISA::IDISA_Builder * builder,
366                        std::string && kernelName,
367                        std::vector<Binding> && stream_inputs,
368                        std::vector<Binding> && stream_outputs,
369                        std::vector<Binding> && scalar_parameters,
370                        std::vector<Binding> && scalar_outputs,
371                        std::vector<Binding> && internal_scalars);
372
373    virtual ~BlockOrientedKernel() { }
374
375private:
376
377    bool useIndirectBr() const {
378        return iBuilder->supportsIndirectBr();
379    }
380
381    void writeDoBlockMethod();
382
383    void writeFinalBlockMethod(llvm::Value * remainingItems);
384
385private:
386
387    llvm::Function *        mDoBlockMethod;
388    llvm::BasicBlock *      mStrideLoopBody;
389    llvm::IndirectBrInst *  mStrideLoopBranch;
390    llvm::PHINode *         mStrideLoopTarget;
391};
392
393
394}
395#endif
Note: See TracBrowser for help on using the repository browser.