BufferAllocator.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /* vim: set expandtab ts=4 sw=4: */
  2. /*
  3. * You may redistribute this program and/or modify it under the terms of
  4. * the GNU General Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. *
  7. * This program is distributed in the hope that it will be useful,
  8. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. * GNU General Public License for more details.
  11. *
  12. * You should have received a copy of the GNU General Public License
  13. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include <stdint.h>
  16. #include "exception/Except.h"
  17. #include "memory/BufferAllocator.h"
  18. #include "util/Bits.h"
  19. #include "util/Identity.h"
  20. /**
  21. * TODO: addOnFreeJob adds a job which is only run when the root allocator is freed
  22. * and it needs to be run when the allocator which called it, or any of that allocator's
  23. * ancestors is freed, not just the root.
  24. */
  25. /* Define alignment as the size of a pointer which is usually 4 or 8 bytes. */
  26. #define ALIGNMENT sizeof(char*)
  27. struct Job {
  28. struct Allocator_OnFreeJob generic;
  29. struct Allocator* alloc;
  30. struct Job* next;
  31. Identity
  32. };
  33. /** Internal state for Allocator. */
  34. struct BufferAllocator {
  35. struct Allocator generic;
  36. /** Pointer to the beginning of the buffer. */
  37. char* basePointer;
  38. /** Pointer to a pointer to the place in the buffer to allocate the next block of memory. */
  39. char** pPointer;
  40. /** Pointer to the end of the buffer. */
  41. char* const endPointer;
  42. struct Job* onFree;
  43. /** Number of onfree jobs which are not yet complete. */
  44. int outstandingJobs;
  45. struct Except* onOOM;
  46. const char* file;
  47. int line;
  48. Identity
  49. };
  50. /**
  51. * Get a pointer which is aligned on memory boundries.
  52. *
  53. * @param pointer the location where the pointer should be.
  54. * @param alignedOn how big the word is that the boundry should be aligned on.
  55. */
  56. #define getAligned(pointer, alignedOn) \
  57. ((char*) ((uintptr_t)( ((char*)(pointer)) + (alignedOn) - 1) & ~ ((alignedOn) - 1)))
  58. /** @see Allocator_malloc() */
  59. static void* allocatorMalloc(unsigned long length,
  60. struct Allocator* allocator,
  61. const char* identFile,
  62. int identLine)
  63. {
  64. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
  65. char* pointer = getAligned((*context->pPointer), ALIGNMENT);
  66. char* endOfAlloc = pointer + length;
  67. if (endOfAlloc >= context->endPointer) {
  68. Except_raise(context->onOOM, -1, "BufferAllocator ran out of memory [%s:%d]",
  69. identFile, identLine);
  70. }
  71. if (endOfAlloc < *(context->pPointer)) {
  72. Except_raise(context->onOOM, -2, "BufferAllocator integer overflow [%s:%d]",
  73. identFile, identLine);
  74. }
  75. (*context->pPointer) = endOfAlloc;
  76. return (void*) pointer;
  77. }
  78. /** @see Allocator->calloc() */
  79. static void* allocatorCalloc(unsigned long length,
  80. unsigned long count,
  81. struct Allocator* allocator,
  82. const char* identFile,
  83. int identLine)
  84. {
  85. void* pointer = allocatorMalloc(length * count, allocator, identFile, identLine);
  86. Bits_memset(pointer, 0, length * count);
  87. return pointer;
  88. }
  89. /** @see Allocator->clone() */
  90. static void* allocatorClone(unsigned long length,
  91. struct Allocator* allocator,
  92. const void* toClone,
  93. const char* identFile,
  94. int identLine)
  95. {
  96. void* pointer = allocatorMalloc(length, allocator, identFile, identLine);
  97. Bits_memcpy(pointer, toClone, length);
  98. return pointer;
  99. }
  100. /** @see Allocator->realloc() */
  101. static void* allocatorRealloc(const void* original,
  102. unsigned long length,
  103. struct Allocator* allocator,
  104. const char* identFile,
  105. int identLine)
  106. {
  107. if (original == NULL) {
  108. return allocatorMalloc(length, allocator, identFile, identLine);
  109. }
  110. // Need to pointer to make sure we dont copy too much.
  111. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
  112. char* pointer = *context->pPointer;
  113. uint32_t amountToClone = (length < (uint32_t)(pointer - (char*)original))
  114. ? length
  115. : (uint32_t)(pointer - (char*)original);
  116. // The likelyhood of nothing having been allocated since is
  117. // almost 0 so we will always create a new
  118. // allocation and copy into it.
  119. void* newAlloc = allocatorMalloc(length, allocator, identFile, identLine);
  120. Bits_memcpy(newAlloc, original, amountToClone);
  121. return newAlloc;
  122. }
  123. /** @see Allocator->free() */
  124. static void freeAllocator(struct Allocator* allocator, const char* identFile, int identLine)
  125. {
  126. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
  127. struct Job* job = context->onFree;
  128. while (job != NULL) {
  129. if (job->generic.callback) {
  130. job->generic.callback(&job->generic);
  131. context->outstandingJobs++;
  132. }
  133. job = job->next;
  134. }
  135. }
  136. static int removeOnFreeJob(struct Allocator_OnFreeJob* toRemove)
  137. {
  138. struct Job* j = Identity_cast((struct Job*) toRemove);
  139. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) j->alloc);
  140. struct Job** jobPtr = &(context->onFree);
  141. while (*jobPtr != NULL) {
  142. if (*jobPtr == j) {
  143. *jobPtr = (*jobPtr)->next;
  144. return 0;
  145. }
  146. jobPtr = &(*jobPtr)->next;
  147. }
  148. return -1;
  149. }
  150. static int onFreeComplete(struct Allocator_OnFreeJob* job)
  151. {
  152. struct BufferAllocator* context =
  153. Identity_cast((struct BufferAllocator*) ((struct Job*)job)->alloc);
  154. if (!--context->outstandingJobs) {
  155. if ((uintptr_t) context > (uintptr_t) context->pPointer) {
  156. // pPointer points to a destination which is > context unless this is a child alloc.
  157. return 0;
  158. }
  159. // complete
  160. (*context->pPointer) = context->basePointer;
  161. }
  162. return 0;
  163. }
  164. /** @see Allocator->onFree() */
  165. static struct Allocator_OnFreeJob* onFree(struct Allocator* alloc,
  166. const char* file,
  167. int line)
  168. {
  169. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
  170. struct Job* newJob = Allocator_clone(alloc, (&(struct Job) {
  171. .generic = {
  172. .cancel = removeOnFreeJob,
  173. .complete = onFreeComplete
  174. },
  175. .alloc = alloc,
  176. }));
  177. Identity_set(&newJob->generic);
  178. struct Job* job = context->onFree;
  179. if (job == NULL) {
  180. context->onFree = newJob;
  181. } else {
  182. while (job->next != NULL) {
  183. job = job->next;
  184. }
  185. job->next = newJob;
  186. }
  187. return &newJob->generic;
  188. }
  189. /** @see Allocator_child() */
  190. static struct Allocator* childAllocator(struct Allocator* alloc,
  191. const char* file,
  192. int line)
  193. {
  194. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
  195. struct BufferAllocator* child =
  196. allocatorClone(sizeof(struct BufferAllocator), alloc, context, file, line);
  197. child->file = file;
  198. child->line = line;
  199. return &child->generic;
  200. }
  201. static void adopt(struct Allocator* alloc, struct Allocator* allocB, const char* file, int line)
  202. {
  203. Assert_always(!"Unimplemented");
  204. }
  205. /** @see BufferAllocator.h */
  206. struct Allocator* BufferAllocator_newWithIdentity(void* buffer,
  207. unsigned long length,
  208. char* file,
  209. int line)
  210. {
  211. struct FirstAlloc {
  212. struct BufferAllocator alloc;
  213. char* pointer;
  214. };
  215. struct FirstAlloc tempAlloc = {
  216. .alloc = {
  217. .generic = {
  218. .free = freeAllocator,
  219. .malloc = allocatorMalloc,
  220. .calloc = allocatorCalloc,
  221. .clone = allocatorClone,
  222. .realloc = allocatorRealloc,
  223. .child = childAllocator,
  224. .onFree = onFree,
  225. .adopt = adopt
  226. },
  227. // Align the pointer to do the first write manually.
  228. .pPointer = NULL,
  229. .basePointer = getAligned(buffer, sizeof(char*)),
  230. .endPointer = ((char*)buffer) + length,
  231. .file = file,
  232. .line = line
  233. },
  234. .pointer = getAligned(buffer, sizeof(char*))
  235. };
  236. tempAlloc.alloc.pPointer = &tempAlloc.pointer;
  237. Identity_set(&tempAlloc.alloc);
  238. if (tempAlloc.alloc.endPointer < (*tempAlloc.alloc.pPointer)) {
  239. // int64_t overflow.
  240. return NULL;
  241. }
  242. if (length + (char*) buffer < (*tempAlloc.alloc.pPointer) + sizeof(struct BufferAllocator)) {
  243. // Not enough space to allocate the context.
  244. return NULL;
  245. }
  246. struct FirstAlloc* alloc = (struct FirstAlloc*) (*tempAlloc.alloc.pPointer);
  247. Bits_memcpyConst(alloc, &tempAlloc, sizeof(struct FirstAlloc));
  248. alloc->pointer += sizeof(struct FirstAlloc);
  249. alloc->alloc.pPointer = &alloc->pointer;
  250. return &alloc->alloc.generic;
  251. }
  252. void BufferAllocator_onOOM(struct Allocator* alloc,
  253. struct Except* exceptionHandler)
  254. {
  255. struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
  256. context->onOOM = exceptionHandler;
  257. }