/* * QBE Helper Functions * * Thin C wrappers called from QBE-generated code for operations * that are too complex to inline: float arithmetic, float comparison, * string comparison, bitwise ops on floats, and boolean conversion. */ #include "quickjs-internal.h" #include /* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */ enum { QBE_CMP_EQ = 0, QBE_CMP_NE = 1, QBE_CMP_LT = 2, QBE_CMP_LE = 3, QBE_CMP_GT = 4, QBE_CMP_GE = 5 }; /* ============================================================ Float binary arithmetic ============================================================ */ static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b, double (*op)(double, double)) { double da, db; JS_ToFloat64(ctx, &da, a); JS_ToFloat64(ctx, &db, b); double r = op(da, db); if (!isfinite(r)) return JS_NULL; return JS_NewFloat64(ctx, r); } static double op_add(double a, double b) { return a + b; } static double op_sub(double a, double b) { return a - b; } static double op_mul(double a, double b) { return a * b; } JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) { return qbe_float_binop(ctx, a, b, op_add); } JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) { return qbe_float_binop(ctx, a, b, op_sub); } JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) { return qbe_float_binop(ctx, a, b, op_mul); } JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) { double da, db; JS_ToFloat64(ctx, &da, a); JS_ToFloat64(ctx, &db, b); if (db == 0.0) return JS_NULL; double r = da / db; if (!isfinite(r)) return JS_NULL; return JS_NewFloat64(ctx, r); } JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) { double da, db; JS_ToFloat64(ctx, &da, a); JS_ToFloat64(ctx, &db, b); if (db == 0.0) return JS_NULL; double r = fmod(da, db); if (!isfinite(r)) return JS_NULL; return JS_NewFloat64(ctx, r); } JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) { double da, db; JS_ToFloat64(ctx, &da, a); JS_ToFloat64(ctx, &db, b); double r = pow(da, db); if (!isfinite(r) && isfinite(da) && isfinite(db)) return JS_NULL; return JS_NewFloat64(ctx, r); } /* ============================================================ Float unary ops ============================================================ */ JSValue qbe_float_neg(JSContext *ctx, JSValue v) { double d; JS_ToFloat64(ctx, &d, v); return JS_NewFloat64(ctx, -d); } JSValue qbe_float_inc(JSContext *ctx, JSValue v) { double d; JS_ToFloat64(ctx, &d, v); return JS_NewFloat64(ctx, d + 1); } JSValue qbe_float_dec(JSContext *ctx, JSValue v) { double d; JS_ToFloat64(ctx, &d, v); return JS_NewFloat64(ctx, d - 1); } /* ============================================================ Float comparison — returns 0 or 1 for QBE branching ============================================================ */ int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) { double da, db; JS_ToFloat64(ctx, &da, a); JS_ToFloat64(ctx, &db, b); switch (op) { case QBE_CMP_EQ: return da == db; case QBE_CMP_NE: return da != db; case QBE_CMP_LT: return da < db; case QBE_CMP_LE: return da <= db; case QBE_CMP_GT: return da > db; case QBE_CMP_GE: return da >= db; default: return 0; } } /* ============================================================ Boolean conversion wrapper ============================================================ */ int qbe_to_bool(JSContext *ctx, JSValue v) { return JS_ToBool(ctx, v); } /* ============================================================ Bitwise not on non-int (float -> int32 -> ~) ============================================================ */ JSValue qbe_bnot(JSContext *ctx, JSValue v) { int32_t i; JS_ToInt32(ctx, &i, v); return JS_NewInt32(ctx, ~i); } /* ============================================================ Bitwise binary ops on floats (convert both to int32, apply, re-tag) ============================================================ */ JSValue qbe_bitwise_and(JSContext *ctx, JSValue a, JSValue b) { int32_t ia, ib; JS_ToInt32(ctx, &ia, a); JS_ToInt32(ctx, &ib, b); return JS_NewInt32(ctx, ia & ib); } JSValue qbe_bitwise_or(JSContext *ctx, JSValue a, JSValue b) { int32_t ia, ib; JS_ToInt32(ctx, &ia, a); JS_ToInt32(ctx, &ib, b); return JS_NewInt32(ctx, ia | ib); } JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) { int32_t ia, ib; JS_ToInt32(ctx, &ia, a); JS_ToInt32(ctx, &ib, b); return JS_NewInt32(ctx, ia ^ ib); } /* ============================================================ Shift ops on floats (convert to int32, shift, re-tag) ============================================================ */ JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) { int32_t ia, ib; JS_ToInt32(ctx, &ia, a); JS_ToInt32(ctx, &ib, b); return JS_NewInt32(ctx, ia << (ib & 31)); } JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) { int32_t ia, ib; JS_ToInt32(ctx, &ia, a); JS_ToInt32(ctx, &ib, b); return JS_NewInt32(ctx, ia >> (ib & 31)); } JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) { int32_t ia, ib; JS_ToInt32(ctx, &ia, a); JS_ToInt32(ctx, &ib, b); return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); } /* ============================================================ cell_rt_* — Runtime support for QBE-compiled code ============================================================ */ #include #include /* --- Property access --- */ JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) { return JS_GetPropertyStr(ctx, obj, name); } void cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj, const char *name) { JS_SetPropertyStr(ctx, obj, name, val); } JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) { if (JS_IsInt(key)) return JS_GetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key)); return JS_GetProperty(ctx, obj, key); } void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj, JSValue key) { if (JS_IsInt(key)) JS_SetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val); else JS_SetProperty(ctx, obj, key, val); } JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) { if (JS_IsInt(idx)) return JS_GetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx)); return JS_GetProperty(ctx, arr, idx); } void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr, JSValue idx) { if (JS_IsInt(idx)) JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val); else JS_SetProperty(ctx, arr, idx, val); } /* --- Intrinsic/global lookup --- */ JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) { return JS_GetPropertyStr(ctx, ctx->global_obj, name); } /* --- Closure access --- Slot 511 in each frame stores a pointer to the enclosing frame. Walking depth levels up the chain gives the target frame. */ #define QBE_FRAME_OUTER_SLOT 511 JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth, int64_t slot) { JSValue *frame = (JSValue *)fp; for (int64_t d = 0; d < depth; d++) { void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT]; if (!outer) return JS_NULL; frame = (JSValue *)outer; } return frame[slot]; } void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth, int64_t slot) { JSValue *frame = (JSValue *)fp; for (int64_t d = 0; d < depth; d++) { void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT]; if (!outer) return; frame = (JSValue *)outer; } frame[slot] = val; } /* --- Function creation and calling --- */ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp); /* Table mapping fn_idx → outer_fp at creation time. Valid for single-threaded, non-recursive closure patterns. */ #define MAX_QBE_FUNCTIONS 256 static void *g_outer_fp[MAX_QBE_FUNCTIONS]; static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic) { char name[64]; snprintf(name, sizeof(name), "cell_fn_%d", magic); cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name); if (!fn) return JS_ThrowTypeError(ctx, "native function %s not found", name); /* Allocate frame: slot 0 = this, slots 1..argc = args */ JSValue frame[512]; memset(frame, 0, sizeof(frame)); frame[0] = this_val; for (int i = 0; i < argc && i < 510; i++) frame[1 + i] = argv[i]; /* Link to outer frame for closure access */ if (magic >= 0 && magic < MAX_QBE_FUNCTIONS) frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_outer_fp[magic]; return fn(ctx, frame); } JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) { if (fn_idx >= 0 && fn_idx < MAX_QBE_FUNCTIONS) g_outer_fp[fn_idx] = outer_fp; return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn", 255, JS_CFUNC_generic_magic, (int)fn_idx); } /* --- Frame-based function calling --- */ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) { if (!JS_IsFunction(fn)) { JS_ThrowTypeError(ctx, "not a function"); return JS_EXCEPTION; } int nr_slots = (int)nargs + 2; JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots); if (!new_frame) return JS_EXCEPTION; new_frame->function = fn; return JS_MKPTR(new_frame); } void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) { JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); fr->slots[idx] = val; } JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) { JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); int nr_slots = (int)objhdr_cap56(fr->header); int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0; /* Copy args to C stack */ JSValue args[c_argc > 0 ? c_argc : 1]; for (int i = 0; i < c_argc; i++) args[i] = fr->slots[i + 1]; JSValue result = JS_Call(ctx, fr->function, fr->slots[0], c_argc, args); if (JS_IsException(result)) return JS_EXCEPTION; return result; } JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) { return cell_rt_frame(ctx, fn, nargs); } void cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) { cell_rt_invoke(ctx, frame_val); } /* --- Array push/pop --- */ void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) { JS_ArrayPush(ctx, &arr, val); } JSValue cell_rt_pop(JSContext *ctx, JSValue arr) { return JS_ArrayPop(ctx, arr); } /* --- Delete --- */ JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key) { int ret = JS_DeleteProperty(ctx, obj, key); return JS_NewBool(ctx, ret >= 0); } /* --- Typeof --- */ JSValue cell_rt_typeof(JSContext *ctx, JSValue val) { if (JS_IsNull(val)) return JS_NewString(ctx, "null"); if (JS_IsInt(val) || JS_IsNumber(val)) return JS_NewString(ctx, "number"); if (JS_IsBool(val)) return JS_NewString(ctx, "logical"); if (JS_IsText(val)) return JS_NewString(ctx, "text"); if (JS_IsFunction(val)) return JS_NewString(ctx, "function"); if (JS_IsArray(val)) return JS_NewString(ctx, "array"); if (JS_IsRecord(val)) return JS_NewString(ctx, "object"); return JS_NewString(ctx, "unknown"); } /* --- Text comparison stubs (called from QBE type-dispatch branches) --- */ JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) { const char *sa = JS_ToCString(ctx, a); const char *sb = JS_ToCString(ctx, b); int r = (sa && sb) ? strcmp(sa, sb) < 0 : 0; return JS_NewBool(ctx, r); } JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) { const char *sa = JS_ToCString(ctx, a); const char *sb = JS_ToCString(ctx, b); int r = (sa && sb) ? strcmp(sa, sb) > 0 : 0; return JS_NewBool(ctx, r); } JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) { const char *sa = JS_ToCString(ctx, a); const char *sb = JS_ToCString(ctx, b); int r = (sa && sb) ? strcmp(sa, sb) <= 0 : 0; return JS_NewBool(ctx, r); } JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) { const char *sa = JS_ToCString(ctx, a); const char *sb = JS_ToCString(ctx, b); int r = (sa && sb) ? strcmp(sa, sb) >= 0 : 0; return JS_NewBool(ctx, r); } JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b) { return JS_NewBool(ctx, a == b); } JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) { return JS_NewBool(ctx, a != b); } /* --- Disruption --- */ void cell_rt_disrupt(JSContext *ctx) { JS_ThrowTypeError(ctx, "type error in native code"); } /* --- Module entry point --- Called as symbol(ctx) by os.dylib_symbol. Looks up cell_main in the loaded dylib, builds a heap-allocated frame (so closures can reference it after the module returns), and runs the module body. */ JSValue cell_rt_module_entry(JSContext *ctx) { cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, "cell_main"); if (!fn) return JS_ThrowTypeError(ctx, "cell_main not found in loaded dylib"); /* Heap-allocate so closures created in cell_main can reference this frame after the module entry returns. */ JSValue *frame = calloc(512, sizeof(JSValue)); if (!frame) return JS_ThrowTypeError(ctx, "frame allocation failed"); return fn(ctx, frame); }