diff --git a/meson.build b/meson.build index d8620c2..16f7dc7 100644 --- a/meson.build +++ b/meson.build @@ -68,7 +68,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.7.7') +version_conf.set('ANT_VERSION', '0.0.7.8') 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 478ebb0..0abda31 100644 --- a/src/ant.c +++ b/src/ant.c @@ -1816,19 +1816,65 @@ static jsval_t do_assign_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) { return assign(js, l, res); } +typedef struct { + char *buffer; + size_t capacity; + size_t size; + bool is_dynamic; +} string_builder_t; + +static void string_builder_init(string_builder_t *sb, char *static_buf, size_t static_cap) { + sb->buffer = static_buf; + sb->capacity = static_cap; + sb->size = 0; + sb->is_dynamic = false; +} + +static bool string_builder_append(string_builder_t *sb, const char *data, size_t len) { + if (sb->size + len > sb->capacity) { + size_t new_capacity = sb->capacity ? sb->capacity * 2 : 256; + while (new_capacity < sb->size + len) new_capacity *= 2; + + char *new_buffer = (char *)ANT_GC_MALLOC(new_capacity); + if (!new_buffer) return false; + + if (sb->size > 0) memcpy(new_buffer, sb->buffer, sb->size); + if (sb->is_dynamic) ANT_GC_FREE(sb->buffer); + + sb->buffer = new_buffer; + sb->capacity = new_capacity; + sb->is_dynamic = true; + } + + if (len > 0) { + memcpy(sb->buffer + sb->size, data, len); + sb->size += len; + } + + return true; +} + +static jsval_t string_builder_finalize(struct js *js, string_builder_t *sb) { + jsval_t result = js_mkstr(js, sb->buffer, sb->size); + if (sb->is_dynamic && sb->buffer) ANT_GC_FREE(sb->buffer); + return result; +} + static jsval_t do_string_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) { jsoff_t n1, off1 = vstr(js, l, &n1); jsoff_t n2, off2 = vstr(js, r, &n2); if (op == TOK_PLUS) { - jsval_t res = js_mkstr(js, NULL, n1 + n2); - if (vtype(res) == T_STR) { - jsoff_t n, off = vstr(js, res, &n); - memmove(&js->mem[off], &js->mem[off1], n1); - memmove(&js->mem[off + n1], &js->mem[off2], n2); + string_builder_t sb; + char static_buffer[512]; + string_builder_init(&sb, static_buffer, sizeof(static_buffer)); + + if (!string_builder_append(&sb, (char *)&js->mem[off1], n1) || + !string_builder_append(&sb, (char *)&js->mem[off2], n2)) { + return js_mkerr(js, "string concatenation failed"); } - return res; + return string_builder_finalize(js, &sb); } else if (op == TOK_EQ) { bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0; return mkval(T_BOOL, eq ? 1 : 0); @@ -2534,7 +2580,10 @@ static jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) { } return do_string_op(js, op, l_str, r_str); } - if (vtype(l) == T_STR && vtype(r) == T_STR) return do_string_op(js, op, l, r); + if (vtype(l) == T_STR && vtype(r) == T_STR) { + // Fast path for string+string without type coercion + return do_string_op(js, op, l, r); + } double a = 0.0, b = 0.0; diff --git a/tests/test_string_concat_perf.cjs b/tests/test_string_concat_perf.cjs new file mode 100644 index 0000000..c8ac708 --- /dev/null +++ b/tests/test_string_concat_perf.cjs @@ -0,0 +1,116 @@ +// Performance test for string concatenation optimization + +console.log("=== String Concatenation Performance Tests ===\n"); + +// Test 1: Simple binary concatenation +console.log("Test 1: Binary Concatenation"); +let start = Date.now(); +for (let i = 0; i < 1000; i++) { + let result = "hello" + "world"; +} +let elapsed = Date.now() - start; +console.log(" 1000 binary concatenations: " + elapsed + "ms"); + +// Test 2: Chained concatenation (4-way) +console.log("\nTest 2: Chained Concatenation (4-way)"); +start = Date.now(); +for (let i = 0; i < 1000; i++) { + let result = "a" + "b" + "c" + "d"; +} +elapsed = Date.now() - start; +console.log(" 1000 chained concatenations (4 parts): " + elapsed + "ms"); + +// Test 3: Long chained concatenation (10-way) +console.log("\nTest 3: Long Chained Concatenation (10-way)"); +start = Date.now(); +for (let i = 0; i < 500; i++) { + let result = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"; +} +elapsed = Date.now() - start; +console.log(" 500 chained concatenations (10 parts): " + elapsed + "ms"); + +// Test 4: Type coercion concatenation +console.log("\nTest 4: Type Coercion in Concatenation"); +start = Date.now(); +for (let i = 0; i < 1000; i++) { + let result = "Value: " + i + " is " + (i % 2 == 0 ? "even" : "odd"); +} +elapsed = Date.now() - start; +console.log(" 1000 concatenations with type coercion: " + elapsed + "ms"); + +// Test 5: Building longer strings progressively +console.log("\nTest 5: Progressive String Building"); +start = Date.now(); +let message = ""; +for (let i = 0; i < 100; i++) { + message = message + "line " + i + ": some content\n"; +} +elapsed = Date.now() - start; +console.log(" 100 progressive concatenations: " + elapsed + "ms"); +console.log(" Result length: " + message.length + " bytes"); + +// Test 6: Concatenation in loop with variables +console.log("\nTest 6: Variable Concatenation in Loop"); +let prefix = "Item"; +let suffix = "end"; +start = Date.now(); +for (let i = 0; i < 1000; i++) { + let result = prefix + " " + i + " " + suffix; +} +elapsed = Date.now() - start; +console.log(" 1000 variable concatenations: " + elapsed + "ms"); + +// Test 7: Large string concatenation +console.log("\nTest 7: Large String Concatenation"); +let largeStr1 = "x".repeat(500); +let largeStr2 = "y".repeat(500); +start = Date.now(); +for (let i = 0; i < 100; i++) { + let result = largeStr1 + largeStr2; +} +elapsed = Date.now() - start; +console.log(" 100 concatenations of 500-byte strings: " + elapsed + "ms"); + +// Test 8: Mixed type chaining +console.log("\nTest 8: Mixed Type Chaining"); +start = Date.now(); +for (let i = 0; i < 1000; i++) { + let result = "Start" + 123 + true + 45.67 + "End"; +} +elapsed = Date.now() - start; +console.log(" 1000 mixed-type chained concatenations: " + elapsed + "ms"); + +// Test 9: Template-like concatenation +console.log("\nTest 9: Template-like String Building"); +start = Date.now(); +for (let i = 0; i < 100; i++) { + let name = "User" + i; + let age = 20 + i; + let result = "Name: " + name + ", Age: " + age + ", Active: " + (i % 2 == 0); +} +elapsed = Date.now() - start; +console.log(" 100 template-like concatenations: " + elapsed + "ms"); + +// Test 10: String concatenation with comparison +console.log("\nTest 10: Concatenation with String Comparison"); +start = Date.now(); +for (let i = 0; i < 1000; i++) { + let str1 = "test" + i; + let str2 = "test" + i; + let isEqual = str1 == str2 ? "equal" : "not equal"; +} +elapsed = Date.now() - start; +console.log(" 1000 concatenations + comparisons: " + elapsed + "ms"); + +// Verification test - ensure correctness +console.log("\n=== Correctness Verification ==="); +let verify1 = "a" + "b" + "c" + "d"; +console.log("Chained concat result: '" + verify1 + "' (expected: 'abcd')"); + +let verify2 = "x" + 42 + "y"; +console.log("Type coercion result: '" + verify2 + "' (expected: 'x42y')"); + +let verify3 = "" + 0 + "" + false + "" + true; +console.log("Boolean concat result: '" + verify3 + "' (expected: '0falsetrue')"); + +console.log("\n=== All performance tests completed ===");