From 0bd752fc8c8e2db421cbc612c626d46f5bd064dc Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 30 Dec 2025 15:30:23 -0600 Subject: [PATCH] specialize add opcodes --- source/quickjs-opcode.h | 4 + source/quickjs.c | 184 +++++++++++++++++++++++++++++----------- source/quickjs.h | 3 +- 3 files changed, 141 insertions(+), 50 deletions(-) diff --git a/source/quickjs-opcode.h b/source/quickjs-opcode.h index 13479376..d418a46d 100644 --- a/source/quickjs-opcode.h +++ b/source/quickjs-opcode.h @@ -210,6 +210,10 @@ DEF( mul, 1, 2, 1, none) DEF( div, 1, 2, 1, none) DEF( mod, 1, 2, 1, none) DEF( add, 1, 2, 1, none) +DEF( add_int, 1, 2, 1, none) +DEF( add_float, 1, 2, 1, none) +DEF( add_string, 1, 2, 1, none) +DEF( add_nullnum, 1, 2, 1, none) DEF( sub, 1, 2, 1, none) DEF( pow, 1, 2, 1, none) DEF( shl, 1, 2, 1, none) diff --git a/source/quickjs.c b/source/quickjs.c index 9ef01c69..08b74c32 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -12670,6 +12670,18 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, #define CASE(op) case op #define DEFAULT default #define BREAK break +#define PATCH_OPCODE(new_op) do { \ + const uint8_t *instr_ptr = pc - 1; \ + uint8_t *bc = (uint8_t *)b->byte_code_buf; \ + size_t instr_idx = (size_t)(instr_ptr - b->byte_code_buf); \ + bc[instr_idx] = (new_op); \ +} while (0) + +#define SWITCH_OPCODE(new_op, target_label) do { \ + PATCH_OPCODE(new_op); \ + goto target_label; \ +} while (0) +#define DEOPT(target_label) goto target_label #else static const void * const dispatch_table[256] = { #define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, @@ -12685,6 +12697,18 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, #define CASE(op) case_ ## op #define DEFAULT case_default #define BREAK SWITCH(pc) +#define PATCH_OPCODE(new_op) do { \ + const uint8_t *instr_ptr = pc - 1; \ + uint8_t *bc = (uint8_t *)b->byte_code_buf; \ + size_t instr_idx = (size_t)(instr_ptr - b->byte_code_buf); \ + bc[instr_idx] = (new_op); \ +} while (0) + +#define SWITCH_OPCODE(new_op, target_label) do { \ + PATCH_OPCODE(new_op); \ + goto target_label; \ +} while (0) +#define DEOPT(target_label) goto target_label #endif if (js_poll_interrupts(caller_ctx)) @@ -14275,58 +14299,120 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, goto exception; } BREAK; + CASE(OP_add): { + OPADD: + JSValue op1 = sp[-2], op2 = sp[-1], res; + int tag1 = JS_VALUE_GET_TAG(op1); + int tag2 = JS_VALUE_GET_TAG(op2); - CASE(OP_add): - { - JSValue op1 = sp[-2], op2 = sp[-1], res; - int tag1 = JS_VALUE_GET_NORM_TAG(op1); - int tag2 = JS_VALUE_GET_NORM_TAG(op2); - - /* 1) both ints? keep fast int path with overflow check */ - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int64_t tmp = (int64_t)JS_VALUE_GET_INT(op1) - + JS_VALUE_GET_INT(op2); - if (likely((int)tmp == tmp)) { - res = JS_NewInt32(ctx, (int)tmp); - } else { - res = __JS_NewFloat64(ctx, (double)JS_VALUE_GET_INT(op1) + (double)JS_VALUE_GET_INT(op2)); - } - } - /* 2) both floats? */ - else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - res = __JS_NewFloat64(ctx, - JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); - } - /* 3) both strings? */ - else if (JS_IsString(op1) && JS_IsString(op2)) { - res = JS_ConcatString(ctx, op1, op2); - if (JS_IsException(res)) - goto exception; - } - /* 4) mixed int/float? promote to float */ - // TODO: Seems slow - else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || - (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { - double a, b; - if(tag1 == JS_TAG_INT) - a = (double)JS_VALUE_GET_INT(op1); - else - a = JS_VALUE_GET_FLOAT64(op1); - if(tag2 == JS_TAG_INT) - b = (double)JS_VALUE_GET_INT(op2); - else - b = JS_VALUE_GET_FLOAT64(op2); - res = __JS_NewFloat64(ctx, a + b); - } - /* 5) anything else → throw */ - else { - JS_ThrowTypeError(ctx, "cannot concatenate with string"); - goto exception; - } - + if (tag1 == JS_TAG_NULL || tag2 == JS_TAG_NULL) { + int other = (tag1 == JS_TAG_NULL) ? tag2 : tag1; + if (JS_TAG_IS_NUMBER(other) || other == JS_TAG_NULL) { + PATCH_OPCODE(OP_add_nullnum); + res = JS_NULL; sp[-2] = res; sp--; + BREAK; + } + goto add_type_error; + } + + if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + PATCH_OPCODE(OP_add_string); + res = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(res)) goto exception; + sp[-2] = res; + sp--; + BREAK; + } + goto add_type_error; + } + + if (likely((tag1 | tag2) == 0)) { + int64_t tmp = (int64_t)JS_VALUE_GET_INT(op1) + (int64_t)JS_VALUE_GET_INT(op2); + if (likely((int32_t)tmp == tmp)) { + PATCH_OPCODE(OP_add_int); + res = JS_NewInt32(ctx, (int32_t)tmp); + } else { + res = __JS_NewFloat64(ctx, (double)tmp); + } + sp[-2] = res; + sp--; + BREAK; + } + + if (JS_TAG_IS_NUMBER(tag1) && JS_TAG_IS_NUMBER(tag2)) { + PATCH_OPCODE(OP_add_float); + res = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + JS_VALUE_GET_FLOAT64(op2)); + sp[-2] = res; + sp--; + BREAK; + } + + add_type_error: + /* throw your “illegal +” error */ + goto exception; + } + CASE(OP_add_int): + { + CASE_OP_add_int_entry: + JSValue op1 = sp[-2], op2 = sp[-1]; + + if (likely((JS_VALUE_GET_TAG(op1) | JS_VALUE_GET_TAG(op2)) == 0)) { + int64_t tmp = (int64_t)JS_VALUE_GET_INT(op1) + (int64_t)JS_VALUE_GET_INT(op2); + if (likely((int32_t)tmp == tmp)) { + sp[-2] = JS_NewInt32(ctx, (int32_t)tmp); + sp--; + } else { + DEOPT(OPADD); + } + } else { + DEOPT(OPADD); + } + } + BREAK; + + CASE(OP_add_float): { + CASE_OP_add_float_entry: + JSValue op1 = sp[-2], op2 = sp[-1], res; + + if (likely(JS_VALUE_IS_BOTH_FLOAT(op1, op2))) { + res = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + JS_VALUE_GET_FLOAT64(op2)); + sp[-2] = res; + sp--; + } else { + DEOPT(OPADD); + } + } + BREAK; + CASE(OP_add_string): { + CASE_OP_add_string_entry: + JSValue op1 = sp[-2], op2 = sp[-1], res; + int t1 = JS_VALUE_GET_TAG(op1), t2 = JS_VALUE_GET_TAG(op2); + + if (likely(t1 == JS_TAG_STRING && t2 == JS_TAG_STRING)) { + res = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(res)) goto exception; + sp[-2] = res; + sp--; + } else { + DEOPT(OPADD); + } + } + BREAK; + CASE(OP_add_nullnum): { + CASE_OP_add_nullnum_entry: + JSValue op1 = sp[-2], op2 = sp[-1]; + int t1 = JS_VALUE_GET_TAG(op1), t2 = JS_VALUE_GET_TAG(op2); + + if ((t1 == JS_TAG_NULL && (JS_TAG_IS_NUMBER(t2) || t2 == JS_TAG_NULL)) || + (t2 == JS_TAG_NULL && (JS_TAG_IS_NUMBER(t1) || t1 == JS_TAG_NULL))) { + sp[-2] = JS_NULL; + sp--; + } else { + DEOPT(OPADD); + } } BREAK; CASE(OP_add_loc): diff --git a/source/quickjs.h b/source/quickjs.h index b959f6fb..b563de51 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -262,7 +262,8 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) #define JS_VALUE_IS_BOTH_INT(v1, v2) ((JS_VALUE_GET_TAG(v1) | JS_VALUE_GET_TAG(v2)) == 0) #define JS_VALUE_IS_BOTH_FLOAT(v1, v2) (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v1)) && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v2))) - +#define JS_TAG_IS_STRING(v1) (v1 == JS_TAG_STRING || v1 == JS_TAG_STRING_ROPE) + #define JS_TAG_IS_NUMBER(v1) (JS_TAG_IS_FLOAT64(v1) || v1 == JS_TAG_INT) #define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST) /* special values */