Merge branch 'optimize_mcode'

This commit is contained in:
2026-02-21 19:42:19 -06:00
34 changed files with 53477 additions and 121900 deletions

View File

@@ -212,34 +212,7 @@ typedef enum MachOpcode {
/* Text */
MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */
/* Typed integer comparisons (ABC) */
MACH_EQ_INT, /* R(A) = (R(B) == R(C)) — int */
MACH_NE_INT, /* R(A) = (R(B) != R(C)) — int */
MACH_LT_INT, /* R(A) = (R(B) < R(C)) — int */
MACH_LE_INT, /* R(A) = (R(B) <= R(C)) — int */
MACH_GT_INT, /* R(A) = (R(B) > R(C)) — int */
MACH_GE_INT, /* R(A) = (R(B) >= R(C)) — int */
/* Typed float comparisons (ABC) */
MACH_EQ_FLOAT, /* R(A) = (R(B) == R(C)) — float */
MACH_NE_FLOAT, /* R(A) = (R(B) != R(C)) — float */
MACH_LT_FLOAT, /* R(A) = (R(B) < R(C)) — float */
MACH_LE_FLOAT, /* R(A) = (R(B) <= R(C)) — float */
MACH_GT_FLOAT, /* R(A) = (R(B) > R(C)) — float */
MACH_GE_FLOAT, /* R(A) = (R(B) >= R(C)) — float */
/* Typed text comparisons (ABC) */
MACH_EQ_TEXT, /* R(A) = (R(B) == R(C)) — text */
MACH_NE_TEXT, /* R(A) = (R(B) != R(C)) — text */
MACH_LT_TEXT, /* R(A) = (R(B) < R(C)) — text */
MACH_LE_TEXT, /* R(A) = (R(B) <= R(C)) — text */
MACH_GT_TEXT, /* R(A) = (R(B) > R(C)) — text */
MACH_GE_TEXT, /* R(A) = (R(B) >= R(C)) — text */
/* Typed bool comparisons (ABC) */
MACH_EQ_BOOL, /* R(A) = (R(B) == R(C)) — bool */
MACH_NE_BOOL, /* R(A) = (R(B) != R(C)) — bool */
MACH_STONE_TEXT, /* stone(R(A)) — freeze mutable text before escape */
/* Special comparisons */
MACH_IS_IDENTICAL, /* R(A) = (R(B) === R(C)) — identity check (ABC) */
@@ -296,6 +269,18 @@ typedef enum MachOpcode {
MACH_IS_STONE, /* R(A) = is_stone(R(B)) */
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
MACH_IS_BLOB, /* R(A) = is_blob(R(B)) */
MACH_IS_DATA, /* R(A) = is_data(R(B)) — plain record, not array/func/blob */
MACH_IS_TRUE, /* R(A) = (R(B) === true) */
MACH_IS_FALSE, /* R(A) = (R(B) === false) */
MACH_IS_FIT, /* R(A) = is_fit(R(B)) — safe integer */
MACH_IS_CHAR, /* R(A) = is_character(R(B)) — single char text */
MACH_IS_DIGIT, /* R(A) = is_digit(R(B)) */
MACH_IS_LETTER, /* R(A) = is_letter(R(B)) */
MACH_IS_LOWER, /* R(A) = is_lower(R(B)) */
MACH_IS_UPPER, /* R(A) = is_upper(R(B)) */
MACH_IS_WS, /* R(A) = is_whitespace(R(B)) */
MACH_IS_ACTOR, /* R(A) = is_actor(R(B)) — has actor_sym property */
MACH_OP_COUNT
} MachOpcode;
@@ -372,26 +357,7 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_NOP] = "nop",
/* Mcode-derived */
[MACH_CONCAT] = "concat",
[MACH_EQ_INT] = "eq_int",
[MACH_NE_INT] = "ne_int",
[MACH_LT_INT] = "lt_int",
[MACH_LE_INT] = "le_int",
[MACH_GT_INT] = "gt_int",
[MACH_GE_INT] = "ge_int",
[MACH_EQ_FLOAT] = "eq_float",
[MACH_NE_FLOAT] = "ne_float",
[MACH_LT_FLOAT] = "lt_float",
[MACH_LE_FLOAT] = "le_float",
[MACH_GT_FLOAT] = "gt_float",
[MACH_GE_FLOAT] = "ge_float",
[MACH_EQ_TEXT] = "eq_text",
[MACH_NE_TEXT] = "ne_text",
[MACH_LT_TEXT] = "lt_text",
[MACH_LE_TEXT] = "le_text",
[MACH_GT_TEXT] = "gt_text",
[MACH_GE_TEXT] = "ge_text",
[MACH_EQ_BOOL] = "eq_bool",
[MACH_NE_BOOL] = "ne_bool",
[MACH_STONE_TEXT] = "stone_text",
[MACH_IS_IDENTICAL] = "is_identical",
[MACH_IS_INT] = "is_int",
[MACH_IS_NUM] = "is_num",
@@ -427,6 +393,18 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_IS_STONE] = "is_stone",
[MACH_LENGTH] = "length",
[MACH_IS_PROXY] = "is_proxy",
[MACH_IS_BLOB] = "is_blob",
[MACH_IS_DATA] = "is_data",
[MACH_IS_TRUE] = "is_true",
[MACH_IS_FALSE] = "is_false",
[MACH_IS_FIT] = "is_fit",
[MACH_IS_CHAR] = "is_char",
[MACH_IS_DIGIT] = "is_digit",
[MACH_IS_LETTER] = "is_letter",
[MACH_IS_LOWER] = "is_lower",
[MACH_IS_UPPER] = "is_upper",
[MACH_IS_WS] = "is_ws",
[MACH_IS_ACTOR] = "is_actor",
};
/* ---- Compile-time constant pool entry ---- */
@@ -1080,10 +1058,6 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
}
}
/* String concat for ADD */
if (op == MACH_ADD && mist_is_text(a) && mist_is_text(b))
return JS_ConcatString(ctx, a, b);
/* Comparison ops allow mixed types — return false for mismatches */
if (op >= MACH_EQ && op <= MACH_GE) {
/* Fast path: identical values (chase pointers for forwarded objects) */
@@ -1142,7 +1116,10 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
default: break;
}
}
/* Different types: EQ→false, NEQ→true, others→false */
/* Different types for ordering comparisons: disrupt */
if (op >= MACH_LT && op <= MACH_GE)
return JS_RaiseDisrupt(ctx, "cannot compare: operands must be same type");
/* EQ/NEQ with different types: false/true */
if (op == MACH_NEQ) return JS_NewBool(ctx, 1);
return JS_NewBool(ctx, 0);
}
@@ -1422,17 +1399,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_EQ_INT), DT(MACH_NE_INT),
DT(MACH_LT_INT), DT(MACH_LE_INT),
DT(MACH_GT_INT), DT(MACH_GE_INT),
DT(MACH_EQ_FLOAT), DT(MACH_NE_FLOAT),
DT(MACH_LT_FLOAT), DT(MACH_LE_FLOAT),
DT(MACH_GT_FLOAT), DT(MACH_GE_FLOAT),
DT(MACH_EQ_TEXT), DT(MACH_NE_TEXT),
DT(MACH_LT_TEXT), DT(MACH_LE_TEXT),
DT(MACH_GT_TEXT), DT(MACH_GE_TEXT),
DT(MACH_EQ_BOOL), DT(MACH_NE_BOOL),
DT(MACH_CONCAT), DT(MACH_STONE_TEXT),
DT(MACH_IS_IDENTICAL),
DT(MACH_IS_INT), DT(MACH_IS_NUM),
DT(MACH_IS_TEXT), DT(MACH_IS_BOOL),
@@ -1453,6 +1420,12 @@ vm_dispatch:
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
DT(MACH_IS_BLOB), DT(MACH_IS_DATA),
DT(MACH_IS_TRUE), DT(MACH_IS_FALSE),
DT(MACH_IS_FIT), DT(MACH_IS_CHAR),
DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER),
DT(MACH_IS_LOWER), DT(MACH_IS_UPPER),
DT(MACH_IS_WS), DT(MACH_IS_ACTOR),
};
#pragma GCC diagnostic pop
#undef DT
@@ -2062,6 +2035,7 @@ vm_dispatch:
}
target = next;
}
stone_mutable_text(target->slots[c]);
frame->slots[a] = target->slots[c];
VM_BREAK();
}
@@ -2171,6 +2145,7 @@ vm_dispatch:
}
VM_CASE(MACH_RETURN):
stone_mutable_text(frame->slots[a]);
result = frame->slots[a];
if (!JS_IsPtr(frame->caller)) goto done;
{
@@ -2338,81 +2313,46 @@ 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;
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]));
VM_BREAK();
VM_CASE(MACH_NE_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) != JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_LT_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) < JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_LE_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) <= JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_GT_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) > JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_GE_INT):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) >= JS_VALUE_GET_INT(frame->slots[c]));
VM_BREAK();
/* Typed float comparisons */
VM_CASE(MACH_EQ_FLOAT): VM_CASE(MACH_NE_FLOAT):
VM_CASE(MACH_LT_FLOAT): VM_CASE(MACH_LE_FLOAT):
VM_CASE(MACH_GT_FLOAT): VM_CASE(MACH_GE_FLOAT): {
double da, db;
JS_ToFloat64(ctx, &da, frame->slots[b]);
JS_ToFloat64(ctx, &db, frame->slots[c]);
int r;
switch (op) {
case MACH_EQ_FLOAT: r = (da == db); break;
case MACH_NE_FLOAT: r = (da != db); break;
case MACH_LT_FLOAT: r = (da < db); break;
case MACH_LE_FLOAT: r = (da <= db); break;
case MACH_GT_FLOAT: r = (da > db); break;
case MACH_GE_FLOAT: r = (da >= db); break;
default: r = 0; break;
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;
}
frame->slots[a] = JS_NewBool(ctx, r);
VM_BREAK();
}
/* Typed text comparisons */
VM_CASE(MACH_EQ_TEXT): VM_CASE(MACH_NE_TEXT):
VM_CASE(MACH_LT_TEXT): VM_CASE(MACH_LE_TEXT):
VM_CASE(MACH_GT_TEXT): VM_CASE(MACH_GE_TEXT): {
int cmp = js_string_compare_value(ctx, frame->slots[b], frame->slots[c], FALSE);
int r;
switch (op) {
case MACH_EQ_TEXT: r = (cmp == 0); break;
case MACH_NE_TEXT: r = (cmp != 0); break;
case MACH_LT_TEXT: r = (cmp < 0); break;
case MACH_LE_TEXT: r = (cmp <= 0); break;
case MACH_GT_TEXT: r = (cmp > 0); break;
case MACH_GE_TEXT: r = (cmp >= 0); break;
default: r = 0; break;
}
frame->slots[a] = JS_NewBool(ctx, r);
VM_BREAK();
}
/* Typed bool comparisons */
VM_CASE(MACH_EQ_BOOL):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_BOOL(frame->slots[b]) == JS_VALUE_GET_BOOL(frame->slots[c]));
VM_BREAK();
VM_CASE(MACH_NE_BOOL):
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_BOOL(frame->slots[b]) != JS_VALUE_GET_BOOL(frame->slots[c]));
/* Stone mutable text compiler-emitted at escape points */
VM_CASE(MACH_STONE_TEXT):
stone_mutable_text(frame->slots[a]);
VM_BREAK();
/* Identity check */
@@ -2476,6 +2416,123 @@ vm_dispatch:
frame->slots[a] = JS_NewBool(ctx, is_proxy);
VM_BREAK();
}
VM_CASE(MACH_IS_BLOB):
frame->slots[a] = JS_NewBool(ctx, mist_is_blob(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_DATA): {
JSValue v = frame->slots[b];
int result = 0;
if (mist_is_gc_object(v) && !mist_is_array(v)
&& !mist_is_function(v) && !mist_is_blob(v))
result = 1;
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_TRUE):
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_TRUE);
VM_BREAK();
VM_CASE(MACH_IS_FALSE):
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_FALSE);
VM_BREAK();
VM_CASE(MACH_IS_FIT): {
JSValue v = frame->slots[b];
int result = 0;
if (JS_IsInt(v)) {
result = 1;
} else if (JS_IsShortFloat(v)) {
double d = JS_VALUE_GET_FLOAT64(v);
result = (isfinite(d) && trunc(d) == d && fabs(d) <= 9007199254740992.0);
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_CHAR): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v))
result = (MIST_GetImmediateASCIILen(v) == 1);
else if (mist_is_text(v))
result = (js_string_value_len(v) == 1);
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_DIGIT): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch >= '0' && ch <= '9');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch >= '0' && ch <= '9');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_LETTER): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_LOWER): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch >= 'a' && ch <= 'z');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch >= 'a' && ch <= 'z');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_UPPER): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch >= 'A' && ch <= 'Z');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch >= 'A' && ch <= 'Z');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_WS): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch == ' ' || ch == '\t' || ch == '\n'
|| ch == '\r' || ch == '\f' || ch == '\v');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch == ' ' || ch == '\t' || ch == '\n'
|| ch == '\r' || ch == '\f' || ch == '\v');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_ACTOR): {
JSValue v = frame->slots[b];
int result = 0;
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym)) {
result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
/* Logical */
VM_CASE(MACH_NOT): {
int bval = JS_ToBool(ctx, frame->slots[b]);
@@ -2622,7 +2679,15 @@ vm_dispatch:
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
int nr = c + 2; /* argc + this + func overhead */
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
int nr;
if (fn->kind == JS_FUNC_KIND_REGISTER) {
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
nr = fn_code->nr_slots;
if (nr < c + 2) nr = c + 2; /* safety: never smaller than argc+2 */
} else {
nr = c + 2;
}
JSFrameRegister *call_frame = alloc_frame_register(ctx, nr);
if (!call_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -2631,6 +2696,7 @@ vm_dispatch:
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
func_val = frame->slots[b]; /* re-read after GC */
call_frame->function = func_val;
call_frame->address = JS_NewInt32(ctx, c); /* store actual argc */
frame->slots[a] = JS_MKPTR(call_frame);
VM_BREAK();
}
@@ -2643,36 +2709,19 @@ vm_dispatch:
VM_CASE(MACH_INVOKE): {
/* A=frame_slot, B=result_slot */
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
int nr = (int)objhdr_cap56(fr->header);
int c_argc = (nr >= 2) ? nr - 2 : 0;
int c_argc = JS_VALUE_GET_INT(fr->address); /* actual argc stored by FRAME */
JSValue fn_val = fr->function;
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
if (!mach_check_call_arity(ctx, fn, c_argc))
goto disrupt;
if (fn->kind == JS_FUNC_KIND_REGISTER) {
/* Register function: switch frames inline (fast path) */
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fn_val = fr->function;
fn = JS_VALUE_GET_FUNCTION(fn_val);
fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
new_frame->function = fn_val;
/* Copy this + args from call frame to new frame */
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
new_frame->slots[0] = fr->slots[0]; /* this */
for (int i = 0; i < copy_count; i++)
new_frame->slots[1 + i] = fr->slots[1 + i];
/* Register function: FRAME already allocated nr_slots — just switch */
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
/* Save return info */
frame->address = JS_NewInt32(ctx, (pc << 16) | b);
new_frame->caller = JS_MKPTR(frame);
frame = new_frame;
fr->caller = JS_MKPTR(frame);
frame = fr;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.cell.env_record;
@@ -2716,8 +2765,7 @@ vm_dispatch:
VM_CASE(MACH_GOINVOKE): {
/* Tail call: replace current frame with callee */
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
int nr = (int)objhdr_cap56(fr->header);
int c_argc = (nr >= 2) ? nr - 2 : 0;
int c_argc = JS_VALUE_GET_INT(fr->address); /* actual argc stored by FRAME */
JSValue fn_val = fr->function;
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
if (!mach_check_call_arity(ctx, fn, c_argc))
@@ -2742,25 +2790,10 @@ vm_dispatch:
env = fn->u.cell.env_record;
pc = code->entry_point;
} else {
/* SLOW PATH: callee needs more slots, must allocate */
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fn_val = fr->function;
fn = JS_VALUE_GET_FUNCTION(fn_val);
fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
new_frame->function = fn_val;
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
new_frame->slots[0] = fr->slots[0]; /* this */
for (int i = 0; i < copy_count; i++)
new_frame->slots[1 + i] = fr->slots[1 + i];
new_frame->caller = frame->caller;
/* SLOW PATH: GOFRAME already allocated nr_slots — use fr directly */
fr->caller = frame->caller;
frame->caller = JS_NULL;
frame = new_frame;
frame = fr;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.cell.env_record;
@@ -3014,10 +3047,10 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
if (s.nr_slots > 255) {
cJSON *nm_chk = cJSON_GetObjectItemCaseSensitive(fobj, "name");
const char *fn_name = nm_chk ? cJSON_GetStringValue(nm_chk) : "<anonymous>";
fprintf(stderr, "ERROR: function '%s' has %d slots (max 255). "
fprintf(stderr, "FATAL: function '%s' has %d slots (max 255). "
"Ensure the streamline optimizer ran before mach compilation.\n",
fn_name, s.nr_slots);
return NULL;
abort();
}
int dis_raw = (int)cJSON_GetNumberValue(
cJSON_GetObjectItemCaseSensitive(fobj, "disruption_pc"));
@@ -3084,6 +3117,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); }
@@ -3103,30 +3137,13 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
else if (strcmp(op, "ceiling") == 0) { ABC3(MACH_CEILING); }
else if (strcmp(op, "round") == 0) { ABC3(MACH_ROUND); }
else if (strcmp(op, "trunc") == 0) { ABC3(MACH_TRUNC); }
/* Typed integer comparisons */
else if (strcmp(op, "eq_int") == 0) { ABC3(MACH_EQ_INT); }
else if (strcmp(op, "ne_int") == 0) { ABC3(MACH_NE_INT); }
else if (strcmp(op, "lt_int") == 0) { ABC3(MACH_LT_INT); }
else if (strcmp(op, "le_int") == 0) { ABC3(MACH_LE_INT); }
else if (strcmp(op, "gt_int") == 0) { ABC3(MACH_GT_INT); }
else if (strcmp(op, "ge_int") == 0) { ABC3(MACH_GE_INT); }
/* Typed float comparisons */
else if (strcmp(op, "eq_float") == 0) { ABC3(MACH_EQ_FLOAT); }
else if (strcmp(op, "ne_float") == 0) { ABC3(MACH_NE_FLOAT); }
else if (strcmp(op, "lt_float") == 0) { ABC3(MACH_LT_FLOAT); }
else if (strcmp(op, "le_float") == 0) { ABC3(MACH_LE_FLOAT); }
else if (strcmp(op, "gt_float") == 0) { ABC3(MACH_GT_FLOAT); }
else if (strcmp(op, "ge_float") == 0) { ABC3(MACH_GE_FLOAT); }
/* Typed text comparisons */
else if (strcmp(op, "eq_text") == 0) { ABC3(MACH_EQ_TEXT); }
else if (strcmp(op, "ne_text") == 0) { ABC3(MACH_NE_TEXT); }
else if (strcmp(op, "lt_text") == 0) { ABC3(MACH_LT_TEXT); }
else if (strcmp(op, "le_text") == 0) { ABC3(MACH_LE_TEXT); }
else if (strcmp(op, "gt_text") == 0) { ABC3(MACH_GT_TEXT); }
else if (strcmp(op, "ge_text") == 0) { ABC3(MACH_GE_TEXT); }
/* Typed bool comparisons */
else if (strcmp(op, "eq_bool") == 0) { ABC3(MACH_EQ_BOOL); }
else if (strcmp(op, "ne_bool") == 0) { ABC3(MACH_NE_BOOL); }
/* Generic comparisons */
else if (strcmp(op, "eq") == 0) { ABC3(MACH_EQ); }
else if (strcmp(op, "ne") == 0) { ABC3(MACH_NEQ); }
else if (strcmp(op, "lt") == 0) { ABC3(MACH_LT); }
else if (strcmp(op, "le") == 0) { ABC3(MACH_LE); }
else if (strcmp(op, "gt") == 0) { ABC3(MACH_GT); }
else if (strcmp(op, "ge") == 0) { ABC3(MACH_GE); }
/* Special comparisons */
else if (strcmp(op, "is_identical") == 0) { ABC3(MACH_IS_IDENTICAL); }
else if (strcmp(op, "eq_tol") == 0) {
@@ -3161,6 +3178,18 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
else if (strcmp(op, "is_stone") == 0) { AB2(MACH_IS_STONE); }
else if (strcmp(op, "length") == 0) { AB2(MACH_LENGTH); }
else if (strcmp(op, "is_proxy") == 0) { AB2(MACH_IS_PROXY); }
else if (strcmp(op, "is_blob") == 0) { AB2(MACH_IS_BLOB); }
else if (strcmp(op, "is_data") == 0) { AB2(MACH_IS_DATA); }
else if (strcmp(op, "is_true") == 0) { AB2(MACH_IS_TRUE); }
else if (strcmp(op, "is_false") == 0) { AB2(MACH_IS_FALSE); }
else if (strcmp(op, "is_fit") == 0) { AB2(MACH_IS_FIT); }
else if (strcmp(op, "is_char") == 0) { AB2(MACH_IS_CHAR); }
else if (strcmp(op, "is_digit") == 0) { AB2(MACH_IS_DIGIT); }
else if (strcmp(op, "is_letter") == 0) { AB2(MACH_IS_LETTER); }
else if (strcmp(op, "is_lower") == 0) { AB2(MACH_IS_LOWER); }
else if (strcmp(op, "is_upper") == 0) { AB2(MACH_IS_UPPER); }
else if (strcmp(op, "is_ws") == 0) { AB2(MACH_IS_WS); }
else if (strcmp(op, "is_actor") == 0) { AB2(MACH_IS_ACTOR); }
/* Logical */
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); }