mem.h raw

   1  // SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
   2  
   3  layout(set = 0, binding = 0) buffer Memory {
   4      // offset into memory of the next allocation, initialized by the user.
   5      uint mem_offset;
   6      // mem_error tracks the status of memory accesses, initialized to NO_ERROR
   7      // by the user. ERR_MALLOC_FAILED is reported for insufficient memory.
   8      // If MEM_DEBUG is defined the following errors are reported:
   9      // - ERR_OUT_OF_BOUNDS is reported for out of bounds writes.
  10      // - ERR_UNALIGNED_ACCESS for memory access not aligned to 32-bit words.
  11      uint mem_error;
  12      uint[] memory;
  13  };
  14  
  15  // Uncomment this line to add the size field to Alloc and enable memory checks.
  16  // Note that the Config struct in setup.h grows size fields as well.
  17  //#define MEM_DEBUG
  18  
  19  #define NO_ERROR 0
  20  #define ERR_MALLOC_FAILED 1
  21  #define ERR_OUT_OF_BOUNDS 2
  22  #define ERR_UNALIGNED_ACCESS 3
  23  
  24  #ifdef MEM_DEBUG
  25  #define Alloc_size 16
  26  #else
  27  #define Alloc_size 8
  28  #endif
  29  
  30  // Alloc represents a memory allocation.
  31  struct Alloc {
  32      // offset in bytes into memory.
  33      uint offset;
  34  #ifdef MEM_DEBUG
  35      // size in bytes of the allocation.
  36      uint size;
  37  #endif
  38  };
  39  
  40  struct MallocResult {
  41      Alloc alloc;
  42      // failed is true if the allocation overflowed memory.
  43      bool failed;
  44  };
  45  
  46  // new_alloc synthesizes an Alloc from an offset and size.
  47  Alloc new_alloc(uint offset, uint size, bool mem_ok) {
  48      Alloc a;
  49      a.offset = offset;
  50  #ifdef MEM_DEBUG
  51      if (mem_ok) {
  52          a.size = size;
  53      } else {
  54          a.size = 0;
  55      }
  56  #endif
  57      return a;
  58  }
  59  
  60  // malloc allocates size bytes of memory.
  61  MallocResult malloc(uint size) {
  62      MallocResult r;
  63      uint offset = atomicAdd(mem_offset, size);
  64      r.failed = offset + size > memory.length() * 4;
  65      r.alloc = new_alloc(offset, size, !r.failed);
  66      if (r.failed) {
  67          atomicMax(mem_error, ERR_MALLOC_FAILED);
  68          return r;
  69      }
  70  #ifdef MEM_DEBUG
  71      if ((size & 3) != 0) {
  72          r.failed = true;
  73          atomicMax(mem_error, ERR_UNALIGNED_ACCESS);
  74          return r;
  75      }
  76  #endif
  77      return r;
  78  }
  79  
  80  // touch_mem checks whether access to the memory word at offset is valid.
  81  // If MEM_DEBUG is defined, touch_mem returns false if offset is out of bounds.
  82  // Offset is in words.
  83  bool touch_mem(Alloc alloc, uint offset) {
  84  #ifdef MEM_DEBUG
  85      if (offset < alloc.offset/4 || offset >= (alloc.offset + alloc.size)/4) {
  86          atomicMax(mem_error, ERR_OUT_OF_BOUNDS);
  87          return false;
  88      }
  89  #endif
  90      return true;
  91  }
  92  
  93  // write_mem writes val to memory at offset.
  94  // Offset is in words.
  95  void write_mem(Alloc alloc, uint offset, uint val) {
  96      if (!touch_mem(alloc, offset)) {
  97          return;
  98      }
  99      memory[offset] = val;
 100  }
 101  
 102  // read_mem reads the value from memory at offset.
 103  // Offset is in words.
 104  uint read_mem(Alloc alloc, uint offset) {
 105      if (!touch_mem(alloc, offset)) {
 106          return 0;
 107      }
 108      uint v = memory[offset];
 109      return v;
 110  }
 111  
 112  // slice_mem returns a sub-allocation inside another. Offset and size are in
 113  // bytes, relative to a.offset.
 114  Alloc slice_mem(Alloc a, uint offset, uint size) {
 115  #ifdef MEM_DEBUG
 116      if ((offset & 3) != 0 || (size & 3) != 0) {
 117          atomicMax(mem_error, ERR_UNALIGNED_ACCESS);
 118          return Alloc(0, 0);
 119      }
 120      if (offset + size > a.size) {
 121          // slice_mem is sometimes used for slices outside bounds,
 122          // but never written.
 123          return Alloc(0, 0);
 124      }
 125      return Alloc(a.offset + offset, size);
 126  #else
 127      return Alloc(a.offset + offset);
 128  #endif
 129  }
 130  
 131  // alloc_write writes alloc to memory at offset bytes.
 132  void alloc_write(Alloc a, uint offset, Alloc alloc) {
 133      write_mem(a, offset >> 2, alloc.offset);
 134  #ifdef MEM_DEBUG
 135      write_mem(a, (offset >> 2) + 1, alloc.size);
 136  #endif
 137  }
 138  
 139  // alloc_read reads an Alloc from memory at offset bytes.
 140  Alloc alloc_read(Alloc a, uint offset) {
 141      Alloc alloc;
 142      alloc.offset = read_mem(a, offset >> 2);
 143  #ifdef MEM_DEBUG
 144      alloc.size = read_mem(a, (offset >> 2) + 1);
 145  #endif
 146      return alloc;
 147  }
 148