├── .gitignore ├── example1.c ├── example4.c ├── example3.c ├── example2.c ├── README.md ├── example5.c ├── example4.js ├── example1.js ├── example2.js ├── example3.js ├── Makefile ├── example5.js └── memory-allocation.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.wat 3 | .vscode 4 | -------------------------------------------------------------------------------- /example1.c: -------------------------------------------------------------------------------- 1 | __attribute__((used)) int addTwoInts (int a, int b) { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /example4.c: -------------------------------------------------------------------------------- 1 | extern void someFunction(int i); 2 | 3 | __attribute__((used)) void callSomeFunction (int i) 4 | { 5 | someFunction(i); 6 | } 7 | -------------------------------------------------------------------------------- /example3.c: -------------------------------------------------------------------------------- 1 | __attribute__((used)) void addArraysInt32 (int *array1, int* array2, int* result, int length) 2 | { 3 | for (int i = 0; i < length; ++i) { 4 | result[i] = array1[i] + array2[i]; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example2.c: -------------------------------------------------------------------------------- 1 | __attribute__((used)) int sumArrayInt32 (int *array, int length) { 2 | int total = 0; 3 | 4 | for (int i = 0; i < length; ++i) { 5 | total += array[i]; 6 | } 7 | 8 | return total; 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to Pass Arrays Between JavaScript and WebAssembly Modules Written in C 2 | 3 | This project explores passing arrays between JavaScript and WebAssembly. 4 | 5 | For more information read the 6 | [blog post](https://rob-blackbourn.github.io/blog/webassembly/wasm/array/arrays/javascript/c/2020/06/07/wasm-arrays.html). 7 | -------------------------------------------------------------------------------- /example5.c: -------------------------------------------------------------------------------- 1 | extern void *allocateMemory(unsigned bytes_required); 2 | 3 | __attribute__((used)) int* addArrays (int *array1, int* array2, int length) 4 | { 5 | int* result = allocateMemory(length * sizeof(int)); 6 | 7 | for (int i = 0; i < length; ++i) { 8 | result[i] = array1[i] + array2[i]; 9 | } 10 | 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /example4.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | async function main() { 4 | const buf = fs.readFileSync('./example4.wasm') 5 | 6 | const res = await WebAssembly.instantiate(buf, { 7 | env: { 8 | someFunction: function (i) { 9 | console.log(`someFunction called with ${i}`) 10 | } 11 | } 12 | }) 13 | 14 | const { callSomeFunction } = res.instance.exports 15 | 16 | callSomeFunction(42) 17 | } 18 | 19 | 20 | main().then(() => console.log('Done')) -------------------------------------------------------------------------------- /example1.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | async function main() { 4 | // Read the wasm file. 5 | const buf = fs.readFileSync('./example1.wasm') 6 | 7 | // Create a WebAssembly instance from the wasm. 8 | const res = await WebAssembly.instantiate(buf, {}) 9 | 10 | // Get the function to call. 11 | const { addTwoInts } = res.instance.exports 12 | 13 | // Call the function. 14 | const a = 38, b = 4 15 | const result = addTwoInts(a, b) 16 | console.log(`${a} + ${b} = ${result}`) 17 | } 18 | 19 | main().then(() => console.log('Done')) -------------------------------------------------------------------------------- /example2.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | async function main() { 4 | // Load the wasm into a buffer. 5 | const buf = fs.readFileSync('./example2.wasm') 6 | 7 | // Instantiate the wasm. 8 | const res = await WebAssembly.instantiate(buf, {}) 9 | 10 | // Get the function out of the exports. 11 | const { sumArrayInt32, memory } = res.instance.exports 12 | 13 | // Create an array that can be passed to the WebAssembly instance. 14 | const array = new Int32Array(memory.buffer, 0, 5) 15 | array.set([3, 15, 18, 4, 2]) 16 | 17 | // Call the function and display the results. 18 | const result = sumArrayInt32(array.byteOffset, array.length) 19 | console.log(`sum([${array.join(',')}]) = ${result}`) 20 | 21 | // This does the same thing! 22 | if (result == sumArrayInt32(0, 5)) { 23 | console.log(`Memory is an integer array starting at 0`) 24 | } 25 | } 26 | 27 | main().then(() => console.log('Done')) -------------------------------------------------------------------------------- /example3.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | async function main() { 4 | // Load the wasm into a buffer. 5 | const buf = fs.readFileSync('./example3.wasm') 6 | 7 | // Make an instance. 8 | const res = await WebAssembly.instantiate(buf, {}) 9 | 10 | // Get function. 11 | const { addArraysInt32, memory } = res.instance.exports 12 | 13 | // Create the arrays. 14 | const length = 5 15 | 16 | let offset = 0 17 | const array1 = new Int32Array(memory.buffer, offset, length) 18 | array1.set([1, 2, 3, 4, 5]) 19 | 20 | offset += length * Int32Array.BYTES_PER_ELEMENT 21 | const array2 = new Int32Array(memory.buffer, offset, length) 22 | array2.set([6, 7, 8, 9, 10]) 23 | 24 | offset += length * Int32Array.BYTES_PER_ELEMENT 25 | const result = new Int32Array(memory.buffer, offset, length) 26 | 27 | // Call the function. 28 | addArraysInt32( 29 | array1.byteOffset, 30 | array2.byteOffset, 31 | result.byteOffset, 32 | length) 33 | 34 | // Show the results. 35 | console.log(`[${array1.join(", ")}] + [${array2.join(", ")}] = [${result.join(", ")}]`) 36 | } 37 | 38 | 39 | main().then(() => console.log('Done')) -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=/opt/clang/bin/clang 2 | CCFLAGS=--target=wasm32-unknown-unknown-wasm --optimize=3 -nostdlib 3 | LDFLAGS=-Wl,--export-all -Wl,--no-entry -Wl,--allow-undefined 4 | 5 | WASM2WAT=/opt/wabt/bin/wasm2wat 6 | 7 | .PHONY: all 8 | 9 | all: example1.wat example2.wat example3.wat example4.wat example5.wat 10 | 11 | example1.wat: example1.wasm 12 | $(WASM2WAT) example1.wasm -o example1.wat 13 | 14 | example1.wasm: example1.c 15 | $(CC) example1.c $(CCFLAGS) $(LDFLAGS) --output example1.wasm 16 | 17 | example2.wat: example2.wasm 18 | $(WASM2WAT) example2.wasm -o example2.wat 19 | 20 | example2.wasm: example2.c 21 | $(CC) example2.c $(CCFLAGS) $(LDFLAGS) --output example2.wasm 22 | 23 | example3.wat: example3.wasm 24 | $(WASM2WAT) example3.wasm -o example3.wat 25 | 26 | example3.wasm: example3.c 27 | $(CC) example3.c $(CCFLAGS) $(LDFLAGS) --output example3.wasm 28 | 29 | example4.wat: example4.wasm 30 | $(WASM2WAT) example4.wasm -o example4.wat 31 | 32 | example4.wasm: example4.c 33 | $(CC) example4.c $(CCFLAGS) $(LDFLAGS) --output example4.wasm 34 | 35 | example5.wat: example5.wasm 36 | $(WASM2WAT) example5.wasm -o example5.wat 37 | 38 | example5.wasm: example5.c memory-allocation.c 39 | $(CC) example5.c memory-allocation.c $(CCFLAGS) $(LDFLAGS) --output example5.wasm 40 | 41 | clean: 42 | rm -f example1.wasm 43 | rm -f example1.wat 44 | rm -f example2.wasm 45 | rm -f example2.wat 46 | rm -f example3.wasm 47 | rm -f example3.wat 48 | rm -f example4.wasm 49 | rm -f example4.wat 50 | rm -f example5.wasm 51 | rm -f example5.wat 52 | -------------------------------------------------------------------------------- /example5.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | /* 4 | * A simple memory manager. 5 | */ 6 | class MemoryManager { 7 | // The WebAssembly.Memory object for the instance. 8 | memory = null 9 | 10 | // Return the buffer length in bytes. 11 | memoryBytesLength() { 12 | return this.memory.buffer.byteLength 13 | } 14 | 15 | // Grow the memory by the requested blocks, returning the new buffer length 16 | // in bytes. 17 | grow(blocks) { 18 | this.memory.grow(blocks) 19 | return this.memory.buffer.byteLength 20 | } 21 | } 22 | 23 | async function main() { 24 | // Read the wasm file. 25 | const buf = fs.readFileSync('./example5.wasm') 26 | 27 | // Create an object to manage the memory. 28 | const memoryManager = new MemoryManager() 29 | 30 | // Instantiate the wasm module. 31 | const res = await WebAssembly.instantiate(buf, { 32 | env: { 33 | // The wasm module calls this function to grow the memory 34 | grow: function(blocks) { 35 | memoryManager.grow(blocks) 36 | }, 37 | // The wasm module calls this function to get the current memory size. 38 | memoryBytesLength: function() { 39 | memoryManager.memoryBytesLength() 40 | } 41 | } 42 | }) 43 | 44 | // Get the memory exports from the wasm instance. 45 | const { 46 | memory, 47 | allocateMemory, 48 | freeMemory, 49 | reportFreeMemory 50 | } = res.instance.exports 51 | 52 | // Give the memory manager access to the instances memory. 53 | memoryManager.memory = memory 54 | 55 | // How many free bytes are there? 56 | const startFreeMemoryBytes = reportFreeMemory() 57 | console.log(`There are ${startFreeMemoryBytes} bytes of free memory`) 58 | 59 | // Get the exported array function. 60 | const { 61 | addArrays 62 | } = res.instance.exports 63 | 64 | // Make the arrays to pass into the wasm function using allocateMemory. 65 | const length = 5 66 | const bytesLength = length * Int32Array.BYTES_PER_ELEMENT 67 | 68 | const array1 = new Int32Array(memory.buffer, allocateMemory(bytesLength), length) 69 | array1.set([1, 2, 3, 4, 5]) 70 | 71 | const array2 = new Int32Array(memory.buffer, allocateMemory(bytesLength), length) 72 | array2.set([6, 7, 8, 9, 10]) 73 | 74 | // Add the arrays. The result is the memory pointer to the result array. 75 | result = new Int32Array( 76 | memory.buffer, 77 | addArrays(array1.byteOffset, array2.byteOffset, length), 78 | length) 79 | 80 | console.log(`[${array1.join(", ")}] + [${array2.join(", ")}] = [${result.join(", ")}]`) 81 | 82 | // Show that some memory has been used. 83 | pctFree = 100 * reportFreeMemory() / startFreeMemoryBytes 84 | console.log(`Free memory ${pctFree}%`) 85 | 86 | // Free the memory. 87 | freeMemory(array1.byteOffset) 88 | freeMemory(array2.byteOffset) 89 | freeMemory(result.byteOffset) 90 | 91 | // Show that all the memory has been released. 92 | pctFree = 100 * reportFreeMemory() / startFreeMemoryBytes 93 | console.log(`Free memory ${pctFree}%`) 94 | } 95 | 96 | main().then(() => console.log('Done')) -------------------------------------------------------------------------------- /memory-allocation.c: -------------------------------------------------------------------------------- 1 | #define NULL 0 2 | #define BLKSIZ 65536 3 | 4 | extern long grow(int blocks); 5 | extern unsigned int memoryBytesLength(); 6 | 7 | void *allocateMemory (unsigned bytes_required); 8 | void freeMemory(void *startOfMemoryToFree); 9 | 10 | typedef long align; 11 | 12 | // header of an allocation block 13 | typedef union header { 14 | struct { 15 | union header *next; // Pointer to circular successor 16 | unsigned int size; // Size of the block 17 | } value; 18 | long align; // Forces block alignment 19 | } header_t; 20 | 21 | static header_t *free_list = (header_t*)NULL; 22 | static unsigned int initial_offset = 8; // preserve address 0 for null and pad by 4 bytes. 23 | static int is_initialised = 0; 24 | 25 | static header_t* getMoreMemory(unsigned bytes_required) 26 | { 27 | // We need to add the header to the bytes required. 28 | bytes_required += sizeof(header_t); 29 | 30 | // The memory gets delivered in blocks. Ensure we get enough. 31 | unsigned int blocks = bytes_required / BLKSIZ; 32 | if (blocks * BLKSIZ < bytes_required) 33 | blocks += 1; 34 | unsigned int start_of_new_memory = memoryBytesLength(); 35 | long end_of_new_memory = grow(blocks); 36 | 37 | if (end_of_new_memory == 0) // grow returns 0 in the event of an error 38 | return NULL; 39 | 40 | // Create the block to insert. 41 | header_t* block_to_insert = (header_t *) start_of_new_memory; 42 | block_to_insert->value.size = end_of_new_memory - start_of_new_memory - sizeof(header_t); 43 | block_to_insert->value.next = NULL; 44 | 45 | // add to the free list 46 | freeMemory((void *) (block_to_insert + 1)); 47 | 48 | return free_list; 49 | } 50 | 51 | static void ensureInitialised() 52 | { 53 | if (is_initialised == 0) 54 | { 55 | is_initialised = 1; 56 | 57 | // initialise the memory allocator. 58 | unsigned int bytes_length = memoryBytesLength(); 59 | 60 | // Start at 1 to save 0 for NULL. 61 | header_t* unallocated = (header_t*) initial_offset; 62 | unallocated->value.size = bytes_length - (sizeof(header_t) + initial_offset); 63 | unallocated->value.next = NULL; 64 | 65 | free_list = unallocated; 66 | } 67 | } 68 | 69 | __attribute__((used)) void *allocateMemory(unsigned bytes_required) 70 | { 71 | ensureInitialised(); 72 | 73 | // Pad to 8 bytes until I find a better solution. 74 | bytes_required += (8 - bytes_required % 8) % 8; 75 | unsigned int bytes_required_plus_header = bytes_required + sizeof(header_t); 76 | 77 | if (free_list == NULL) 78 | { 79 | free_list = getMoreMemory(bytes_required_plus_header); 80 | if (free_list == NULL) 81 | return NULL; 82 | } 83 | 84 | header_t* current = free_list; 85 | header_t* previous = current; 86 | while (current != NULL) 87 | { 88 | if (current->value.size == bytes_required) 89 | { 90 | // exact match 91 | if (current == free_list) 92 | { 93 | // allocate all of the free list 94 | free_list = NULL; 95 | current->value.next = NULL; 96 | return current + 1; 97 | } 98 | else 99 | { 100 | // remove the block 101 | previous->value.next = current->value.next; 102 | current->value.next = NULL; 103 | return current + 1; 104 | } 105 | } 106 | else if (current->value.size > bytes_required) 107 | { 108 | // split the bigger block 109 | 110 | // create the unallocated portion 111 | header_t* unallocated = (header_t*)((char*)current + bytes_required_plus_header); 112 | unallocated->value.size = current->value.size - bytes_required_plus_header; 113 | unallocated->value.next = current->value.next; 114 | 115 | if (current == free_list) 116 | { 117 | // We are at the start of the list so make the free listthe unallocated block. 118 | free_list = unallocated; 119 | } 120 | else 121 | { 122 | // We past the start of the list so remove the current block. 123 | previous->value.next = unallocated; 124 | } 125 | 126 | // prepare the allocated portion. 127 | current->value.size = bytes_required; 128 | current->value.next = NULL; 129 | 130 | return current + 1; 131 | } 132 | 133 | previous = current; 134 | current = current->value.next; 135 | } 136 | 137 | // No block was big enough. Grow the memory and try again 138 | if (getMoreMemory(bytes_required) == NULL) 139 | return NULL; 140 | 141 | return allocateMemory(bytes_required_plus_header); 142 | } 143 | 144 | __attribute__((used)) void freeMemory(void *ptr) 145 | { 146 | ensureInitialised(); 147 | 148 | if (ptr == NULL) 149 | return; 150 | 151 | header_t* unallocated = ((header_t *) ptr) - 1; 152 | 153 | if (free_list == NULL) 154 | { 155 | // If the free list is null the unallocated block becomes the free list. 156 | free_list = unallocated; 157 | return; 158 | } 159 | 160 | // Find the place in the free list where the unallocated block should be inserted. 161 | header_t* current = free_list; 162 | header_t* previous = current; 163 | while (current != NULL) 164 | { 165 | if (unallocated > previous && unallocated < current) 166 | break; // The unallocated block is between the previous and current. 167 | else if (current == previous && unallocated < current) 168 | { 169 | // There is only one block in the list and it is after the unallocated. 170 | previous = NULL; 171 | break; 172 | } 173 | 174 | // Step forward. 175 | previous = current; 176 | current = current->value.next; 177 | } 178 | 179 | // Attach the unallocated block to the current block. 180 | if (current != NULL) 181 | { 182 | // Are the blocks adjacent? 183 | if (current == (header_t*)((char*)(unallocated + 1) + unallocated->value.size)) 184 | { 185 | // Merge the unallocated with the current block. 186 | unallocated->value.size += current->value.size + sizeof(header_t); 187 | unallocated->value.next = current->value.next; 188 | } 189 | else 190 | { 191 | // Chain the unallocated block to the current. 192 | unallocated->value.next = current; 193 | } 194 | } 195 | 196 | if (previous == NULL) 197 | { 198 | // The unallocated block now starts the free list. 199 | free_list = unallocated; 200 | } 201 | else 202 | { 203 | // Are the blocks adjacent? 204 | if (unallocated == (header_t*)((char*)(previous + 1) + previous->value.size)) 205 | { 206 | // Merge the previous block with the unallocated. 207 | previous->value.size += unallocated->value.size + sizeof(header_t); 208 | previous->value.next = unallocated->value.next; 209 | } 210 | else 211 | { 212 | // Chain the previous block to the unallocated. 213 | previous->value.next = unallocated; 214 | } 215 | } 216 | } 217 | 218 | __attribute__((used)) double reportFreeMemory() 219 | { 220 | ensureInitialised(); 221 | 222 | if (free_list == NULL) 223 | return 0; 224 | 225 | header_t* current = free_list; 226 | unsigned int total = 0; 227 | while (current != NULL) 228 | { 229 | total += current->value.size; 230 | current = current->value.next; 231 | } 232 | 233 | return (double)total; 234 | } --------------------------------------------------------------------------------