Don't serialize IC opcodes (#334)

Translate IC opcodes to their non-IC variants before writing them out.
Before this commit they were not byte-swapped properly, breaking the
ability to load serialized bytecode containing ICs on systems with
different endianness. Inline caches are recomputed as needed now.

A pleasing side effect of this change is that serialized bytecode is,
on average, a little smaller because fewer atoms are duplicated now.
This commit is contained in:
Ben Noordhuis 2024-03-27 12:07:11 +01:00 committed by GitHub
parent f02ed184a2
commit c7ca3febd3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 55 additions and 71 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -136,9 +136,12 @@ DEF( put_ref_value, 1, 3, 0, none)
DEF( define_var, 6, 0, 0, atom_u8) DEF( define_var, 6, 0, 0, atom_u8)
DEF(check_define_var, 6, 0, 0, atom_u8) DEF(check_define_var, 6, 0, 0, atom_u8)
DEF( define_func, 6, 1, 0, atom_u8) DEF( define_func, 6, 1, 0, atom_u8)
// order matters, see IC counterparts
DEF( get_field, 5, 1, 1, atom) DEF( get_field, 5, 1, 1, atom)
DEF( get_field2, 5, 1, 2, atom) DEF( get_field2, 5, 1, 2, atom)
DEF( put_field, 5, 2, 0, atom) DEF( put_field, 5, 2, 0, atom)
DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */
DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */
DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */
@ -358,6 +361,7 @@ DEF( is_null, 1, 1, 1, none)
DEF(typeof_is_undefined, 1, 1, 1, none) DEF(typeof_is_undefined, 1, 1, 1, none)
DEF( typeof_is_function, 1, 1, 1, none) DEF( typeof_is_function, 1, 1, 1, none)
// order matters, see non-IC counterparts
DEF( get_field_ic, 5, 1, 1, none) DEF( get_field_ic, 5, 1, 1, none)
DEF( get_field2_ic, 5, 1, 2, none) DEF( get_field2_ic, 5, 1, 2, none)
DEF( put_field_ic, 5, 2, 0, none) DEF( put_field_ic, 5, 2, 0, none)

122
quickjs.c
View file

