source: icGREP/icgrep-devel/icgrep/kernels/source_kernel.cpp @ 5589

Last change on this file since 5589 was 5513, checked in by cameron, 2 years ago

setCapacity for memory source kernel

File size: 18.2 KB
Line 
1/*
2 *  Copyright (c) 2017 International Characters.
3 *  This software is licensed to the public under the Open Software License 3.0.
4 */
5#include "source_kernel.h"
6#include <kernels/kernel_builder.h>
7#include <kernels/streamset.h>
8#include <llvm/IR/Module.h>
9#include <sys/stat.h>
10#include <fcntl.h>
11
12using namespace llvm;
13
14inline static size_t round_up_to_nearest(const size_t x, const size_t y) {
15    return (((x - 1) | (y - 1)) + 1);
16}
17
18uint64_t file_size(const uint32_t fd) {
19    struct stat st;
20    if (LLVM_UNLIKELY(fstat(fd, &st) != 0)) {
21        st.st_size = 0;
22    }
23    return st.st_size;
24}
25
26namespace kernel {
27
28/// MMAP SOURCE KERNEL
29
30void MMapSourceKernel::linkExternalMethods(const std::unique_ptr<kernel::KernelBuilder> & kb) {
31    mFileSizeFunction = kb->LinkFunction("file_size", &file_size);
32}
33
34void MMapSourceKernel::generateInitializeMethod(const std::unique_ptr<KernelBuilder> & kb) {
35    BasicBlock * const emptyFile = kb->CreateBasicBlock("EmptyFile");
36    BasicBlock * const nonEmptyFile = kb->CreateBasicBlock("NonEmptyFile");
37    BasicBlock * const exit = kb->CreateBasicBlock("Exit");
38    IntegerType * const sizeTy = kb->getSizeTy();
39    assert (kb->getKernel() == this);
40    Value * const fd = kb->getScalarField("fileDescriptor");
41    assert (mFileSizeFunction);
42    Value * fileSize = kb->CreateCall(mFileSizeFunction, fd);
43    fileSize = kb->CreateZExtOrTrunc(fileSize, sizeTy);
44    if (mCodeUnitWidth > 8) {
45        fileSize = kb->CreateUDiv(fileSize, kb->getSize(mCodeUnitWidth / 8));
46    }
47    Value * const isEmpty = kb->CreateICmpEQ(fileSize, ConstantInt::getNullValue(fileSize->getType()));
48    kb->CreateUnlikelyCondBr(isEmpty, emptyFile, nonEmptyFile);
49    // we cannot mmap a 0 length file; just create a 1-page sized fake file buffer for simplicity
50    kb->SetInsertPoint(emptyFile);
51    Constant * pageSize = kb->getSize(getpagesize());
52    Value * fakeFileBuffer = kb->CreateAnonymousMMap(pageSize);
53    kb->CreateBr(exit);
54
55    kb->SetInsertPoint(nonEmptyFile);
56    Value * fileBackedBuffer = kb->CreateFileSourceMMap(fd, fileSize);
57    kb->CreateBr(exit);
58
59    kb->SetInsertPoint(exit);
60    PHINode * buffer = kb->CreatePHI(fileBackedBuffer->getType(), 2);
61    buffer->addIncoming(fakeFileBuffer, emptyFile);
62    buffer->addIncoming(fileBackedBuffer, nonEmptyFile);
63    PHINode * size = kb->CreatePHI(sizeTy, 2);
64    size->addIncoming(pageSize, emptyFile);
65    size->addIncoming(fileSize, nonEmptyFile);
66    kb->setBaseAddress("sourceBuffer", buffer);
67    kb->setBufferedSize("sourceBuffer", size);
68    kb->setScalarField("readableBuffer", buffer);
69    kb->setScalarField("fileSize", fileSize);
70    kb->setCapacity("sourceBuffer", fileSize);
71    kb->CreateMAdvise(buffer, fileSize, CBuilder::ADVICE_WILLNEED);
72
73}
74
75void MMapSourceKernel::generateDoSegmentMethod(const std::unique_ptr<KernelBuilder> & kb) {
76
77    BasicBlock * dropPages = kb->CreateBasicBlock("dropPages");
78    BasicBlock * processSegment = kb->CreateBasicBlock("produceData");
79    BasicBlock * setTermination = kb->CreateBasicBlock("setTermination");
80    BasicBlock * mmapSourceExit = kb->CreateBasicBlock("mmapSourceExit");
81
82    // instruct the OS that it can safely drop any fully consumed pages
83    Value * consumed = kb->getConsumedItemCount("sourceBuffer");
84    IntegerType * const consumedTy = cast<IntegerType>(consumed->getType());
85    Type * const voidPtrTy = kb->getVoidPtrTy();
86
87    DataLayout DL(kb->getModule());
88    IntegerType * const intAddrTy = kb->getIntPtrTy(DL);
89
90    // multiply the consumed count by the code unit size then mask off any partial pages
91    if (mCodeUnitWidth > 8) {
92        consumed = kb->CreateMul(consumed, ConstantInt::get(consumedTy, mCodeUnitWidth / 8));
93    }
94    const auto pageSize = getpagesize();
95    if (LLVM_LIKELY((pageSize & (pageSize - 1)) == 0)) {
96        consumed = kb->CreateAnd(consumed, ConstantExpr::getNeg(ConstantInt::get(consumedTy, pageSize)));
97    } else {
98        consumed = kb->CreateSub(consumed, kb->CreateURem(consumed, ConstantInt::get(consumedTy, pageSize)));
99    }
100
101    Value * sourceBuffer = kb->getBaseAddress("sourceBuffer");
102    sourceBuffer = kb->CreatePtrToInt(sourceBuffer, intAddrTy);
103    if (LLVM_UNLIKELY(intAddrTy->getBitWidth() > consumedTy->getBitWidth())) {
104        consumed = kb->CreateZExt(consumed, intAddrTy);
105    } else if (LLVM_UNLIKELY(intAddrTy->getBitWidth() < consumedTy->getBitWidth())) {
106        sourceBuffer = kb->CreateZExt(sourceBuffer, consumedTy);
107    }
108    Value * consumedBuffer = kb->CreateAdd(sourceBuffer, consumed);
109    Value * readableBuffer = kb->getScalarField("readableBuffer");
110    readableBuffer = kb->CreatePtrToInt(readableBuffer, consumedBuffer->getType());
111    Value * unnecessaryBytes = kb->CreateSub(consumedBuffer, readableBuffer);
112
113    // avoid calling madvise unless an actual page table change could occur
114    Value * hasPagesToDrop = kb->CreateICmpEQ(unnecessaryBytes, ConstantInt::getNullValue(intAddrTy));
115    kb->CreateLikelyCondBr(hasPagesToDrop, processSegment, dropPages);
116
117    kb->SetInsertPoint(dropPages);
118    kb->CreateMAdvise(kb->CreateIntToPtr(readableBuffer, voidPtrTy), unnecessaryBytes, CBuilder::ADVICE_DONTNEED);
119    readableBuffer = kb->CreateIntToPtr(kb->CreateAdd(readableBuffer, unnecessaryBytes), voidPtrTy);
120    kb->setScalarField("readableBuffer", readableBuffer);
121    kb->CreateBr(processSegment);
122
123    // determine whether or not we've exhausted the file buffer
124    kb->SetInsertPoint(processSegment);
125    ConstantInt * segmentItems = kb->getSize(mSegmentBlocks * kb->getBitBlockWidth());
126    Value * const fileSize = kb->getScalarField("fileSize");
127    Value * const produced = kb->CreateAdd(kb->getProducedItemCount("sourceBuffer"), segmentItems);
128    Value * const lessThanFullSegment = kb->CreateICmpULT(fileSize, produced);
129    kb->CreateUnlikelyCondBr(lessThanFullSegment, setTermination, mmapSourceExit);
130    kb->SetInsertPoint(setTermination);
131
132    kb->setTerminationSignal();
133    kb->CreateBr(mmapSourceExit);
134
135    // finally, set the "produced" count to reflect current position in the file
136    kb->SetInsertPoint(mmapSourceExit);
137    PHINode * itemsRead = kb->CreatePHI(produced->getType(), 2);
138    itemsRead->addIncoming(produced, processSegment);
139    itemsRead->addIncoming(fileSize, setTermination);
140    kb->setProducedItemCount("sourceBuffer", itemsRead);
141}
142
143void MMapSourceKernel::generateFinalizeMethod(const std::unique_ptr<KernelBuilder> & kb) {
144    kb->CreateMUnmap(kb->getBaseAddress("sourceBuffer"), kb->getBufferedSize("sourceBuffer"));
145}
146
147MMapSourceKernel::MMapSourceKernel(const std::unique_ptr<kernel::KernelBuilder> & kb, unsigned blocksPerSegment, unsigned codeUnitWidth)
148: SegmentOrientedKernel("mmap_source" + std::to_string(blocksPerSegment) + "@" + std::to_string(codeUnitWidth),
149{},
150{Binding{kb->getStreamSetTy(1, codeUnitWidth), "sourceBuffer"}},
151{Binding{kb->getInt32Ty(), "fileDescriptor"}},
152{Binding{kb->getSizeTy(), "fileSize"}}, {Binding{kb->getVoidPtrTy(), "readableBuffer"}})
153, mSegmentBlocks(blocksPerSegment)
154, mCodeUnitWidth(codeUnitWidth)
155, mFileSizeFunction(nullptr) {
156
157}
158
159/// READ SOURCE KERNEL
160
161void ReadSourceKernel::generateInitializeMethod(const std::unique_ptr<KernelBuilder> & kb) {
162    const size_t initialBufferSize = 8 * getpagesize() * mCodeUnitWidth;
163    ConstantInt * const bufferBytes = kb->getSize(initialBufferSize * mCodeUnitWidth/8);
164    PointerType * const codeUnitPtrTy = IntegerType::get(kb->getContext(), mCodeUnitWidth)->getPointerTo();
165    Value * const buffer = kb->CreatePointerCast(kb->CreateCacheAlignedMalloc(bufferBytes), codeUnitPtrTy);
166    kb->setScalarField("buffer", buffer);
167    kb->setScalarField("capacity", kb->getSize(initialBufferSize));
168    kb->setBaseAddress("sourceBuffer", buffer);
169    kb->setBufferedSize("sourceBuffer", kb->getSize(0));
170    kb->setCapacity("sourceBuffer", kb->getSize(initialBufferSize));
171}
172
173void ReadSourceKernel::generateDoSegmentMethod(const std::unique_ptr<KernelBuilder> & kb) {
174
175    ConstantInt * const readSize = kb->getSize(getpagesize() * 8/mCodeUnitWidth);
176    PointerType * const codeUnitPtrTy = IntegerType::get(kb->getContext(), mCodeUnitWidth)->getPointerTo();
177    PointerType * const i8PtrTy = IntegerType::get(kb->getContext(), 8)->getPointerTo();
178    ConstantInt * const codeUnitBytes = kb->getSize(mCodeUnitWidth/8);
179    BasicBlock * const entryBlock = kb->GetInsertBlock();
180    BasicBlock * const exhaustedBuffer = kb->CreateBasicBlock("ExhaustedBuffer");
181    BasicBlock * const waitOnConsumers = kb->CreateBasicBlock("WaitOnConsumers");
182    BasicBlock * const readData = kb->CreateBasicBlock("ReadData");
183    BasicBlock * const stdInExit = kb->CreateBasicBlock("StdInExit");
184
185    assert(kb->getKernel() == this);
186
187    // Check whether we need to read another page of data
188    ConstantInt * const segmentSize = kb->getSize(mSegmentBlocks * kb->getBitBlockWidth());
189    Value * bufferedSize = kb->getBufferedSize("sourceBuffer");
190    Value * const produced = kb->getProducedItemCount("sourceBuffer");
191    Value * unreadSize = kb->CreateSub(bufferedSize, produced);
192    kb->CreateUnlikelyCondBr(kb->CreateICmpULT(unreadSize, segmentSize), exhaustedBuffer, stdInExit);
193
194    // If so, it checks whether it can simply append another page to the existing buffer or whether
195    // we need to perform a copyback.
196
197    kb->SetInsertPoint(exhaustedBuffer);
198
199    // Otherwise, we're going to have to perform a copy back...
200
201    // Let L be the logical buffer address (i.e., the position of the "first code unit" of the input stream)
202    // and B be the address pointing to the beginning of our actual buffer. Check whether:
203
204    //     L + produced + readSize < B + capacity
205
206    // If so, we can append to our existing buffer without impacting any subsequent kernel.
207
208    Value * inputStream = kb->getRawOutputPointer("sourceBuffer", kb->getInt32(0), kb->getInt32(0));
209    Value * const originalPtr = kb->CreateGEP(inputStream, produced);
210
211    Value * const buffer = kb->getScalarField("buffer");
212    Value * const capacity = kb->getScalarField("capacity");
213
214    Value * L = kb->CreateGEP(originalPtr, readSize);
215    Value * B = kb->CreateGEP(buffer, capacity);
216    Value * const canAppend = kb->CreateICmpULT(L, B);
217    kb->CreateLikelyCondBr(canAppend, readData, waitOnConsumers);
218
219    // First wait on any consumers to finish processing then check how much data has been consumed.
220    kb->SetInsertPoint(waitOnConsumers);
221    kb->CreateConsumerWait();
222
223    // Then determine how much data has been consumed and how much needs to be copied back, noting
224    // that our "unproduced" data must be block aligned.
225    const size_t blockAlignment = kb->getBitBlockWidth() / 8;
226    Constant * const alignmentMask = kb->getSize(-(blockAlignment * 8 / mCodeUnitWidth));
227    Value * const consumed = kb->CreateAnd(kb->getConsumedItemCount("sourceBuffer"), alignmentMask);
228    Value * const remaining = kb->CreateSub(bufferedSize, consumed);
229    Value * const unconsumedPtr = kb->CreateGEP(inputStream, consumed);
230    Value * const consumedMajority = kb->CreateICmpULT(kb->CreateGEP(buffer, remaining), unconsumedPtr);
231    Value * target = buffer;
232    Value * source = unconsumedPtr;
233    Value * toCopy = remaining;
234    if (mCodeUnitWidth != 8) {
235        source = kb->CreatePointerCast(unconsumedPtr, i8PtrTy);
236        toCopy = kb->CreateMul(remaining, codeUnitBytes);
237    }
238
239    BasicBlock * const copyBack = kb->CreateBasicBlock("CopyBack");
240    BasicBlock * const expandAndCopyBack = kb->CreateBasicBlock("ExpandAndCopyBack");
241    BasicBlock * const calculateLogicalAddress = kb->CreateBasicBlock("CalculateLogicalAddress");
242
243    // Have we consumed enough data that we can safely copy back the unconsumed data without needing
244    // a temporary buffer? (i.e., B + remaining < L + consumed)
245    kb->CreateLikelyCondBr(consumedMajority, copyBack, expandAndCopyBack);
246    kb->SetInsertPoint(copyBack);
247    // If so, just copy the data ...
248    if (mCodeUnitWidth != 8) {
249        target = kb->CreatePointerCast(buffer, i8PtrTy);
250    }
251    kb->CreateMemCpy(target, source, toCopy, 1);
252    kb->CreateBr(calculateLogicalAddress);
253   
254    // Otherwise, allocate a buffer with twice the capacity and copy the unconsumed data back into it
255    kb->SetInsertPoint(expandAndCopyBack);
256    Value * const expandedCapacity = kb->CreateShl(capacity, 1);
257    Value * const expandedBytes = mCodeUnitWidth == 8 ? expandedCapacity : kb->CreateMul(expandedCapacity, codeUnitBytes);
258    Value * const expandedBuffer = kb->CreatePointerCast(kb->CreateCacheAlignedMalloc(expandedBytes), codeUnitPtrTy);
259    target = mCodeUnitWidth == 8 ? expandedBuffer : kb->CreatePointerCast(expandedBuffer, i8PtrTy);
260    kb->CreateMemCpy(target, source, toCopy, 1);
261    kb->CreateFree(buffer);
262    kb->setScalarField("buffer", expandedBuffer);
263    kb->setScalarField("capacity", expandedCapacity);
264    kb->setCapacity("sourceBuffer", expandedCapacity);
265    kb->CreateBr(calculateLogicalAddress);
266
267    // Update the logical address for this buffer....
268    kb->SetInsertPoint(calculateLogicalAddress);
269    PHINode * const baseAddress = kb->CreatePHI(codeUnitPtrTy, 2);
270    baseAddress->addIncoming(buffer, copyBack);
271    baseAddress->addIncoming(expandedBuffer, expandAndCopyBack);
272    Value * const logicalAddress = kb->CreateGEP(baseAddress, kb->CreateNeg(consumed));
273    Value * const modifiedPtr = kb->CreateGEP(baseAddress, remaining);
274    kb->setBaseAddress("sourceBuffer", logicalAddress);
275    kb->CreateBr(readData);
276
277    // Regardless of whether we're simply appending data or had to allocate a new buffer, read a new page
278    // of data into the input source buffer. If we fail to read a full segment ...
279    readData->moveAfter(calculateLogicalAddress);
280    kb->SetInsertPoint(readData);
281    calculateLogicalAddress->moveAfter(calculateLogicalAddress);
282    PHINode * const addr = kb->CreatePHI(codeUnitPtrTy, 2);
283    addr->addIncoming(originalPtr, exhaustedBuffer);
284    addr->addIncoming(modifiedPtr, calculateLogicalAddress);
285    assert(kb->getKernel() == this);
286    Value * const fd = kb->getScalarField("fileDescriptor");
287    Value * toRead = readSize;
288    if (mCodeUnitWidth != 8) {
289        toRead = kb->CreateMul(toRead, codeUnitBytes);
290    }
291    Value * bytesRead = kb->CreateReadCall(fd, addr, toRead);
292    Value * itemsRead = bytesRead;
293    if (mCodeUnitWidth != 8) {
294        itemsRead = kb->CreateUDiv(bytesRead, codeUnitBytes);
295    }
296    unreadSize = kb->CreateAdd(unreadSize, itemsRead);
297    bufferedSize = kb->CreateAdd(bufferedSize, itemsRead);
298    kb->setBufferedSize("sourceBuffer", bufferedSize);
299    Value * const exhaustedInputSource = kb->CreateICmpULT(unreadSize, segmentSize);
300    BasicBlock * const setTermination = kb->CreateBasicBlock("SetTermination");
301    kb->CreateUnlikelyCondBr(exhaustedInputSource, setTermination, stdInExit);
302
303    // ... zero out the remaining bytes and set the termination signal.
304    kb->SetInsertPoint(setTermination);
305    Value * bytesToZero = kb->CreateSub(segmentSize, unreadSize);
306    Value * unreadPtr = kb->CreateGEP(addr, unreadSize);
307    bytesToZero = mCodeUnitWidth == 8 ? bytesToZero : kb->CreateMul(bytesToZero, codeUnitBytes);
308    if (mCodeUnitWidth != 8) {
309        bytesToZero = kb->CreateMul(bytesToZero, codeUnitBytes);
310        unreadPtr = kb->CreatePointerCast(unreadPtr, i8PtrTy);
311    }
312    kb->CreateMemZero(unreadPtr, bytesToZero);
313    kb->setTerminationSignal();
314    kb->CreateBr(stdInExit);
315
316    // finally add the segment item count to the produced item count to inform the subsequent kernels how
317    // much data is available for processing
318    kb->SetInsertPoint(stdInExit);
319    stdInExit->moveAfter(setTermination);
320    PHINode * const items = kb->CreatePHI(produced->getType(), 3);
321    items->addIncoming(segmentSize, entryBlock);
322    items->addIncoming(segmentSize, readData);
323    items->addIncoming(unreadSize, setTermination);
324    kb->setProducedItemCount("sourceBuffer", kb->CreateAdd(produced, items));
325}
326
327void ReadSourceKernel::generateFinalizeMethod(const std::unique_ptr<KernelBuilder> & kb) {
328    kb->CreateFree(kb->getScalarField("buffer"));
329}
330
331ReadSourceKernel::ReadSourceKernel(const std::unique_ptr<kernel::KernelBuilder> & kb, unsigned blocksPerSegment, unsigned codeUnitWidth)
332: SegmentOrientedKernel("read_source"
333, {}
334, {Binding{kb->getStreamSetTy(1, codeUnitWidth), "sourceBuffer"}}
335, {Binding{kb->getInt32Ty(), "fileDescriptor"}}
336, {}
337, {Binding{IntegerType::get(kb->getContext(), codeUnitWidth)->getPointerTo(), "buffer"}, Binding{kb->getSizeTy(), "capacity"}})
338, mSegmentBlocks(blocksPerSegment)
339, mCodeUnitWidth(codeUnitWidth) {
340
341}
342
343/// MEMORY SOURCE KERNEL
344
345void MemorySourceKernel::generateInitializeMethod(const std::unique_ptr<KernelBuilder> & kb) {
346    kb->setBaseAddress("sourceBuffer", kb->CreatePointerCast(kb->getScalarField("fileSource"), kb->getVoidPtrTy()));
347    kb->setBufferedSize("sourceBuffer", kb->getScalarField("fileSize"));
348    kb->setCapacity("sourceBuffer", kb->getScalarField("fileSize"));
349}
350
351void MemorySourceKernel::generateDoSegmentMethod(const std::unique_ptr<KernelBuilder> & kb) {
352
353    BasicBlock * entryBlock = kb->GetInsertBlock();
354    BasicBlock * setTermination = kb->CreateBasicBlock("setTermination");
355    BasicBlock * mmapSourceExit = kb->CreateBasicBlock("sourceExit");
356    ConstantInt * segmentItems = kb->getSize(mSegmentBlocks * kb->getBitBlockWidth());
357    Value * fileItems = kb->getScalarField("fileSize");
358    if (mCodeUnitWidth > 8) {
359        fileItems = kb->CreateUDiv(fileItems, kb->getSize(mCodeUnitWidth / 8));
360    }
361    Value * produced = kb->getProducedItemCount("sourceBuffer");
362    produced = kb->CreateAdd(produced, segmentItems);
363    Value * lessThanFullSegment = kb->CreateICmpULT(fileItems, produced);
364    kb->CreateCondBr(lessThanFullSegment, setTermination, mmapSourceExit);
365    kb->SetInsertPoint(setTermination);
366    kb->setTerminationSignal();
367    kb->CreateBr(mmapSourceExit);
368
369    kb->SetInsertPoint(mmapSourceExit);
370
371    PHINode * itemsRead = kb->CreatePHI(produced->getType(), 2);
372    itemsRead->addIncoming(produced, entryBlock);
373    itemsRead->addIncoming(fileItems, setTermination);
374    kb->setProducedItemCount("sourceBuffer", itemsRead);
375}
376
377MemorySourceKernel::MemorySourceKernel(const std::unique_ptr<kernel::KernelBuilder> & kb, Type * type, unsigned blocksPerSegment, unsigned codeUnitWidth)
378: SegmentOrientedKernel("memory_source",
379    {},
380    {Binding{kb->getStreamSetTy(1, codeUnitWidth), "sourceBuffer"}},
381    {Binding{cast<PointerType>(type), "fileSource"}, Binding{kb->getSizeTy(), "fileSize"}}, {}, {})
382, mSegmentBlocks(blocksPerSegment)
383, mCodeUnitWidth(codeUnitWidth) {
384
385}
386
387}
Note: See TracBrowser for help on using the repository browser.