xbintsc 0.3.81 → 0.3.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +12 -2
  2. package/README.zh-CN.md +12 -4
  3. package/dist/src/ast/common.d.ts +23 -0
  4. package/dist/src/cli/main.js +68 -24
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/invocation.js +5 -1
  7. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  8. package/dist/src/codegen/generator/context.d.ts +50 -0
  9. package/dist/src/codegen/generator/context.js +124 -13
  10. package/dist/src/codegen/generator/context.js.map +1 -1
  11. package/dist/src/codegen/generator/debug.d.ts +33 -0
  12. package/dist/src/codegen/generator/debug.js +97 -0
  13. package/dist/src/codegen/generator/debug.js.map +1 -0
  14. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  15. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  16. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  17. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  18. package/dist/src/codegen/generator/expressions/primary.js +5 -2
  19. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  20. package/dist/src/codegen/generator/generator.d.ts +3 -1
  21. package/dist/src/codegen/generator/generator.js +3 -1
  22. package/dist/src/codegen/generator/generator.js.map +1 -1
  23. package/dist/src/codegen/generator/module.d.ts +1 -0
  24. package/dist/src/codegen/generator/module.js +76 -6
  25. package/dist/src/codegen/generator/module.js.map +1 -1
  26. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  27. package/dist/src/codegen/generator/numeric.js +127 -0
  28. package/dist/src/codegen/generator/numeric.js.map +1 -0
  29. package/dist/src/codegen/generator/state.d.ts +17 -0
  30. package/dist/src/codegen/generator/state.js +5 -0
  31. package/dist/src/codegen/generator/state.js.map +1 -1
  32. package/dist/src/codegen/generator/statements/control-flow.js +1 -1
  33. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  34. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  35. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/variables.js +8 -2
  37. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  38. package/dist/src/codegen/generator/tables.d.ts +7 -0
  39. package/dist/src/codegen/generator/tables.js +25 -0
  40. package/dist/src/codegen/generator/tables.js.map +1 -1
  41. package/dist/src/driver/bundler/merge.js +41 -5
  42. package/dist/src/driver/bundler/merge.js.map +1 -1
  43. package/dist/src/driver/compiler.d.ts +22 -3
  44. package/dist/src/driver/compiler.js +43 -57
  45. package/dist/src/driver/compiler.js.map +1 -1
  46. package/dist/src/driver/fingerprint.d.ts +27 -0
  47. package/dist/src/driver/fingerprint.js +88 -0
  48. package/dist/src/driver/fingerprint.js.map +1 -0
  49. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  50. package/dist/src/driver/toolchain-provider.js +15 -15
  51. package/dist/src/driver/toolchain-provider.js.map +1 -1
  52. package/dist/src/driver/toolchain.d.ts +58 -1
  53. package/dist/src/driver/toolchain.js +169 -22
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/parser/expressions.js +20 -1
  56. package/dist/src/parser/expressions.js.map +1 -1
  57. package/dist/tests/cli/main.test.js +41 -1
  58. package/dist/tests/cli/main.test.js.map +1 -1
  59. package/dist/tests/codegen/expressions.test.js +14 -3
  60. package/dist/tests/codegen/expressions.test.js.map +1 -1
  61. package/dist/tests/codegen/llvm.test.js +23 -0
  62. package/dist/tests/codegen/llvm.test.js.map +1 -1
  63. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  64. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  65. package/dist/tests/driver/toolchain.test.js +62 -3
  66. package/dist/tests/driver/toolchain.test.js.map +1 -1
  67. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  68. package/dist/tests/e2e/child-process.test.js +59 -0
  69. package/dist/tests/e2e/child-process.test.js.map +1 -0
  70. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  71. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  72. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  73. package/dist/tests/e2e/error-stack.test.js +94 -0
  74. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  75. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  76. package/dist/tests/e2e/string-concat.test.js +102 -0
  77. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  78. package/dist/tests/helpers.d.ts +9 -0
  79. package/dist/tests/helpers.js +26 -3
  80. package/dist/tests/helpers.js.map +1 -1
  81. package/dist/tests/parser/expressions.test.js +7 -0
  82. package/dist/tests/parser/expressions.test.js.map +1 -1
  83. package/dist/tests/version.test.d.ts +1 -0
  84. package/dist/tests/version.test.js +24 -0
  85. package/dist/tests/version.test.js.map +1 -0
  86. package/doc/DESIGN.md +20 -12
  87. package/doc/ai/cli.md +2 -0
  88. package/doc/ai/contributing.md +7 -1
  89. package/doc/ai/language-support.md +4 -4
  90. package/doc/ai/troubleshooting.md +39 -2
  91. package/doc/ai/zh-CN/cli.md +2 -0
  92. package/doc/ai/zh-CN/contributing.md +6 -2
  93. package/doc/ai/zh-CN/language-support.md +4 -4
  94. package/doc/ai/zh-CN/troubleshooting.md +35 -2
  95. package/doc/implemented.md +3 -3
  96. package/doc/requirements.md +13 -4
  97. package/doc/unimplemented.md +11 -10
  98. package/doc/zh-CN/DESIGN.md +15 -9
  99. package/doc/zh-CN/implemented.md +3 -3
  100. package/doc/zh-CN/requirements.md +12 -3
  101. package/doc/zh-CN/unimplemented.md +10 -9
  102. package/package.json +3 -1
  103. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  104. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  105. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  106. package/runtime/ext_node/child_process/child_process.c +25 -0
  107. package/runtime/ext_node/fs/fd_ops.c +1 -1
  108. package/runtime/rt.h +17 -1
  109. package/runtime/rt_internal.h +47 -4
  110. package/runtime/xt_alloc.c +14 -2
  111. package/runtime/xt_bigint/conversions.inc +1 -1
  112. package/runtime/xt_builtins.c +136 -14
  113. package/runtime/xt_containers/access.inc +17 -16
  114. package/runtime/xt_containers/objects.inc +4 -3
  115. package/runtime/xt_io/exception.inc +171 -2
  116. package/runtime/xt_io/inspect.inc +15 -13
  117. package/runtime/xt_io.c +5 -0
  118. package/runtime/xt_stdlib/arrays.inc +2 -2
  119. package/runtime/xt_stdlib/dispatch.inc +2 -2
  120. package/runtime/xt_stdlib/strings.inc +33 -21
  121. package/runtime/xt_stdlib2/array-statics.inc +2 -1
  122. package/runtime/xt_stdlib2/arrays.inc +1 -1
  123. package/runtime/xt_stdlib2/error.inc +22 -6
  124. package/runtime/xt_stdlib2/json.inc +3 -2
  125. package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
  126. package/runtime/xt_stdlib2/regexp-match.inc +48 -33
  127. package/runtime/xt_stdlib2/strings.inc +23 -14
  128. package/runtime/xt_symbol.c +1 -1
  129. package/runtime/xt_values/comparison.inc +2 -2
  130. package/runtime/xt_values/constructors.inc +10 -5
  131. package/runtime/xt_values/conversions.inc +4 -3
  132. package/runtime/xt_values/strings.inc +78 -4
  133. package/scripts/bench.ts +172 -0
  134. package/scripts/build-runtime.ts +2 -1
  135. package/scripts/check-version.ts +53 -0
  136. package/src/ast/common.ts +23 -0
  137. package/src/cli/main.ts +68 -26
  138. package/src/codegen/generator/calls/invocation.ts +5 -0
  139. package/src/codegen/generator/context.ts +129 -18
  140. package/src/codegen/generator/debug.ts +132 -0
  141. package/src/codegen/generator/expressions/assignment.ts +9 -5
  142. package/src/codegen/generator/expressions/operators.ts +47 -8
  143. package/src/codegen/generator/expressions/primary.ts +5 -2
  144. package/src/codegen/generator/generator.ts +18 -2
  145. package/src/codegen/generator/module.ts +78 -6
  146. package/src/codegen/generator/numeric.ts +158 -0
  147. package/src/codegen/generator/state.ts +22 -0
  148. package/src/codegen/generator/statements/control-flow.ts +1 -1
  149. package/src/codegen/generator/statements/dispatch.ts +3 -0
  150. package/src/codegen/generator/statements/variables.ts +8 -2
  151. package/src/codegen/generator/tables.ts +26 -0
  152. package/src/driver/bundler/merge.ts +39 -5
  153. package/src/driver/compiler.ts +62 -55
  154. package/src/driver/fingerprint.ts +85 -0
  155. package/src/driver/toolchain-provider.ts +17 -16
  156. package/src/driver/toolchain.ts +210 -21
  157. package/src/parser/expressions.ts +20 -1