@ -593,7 +593,6 @@ static int resize_ic_hash(JSContext *ctx, JSInlineCache *ic);
static int free_ic(JSRuntime *rt, JSInlineCache *ic); static int free_ic(JSRuntime *rt, JSInlineCache *ic);
static uint32_t add_ic_slot(JSContext *ctx, JSInlineCache *ic, JSAtom atom, JSObject *object, static uint32_t add_ic_slot(JSContext *ctx, JSInlineCache *ic, JSAtom atom, JSObject *object,
uint32_t prop_offset); uint32_t prop_offset);
static uint32_t add_ic_slot1(JSContext *ctx, JSInlineCache *ic, JSAtom atom);
static int32_t get_ic_prop_offset(JSInlineCache *ic, uint32_t cache_offset, static int32_t get_ic_prop_offset(JSInlineCache *ic, uint32_t cache_offset,
JSShape *shape) JSShape *shape)
@ -19995,8 +19994,34 @@ static void emit_atom(JSParseState *s, JSAtom name)
emit_u32(s, JS_DupAtom(s->ctx, name)); emit_u32(s, JS_DupAtom(s->ctx, name));
} }
static void emit_ic(JSParseState *s, JSAtom atom) { static force_inline uint32_t get_index_hash(JSAtom atom, int hash_bits)
add_ic_slot1(s->ctx, s->cur_func->ic, atom); {
return (atom * 0x9e370001) >> (32 - hash_bits);
}
static void emit_ic(JSParseState *s, JSAtom atom)
{
uint32_t h;
JSContext *ctx;
JSInlineCache *ic;
JSInlineCacheHashSlot *ch;
ic = s->cur_func->ic;
ctx = s->ctx;
if (ic->count + 1 >= ic->capacity && resize_ic_hash(ctx, ic))
return;
h = get_index_hash(atom, ic->hash_bits);
for (ch = ic->hash[h]; ch != NULL; ch = ch->next)
if (ch->atom == atom)
return;
ch = js_malloc(ctx, sizeof(*ch));
if (unlikely(!ch))
return;
ch->atom = JS_DupAtom(ctx, atom);
ch->index = 0;
ch->next = ic->hash[h];
ic->hash[h] = ch;
ic->count += 1;
} }
static int update_label(JSFunctionDef *s, int label, int delta) static int update_label(JSFunctionDef *s, int label, int delta)
@ -32518,7 +32543,7 @@ typedef enum BCTagEnum {
BC_TAG_OBJECT_REFERENCE, BC_TAG_OBJECT_REFERENCE,
} BCTagEnum; } BCTagEnum;
#define BC_VERSION 8 #define BC_VERSION 9
typedef struct BCWriterState { typedef struct BCWriterState {
JSContext *ctx; JSContext *ctx;
@ -32722,18 +32747,24 @@ static void bc_byte_swap(uint8_t *bc_buf, int bc_len)
} }
} }
static int JS_WriteFunctionBytecode(BCWriterState *s, static BOOL is_ic_op(uint8_t op)
const uint8_t *bc_buf1, int bc_len)
{ {
int pos, len, op; return op >= OP_get_field_ic && op <= OP_put_field_ic;
}
static int JS_WriteFunctionBytecode(BCWriterState *s,
const JSFunctionBytecode *b)
{
int pos, len, bc_len, op;
JSAtom atom; JSAtom atom;
uint8_t *bc_buf; uint8_t *bc_buf;
uint32_t val; uint32_t val;
bc_len = b->byte_code_len;
bc_buf = js_malloc(s->ctx, bc_len); bc_buf = js_malloc(s->ctx, bc_len);
if (!bc_buf) if (!bc_buf)
return -1; return -1;
memcpy(bc_buf, bc_buf1, bc_len); memcpy(bc_buf, b->byte_code_buf, bc_len);
pos = 0; pos = 0;
while (pos < bc_len) { while (pos < bc_len) {
@ -32751,6 +32782,16 @@ static int JS_WriteFunctionBytecode(BCWriterState *s,
put_u32(bc_buf + pos + 1, val); put_u32(bc_buf + pos + 1, val);
break; break;
default: default:
// IC (inline cache) opcodes should not end up in the serialized
// bytecode; translate them to their non-IC counterparts here
if (is_ic_op(op)) {
val = get_u32(bc_buf + pos + 1);
atom = get_ic_atom(b->ic, val);
if (bc_atom_to_idx(s, &val, atom))
goto fail;
put_u32(bc_buf + pos + 1, val);
bc_buf[pos] -= (OP_get_field_ic - OP_get_field);
}
break; break;
} }
pos += len; pos += len;
@ -32918,7 +32959,7 @@ static int JS_WriteFunctionTag(BCWriterState *s, JSValue obj)
bc_put_u8(s, flags); bc_put_u8(s, flags);
} }
if (JS_WriteFunctionBytecode(s, b->byte_code_buf, b->byte_code_len)) if (JS_WriteFunctionBytecode(s, b))
goto fail; goto fail;
bc_put_atom(s, b->filename); bc_put_atom(s, b->filename);
@ -32929,19 +32970,6 @@ static int JS_WriteFunctionTag(BCWriterState *s, JSValue obj)
bc_put_leb128(s, b->source_len); bc_put_leb128(s, b->source_len);
dbuf_put(&s->dbuf, b->source, b->source_len); dbuf_put(&s->dbuf, b->source, b->source_len);
/* compatibility */
dbuf_putc(&s->dbuf, 255);
dbuf_putc(&s->dbuf, 73); // 'I'
dbuf_putc(&s->dbuf, 67); // 'C'
if (b->ic == NULL) {
bc_put_leb128(s, 0);
} else {
bc_put_leb128(s, b->ic->count);
for (i = 0; i < b->ic->count; i++) {
bc_put_atom(s, b->ic->cache[i].atom);
}
}
for(i = 0; i < b->cpool_count; i++) { for(i = 0; i < b->cpool_count; i++) {
if (JS_WriteObjectRec(s, b->cpool[i])) if (JS_WriteObjectRec(s, b->cpool[i]))
goto fail; goto fail;
@ -33643,6 +33671,7 @@ static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b,
#endif #endif
break; break;
default: default:
assert(!is_ic_op(op)); // should not end up in serialized bytecode
break; break;
} }
pos += len; pos += len;
@ -33919,28 +33948,6 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s)
if (bc_get_buf(s, b->source, b->source_len)) if (bc_get_buf(s, b->source, b->source_len))
goto fail; goto fail;
} }
if (s->buf_end - s->ptr > 3 && s->ptr[0] == 255 &&
s->ptr[1] == 73 && s->ptr[2] == 67) {
s->ptr += 3;
bc_get_leb128(s, &ic_len);
if (ic_len == 0) {
b->ic = NULL;
} else {
b->ic = init_ic(ctx);
if (b->ic == NULL)
goto fail;
for (i = 0; i < ic_len; i++) {
bc_get_atom(s, &atom);
add_ic_slot1(ctx, b->ic, atom);
JS_FreeAtom(ctx, atom);
}
rebuild_ic(ctx, b->ic);
}
#ifdef DUMP_READ_OBJECT
bc_read_trace(s, "filename: "); print_atom(s->ctx, b->filename); printf("\n");
#endif
bc_read_trace(s, "}\n");
}
if (b->cpool_count != 0) { if (b->cpool_count != 0) {
bc_read_trace(s, "cpool {\n"); bc_read_trace(s, "cpool {\n");
for(i = 0; i < b->cpool_count; i++) { for(i = 0; i < b->cpool_count; i++) {
@ -52120,11 +52127,6 @@ static void insert_weakref_record(JSValue target, struct JSWeakRefRecord *wr)
/* Poly IC */ /* Poly IC */
static force_inline uint32_t get_index_hash(JSAtom atom, int hash_bits)
{
return (atom * 0x9e370001) >> (32 - hash_bits);
}
JSInlineCache *init_ic(JSContext *ctx) JSInlineCache *init_ic(JSContext *ctx)
{ {
JSInlineCache *ic; JSInlineCache *ic;
@ -52258,28 +52260,6 @@ end:
return ch->index; return ch->index;
} }
uint32_t add_ic_slot1(JSContext *ctx, JSInlineCache *ic, JSAtom atom)
{
uint32_t h;
JSInlineCacheHashSlot *ch;
if (ic->count + 1 >= ic->capacity && resize_ic_hash(ctx, ic))
goto end;
h = get_index_hash(atom, ic->hash_bits);
for (ch = ic->hash[h]; ch != NULL; ch = ch->next)
if (ch->atom == atom)
goto end;
ch = js_malloc(ctx, sizeof(*ch));
if (unlikely(!ch))
goto end;
ch->atom = JS_DupAtom(ctx, atom);
ch->index = 0;
ch->next = ic->hash[h];
ic->hash[h] = ch;
ic->count += 1;
end:
return 0;
}
/* CallSite */ /* CallSite */
static void js_callsite_finalizer(JSRuntime *rt, JSValue val) static void js_callsite_finalizer(JSRuntime *rt, JSValue val)