str stone; concat
This commit is contained in:
@@ -212,6 +212,7 @@ typedef enum MachOpcode {
|
||||
|
||||
/* Text */
|
||||
MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */
|
||||
MACH_STONE_TEXT, /* stone(R(A)) — freeze mutable text before escape */
|
||||
|
||||
/* Typed integer comparisons (ABC) */
|
||||
MACH_EQ_INT, /* R(A) = (R(B) == R(C)) — int */
|
||||
@@ -372,6 +373,7 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
||||
[MACH_NOP] = "nop",
|
||||
/* Mcode-derived */
|
||||
[MACH_CONCAT] = "concat",
|
||||
[MACH_STONE_TEXT] = "stone_text",
|
||||
[MACH_EQ_INT] = "eq_int",
|
||||
[MACH_NE_INT] = "ne_int",
|
||||
[MACH_LT_INT] = "lt_int",
|
||||
@@ -1392,7 +1394,7 @@ vm_dispatch:
|
||||
DT(MACH_HASPROP), DT(MACH_REGEXP),
|
||||
DT(MACH_EQ_TOL), DT(MACH_NEQ_TOL),
|
||||
DT(MACH_NOP),
|
||||
DT(MACH_CONCAT),
|
||||
DT(MACH_CONCAT), DT(MACH_STONE_TEXT),
|
||||
DT(MACH_EQ_INT), DT(MACH_NE_INT),
|
||||
DT(MACH_LT_INT), DT(MACH_LE_INT),
|
||||
DT(MACH_GT_INT), DT(MACH_GE_INT),
|
||||
@@ -2026,6 +2028,7 @@ vm_dispatch:
|
||||
}
|
||||
target = next;
|
||||
}
|
||||
stone_mutable_text(target->slots[c]);
|
||||
frame->slots[a] = target->slots[c];
|
||||
VM_BREAK();
|
||||
}
|
||||
@@ -2123,6 +2126,7 @@ vm_dispatch:
|
||||
}
|
||||
|
||||
VM_CASE(MACH_RETURN):
|
||||
stone_mutable_text(frame->slots[a]);
|
||||
result = frame->slots[a];
|
||||
if (!JS_IsPtr(frame->caller)) goto done;
|
||||
{
|
||||
@@ -2285,15 +2289,48 @@ vm_dispatch:
|
||||
|
||||
/* === New mcode-derived opcodes === */
|
||||
|
||||
/* Text concatenation */
|
||||
/* Text concatenation — with in-place append fast path for s = s + x */
|
||||
VM_CASE(MACH_CONCAT): {
|
||||
JSValue res = JS_ConcatString(ctx, frame->slots[b], frame->slots[c]);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(res)) goto disrupt;
|
||||
frame->slots[a] = res;
|
||||
if (a == b) {
|
||||
/* Self-assign pattern: slot[a] = slot[a] + slot[c] */
|
||||
JSValue left = frame->slots[a];
|
||||
JSValue right = frame->slots[c];
|
||||
/* Inline fast path: mutable heap text with enough capacity */
|
||||
if (JS_IsPtr(left)) {
|
||||
JSText *s = (JSText *)chase(left);
|
||||
int slen = (int)s->length;
|
||||
int rlen = js_string_value_len(right);
|
||||
int cap = (int)objhdr_cap56(s->hdr);
|
||||
if (objhdr_type(s->hdr) == OBJ_TEXT
|
||||
&& !(s->hdr & OBJHDR_S_MASK)
|
||||
&& slen + rlen <= cap) {
|
||||
/* Append in-place — zero allocation, no GC possible */
|
||||
for (int i = 0; i < rlen; i++)
|
||||
string_put(s, slen + i, js_string_value_get(right, i));
|
||||
s->length = slen + rlen;
|
||||
VM_BREAK();
|
||||
}
|
||||
}
|
||||
/* Slow path: allocate with growth factor, leave unstoned */
|
||||
JSValue res = JS_ConcatStringGrow(ctx, frame->slots[b], frame->slots[c]);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(res)) goto disrupt;
|
||||
frame->slots[a] = res;
|
||||
} else {
|
||||
/* Different target: use existing exact-fit stoned path */
|
||||
JSValue res = JS_ConcatString(ctx, frame->slots[b], frame->slots[c]);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(res)) goto disrupt;
|
||||
frame->slots[a] = res;
|
||||
}
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
/* Stone mutable text — compiler-emitted at escape points */
|
||||
VM_CASE(MACH_STONE_TEXT):
|
||||
stone_mutable_text(frame->slots[a]);
|
||||
VM_BREAK();
|
||||
|
||||
/* Typed integer comparisons */
|
||||
VM_CASE(MACH_EQ_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) == JS_VALUE_GET_INT(frame->slots[c]));
|
||||
@@ -3026,6 +3063,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
else if (strcmp(op, "move") == 0) { AB2(MACH_MOVE); }
|
||||
/* Text */
|
||||
else if (strcmp(op, "concat") == 0) { ABC3(MACH_CONCAT); }
|
||||
else if (strcmp(op, "stone_text") == 0) { EM(MACH_ABC(MACH_STONE_TEXT, A1, 0, 0)); }
|
||||
/* Generic arithmetic */
|
||||
else if (strcmp(op, "add") == 0) { ABC3(MACH_ADD); }
|
||||
else if (strcmp(op, "subtract") == 0) { ABC3(MACH_SUB); }
|
||||
|
||||
@@ -478,6 +478,17 @@ static inline void mach_resolve_forward(JSValue *slot) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Stone a mutable (unstoned) heap text in-place. Used at escape points
|
||||
in the VM to enforce the invariant that an unstoned text is uniquely
|
||||
referenced by exactly one slot. */
|
||||
static inline void stone_mutable_text(JSValue v) {
|
||||
if (JS_IsPtr(v)) {
|
||||
objhdr_t *oh = (objhdr_t *)JS_VALUE_GET_PTR(v);
|
||||
if (objhdr_type(*oh) == OBJ_TEXT && !(*oh & OBJHDR_S_MASK))
|
||||
*oh = objhdr_set_s(*oh, true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline type checks — use these in the VM dispatch loop to avoid
|
||||
function call overhead. The public API (JS_IsArray etc. in quickjs.h)
|
||||
remains non-inline for external callers; those wrappers live in runtime.c. */
|
||||
@@ -1205,6 +1216,7 @@ int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue va
|
||||
void *js_realloc_rt (void *ptr, size_t size);
|
||||
char *js_strdup_rt (const char *str);
|
||||
JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2);
|
||||
JSValue JS_ConcatStringGrow (JSContext *ctx, JSValue op1, JSValue op2);
|
||||
JSText *pretext_init (JSContext *ctx, int capacity);
|
||||
JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c);
|
||||
JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v);
|
||||
|
||||
@@ -2900,6 +2900,84 @@ JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) {
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
/* Concat with over-allocated capacity and NO stoning.
|
||||
Used by MACH_CONCAT self-assign (s = s + x) slow path so that
|
||||
subsequent appends can reuse the excess capacity in-place. */
|
||||
JSValue JS_ConcatStringGrow (JSContext *ctx, JSValue op1, JSValue op2) {
|
||||
if (unlikely (!JS_IsText (op1))) {
|
||||
JSGCRef op2_guard;
|
||||
JS_PushGCRef (ctx, &op2_guard);
|
||||
op2_guard.val = op2;
|
||||
op1 = JS_ToString (ctx, op1);
|
||||
op2 = op2_guard.val;
|
||||
JS_PopGCRef (ctx, &op2_guard);
|
||||
if (JS_IsException (op1)) return JS_EXCEPTION;
|
||||
}
|
||||
if (unlikely (!JS_IsText (op2))) {
|
||||
JSGCRef op1_guard;
|
||||
JS_PushGCRef (ctx, &op1_guard);
|
||||
op1_guard.val = op1;
|
||||
op2 = JS_ToString (ctx, op2);
|
||||
op1 = op1_guard.val;
|
||||
JS_PopGCRef (ctx, &op1_guard);
|
||||
if (JS_IsException (op2)) return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
int len1 = js_string_value_len (op1);
|
||||
int len2 = js_string_value_len (op2);
|
||||
int new_len = len1 + len2;
|
||||
|
||||
/* Try immediate ASCII for short results */
|
||||
if (new_len <= MIST_ASCII_MAX_LEN) {
|
||||
char buf[8];
|
||||
BOOL all_ascii = TRUE;
|
||||
for (int i = 0; i < len1 && all_ascii; i++) {
|
||||
uint32_t c = js_string_value_get (op1, i);
|
||||
if (c >= 0x80) all_ascii = FALSE;
|
||||
else buf[i] = (char)c;
|
||||
}
|
||||
for (int i = 0; i < len2 && all_ascii; i++) {
|
||||
uint32_t c = js_string_value_get (op2, i);
|
||||
if (c >= 0x80) all_ascii = FALSE;
|
||||
else buf[len1 + i] = (char)c;
|
||||
}
|
||||
if (all_ascii) {
|
||||
JSValue imm = MIST_TryNewImmediateASCII (buf, new_len);
|
||||
if (!JS_IsNull (imm)) return imm;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate with 2x growth factor, minimum 16 */
|
||||
int capacity = new_len * 2;
|
||||
if (capacity < 16) capacity = 16;
|
||||
|
||||
JSGCRef op1_ref, op2_ref;
|
||||
JS_PushGCRef (ctx, &op1_ref);
|
||||
op1_ref.val = op1;
|
||||
JS_PushGCRef (ctx, &op2_ref);
|
||||
op2_ref.val = op2;
|
||||
|
||||
JSText *p = js_alloc_string (ctx, capacity);
|
||||
if (!p) {
|
||||
JS_PopGCRef (ctx, &op2_ref);
|
||||
JS_PopGCRef (ctx, &op1_ref);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
op1 = op1_ref.val;
|
||||
op2 = op2_ref.val;
|
||||
JS_PopGCRef (ctx, &op2_ref);
|
||||
JS_PopGCRef (ctx, &op1_ref);
|
||||
|
||||
for (int i = 0; i < len1; i++)
|
||||
string_put (p, i, js_string_value_get (op1, i));
|
||||
for (int i = 0; i < len2; i++)
|
||||
string_put (p, len1 + i, js_string_value_get (op2, i));
|
||||
p->length = new_len;
|
||||
/* Do NOT stone — leave mutable so in-place append can reuse capacity */
|
||||
return JS_MKPTR (p);
|
||||
}
|
||||
|
||||
/* WARNING: proto must be an object or JS_NULL */
|
||||
JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val, JSClassID class_id) {
|
||||
JSGCRef proto_ref;
|
||||
|
||||
Reference in New Issue
Block a user