@@ -9,13 +9,16 @@ static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value pad
9
9
  if (!pad || pad->length == 0) return value;
10
10
  size_t total = (size_t)target;
11
11
  xt_string *out = xt_string_alloc(total);
12
+ char *mutable = xt_string_mutable(out);
13
+ const char *text = xt_string_bytes(s);
14
+ const char *padBytes = xt_string_bytes(pad);
12
15
  size_t needed = total - s->length;
13
16
  if (start) {
14
- for (size_t i = 0; i < needed; i++) out->data[i] = pad->data[i % pad->length];
15
- memcpy(out->data + needed, s->data, s->length);
17
+ for (size_t i = 0; i < needed; i++) mutable[i] = padBytes[i % pad->length];
18
+ memcpy(mutable + needed, text, s->length);
16
19
  } else {
17
- memcpy(out->data, s->data, s->length);
18
- for (size_t i = 0; i < needed; i++) out->data[s->length + i] = pad->data[i % pad->length];
20
+ memcpy(mutable, text, s->length);
21
+ for (size_t i = 0; i < needed; i++) mutable[s->length + i] = padBytes[i % pad->length];
19
22
  }
20
23
  return XT_FROM_PTR(XT_TAG_STRING, out);
21
24
  }
@@ -23,11 +26,12 @@ static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value pad
23
26
  static xt_value xt_string_trim_side(xt_value value, int start) {
24
27
  xt_string *s = xt_as_string(value);
25
28
  if (!s) return value;
29
+ const char *bytes = xt_string_bytes(s);
26
30
  uint32_t begin = 0;
27
31
  uint32_t end = s->length;
28
- if (start) { while (begin < end && isspace((unsigned char)s->data[begin])) begin++; }
29
- else { while (end > begin && isspace((unsigned char)s->data[end - 1])) end--; }
30
- return xt_string_new(s->data + begin, end - begin);
32
+ if (start) { while (begin < end && isspace((unsigned char)bytes[begin])) begin++; }
33
+ else { while (end > begin && isspace((unsigned char)bytes[end - 1])) end--; }
34
+ return xt_string_new(bytes + begin, end - begin);
31
35
  }
