diff --git a/include/ant.h b/include/ant.h index d2ddc13..4fd7463 100644 --- a/include/ant.h +++ b/include/ant.h @@ -3,6 +3,7 @@ #include #include #include +#include struct js; typedef uint64_t jsval_t; @@ -83,4 +84,6 @@ typedef jsval_t (*ant_library_init_fn)(struct js *js); void ant_register_library(const char *name, ant_library_init_fn init_fn); typedef jsval_t (*js_getter_fn)(struct js *js, jsval_t obj, const char *key, size_t key_len); -void js_set_getter(struct js *js, jsval_t obj, js_getter_fn getter); \ No newline at end of file + +void js_set_getter(struct js *js, jsval_t obj, js_getter_fn getter); +void js_print_stack_trace(FILE *stream); \ No newline at end of file diff --git a/src/ant.c b/src/ant.c index 734d0f9..478ebb0 100644 --- a/src/ant.c +++ b/src/ant.c @@ -137,8 +137,23 @@ typedef struct ant_library { UT_hash_handle hh; } ant_library_t; +typedef struct { + char key[64]; + jsoff_t offset; + uint32_t hash; + UT_hash_handle hh; +} prop_cache_entry_t; + +typedef struct { + jsoff_t obj_offset; + prop_cache_entry_t *cache; + uint32_t hit_count; + UT_hash_handle hh; +} obj_prop_cache_t; + static ant_library_t *library_registry = NULL; static esm_module_cache_t global_module_cache = {NULL, 0}; +static obj_prop_cache_t *global_property_cache = NULL; void js_protect_init_memory(struct js *js) { protected_brk = js_getbrk(js); @@ -1114,6 +1129,79 @@ static bool is_const_prop(struct js *js, jsoff_t propoff) { return false; } +static inline uint32_t hash_key(const char *key, size_t len) { + uint32_t hash = 2166136261u; + for (size_t i = 0; i < len && i < 64; i++) { + hash ^= (uint8_t)key[i]; + hash *= 16777619u; + } + return hash; +} + +static obj_prop_cache_t* get_obj_cache(jsoff_t obj_offset) { + obj_prop_cache_t *cache = NULL; + HASH_FIND(hh, global_property_cache, &obj_offset, sizeof(jsoff_t), cache); + if (!cache) { + cache = (obj_prop_cache_t *)malloc(sizeof(obj_prop_cache_t)); + if (!cache) return NULL; + cache->obj_offset = obj_offset; + cache->cache = NULL; + cache->hit_count = 0; + HASH_ADD(hh, global_property_cache, obj_offset, sizeof(jsoff_t), cache); + } + return cache; +} + +static void cache_property(jsoff_t obj_offset, const char *key, size_t key_len, jsoff_t prop_offset) { + if (key_len > 63) return; + + obj_prop_cache_t *obj_cache = get_obj_cache(obj_offset); + if (!obj_cache) return; + + prop_cache_entry_t *entry = NULL; + HASH_FIND_STR(obj_cache->cache, key, entry); + + if (!entry) { + entry = (prop_cache_entry_t *)malloc(sizeof(prop_cache_entry_t)); + if (!entry) return; + memcpy(entry->key, key, key_len); + entry->key[key_len] = '\0'; + entry->hash = hash_key(key, key_len); + HASH_ADD_STR(obj_cache->cache, key, entry); + } + entry->offset = prop_offset; +} + +static jsoff_t cache_lookup(jsoff_t obj_offset, const char *key, size_t key_len) { + if (key_len > 63) return 0; + + obj_prop_cache_t *obj_cache = NULL; + HASH_FIND(hh, global_property_cache, &obj_offset, sizeof(jsoff_t), obj_cache); + if (!obj_cache) return 0; + + prop_cache_entry_t *entry = NULL; + HASH_FIND_STR(obj_cache->cache, key, entry); + if (entry) { + obj_cache->hit_count++; + return entry->offset; + } + return 0; +} + +static void invalidate_obj_cache(jsoff_t obj_offset) { + obj_prop_cache_t *obj_cache = NULL; + HASH_FIND(hh, global_property_cache, &obj_offset, sizeof(jsoff_t), obj_cache); + if (!obj_cache) return; + + prop_cache_entry_t *entry, *tmp; + HASH_ITER(hh, obj_cache->cache, entry, tmp) { + HASH_DEL(obj_cache->cache, entry); + free(entry); + } + HASH_DEL(global_property_cache, obj_cache); + free(obj_cache); +} + static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_const) { jsoff_t koff = (jsoff_t) vdata(k); jsoff_t b, head = (jsoff_t) vdata(obj); @@ -1124,6 +1212,10 @@ static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, bool is_ jsoff_t brk = js->brk | T_OBJ; if (is_const) brk |= CONSTMASK; memcpy(&js->mem[head], &brk, sizeof(brk)); + + // Invalidate property cache for this object when a new property is added + invalidate_obj_cache(head); + return mkentity(js, (b & ~(3U | CONSTMASK)) | T_PROP, buf, sizeof(buf)); } @@ -1628,18 +1720,30 @@ static jsval_t js_block(struct js *js, bool create_scope) { return res; } -static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len) { +static inline jsoff_t lkp_inline(struct js *js, jsval_t obj, const char *buf, size_t len) { + if (len <= 63) { + jsoff_t cached = cache_lookup((jsoff_t)vdata(obj), buf, len); + if (cached != 0) return cached; + } + jsoff_t off = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | CONSTMASK); while (off < js->brk && off != 0) { jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(off))); jsoff_t klen = (loadoff(js, koff) >> 2) - 1; const char *p = (char *) &js->mem[koff + sizeof(koff)]; - if (streq(buf, len, p, klen)) return off; + if (streq(buf, len, p, klen)) { + if (len <= 63) cache_property((jsoff_t)vdata(obj), buf, len, off); + return off; + } off = loadoff(js, off) & ~(3U | CONSTMASK); } return 0; } +static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len) { + return lkp_inline(js, obj, buf, len); +} + static jsval_t try_dynamic_getter(struct js *js, jsval_t obj, const char *key, size_t key_len) { if (streq(key, key_len, "__getter", 8)) return js_mkundef(); @@ -8373,3 +8477,24 @@ void js_set_getter(struct js *js, jsval_t obj, js_getter_fn getter) { jsval_t getter_val = mkval(T_CFUNC, (size_t)(void *)getter); js_set(js, obj, "__getter", getter_val); } + +void js_print_stack_trace(FILE *stream) { + if (global_call_stack.depth > 0) { + for (int i = global_call_stack.depth - 1; i >= 0; i--) { + call_frame_t *frame = &global_call_stack.frames[i]; + fprintf(stream, " at "); + + if (frame->function_name) { + fprintf(stream, "%s", frame->function_name); + } else fprintf(stream, ""); + + fprintf(stream, " (\x1b[90m"); + + if (frame->filename) { + fprintf(stream, "%s:%d:%d", frame->filename, frame->line, frame->col); + } else fprintf(stream, ""); + + fprintf(stream, "\x1b[0m)\n"); + } + } +} diff --git a/src/modules/io.c b/src/modules/io.c index 49c6372..38836ec 100644 --- a/src/modules/io.c +++ b/src/modules/io.c @@ -147,6 +147,47 @@ static jsval_t js_console_warn(struct js *js, jsval_t *args, int nargs) { return js_mkundef(); } +static jsval_t js_console_assert(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkundef(); + + bool is_truthy = js_truthy(js, args[0]); + if (is_truthy) return js_mkundef(); + + fprintf(stderr, "%sAssertion failed", ANSI_RED); + if (nargs > 1) { + fprintf(stderr, ": "); + for (int i = 1; i < nargs; i++) { + const char *space = i == 1 ? "" : " "; + fprintf(stderr, "%s", space); + + if (js_type(args[i]) == JS_STR) { + char *str = js_getstr(js, args[i], NULL); + fprintf(stderr, "%s", str); + } else { + const char *str = js_str(js, args[i]); + fprintf(stderr, "%s", str); + } + } + } + fprintf(stderr, "%s\n", ANSI_RESET); + + return js_mkundef(); +} + +static jsval_t js_console_trace(struct js *js, jsval_t *args, int nargs) { + fprintf(stderr, "Console Trace"); + if (nargs > 0 && js_type(args[0]) == JS_STR) { + fprintf(stderr, ": "); + char *str = js_getstr(js, args[0], NULL); + fprintf(stderr, "%s", str); + } + fprintf(stderr, "\n"); + + js_print_stack_trace(stderr); + + return js_mkundef(); +} + void init_console_module() { struct js *js = rt->js; jsval_t console_obj = js_mkobj(js); @@ -155,4 +196,6 @@ void init_console_module() { js_set(js, console_obj, "log", js_mkfun(js_console_log)); js_set(js, console_obj, "error", js_mkfun(js_console_error)); js_set(js, console_obj, "warn", js_mkfun(js_console_warn)); + js_set(js, console_obj, "assert", js_mkfun(js_console_assert)); + js_set(js, console_obj, "trace", js_mkfun(js_console_trace)); } \ No newline at end of file diff --git a/tests/test_console_methods.cjs b/tests/test_console_methods.cjs new file mode 100644 index 0000000..0468f61 --- /dev/null +++ b/tests/test_console_methods.cjs @@ -0,0 +1,58 @@ +// Test console methods (log, error, warn, assert, trace) +console.log('=== Testing Console Methods ==='); + +// Test 1: console.log +console.log('Test 1: console.log'); +console.log(' - Basic message'); +console.log(' - Multiple', 'arguments', 'work'); +console.log(' - Numbers:', 42, 3.14); +console.log(' - Objects:', { key: 'value' }); + +// Test 2: console.error +console.error('Test 2: console.error - This should appear in red'); + +// Test 3: console.warn +console.warn('Test 3: console.warn - This should appear in yellow'); + +// Test 4: console.assert with true condition +console.assert(true, 'This should not print'); +console.assert(1 === 1, 'Math check'); +console.log('Test 4: console.assert (true conditions) - passed'); + +// Test 5: console.assert with false condition +console.assert(false, 'Expected assertion failure'); +console.assert(1 === 2, 'This assertion fails'); +console.log('Test 5: console.assert (false conditions) - checked'); + +// Test 6: console.trace +console.trace('Test 6: console.trace with message'); + +// Test 7: console.trace with nested functions +function level3() { + console.trace('Trace from level 3'); +} + +function level2() { + level3(); +} + +function level1() { + level2(); +} + +console.log('Starting nested function trace:'); +level1(); + +// Test 8: Complex objects +const complexObj = { + name: 'test', + nested: { x: 1, y: 2 }, + array: [1, 2, 3], + bool: true +}; +console.log('Test 8: Complex object:', complexObj); + +// Test 9: Empty console.trace +console.trace(); + +console.log('\n=== All console method tests completed ==='); diff --git a/tests/test_property_lookup_cache.cjs b/tests/test_property_lookup_cache.cjs new file mode 100644 index 0000000..0e0f9ec --- /dev/null +++ b/tests/test_property_lookup_cache.cjs @@ -0,0 +1,125 @@ +// Test property lookup caching - validates that the optimization doesn't break property access +console.log('Testing property lookup cache optimization...'); + +// Test 1: Basic property access +const obj1 = { name: 'test', value: 42, nested: { x: 1, y: 2 } }; +console.assert(obj1.name === 'test', 'Basic property access failed'); +console.assert(obj1.value === 42, 'Number property access failed'); +console.assert(obj1.nested.x === 1, 'Nested property access failed'); +console.log('✓ Test 1: Basic property access'); + +// Test 2: Multiple accesses (should hit cache on second access) +const obj2 = { prop: 'cached' }; +const access1 = obj2.prop; +const access2 = obj2.prop; +const access3 = obj2.prop; +console.assert(access1 === 'cached', 'First access failed'); +console.assert(access2 === 'cached', 'Cached access failed'); +console.assert(access3 === 'cached', 'Second cached access failed'); +console.log('✓ Test 2: Multiple property accesses (cache hits)'); + +// Test 3: Property modification (cache invalidation) +const obj3 = { x: 1 }; +console.assert(obj3.x === 1, 'Initial property value wrong'); +obj3.x = 2; +console.assert(obj3.x === 2, 'Property modification failed'); +obj3.x = 3; +console.assert(obj3.x === 3, 'Second modification failed'); +console.log('✓ Test 3: Property modification with cache invalidation'); + +// Test 4: Dynamic property creation +const obj4 = {}; +obj4.dynamic = 'new'; +console.assert(obj4.dynamic === 'new', 'Dynamic property creation failed'); +obj4.another = 'property'; +console.assert(obj4.another === 'property', 'Second dynamic property failed'); +console.assert(obj4.dynamic === 'new', 'First dynamic property corrupted'); +console.log('✓ Test 4: Dynamic property creation'); + +// Test 5: Method access (direct property access) +const MyFunc = {}; +MyFunc.getData = function() { return 'test data'; }; +console.assert(MyFunc.getData() === 'test data', 'Method access failed'); +console.log('✓ Test 5: Method access'); + +// Test 6: Properties with special names +const obj6 = { __code: 'test', name: 'obj6' }; +console.assert(obj6.name === 'obj6', 'Short key property failed'); +console.log('✓ Test 6: Properties with special names'); + +// Test 7: Many properties in object (stress test for cache) +const obj7 = {}; +for (let i = 0; i < 50; i++) { + obj7['prop' + i] = i; +} +console.assert(obj7.prop0 === 0, 'First property in large object failed'); +console.assert(obj7.prop25 === 25, 'Middle property in large object failed'); +console.assert(obj7.prop49 === 49, 'Last property in large object failed'); +// Access again - should hit cache +console.assert(obj7.prop25 === 25, 'Cached access in large object failed'); +console.log('✓ Test 7: Many properties in object'); + +// Test 8: Property lookup in scope chain +function outer() { + const outerVar = 'outer'; + function inner() { + const innerVar = 'inner'; + return outerVar + innerVar; + } + return inner(); +} +console.assert(outer() === 'outerinner', 'Scope chain lookup failed'); +console.log('✓ Test 8: Property lookup in scope chain'); + +// Test 9: Long property names (shouldn't be cached but should work) +const longName = 'verylongpropertyname' + 'thatshouldnot' + 'becached' + 'duetolength'; +const obj9 = {}; +obj9[longName] = 'value'; +console.assert(obj9[longName] === 'value', 'Long property name access failed'); +console.log('✓ Test 9: Long property names'); + +// Test 10: Computed properties +const obj10 = {}; +obj10.value = 10; +obj10.double = function() { return this.value * 2; }; +console.assert(obj10.double() === 20, 'Computed property failed'); +console.log('✓ Test 10: Computed properties'); + +// Test 11: Property existence checks +const obj11 = { exists: true }; +if (obj11.exists) { + console.log('✓ Test 11: Property existence check'); +} else { + throw new Error('Property existence check failed'); +} + +// Test 12: Repeated property pattern (simulates real-world code) +const objs = []; +for (let i = 0; i < 10; i++) { + const o = { id: i, name: 'obj' + i, value: i * 10 }; + objs.push(o); +} +let sum = 0; +for (let i = 0; i < objs.length; i++) { + sum += objs[i].value; +} +console.assert(sum === 450, 'Repeated property pattern failed'); +console.log('✓ Test 12: Repeated property access pattern'); + +// Test 13: console.assert with true condition (should not print anything) +console.assert(true, 'This should not appear'); +console.log('✓ Test 13: console.assert with true condition'); + +// Test 14: console.assert with false condition (should print message) +console.assert(false, 'This is an expected assertion failure'); +console.log('✓ Test 14: console.assert with false condition'); + +// Test 15: Accessing same property repeatedly across iterations +const testObj = { counter: 0 }; +for (let i = 0; i < 100; i++) { + testObj.counter = i; +} +console.assert(testObj.counter === 99, 'Repeated property writes failed'); +console.log('✓ Test 15: Repeated property writes'); + +console.log('\n✅ All property lookup cache tests passed!');