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