32
36
 
33
37
  static xt_value xt_string_at(xt_value value, xt_value indexValue) {
@@ -36,7 +40,7 @@ static xt_value xt_string_at(xt_value value, xt_value indexValue) {
36
40
  int32_t index = indexValue == XT_UNDEFINED ? 0 : xt_to_int32(indexValue);
37
41
  if (index < 0) index += (int32_t)s->length;
38
42
  if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
39
- return xt_string_new(s->data + index, 1);
43
+ return xt_string_new(xt_string_bytes(s) + index, 1);
40
44
  }
41
45
 
42
46
  static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_value replacementValue) {
@@ -46,20 +50,23 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
46
50
  xt_string *search = xt_as_string(xt_to_string(searchValue));
47
51
  xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
48
52
  if (!search || search->length == 0) return value;
53
+ const char *text = xt_string_bytes(s);
54
+ const char *needle = xt_string_bytes(search);
55
+ const char *repl = xt_string_bytes(replacement);
49
56
  size_t capacity = s->length + replacement->length * 4 + 16;
50
57
  char *out = (char *)malloc(capacity);
51
58
  if (!out) abort();
52
59
  size_t length = 0;
53
60
  uint32_t i = 0;
54
61
  while (i < s->length) {
55
- if (i + search->length <= s->length && memcmp(s->data + i, search->data, search->length) == 0) {
62
+ if (i + search->length <= s->length && memcmp(text + i, needle, search->length) == 0) {
56
63
  if (length + replacement->length + 2 > capacity) { capacity = (length + replacement->length + 2) * 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
57
- memcpy(out + length, replacement->data, replacement->length);
64
+ memcpy(out + length, repl, replacement->length);
58
65
  length += replacement->length;
59
66
  i += search->length;
60
67
  } else {
61
68
  if (length + 2 > capacity) { capacity *= 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
62
- out[length++] = s->data[i++];
69
+ out[length++] = text[i++];
63
70
  }
64
71
  }
65
72
  xt_value result = xt_string_new(out, length);
@@ -106,17 +113,19 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
106
113
  if (limit > (int32_t)s->length) limit = (int32_t)s->length;
107
114
  int32_t nlen = (int32_t)needle->length;
108
115
  if (nlen == 0) return xt_number((double)(limit < 0 ? 0 : limit));
116
+ const char *text = xt_string_bytes(s);
117
+ const char *need = xt_string_bytes(needle);
109
118
  int32_t i = limit;
110
119
  if (i > (int32_t)s->length - nlen) i = (int32_t)s->length - nlen;
111
120
  for (; i >= 0; i--) {
112
- if (memcmp(s->data + i, needle->data, (size_t)nlen) == 0) return xt_number((double)i);
121
+ if (memcmp(text + i, need, (size_t)nlen) == 0) return xt_number((double)i);
113
122
  }
114
123
  return xt_number(-1);
115
124
  }
116
125
  if (strcmp(method, "localeCompare") == 0) {
117
126
  xt_string *a = xt_as_string(target);
118
127
  xt_string *b = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));
119
- int cmp = strcmp(a ? a->data : "", b ? b->data : "");
128
+ int cmp = strcmp(a ? xt_string_bytes(a) : "", b ? xt_string_bytes(b) : "");
120
129
  return xt_number(cmp < 0 ? -1 : cmp > 0 ? 1 : 0);
121
130
  }
122
131
  if (strcmp(method, "codePointAt") == 0) {
@@ -124,7 +133,7 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
124
133
  if (!s) return XT_UNDEFINED;
125
134
  int32_t index = xt_to_int32(xt_arg_at(argc, argv, 0));
126
135
  if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
127
- return xt_number((double)(unsigned char)s->data[index]);
136
+ return xt_number((double)(unsigned char)xt_string_bytes(s)[index]);
128
137
  }
129
138
  if (strcmp(method, "valueOf") == 0 || strcmp(method, "toString") == 0) return target;
130
139
  *handled = 0;
@@ -98,7 +98,7 @@ xt_value xt_symbol_to_string(xt_value value) {
98
98
  char *buffer = (char *)malloc(8 + length);
99
99
  if (!buffer) abort();
100
100
  memcpy(buffer, "Symbol(", 7);
101
- if (description) memcpy(buffer + 7, description->data, length);
101
+ if (description) memcpy(buffer + 7, xt_string_bytes(description), length);
102
102
  buffer[7 + length] = ')';
103
103
  xt_value result = xt_string_new(buffer, 8 + length);
104
104
  free(buffer);
@@ -33,7 +33,7 @@ xt_value xt_lt(xt_value a, xt_value b) {
33
33
  xt_string *x = xt_as_string(a);
34
34
  xt_string *y = xt_as_string(b);
35
35
  size_t n = x->length < y->length ? x->length : y->length;
36
- int cmp = memcmp(x->data, y->data, n);
36
+ int cmp = memcmp(xt_string_bytes(x), xt_string_bytes(y), n);
37
37
  if (cmp == 0) return xt_bool(x->length < y->length);
38
38
  return xt_bool(cmp < 0);
39
39
  }
@@ -48,7 +48,7 @@ xt_value xt_le(xt_value a, xt_value b) {
48
48
  xt_string *x = xt_as_string(a);
49
49
  xt_string *y = xt_as_string(b);
50
50
  size_t n = x->length < y->length ? x->length : y->length;
51
- int cmp = memcmp(x->data, y->data, n);
51
+ int cmp = memcmp(xt_string_bytes(x), xt_string_bytes(y), n);
52
52
  if (cmp == 0) return xt_bool(x->length <= y->length);
53
53
  return xt_bool(cmp < 0);
54
54
  }
@@ -10,19 +10,22 @@ xt_value xt_number(double d) { return xt_from_double(d); }
10
10
  xt_string *xt_string_alloc(size_t length) {
11
11
  xt_string *s = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
12
12
  s->length = (uint32_t)length;
13
+ /* Keep the terminator in the allocation: many consumers hand `flat` to libc. */
13
14
  s->capacity = (uint32_t)length + 1;
14
- s->data = (char *)malloc(s->capacity);
15
- if (!s->data) {
15
+ s->flat = (char *)malloc(s->capacity);
16
+ if (!s->flat) {
16
17
  fprintf(stderr, "xbintsc: out of memory allocating string\n");
17
18
  abort();
18
19
  }
19
- s->data[length] = '\0';
20
+ s->flat[length] = '\0';
21
+ s->left = NULL;
22
+ s->right = NULL;
20
23
  return s;
21
24
  }
22
25
 
23
26
  xt_value xt_string_new(const char *data, size_t len) {
24
27
  xt_string *s = xt_string_alloc(len);
25
- if (len > 0) memcpy(s->data, data, len);
28
+ if (len > 0) memcpy(s->flat, data, len);
26
29
  return XT_FROM_PTR(XT_TAG_STRING, s);
27
30
  }
28
31
 
@@ -30,9 +33,11 @@ xt_value xt_string_from_cstr(const char *data) { return xt_string_new(data, strl
30
33
 
31
34
  xt_string *xt_as_string(xt_value v);
32
35
 
36
+ char *xt_string_mutable(xt_string *s) { return s ? s->flat : NULL; }
37
+
33
38
  const char *xt_string_data(xt_value value) {
34
39
  xt_string *s = xt_as_string(value);
35
- return s ? s->data : NULL;
40
+ return s ? xt_string_bytes(s) : NULL;
36
41
  }
37
42
 
38
43
  int32_t xt_string_length_value(xt_value value) {
@@ -24,8 +24,9 @@ double xt_to_number(xt_value v) {
24
24
  if (XT_IS_BIGINT(v)) return xt_bigint_to_double_value(v);
25
25
  if (XT_IS_STRING(v)) {
26
26
  xt_string *s = xt_as_string(v);
27
- const char *begin = s->data;
28
- const char *end = s->data + s->length;
27
+ const char *bytes = xt_string_bytes(s);
28
+ const char *begin = bytes;
29
+ const char *end = bytes + s->length;
29
30
  while (begin < end && isspace((unsigned char)*begin)) begin++;
30
31
  while (end > begin && isspace((unsigned char)end[-1])) end--;
31
32
  size_t length = (size_t)(end - begin);
@@ -83,7 +84,7 @@ xt_value xt_to_string(xt_value v) {
83
84
  out = (char *)realloc(out, capacity);
84
85
  }
85
86
  if (i > 0) out[length++] = ',';
86
- memcpy(out + length, str->data, str->length);
87
+ memcpy(out + length, xt_string_bytes(str), str->length);
87
88
  length += str->length;
88
89
  out[length] = '\0';
89
90
  }
@@ -2,6 +2,12 @@
2
2
  /* Strings */
3
3
  /* ------------------------------------------------------------------------- */
4
4
 
5
+ /* Concatenations up to this many bytes are copied eagerly. The copy is cheaper
6
+ * than a cons node for the short strings that dominate everyday code, and a
7
+ * loop that keeps appending crosses the threshold quickly, after which every
8
+ * append is O(1). */
9
+ #define XT_STRING_CONS_THRESHOLD 32
10
+
5
11
  xt_string *xt_as_string(xt_value v) {
6
12
  if (XT_IS_STRING(v)) return (xt_string *)XT_GET_PTR(v);
7
13
  return NULL;
@@ -9,14 +15,82 @@ xt_string *xt_as_string(xt_value v) {
9
15
 
10
16
  int32_t xt_string_length(xt_string *s) { return (int32_t)s->length; }
11
17
 
18
+ const char *xt_string_bytes(xt_string *s) {
19
+ if (!s) return NULL;
20
+ if (s->flat) return s->flat;
21
+
22
+ /* The tree is traversed depth-first without recursion: a loop that appends
23
+ * 100k times produces a 100k-deep left spine, which would overflow the C
24
+ * stack. Right children are pushed so the leftmost leaf is emitted first. */
25
+ size_t total = (size_t)s->length;
26
+ char *out = (char *)malloc(total + 1);
27
+ if (!out) {
28
+ fprintf(stderr, "xbintsc: out of memory flattening string\n");
29
+ abort();
30
+ }
31
+
32
+ size_t capacity = 64;
33
+ size_t count = 0;
34
+ xt_string **stack = (xt_string **)malloc(sizeof(xt_string *) * capacity);
35
+ if (!stack) {
36
+ fprintf(stderr, "xbintsc: out of memory flattening string\n");
37
+ abort();
38
+ }
39
+ stack[count++] = s;
40
+
41
+ size_t offset = 0;
42
+ while (count > 0) {
43
+ xt_string *node = stack[--count];
44
+ if (node->flat) {
45
+ memcpy(out + offset, node->flat, node->length);
46
+ offset += node->length;
47
+ continue;
48
+ }
49
+ if (count + 2 > capacity) {
50
+ while (count + 2 > capacity) capacity *= 2;
51
+ xt_string **grown = (xt_string **)realloc(stack, sizeof(xt_string *) * capacity);
52
+ if (!grown) {
53
+ fprintf(stderr, "xbintsc: out of memory flattening string\n");
54
+ abort();
55
+ }
56
+ stack = grown;
57
+ }
58
+ if (node->right) stack[count++] = node->right;
59
+ if (node->left) stack[count++] = node->left;
60
+ }
61
+ free(stack);
62
+
63
+ out[offset] = '\0';
64
+ s->flat = out;
65
+ s->capacity = (uint32_t)(total + 1);
66
+ s->left = NULL;
67
+ s->right = NULL;
68
+ return s->flat;
69
+ }
70
+
12
71
  static xt_value xt_string_concat(xt_string *a, xt_string *b) {
72
+ if (a->length == 0) return XT_FROM_PTR(XT_TAG_STRING, b);
73
+ if (b->length == 0) return XT_FROM_PTR(XT_TAG_STRING, a);
13
74
  size_t total = (size_t)a->length + (size_t)b->length;
14
- xt_string *out = xt_string_alloc(total);
15
- memcpy(out->data, a->data, a->length);
16
- memcpy(out->data + a->length, b->data, b->length);
75
+ if (total <= XT_STRING_CONS_THRESHOLD) {
76
+ xt_string *out = xt_string_alloc(total);
77
+ memcpy(out->flat, xt_string_bytes(a), a->length);
78
+ memcpy(out->flat + a->length, xt_string_bytes(b), b->length);
79
+ return XT_FROM_PTR(XT_TAG_STRING, out);
80
+ }
81
+ xt_string *out = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
82
+ out->length = (uint32_t)total;
83
+ out->capacity = 0;
84
+ out->flat = NULL;
85
+ out->left = a;
86
+ out->right = b;
17
87
  return XT_FROM_PTR(XT_TAG_STRING, out);
18
88
  }
19
89
 
20
90
  int xt_string_equals(xt_string *a, xt_string *b) {
21
- return a->length == b->length && memcmp(a->data, b->data, a->length) == 0;
91
+ /* Interned literals (property names above all) are shared, so identity is the
92
+ * common case and skips the length, flattening and byte comparison entirely. */
93
+ if (a == b) return 1;
94
+ if (a->length != b->length) return 0;
95
+ return memcmp(xt_string_bytes(a), xt_string_bytes(b), a->length) == 0;
22
96
  }
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build and time a small, fixed benchmark suite.
4
+ *
5
+ * The numbers are wall-clock times on the current machine, so they are only
6
+ * useful for comparing revisions of the compiler (or the same revision against
7
+ * Node): run `npm run bench` before and after a change to the runtime or
8
+ * codegen. Each program is compiled with the normal driver (cache bypassed) and
9
+ * executed several times; the table reports the fastest run to reduce noise.
10
+ *
11
+ * Usage:
12
+ * npm run bench # every benchmark, 3 runs, with Node for scale
13
+ * npm run bench -- --runs 5 # more runs (minimum is reported)
14
+ * npm run bench -- --filter prop
15
+ * npm run bench -- --no-node # skip the Node comparison
16
+ */
17
+
18
+ import { spawnSync } from "node:child_process";
19
+ import { mkdirSync, statSync, writeFileSync } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ import { build } from "../src/driver/index.js";
23
+
24
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
25
+ const outDir = join(root, "build", "bench");
26
+ const cacheDir = join(root, "build", "bench-cache");
27
+
28
+ interface Benchmark {
29
+ readonly name: string;
30
+ /** Plain JavaScript so the same source can run under Node. */
31
+ readonly source: string;
32
+ }
33
+
34
+ const BENCHMARKS: readonly Benchmark[] = [
35
+ {
36
+ name: "numeric",
37
+ source: `
38
+ let total = 0;
39
+ for (let i = 0; i < 50000000; i++) total = total + i;
40
+ console.log(total);
41
+ `,
42
+ },
43
+ {
44
+ name: "property",
45
+ source: `
46
+ const object = { x: 0 };
47
+ for (let i = 0; i < 5000000; i++) object.x = object.x + 1;
48
+ console.log(object.x);
49
+ `,
50
+ },
51
+ {
52
+ name: "array",
53
+ source: `
54
+ const values = [];
55
+ for (let i = 0; i < 3000000; i++) values[i] = i;
56
+ let total = 0;
57
+ for (let i = 0; i < 3000000; i++) total = total + values[i];
58
+ console.log(total);
59
+ `,
60
+ },
61
+ {
62
+ name: "string-concat",
63
+ source: `
64
+ let text = "";
65
+ for (let i = 0; i < 100000; i++) text = text + "a";
66
+ console.log(text.length);
67
+ `,
68
+ },
69
+ ];
70
+
71
+ interface Options {
72
+ readonly runs: number;
73
+ readonly filter?: string;
74
+ readonly node: boolean;
75
+ }
76
+
77
+ function parseOptions(argv: readonly string[]): Options {
78
+ let runs = 3;
79
+ let filter: string | undefined;
80
+ let node = true;
81
+ for (let index = 0; index < argv.length; index++) {
82
+ const arg = argv[index]!;
83
+ if (arg === "--no-node") node = false;
84
+ else if (arg === "--runs") runs = Math.max(1, Number(argv[++index]) || 1);
85
+ else if (arg.startsWith("--runs=")) runs = Math.max(1, Number(arg.slice("--runs=".length)) || 1);
86
+ else if (arg === "--filter") filter = argv[++index];
87
+ else if (arg.startsWith("--filter=")) filter = arg.slice("--filter=".length);
88
+ else if (arg === "--help" || arg === "-h") {
89
+ process.stdout.write(
90
+ "usage: npm run bench [-- --runs N] [--filter <substr>] [--no-node]\n",
91
+ );
92
+ process.exit(0);
93
+ }
94
+ }
95
+ return { runs, filter, node };
96
+ }
97
+
98
+ function minTimeMs(runs: number, execute: () => void): number {
99
+ let best = Number.POSITIVE_INFINITY;
100
+ for (let run = 0; run < runs; run++) {
101
+ const start = process.hrtime.bigint();
102
+ execute();
103
+ const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
104
+ if (elapsed < best) best = elapsed;
105
+ }
106
+ return best;
107
+ }
108
+
109
+ function formatMs(value: number | undefined): string {
110
+ return value === undefined ? "-" : `${value.toFixed(1)} ms`;
111
+ }
112
+
113
+ function main(): number {
114
+ const options = parseOptions(process.argv.slice(2));
115
+ mkdirSync(outDir, { recursive: true });
116
+ mkdirSync(cacheDir, { recursive: true });
117
+ const benchmarks = BENCHMARKS.filter(
118
+ (benchmark) => options.filter === undefined || benchmark.name.includes(options.filter),
119
+ );
120
+ if (benchmarks.length === 0) {
121
+ process.stderr.write(`xbintsc: no benchmark matches '${options.filter}'\n`);
122
+ return 1;
123
+ }
124
+
125
+ const rows: { name: string; ours?: number; node?: number; bytes?: number }[] = [];
126
+ for (const benchmark of benchmarks) {
127
+ const entry = join(outDir, `${benchmark.name}.ts`);
128
+ writeFileSync(entry, benchmark.source);
129
+ const result = build(entry, {
130
+ emit: "exe",
131
+ outDir: join(outDir, "bin"),
132
+ cacheDir,
133
+ force: true,
134
+ });
135
+ const errors = result.diagnostics.filter((diagnostic) => diagnostic.category === "error");
136
+ if (errors.length > 0) {
137
+ for (const error of errors) process.stderr.write(`xbintsc: ${error.message}\n`);
138
+ return 1;
139
+ }
140
+ const bytes = statSync(result.outputPath).size;
141
+ const ours = minTimeMs(options.runs, () => {
142
+ const executed = spawnSync(result.outputPath, [], { stdio: "ignore" });
143
+ if (executed.status !== 0) throw new Error(`${benchmark.name} exited with ${executed.status}`);
144
+ });
145
+
146
+ let nodeTime: number | undefined;
147
+ if (options.node) {
148
+ const script = join(outDir, `${benchmark.name}.mjs`);
149
+ writeFileSync(script, benchmark.source);
150
+ nodeTime = minTimeMs(options.runs, () => {
151
+ const executed = spawnSync(process.execPath, [script], { stdio: "ignore" });
152
+ if (executed.status !== 0) throw new Error(`${benchmark.name} (node) exited with ${executed.status}`);
153
+ });
154
+ }
155
+ rows.push({ name: benchmark.name, ours, node: nodeTime, bytes });
156
+ }
157
+
158
+ const header = ["benchmark", "xbintsc", "node", "size"];
159
+ process.stdout.write(
160
+ `${header[0]!.padEnd(16)}${header[1]!.padStart(12)}${header[2]!.padStart(12)}${header[3]!.padStart(12)}\n`,
161
+ );
162
+ for (const row of rows) {
163
+ const size = row.bytes === undefined ? "-" : `${Math.round(row.bytes / 1024)} KB`;
164
+ process.stdout.write(
165
+ `${row.name.padEnd(16)}${formatMs(row.ours).padStart(12)}${formatMs(row.node).padStart(12)}${size.padStart(12)}\n`,
166
+ );
167
+ }
168
+ process.stdout.write(`\nfastest of ${options.runs} run(s) on ${process.platform}-${process.arch}\n`);
169
+ return 0;
170
+ }
171
+
172
+ process.exit(main());
@@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url";
21
21
  import { RUNTIME_SOURCES } from "../src/driver/compiler.js";
22
22
  import { platformSlug } from "../src/driver/paths.js";
23
23
  import { resolveToolchain } from "../src/driver/toolchain-provider.js";
24
+ import { RUNTIME_CFLAGS } from "../src/driver/toolchain.js";
24
25
  import { nodeExtension } from "../src/extensions/node/index.js";
25
26
 
26
27
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -72,7 +73,7 @@ function compile(sources: readonly string[], extension: string): string[] {
72
73
  const object = join(objDir, `${extension}_${index}_${basename(source, ".c")}.o`);
73
74
  const result = spawnSync(
74
75
  clang,
75
- ["-O2", "-Wall", "-Wextra", "-D_CRT_SECURE_NO_WARNINGS", "-c", source, "-o", object, `-I${runtimeDir}`],
76
+ [...RUNTIME_CFLAGS, "-c", source, "-o", object, `-I${runtimeDir}`],
76
77
  { stdio: "inherit", env: childEnv },
77
78
  );
78
79
  if (result.status !== 0) {
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Verify that `COMPILER_VERSION` (reported by `xbintsc version` / `doctor` and
4
+ * embedded in the incremental cache key) matches the version in `package.json`
5
+ * (which names the release archives).
6
+ *
7
+ * Release automation runs this before tagging. A mismatch means an upgraded
8
+ * compiler can reuse binaries produced by the previous one while presenting the
9
+ * wrong version to bug reports.
10
+ */
11
+
12
+ import { readFileSync } from "node:fs";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
+
16
+ /** Read the version literal from the compiler source. */
17
+ export function compilerVersionOf(root: string): string {
18
+ const text = readFileSync(join(root, "src", "driver", "compiler.ts"), "utf8");
19
+ const match = /export const COMPILER_VERSION = "([^"]+)"/.exec(text);
20
+ if (!match) throw new Error("COMPILER_VERSION is not declared in src/driver/compiler.ts");
21
+ return match[1]!;
22
+ }
23
+
24
+ /** Read the published version from package.json. */
25
+ export function packageVersionOf(root: string): string {
26
+ const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { version?: string };
27
+ if (!parsed.version) throw new Error("package.json has no version field");
28
+ return parsed.version;
29
+ }
30
+
31
+ /** Returns a human-readable mismatch message, or `undefined` when in sync. */
32
+ export function checkCompilerVersion(root: string): string | undefined {
33
+ const packageVersion = packageVersionOf(root);
34
+ const compilerVersion = compilerVersionOf(root);
35
+ if (packageVersion === compilerVersion) return undefined;
36
+ return (
37
+ `COMPILER_VERSION is "${compilerVersion}" but package.json is "${packageVersion}".\n` +
38
+ 'Update COMPILER_VERSION in src/driver/compiler.ts (it keys the incremental cache), or fix package.json.'
39
+ );
40
+ }
41
+
42
+ const invokedDirectly =
43
+ process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
44
+
45
+ if (invokedDirectly) {
46
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
47
+ const mismatch = checkCompilerVersion(root);
48
+ if (mismatch) {
49
+ process.stderr.write(`xbintsc: version mismatch\n${mismatch}\n`);
50
+ process.exit(1);
51
+ }
52
+ process.stdout.write(`xbintsc: version ${compilerVersionOf(root)} matches package.json\n`);
53
+ }
package/src/ast/common.ts CHANGED
@@ -207,4 +207,27 @@ export interface SourceFileNode extends Node {
207
207
  readonly statements: Statement[];
208
208
  readonly fileName: string;
209
209
  readonly text: string;
210
+ /**
211
+ * For a bundled program: the original module each node was parsed from.
212
+ *
213
+ * The bundler merges every module into one `SourceFile`, but the nodes keep
214
+ * their module-local `start`/`end` offsets. Diagnostics must therefore report
215
+ * the module a node came from, not the entry file, or a cross-module error
216
+ * shows the wrong file and a nonsensical excerpt. Absent for a single-file
217
+ * compile, where `fileName` is the only source.
218
+ */
219
+ readonly origins?: Map<object, string>;
220
+ /**
221
+ * For a bundled program: the original text of every module, keyed like
222
+ * {@link origins}. A node's `start` is relative to its module's text, so
223
+ * computing a frame's line number needs that text rather than the merged
224
+ * concatenation in `text`.
225
+ */
226
+ readonly moduleTexts?: Map<string, string>;
227
+ /**
228
+ * Bundler-generated symbol prefixes (`m0_`, `m1_`, …). Display names (stack
229
+ * frames, symbol suffixes) strip them so a bundled `fail()` still shows as
230
+ * `fail`, not `m1_fail`.
231
+ */
232
+ readonly modulePrefixes?: readonly string[];
210
233
  }