diff --git a/README.txt b/README.txt index 61a442c..6dfffcf 100644 --- a/README.txt +++ b/README.txt @@ -15,16 +15,21 @@ EXAMPLE: with Radix3 routing, parameter handling, and various response types. MODULES: - - Ant.serve() - HTTP server - - import() - ESM module loading - - Timers - setTimeout, setInterval, queueMicrotask - - fetch() - HTTP client - - crypto.* - Cryptography - - JSON.* - JSON parsing - - console.* - Logging + - Ant.serve() - HTTP server + - import() - ESM module loading + - Timers - setTimeout, setInterval, queueMicrotask + - fetch() - HTTP client + - crypto.* - Cryptography + - JSON.* - JSON parsing + - console.* - Logging + - Atomics.* - Atomic operations (add, sub, and, or, xor, etc.) + - SharedArrayBuffer - Shared memory buffers + - TypedArrays - Int8Array, Uint8Array, Int32Array, etc. FEATURES: - Async/await and Promises - ES6+ syntax (arrow functions, classes, template literals) + - Number literals (binary 0b1010, octal 0o755, hex 0xFF) + - Atomic operations for concurrent programming - Signal handlers (SIGINT, SIGTERM, etc.) - Embedded garbage collector \ No newline at end of file diff --git a/examples/atomics.js b/examples/atomics.js new file mode 100644 index 0000000..c3b17a0 --- /dev/null +++ b/examples/atomics.js @@ -0,0 +1,88 @@ +console.log('1. Creating SharedArrayBuffer'); +const sharedBuffer = new SharedArrayBuffer(256); +console.log(' SharedArrayBuffer created with size:', sharedBuffer.byteLength, 'bytes\n'); + +console.log('2. Creating TypedArrays on SharedArrayBuffer'); +const int32View = new Int32Array(sharedBuffer); +const uint8View = new Uint8Array(sharedBuffer); +console.log(' Int32Array length:', int32View.length); +console.log(' Uint8Array length:', uint8View.length, '\n'); + +console.log('3. Basic atomic store and load'); +Atomics.store(int32View, 0, 42); +const value = Atomics.load(int32View, 0); +console.log(' Stored 42, loaded:', value, '\n'); + +console.log('4. Atomic add operation'); +Atomics.store(int32View, 1, 10); +const oldValue = Atomics.add(int32View, 1, 5); +const newValue = Atomics.load(int32View, 1); +console.log(' Old value:', oldValue); +console.log(' Added 5, new value:', newValue, '\n'); + +console.log('5. Compare and exchange'); +Atomics.store(int32View, 2, 100); +console.log(' Initial value:', Atomics.load(int32View, 2)); + +const result1 = Atomics.compareExchange(int32View, 2, 50, 200); +console.log(' Expected 50, got:', result1, '(no change)'); +console.log(' Current value:', Atomics.load(int32View, 2)); + +const result2 = Atomics.compareExchange(int32View, 2, 100, 200); +console.log(' Expected 100, got:', result2, '(changed!)'); +console.log(' Current value:', Atomics.load(int32View, 2), '\n'); + +console.log('6. Bitwise operations'); +Atomics.store(uint8View, 0, 0b11110000); +console.log(' Initial: 0b' + Atomics.load(uint8View, 0).toString(2).padStart(8, '0')); + +Atomics.and(uint8View, 0, 0b00111100); +console.log(' After AND with 0b00111100: 0b' + Atomics.load(uint8View, 0).toString(2).padStart(8, '0')); + +Atomics.or(uint8View, 0, 0b00000011); +console.log(' After OR with 0b00000011: 0b' + Atomics.load(uint8View, 0).toString(2).padStart(8, '0')); + +Atomics.xor(uint8View, 0, 0b11111111); +console.log(' After XOR with 0b11111111: 0b' + Atomics.load(uint8View, 0).toString(2).padStart(8, '0'), '\n'); + +console.log('7. Lock-free operations check'); +console.log(' 1 byte:', Atomics.isLockFree(1) ? 'Lock-free ✓' : 'Uses locks'); +console.log(' 2 bytes:', Atomics.isLockFree(2) ? 'Lock-free ✓' : 'Uses locks'); +console.log(' 4 bytes:', Atomics.isLockFree(4) ? 'Lock-free ✓' : 'Uses locks'); +console.log(' 8 bytes:', Atomics.isLockFree(8) ? 'Lock-free ✓' : 'Uses locks', '\n'); + +console.log('8. Atomic counter example'); +Atomics.store(int32View, 10, 0); +console.log(' Counter initialized to:', Atomics.load(int32View, 10)); + +for (let i = 0; i < 10; i++) { + Atomics.add(int32View, 10, 1); +} +console.log(' After 10 atomic increments:', Atomics.load(int32View, 10), '\n'); + +console.log('9. Spin-lock pattern example'); +const LOCK_INDEX = 20; +const UNLOCKED = 0; +const LOCKED = 1; + +Atomics.store(int32View, LOCK_INDEX, UNLOCKED); +console.log(' Lock initialized'); + +function tryAcquireLock() { + return Atomics.compareExchange(int32View, LOCK_INDEX, UNLOCKED, LOCKED) === UNLOCKED; +} + +function releaseLock() { + Atomics.store(int32View, LOCK_INDEX, UNLOCKED); +} + +if (tryAcquireLock()) { + console.log(' Lock acquired ✓'); + console.log(' Performing critical operation...'); + releaseLock(); + console.log(' Lock released ✓'); +} else { + console.log(' Failed to acquire lock'); +} + +console.log('\n=== Example completed successfully ==='); diff --git a/include/modules/atomics.h b/include/modules/atomics.h new file mode 100644 index 0000000..bd46ef4 --- /dev/null +++ b/include/modules/atomics.h @@ -0,0 +1,30 @@ +#ifndef ATOMICS_H +#define ATOMICS_H + +#include +#include +#include + +void init_atomics_module(void); + +typedef struct WaitQueueEntry { + pthread_cond_t cond; + pthread_mutex_t mutex; + int32_t *address; + int notified; + struct WaitQueueEntry *next; +} WaitQueueEntry; + +typedef struct { + WaitQueueEntry *head; + pthread_mutex_t lock; +} WaitQueue; + +void wait_queue_init(WaitQueue *queue); +void wait_queue_cleanup(WaitQueue *queue); + +void wait_queue_add(WaitQueue *queue, WaitQueueEntry *entry); +void wait_queue_remove(WaitQueue *queue, WaitQueueEntry *entry); +int wait_queue_notify(WaitQueue *queue, int32_t *address, int count); + +#endif diff --git a/include/modules/buffer.h b/include/modules/buffer.h index 2044396..04d3029 100644 --- a/include/modules/buffer.h +++ b/include/modules/buffer.h @@ -11,6 +11,7 @@ typedef struct { size_t length; size_t capacity; int ref_count; + int is_shared; } ArrayBufferData; typedef enum { diff --git a/meson.build b/meson.build index 9f481b0..bd56a17 100644 --- a/meson.build +++ b/meson.build @@ -74,7 +74,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.0.8.3') +version_conf.set('ANT_VERSION', '0.0.8.4') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/ant.c b/src/ant.c index 4edf48d..d08425a 100644 --- a/src/ant.c +++ b/src/ant.c @@ -2245,9 +2245,38 @@ static uint8_t next(struct js *js) { js->tlen = 1; break; } + char *end; - js->tval = tov(strtod(buf, &end)); - jsoff_t numlen = (jsoff_t) (end - buf); + double value = 0; + jsoff_t numlen = 0; + + if (buf[0] == '0' && js->toff + 2 < js->clen) { + if (buf[1] == 'b' || buf[1] == 'B') { + numlen = 2; + while (js->toff + numlen < js->clen && (buf[numlen] == '0' || buf[numlen] == '1')) { + value = value * 2 + (buf[numlen] - '0'); + numlen++; + } + js->tval = tov(value); + } else if (buf[1] == 'o' || buf[1] == 'O') { + numlen = 2; + while (js->toff + numlen < js->clen && buf[numlen] >= '0' && buf[numlen] <= '7') { + value = value * 8 + (buf[numlen] - '0'); + numlen++; + } + js->tval = tov(value); + } else if (buf[1] == 'x' || buf[1] == 'X') { + js->tval = tov(strtod(buf, &end)); + numlen = (jsoff_t) (end - buf); + } else { + js->tval = tov(strtod(buf, &end)); + numlen = (jsoff_t) (end - buf); + } + } else { + js->tval = tov(strtod(buf, &end)); + numlen = (jsoff_t) (end - buf); + } + if (js->toff + numlen < js->clen && buf[numlen] == 'n') { js->tok = TOK_BIGINT; js->tlen = numlen + 1; diff --git a/src/main.c b/src/main.c index 3a1f686..5f7d165 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #include "modules/builtin.h" #include "modules/buffer.h" +#include "modules/atomics.h" #include "modules/io.h" #include "modules/fs.h" #include "modules/crypto.h" @@ -156,6 +157,7 @@ int main(int argc, char *argv[]) { init_builtin_module(); init_buffer_module(); + init_atomics_module(); init_crypto_module(); init_fetch_module(); init_console_module(); diff --git a/src/modules/atomics.c b/src/modules/atomics.c new file mode 100644 index 0000000..ca6e979 --- /dev/null +++ b/src/modules/atomics.c @@ -0,0 +1,841 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "ant.h" +#include "runtime.h" +#include "modules/buffer.h" +#include "modules/atomics.h" + +static WaitQueue global_wait_queue; +static pthread_once_t wait_queue_init_once = PTHREAD_ONCE_INIT; + +static void init_wait_queue(void) { + wait_queue_init(&global_wait_queue); +} + +void wait_queue_init(WaitQueue *queue) { + queue->head = NULL; + pthread_mutex_init(&queue->lock, NULL); +} + +void wait_queue_cleanup(WaitQueue *queue) { + pthread_mutex_lock(&queue->lock); + WaitQueueEntry *current = queue->head; + while (current) { + WaitQueueEntry *next = current->next; + pthread_cond_destroy(¤t->cond); + pthread_mutex_destroy(¤t->mutex); + free(current); + current = next; + } + queue->head = NULL; + pthread_mutex_unlock(&queue->lock); + pthread_mutex_destroy(&queue->lock); +} + +void wait_queue_add(WaitQueue *queue, WaitQueueEntry *entry) { + pthread_mutex_lock(&queue->lock); + entry->next = queue->head; + queue->head = entry; + pthread_mutex_unlock(&queue->lock); +} + +void wait_queue_remove(WaitQueue *queue, WaitQueueEntry *entry) { + pthread_mutex_lock(&queue->lock); + WaitQueueEntry **current = &queue->head; + while (*current) { + if (*current == entry) { + *current = entry->next; + break; + } + current = &(*current)->next; + } + pthread_mutex_unlock(&queue->lock); +} + +int wait_queue_notify(WaitQueue *queue, int32_t *address, int count) { + pthread_mutex_lock(&queue->lock); + int notified = 0; + WaitQueueEntry *current = queue->head; + + while (current && (count == -1 || notified < count)) { + if (current->address == address) { + pthread_mutex_lock(¤t->mutex); + current->notified = 1; + pthread_cond_signal(¤t->cond); + pthread_mutex_unlock(¤t->mutex); + notified++; + } + current = current->next; + } + + pthread_mutex_unlock(&queue->lock); + return notified; +} + +static bool get_atomic_array_data(struct js *js, jsval_t this_val, TypedArrayData **out_data, uint8_t **out_ptr) { + jsval_t ta_data_val = js_get(js, this_val, "_typedarray_data"); + if (js_type(ta_data_val) != JS_NUM) { + return false; + } + + TypedArrayData *ta_data = (TypedArrayData *)(uintptr_t)js_getnum(ta_data_val); + if (!ta_data || !ta_data->buffer) { + return false; + } + + *out_data = ta_data; + *out_ptr = ta_data->buffer->data + ta_data->byte_offset; + return true; +} + +// Atomics.add(typedArray, index, value) +static jsval_t js_atomics_add(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.add requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + int32_t old_value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + old_value = atomic_fetch_add(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + old_value = atomic_fetch_add(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + old_value = atomic_fetch_add(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + old_value = atomic_fetch_add(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + old_value = atomic_fetch_add(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + old_value = atomic_fetch_add(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic operations"); + } + + return js_mknum((double)old_value); +} + +// Atomics.and(typedArray, index, value) +static jsval_t js_atomics_and(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.and requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + int32_t old_value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + old_value = atomic_fetch_and(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + old_value = atomic_fetch_and(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + old_value = atomic_fetch_and(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + old_value = atomic_fetch_and(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + old_value = atomic_fetch_and(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + old_value = atomic_fetch_and(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic bitwise operations"); + } + + return js_mknum((double)old_value); +} + +// Atomics.compareExchange(typedArray, index, expectedValue, replacementValue) +static jsval_t js_atomics_compareExchange(struct js *js, jsval_t *args, int nargs) { + if (nargs < 4) { + return js_mkerr(js, "Atomics.compareExchange requires 4 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t expected = (int32_t)js_getnum(args[2]); + int32_t replacement = (int32_t)js_getnum(args[3]); + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + int8_t exp_i8 = (int8_t)expected; + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + atomic_compare_exchange_strong(atomic_ptr, &exp_i8, (int8_t)replacement); + expected = (int32_t)exp_i8; + break; + } + case TYPED_ARRAY_UINT8: { + uint8_t exp_u8 = (uint8_t)expected; + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + atomic_compare_exchange_strong(atomic_ptr, &exp_u8, (uint8_t)replacement); + expected = (int32_t)exp_u8; + break; + } + case TYPED_ARRAY_INT16: { + int16_t exp_i16 = (int16_t)expected; + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + atomic_compare_exchange_strong(atomic_ptr, &exp_i16, (int16_t)replacement); + expected = (int32_t)exp_i16; + break; + } + case TYPED_ARRAY_UINT16: { + uint16_t exp_u16 = (uint16_t)expected; + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + atomic_compare_exchange_strong(atomic_ptr, &exp_u16, (uint16_t)replacement); + expected = (int32_t)exp_u16; + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + atomic_compare_exchange_strong(atomic_ptr, &expected, replacement); + break; + } + case TYPED_ARRAY_UINT32: { + uint32_t exp_u32 = (uint32_t)expected; + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + atomic_compare_exchange_strong(atomic_ptr, &exp_u32, (uint32_t)replacement); + expected = (int32_t)exp_u32; + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic operations"); + } + + return js_mknum((double)expected); +} + +// Atomics.exchange(typedArray, index, value) +static jsval_t js_atomics_exchange(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.exchange requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + int32_t old_value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + old_value = atomic_exchange(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + old_value = atomic_exchange(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + old_value = atomic_exchange(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + old_value = atomic_exchange(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + old_value = atomic_exchange(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + old_value = atomic_exchange(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic operations"); + } + + return js_mknum((double)old_value); +} + +// Atomics.isLockFree(size) +static jsval_t js_atomics_isLockFree(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) { + return js_mkerr(js, "Atomics.isLockFree requires 1 argument"); + } + + int size = (int)js_getnum(args[0]); + bool is_lock_free = false; + + switch (size) { + case 1: + is_lock_free = ATOMIC_CHAR_LOCK_FREE == 2; + break; + case 2: + is_lock_free = ATOMIC_SHORT_LOCK_FREE == 2; + break; + case 4: + is_lock_free = ATOMIC_INT_LOCK_FREE == 2; + break; + case 8: + is_lock_free = ATOMIC_LLONG_LOCK_FREE == 2; + break; + default: + is_lock_free = false; + } + + return is_lock_free ? js_mktrue() : js_mkfalse(); +} + +// Atomics.load(typedArray, index) +static jsval_t js_atomics_load(struct js *js, jsval_t *args, int nargs) { + if (nargs < 2) { + return js_mkerr(js, "Atomics.load requires 2 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + value = atomic_load(atomic_ptr); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + value = atomic_load(atomic_ptr); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + value = atomic_load(atomic_ptr); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + value = atomic_load(atomic_ptr); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + value = atomic_load(atomic_ptr); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + value = atomic_load(atomic_ptr); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic operations"); + } + + return js_mknum((double)value); +} + +// Atomics.or(typedArray, index, value) +static jsval_t js_atomics_or(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.or requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + int32_t old_value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + old_value = atomic_fetch_or(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + old_value = atomic_fetch_or(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + old_value = atomic_fetch_or(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + old_value = atomic_fetch_or(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + old_value = atomic_fetch_or(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + old_value = atomic_fetch_or(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic bitwise operations"); + } + + return js_mknum((double)old_value); +} + +// Atomics.store(typedArray, index, value) +static jsval_t js_atomics_store(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.store requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + atomic_store(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + atomic_store(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + atomic_store(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + atomic_store(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + atomic_store(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + atomic_store(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic operations"); + } + + return js_mknum((double)value); +} + +// Atomics.sub(typedArray, index, value) +static jsval_t js_atomics_sub(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.sub requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + int32_t old_value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + old_value = atomic_fetch_sub(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + old_value = atomic_fetch_sub(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + old_value = atomic_fetch_sub(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + old_value = atomic_fetch_sub(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + old_value = atomic_fetch_sub(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + old_value = atomic_fetch_sub(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic operations"); + } + + return js_mknum((double)old_value); +} + +// Atomics.xor(typedArray, index, value) +static jsval_t js_atomics_xor(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.xor requires 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t value = (int32_t)js_getnum(args[2]); + int32_t old_value; + + switch (ta_data->type) { + case TYPED_ARRAY_INT8: { + _Atomic int8_t *atomic_ptr = (_Atomic int8_t *)(ptr + index); + old_value = atomic_fetch_xor(atomic_ptr, (int8_t)value); + break; + } + case TYPED_ARRAY_UINT8: { + _Atomic uint8_t *atomic_ptr = (_Atomic uint8_t *)(ptr + index); + old_value = atomic_fetch_xor(atomic_ptr, (uint8_t)value); + break; + } + case TYPED_ARRAY_INT16: { + _Atomic int16_t *atomic_ptr = (_Atomic int16_t *)(ptr + index * 2); + old_value = atomic_fetch_xor(atomic_ptr, (int16_t)value); + break; + } + case TYPED_ARRAY_UINT16: { + _Atomic uint16_t *atomic_ptr = (_Atomic uint16_t *)(ptr + index * 2); + old_value = atomic_fetch_xor(atomic_ptr, (uint16_t)value); + break; + } + case TYPED_ARRAY_INT32: { + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + old_value = atomic_fetch_xor(atomic_ptr, value); + break; + } + case TYPED_ARRAY_UINT32: { + _Atomic uint32_t *atomic_ptr = (_Atomic uint32_t *)(ptr + index * 4); + old_value = atomic_fetch_xor(atomic_ptr, (uint32_t)value); + break; + } + default: + return js_mkerr(js, "TypedArray type not supported for atomic bitwise operations"); + } + + return js_mknum((double)old_value); +} + +// Atomics.wait(typedArray, index, value, timeout) +static jsval_t js_atomics_wait(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.wait requires at least 3 arguments"); + } + + pthread_once(&wait_queue_init_once, init_wait_queue); + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + if (ta_data->type != TYPED_ARRAY_INT32) { + return js_mkerr(js, "Atomics.wait only works with Int32Array"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t expected_value = (int32_t)js_getnum(args[2]); + int64_t timeout_ms = -1; + + if (nargs > 3 && js_type(args[3]) == JS_NUM) { + timeout_ms = (int64_t)js_getnum(args[3]); + } + + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + int32_t current_value = atomic_load(atomic_ptr); + + if (current_value != expected_value) { + return js_mkstr(js, "not-equal", 9); + } + + WaitQueueEntry entry; + pthread_cond_init(&entry.cond, NULL); + pthread_mutex_init(&entry.mutex, NULL); + entry.address = (int32_t *)atomic_ptr; + entry.notified = 0; + entry.next = NULL; + + wait_queue_add(&global_wait_queue, &entry); + pthread_mutex_lock(&entry.mutex); + + const char *result = "ok"; + if (timeout_ms < 0) { + while (!entry.notified) pthread_cond_wait(&entry.cond, &entry.mutex); + } else { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout_ms / 1000; + ts.tv_nsec += (timeout_ms % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + + int wait_result = pthread_cond_timedwait(&entry.cond, &entry.mutex, &ts); + if (wait_result == ETIMEDOUT && !entry.notified) result = "timed-out"; + } + + pthread_mutex_unlock(&entry.mutex); + wait_queue_remove(&global_wait_queue, &entry); + + pthread_cond_destroy(&entry.cond); + pthread_mutex_destroy(&entry.mutex); + + return js_mkstr(js, result, strlen(result)); +} + +// Atomics.notify(typedArray, index, count) +static jsval_t js_atomics_notify(struct js *js, jsval_t *args, int nargs) { + if (nargs < 2) { + return js_mkerr(js, "Atomics.notify requires at least 2 arguments"); + } + + pthread_once(&wait_queue_init_once, init_wait_queue); + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + if (ta_data->type != TYPED_ARRAY_INT32) { + return js_mkerr(js, "Atomics.notify only works with Int32Array"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int count = -1; + if (nargs > 2 && js_type(args[2]) == JS_NUM) { + count = (int)js_getnum(args[2]); + } + + int32_t *address = (int32_t *)(ptr + index * 4); + int notified = wait_queue_notify(&global_wait_queue, address, count); + + return js_mknum((double)notified); +} + +// Atomics.waitAsync(typedArray, index, value, timeout) +static jsval_t js_atomics_waitAsync(struct js *js, jsval_t *args, int nargs) { + if (nargs < 3) { + return js_mkerr(js, "Atomics.waitAsync requires at least 3 arguments"); + } + + TypedArrayData *ta_data; + uint8_t *ptr; + if (!get_atomic_array_data(js, args[0], &ta_data, &ptr)) { + return js_mkerr(js, "First argument must be a TypedArray"); + } + + if (ta_data->type != TYPED_ARRAY_INT32) { + return js_mkerr(js, "Atomics.waitAsync only works with Int32Array"); + } + + size_t index = (size_t)js_getnum(args[1]); + if (index >= ta_data->length) { + return js_mkerr(js, "Index out of bounds"); + } + + int32_t expected_value = (int32_t)js_getnum(args[2]); + _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); + int32_t current_value = atomic_load(atomic_ptr); + + jsval_t result_obj = js_mkobj(js); + if (current_value != expected_value) { + js_set(js, result_obj, "async", js_mkfalse()); + js_set(js, result_obj, "value", js_mkstr(js, "not-equal", 9)); + return result_obj; + } + + jsval_t promise = js_mkpromise(js); + js_set(js, result_obj, "async", js_mktrue()); + js_set(js, result_obj, "value", promise); + js_resolve_promise(js, promise, js_mkstr(js, "ok", 2)); + + return result_obj; +} + +// Atomics.pause() +static jsval_t js_atomics_pause(struct js *js, jsval_t *args, int nargs) { + (void)js; + (void)args; + (void)nargs; + +#if defined(__x86_64__) || defined(__i386__) + __builtin_ia32_pause(); +#elif defined(__aarch64__) || defined(__arm__) + __asm__ __volatile__("yield"); +#endif + + return js_mkundef(); +} + +void init_atomics_module(void) { + struct js *js = rt->js; + + jsval_t glob = js_glob(js); + jsval_t atomics = js_mkobj(js); + + js_set(js, atomics, "add", js_mkfun(js_atomics_add)); + js_set(js, atomics, "and", js_mkfun(js_atomics_and)); + js_set(js, atomics, "compareExchange", js_mkfun(js_atomics_compareExchange)); + js_set(js, atomics, "exchange", js_mkfun(js_atomics_exchange)); + js_set(js, atomics, "isLockFree", js_mkfun(js_atomics_isLockFree)); + js_set(js, atomics, "load", js_mkfun(js_atomics_load)); + js_set(js, atomics, "notify", js_mkfun(js_atomics_notify)); + js_set(js, atomics, "or", js_mkfun(js_atomics_or)); + js_set(js, atomics, "pause", js_mkfun(js_atomics_pause)); + js_set(js, atomics, "store", js_mkfun(js_atomics_store)); + js_set(js, atomics, "sub", js_mkfun(js_atomics_sub)); + js_set(js, atomics, "wait", js_mkfun(js_atomics_wait)); + js_set(js, atomics, "waitAsync", js_mkfun(js_atomics_waitAsync)); + js_set(js, atomics, "xor", js_mkfun(js_atomics_xor)); + + js_set(js, atomics, "@@toStringTag", js_mkstr(js, "Atomics", 7)); + js_set(js, glob, "Atomics", atomics); +} diff --git a/src/modules/buffer.c b/src/modules/buffer.c index d837722..4ca15f1 100644 --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -95,6 +95,13 @@ static ArrayBufferData *create_array_buffer_data(size_t length) { data->length = length; data->capacity = length; data->ref_count = 1; + data->is_shared = 0; + return data; +} + +static ArrayBufferData *create_shared_array_buffer_data(size_t length) { + ArrayBufferData *data = create_array_buffer_data(length); + if (data) data->is_shared = 1; return data; } @@ -672,6 +679,26 @@ static jsval_t js_buffer_write(struct js *js, jsval_t *args, int nargs) { return js_mknum((double)to_write); } +static jsval_t js_sharedarraybuffer_constructor(struct js *js, jsval_t *args, int nargs) { + size_t length = 0; + if (nargs > 0 && js_type(args[0]) == JS_NUM) { + length = (size_t)js_getnum(args[0]); + } + + ArrayBufferData *data = create_shared_array_buffer_data(length); + if (!data) { + return js_mkerr(js, "Failed to allocate SharedArrayBuffer"); + } + + jsval_t obj = js_mkobj(js); + js_set(js, obj, "_arraybuffer_data", js_mknum((double)(uintptr_t)data)); + js_set(js, obj, "_shared", js_mktrue()); + js_set(js, obj, "byteLength", js_mknum((double)length)); + js_set(js, obj, "slice", js_mkfun(js_arraybuffer_slice)); + + return obj; +} + void init_buffer_module() { struct js *js = rt->js; jsval_t glob = js_glob(js); @@ -714,6 +741,12 @@ void init_buffer_module() { js_set(js, dataview_constructor, "prototype", dataview_proto); js_set(js, glob, "DataView", dataview_constructor); + jsval_t sharedarraybuffer_constructor = js_mkfun(js_sharedarraybuffer_constructor); + jsval_t sharedarraybuffer_proto = js_mkobj(js); + js_set(js, sharedarraybuffer_proto, "slice", js_mkfun(js_arraybuffer_slice)); + js_set(js, sharedarraybuffer_constructor, "prototype", sharedarraybuffer_proto); + js_set(js, glob, "SharedArrayBuffer", sharedarraybuffer_constructor); + jsval_t buffer_obj = js_mkobj(js); js_set(js, buffer_obj, "from", js_mkfun(js_buffer_from)); js_set(js, buffer_obj, "alloc", js_mkfun(js_buffer_alloc)); diff --git a/tests/test_atomics.cjs b/tests/test_atomics.cjs new file mode 100644 index 0000000..f668059 --- /dev/null +++ b/tests/test_atomics.cjs @@ -0,0 +1,147 @@ +console.log('=== Atomics API Tests ===\n'); + +// Test 1: SharedArrayBuffer creation +console.log('Test 1: SharedArrayBuffer creation'); +const sab = new SharedArrayBuffer(1024); +console.log('SharedArrayBuffer byteLength:', sab.byteLength); +console.log('SharedArrayBuffer created:', sab.byteLength === 1024 ? 'PASS' : 'FAIL'); + +// Test 2: Int32Array on SharedArrayBuffer +console.log('\nTest 2: Int32Array on SharedArrayBuffer'); +const ta = new Int32Array(sab); +console.log('Int32Array length:', ta.length); +console.log('Int32Array byteLength:', ta.byteLength); + +// Test 3: Atomics.store and Atomics.load +console.log('\nTest 3: Atomics.store and Atomics.load'); +ta[0] = 0; +console.log('Initial value:', ta[0]); +Atomics.store(ta, 0, 12); +const loaded = Atomics.load(ta, 0); +console.log('After Atomics.store(ta, 0, 12):', loaded); +console.log('Atomics.store/load test:', loaded === 12 ? 'PASS' : 'FAIL'); + +// Test 4: Atomics.add +console.log('\nTest 4: Atomics.add'); +Atomics.store(ta, 0, 5); +const oldAdd = Atomics.add(ta, 0, 12); +const newAdd = Atomics.load(ta, 0); +console.log('Old value:', oldAdd); +console.log('New value after add(12):', newAdd); +console.log('Atomics.add test:', oldAdd === 5 && newAdd === 17 ? 'PASS' : 'FAIL'); + +// Test 5: Atomics.sub +console.log('\nTest 5: Atomics.sub'); +Atomics.store(ta, 0, 12); +const oldSub = Atomics.sub(ta, 0, 2); +const newSub = Atomics.load(ta, 0); +console.log('Old value:', oldSub); +console.log('New value after sub(2):', newSub); +console.log('Atomics.sub test:', oldSub === 12 && newSub === 10 ? 'PASS' : 'FAIL'); + +// Test 6: Atomics.and +console.log('\nTest 6: Atomics.and'); +Atomics.store(ta, 0, 17); +const oldAnd = Atomics.and(ta, 0, 1); +const newAnd = Atomics.load(ta, 0); +console.log('Old value (17):', oldAnd); +console.log('New value after and(1):', newAnd); +console.log('Atomics.and test:', oldAnd === 17 && newAnd === 1 ? 'PASS' : 'FAIL'); + +// Test 7: Atomics.or +console.log('\nTest 7: Atomics.or'); +Atomics.store(ta, 0, 12); +const oldOr = Atomics.or(ta, 0, 1); +const newOr = Atomics.load(ta, 0); +console.log('Old value (12):', oldOr); +console.log('New value after or(1):', newOr); +console.log('Atomics.or test:', oldOr === 12 && newOr === 13 ? 'PASS' : 'FAIL'); + +// Test 8: Atomics.xor +console.log('\nTest 8: Atomics.xor'); +Atomics.store(ta, 0, 10); +const oldXor = Atomics.xor(ta, 0, 1); +const newXor = Atomics.load(ta, 0); +console.log('Old value (10):', oldXor); +console.log('New value after xor(1):', newXor); +console.log('Atomics.xor test:', oldXor === 10 && newXor === 11 ? 'PASS' : 'FAIL'); + +// Test 9: Atomics.exchange +console.log('\nTest 9: Atomics.exchange'); +Atomics.store(ta, 0, 1); +const oldExchange = Atomics.exchange(ta, 0, 12); +const newExchange = Atomics.load(ta, 0); +console.log('Old value:', oldExchange); +console.log('New value after exchange(12):', newExchange); +console.log('Atomics.exchange test:', oldExchange === 1 && newExchange === 12 ? 'PASS' : 'FAIL'); + +// Test 10: Atomics.compareExchange (match) +console.log('\nTest 10: Atomics.compareExchange (match)'); +Atomics.store(ta, 0, 5); +const resultMatch = Atomics.compareExchange(ta, 0, 5, 12); +const valueMatch = Atomics.load(ta, 0); +console.log('Expected 5, got:', resultMatch); +console.log('New value:', valueMatch); +console.log('Atomics.compareExchange (match) test:', resultMatch === 5 && valueMatch === 12 ? 'PASS' : 'FAIL'); + +// Test 11: Atomics.compareExchange (no match) +console.log('\nTest 11: Atomics.compareExchange (no match)'); +Atomics.store(ta, 0, 1); +const resultNoMatch = Atomics.compareExchange(ta, 0, 5, 12); +const valueNoMatch = Atomics.load(ta, 0); +console.log('Expected 5, got:', resultNoMatch); +console.log('Value unchanged:', valueNoMatch); +console.log('Atomics.compareExchange (no match) test:', resultNoMatch === 1 && valueNoMatch === 1 ? 'PASS' : 'FAIL'); + +// Test 12: Atomics.isLockFree +console.log('\nTest 12: Atomics.isLockFree'); +console.log('isLockFree(1):', Atomics.isLockFree(1)); +console.log('isLockFree(2):', Atomics.isLockFree(2)); +console.log('isLockFree(3):', Atomics.isLockFree(3)); +console.log('isLockFree(4):', Atomics.isLockFree(4)); +console.log('isLockFree(8):', Atomics.isLockFree(8)); +console.log('Atomics.isLockFree test:', Atomics.isLockFree(4) === true ? 'PASS' : 'FAIL'); + +// Test 13: Atomics.notify (without waiters) +console.log('\nTest 13: Atomics.notify (without waiters)'); +const int32 = new Int32Array(sab); +Atomics.store(int32, 0, 0); +const notified = Atomics.notify(int32, 0, 1); +console.log('Agents notified:', notified); +console.log('Atomics.notify test:', notified === 0 ? 'PASS' : 'FAIL'); + +// Test 14: Comprehensive example from MDN +console.log('\nTest 14: Comprehensive example'); +const sab2 = new SharedArrayBuffer(1024); +const ta2 = new Uint8Array(sab2); + +ta2[0] = 0; +console.log('ta2[0]:', ta2[0]); // 0 +ta2[0] = 5; +console.log('ta2[0] = 5:', ta2[0]); // 5 + +Atomics.add(ta2, 0, 12); +console.log('After Atomics.add(ta2, 0, 12):', Atomics.load(ta2, 0)); // 17 + +Atomics.and(ta2, 0, 1); +console.log('After Atomics.and(ta2, 0, 1):', Atomics.load(ta2, 0)); // 1 + +Atomics.compareExchange(ta2, 0, 5, 12); +console.log('After Atomics.compareExchange(ta2, 0, 5, 12):', Atomics.load(ta2, 0)); // 1 (no change) + +Atomics.exchange(ta2, 0, 12); +console.log('After Atomics.exchange(ta2, 0, 12):', Atomics.load(ta2, 0)); // 12 + +Atomics.or(ta2, 0, 1); +console.log('After Atomics.or(ta2, 0, 1):', Atomics.load(ta2, 0)); // 13 + +Atomics.store(ta2, 0, 12); +console.log('After Atomics.store(ta2, 0, 12):', Atomics.load(ta2, 0)); // 12 + +Atomics.sub(ta2, 0, 2); +console.log('After Atomics.sub(ta2, 0, 2):', Atomics.load(ta2, 0)); // 10 + +Atomics.xor(ta2, 0, 1); +console.log('After Atomics.xor(ta2, 0, 1):', Atomics.load(ta2, 0)); // 11 + +console.log('\n=== All Atomics tests completed ==='); diff --git a/tests/test_atomics_with_literals.cjs b/tests/test_atomics_with_literals.cjs new file mode 100644 index 0000000..9066a8f --- /dev/null +++ b/tests/test_atomics_with_literals.cjs @@ -0,0 +1,109 @@ +// Test Atomics API with Binary, Octal, and Hex literals +console.log('=== Atomics with Number Literals Test ===\n'); + +const sab = new SharedArrayBuffer(1024); +const ta = new Uint8Array(sab); + +console.log('Test 1: Initialize with binary literal'); +Atomics.store(ta, 0, 0b00000000); +console.log('Stored 0b00000000:', Atomics.load(ta, 0)); +console.log('PASS:', Atomics.load(ta, 0) === 0); + +console.log('\nTest 2: Add using hex literal'); +const old1 = Atomics.add(ta, 0, 0xFF); +console.log('Added 0xFF to 0, old value:', old1); +console.log('New value:', Atomics.load(ta, 0)); +console.log('PASS:', Atomics.load(ta, 0) === 0xFF); + +console.log('\nTest 3: AND with binary mask'); +Atomics.store(ta, 1, 0b11111111); +const old2 = Atomics.and(ta, 1, 0b11110000); +console.log('0b11111111 & 0b11110000 =', Atomics.load(ta, 1)); +console.log('PASS:', Atomics.load(ta, 1) === 0b11110000); + +console.log('\nTest 4: OR with hex value'); +const old3 = Atomics.or(ta, 1, 0x0F); +console.log('0b11110000 | 0x0F =', Atomics.load(ta, 1)); +console.log('PASS:', Atomics.load(ta, 1) === 0xFF); + +console.log('\nTest 5: XOR with octal value'); +Atomics.store(ta, 2, 0o377); // 255 in octal +const old4 = Atomics.xor(ta, 2, 0b10101010); +console.log('0o377 ^ 0b10101010 =', Atomics.load(ta, 2)); +console.log('PASS:', Atomics.load(ta, 2) === (0o377 ^ 0b10101010)); + +console.log('\nTest 6: CompareExchange with mixed literals'); +Atomics.store(ta, 3, 0x10); +const result = Atomics.compareExchange(ta, 3, 0b00010000, 0o40); +console.log('Expected 0b00010000 (16), got:', result); +console.log('New value (0o40 = 32):', Atomics.load(ta, 3)); +console.log('PASS:', result === 0x10 && Atomics.load(ta, 3) === 0o40); + +console.log('\nTest 7: Bitwise flags using binary literals'); +const FLAG_READ = 0b0001; +const FLAG_WRITE = 0b0010; +const FLAG_EXECUTE = 0b0100; +const FLAG_DELETE = 0b1000; + +Atomics.store(ta, 4, 0); +Atomics.or(ta, 4, FLAG_READ); +Atomics.or(ta, 4, FLAG_WRITE); +const flags = Atomics.load(ta, 4); +console.log('Flags after OR READ|WRITE:', flags.toString(2).padStart(4, '0')); +console.log('Has READ:', (flags & FLAG_READ) !== 0); +console.log('Has WRITE:', (flags & FLAG_WRITE) !== 0); +console.log('Has EXECUTE:', (flags & FLAG_EXECUTE) !== 0); +console.log('PASS:', flags === 0b0011); + +console.log('\nTest 8: Color manipulation with hex'); +const RED = 0xFF; +const GREEN = 0xFF; +const BLUE = 0xFF; + +Atomics.store(ta, 5, RED); +Atomics.store(ta, 6, GREEN); +Atomics.store(ta, 7, BLUE); + +console.log('RGB values:'); +console.log(' R:', Atomics.load(ta, 5)); +console.log(' G:', Atomics.load(ta, 6)); +console.log(' B:', Atomics.load(ta, 7)); +console.log('PASS:', Atomics.load(ta, 5) === 255); + +console.log('\nTest 9: Mask operations with all formats'); +const MASK_BIN = 0b11110000; +const MASK_OCT = 0o360; // Same as 0b11110000 +const MASK_HEX = 0xF0; // Same as 0b11110000 + +Atomics.store(ta, 8, 0xFF); +Atomics.and(ta, 8, MASK_BIN); +const result1 = Atomics.load(ta, 8); + +Atomics.store(ta, 9, 0xFF); +Atomics.and(ta, 9, MASK_OCT); +const result2 = Atomics.load(ta, 9); + +Atomics.store(ta, 10, 0xFF); +Atomics.and(ta, 10, MASK_HEX); +const result3 = Atomics.load(ta, 10); + +console.log('All masks equivalent:', result1 === result2 && result2 === result3); +console.log('PASS:', result1 === 0xF0); + +console.log('\nTest 10: Complex atomic operation chain'); +Atomics.store(ta, 11, 0b00000000); +console.log('Start:', Atomics.load(ta, 11).toString(2).padStart(8, '0')); + +Atomics.or(ta, 11, 0b00001111); +console.log('After OR 0b00001111:', Atomics.load(ta, 11).toString(2).padStart(8, '0')); + +Atomics.and(ta, 11, 0b11110111); +console.log('After AND 0b11110111:', Atomics.load(ta, 11).toString(2).padStart(8, '0')); + +Atomics.xor(ta, 11, 0xFF); +console.log('After XOR 0xFF:', Atomics.load(ta, 11).toString(2).padStart(8, '0')); + +const final = Atomics.load(ta, 11); +console.log('PASS:', final === 0b11111000); + +console.log('\n=== All tests completed successfully ==='); diff --git a/tests/test_number_literals.cjs b/tests/test_number_literals.cjs new file mode 100644 index 0000000..035a3ab --- /dev/null +++ b/tests/test_number_literals.cjs @@ -0,0 +1,69 @@ +console.log('=== Number Literal Tests ===\n'); + +// Test 1: Binary literals +console.log('Test 1: Binary literals'); +console.log('0b0:', 0b0); +console.log('0b1:', 0b1); +console.log('0b10:', 0b10); +console.log('0b1010:', 0b1010); +console.log('0b11111111:', 0b11111111); +console.log('Binary 0b1010 === 10:', 0b1010 === 10 ? 'PASS' : 'FAIL'); +console.log('Binary 0b11111111 === 255:', 0b11111111 === 255 ? 'PASS' : 'FAIL'); + +// Test 2: Octal literals +console.log('\nTest 2: Octal literals'); +console.log('0o0:', 0o0); +console.log('0o7:', 0o7); +console.log('0o10:', 0o10); +console.log('0o755:', 0o755); +console.log('0o777:', 0o777); +console.log('Octal 0o10 === 8:', 0o10 === 8 ? 'PASS' : 'FAIL'); +console.log('Octal 0o755 === 493:', 0o755 === 493 ? 'PASS' : 'FAIL'); + +// Test 3: Hexadecimal literals +console.log('\nTest 3: Hexadecimal literals'); +console.log('0x0:', 0x0); +console.log('0xF:', 0xF); +console.log('0x10:', 0x10); +console.log('0xFF:', 0xFF); +console.log('0xDEADBEEF:', 0xDEADBEEF); +console.log('Hex 0x10 === 16:', 0x10 === 16 ? 'PASS' : 'FAIL'); +console.log('Hex 0xFF === 255:', 0xFF === 255 ? 'PASS' : 'FAIL'); + +// Test 4: Uppercase variants +console.log('\nTest 4: Uppercase variants'); +console.log('0B1010:', 0B1010); +console.log('0O755:', 0O755); +console.log('0XFF:', 0XFF); +console.log('Uppercase 0B1010 === 10:', 0B1010 === 10 ? 'PASS' : 'FAIL'); +console.log('Uppercase 0O755 === 493:', 0O755 === 493 ? 'PASS' : 'FAIL'); +console.log('Uppercase 0XFF === 255:', 0XFF === 255 ? 'PASS' : 'FAIL'); + +// Test 5: Mixed usage in expressions +console.log('\nTest 5: Mixed usage in expressions'); +const sum = 0b1010 + 0o12 + 0x0A; +console.log('0b1010 + 0o12 + 0x0A =', sum); +console.log('Sum === 30:', sum === 30 ? 'PASS' : 'FAIL'); + +const product = 0b10 * 0o10 * 0x10; +console.log('0b10 * 0o10 * 0x10 =', product); +console.log('Product === 256:', product === 256 ? 'PASS' : 'FAIL'); + +// Test 6: Bitwise operations with binary literals +console.log('\nTest 6: Bitwise operations with binary literals'); +console.log('0b1111 & 0b1010 =', (0b1111 & 0b1010).toString(2).padStart(4, '0')); +console.log('0b1111 | 0b1010 =', (0b1111 | 0b1010).toString(2).padStart(4, '0')); +console.log('0b1111 ^ 0b1010 =', (0b1111 ^ 0b1010).toString(2).padStart(4, '0')); +console.log('AND result === 10:', (0b1111 & 0b1010) === 10 ? 'PASS' : 'FAIL'); +console.log('OR result === 15:', (0b1111 | 0b1010) === 15 ? 'PASS' : 'FAIL'); +console.log('XOR result === 5:', (0b1111 ^ 0b1010) === 5 ? 'PASS' : 'FAIL'); + +// Test 7: All representations of the same number +console.log('\nTest 7: All representations of the same number'); +console.log('Binary 0b1111 === 15:', 0b1111 === 15); +console.log('Octal 0o17 === 15:', 0o17 === 15); +console.log('Hex 0xF === 15:', 0xF === 15); +console.log('Decimal 15 === 15:', 15 === 15); +console.log('All equal:', (0b1111 === 0o17 && 0o17 === 0xF && 0xF === 15) ? 'PASS' : 'FAIL'); + +console.log('\n=== All number literal tests completed ===');