diff --git a/examples/demo/fibonacci_tco.js b/examples/demo/fibonacci_tco.js index 8623f1a..69dfdd0 100644 --- a/examples/demo/fibonacci_tco.js +++ b/examples/demo/fibonacci_tco.js @@ -3,9 +3,27 @@ function fib(n, a = 0, b = 1) { return fib(n - 1, b, a + b); } -const start = performance.now(); +let start = performance.now(); const result = fib(35); -const end = performance.now(); +const singleShotMs = performance.now() - start; + +const warmup = 10000; +const iterations = 200000; + +for (let i = 0; i < warmup; i++) fib(35); + +let sink = 0; +start = performance.now(); +for (let i = 0; i < iterations; i++) sink += i & 1; +const loopOverhead = performance.now() - start; + +start = performance.now(); +for (let i = 0; i < iterations; i++) sink += fib(35); +const elapsed = performance.now() - start; + +const net = Math.max(0, elapsed - loopOverhead); +const perCallUs = (net * 1000) / iterations; console.log(`fibonacci(35) = ${result}`); -console.log(`Time: ${(end - start).toFixed(4)} ms (${((end - start) * 1000).toFixed(2)} µs)`); +console.log(`Single-shot: ${singleShotMs.toFixed(4)} ms (${(singleShotMs * 1000).toFixed(2)} µs)`); +console.log(`Time: ${perCallUs.toFixed(4)} µs/call (${iterations} iterations)`); diff --git a/include/silver/engine.h b/include/silver/engine.h index 99e821d..c1d09e6 100644 --- a/include/silver/engine.h +++ b/include/silver/engine.h @@ -73,10 +73,16 @@ typedef struct { uint32_t cached_index; uint32_t epoch; uintptr_t cached_aux; - ant_shape_t *add_from_shape; - ant_shape_t *add_to_shape; - uint32_t add_slot; - uint32_t add_epoch; + // keep this union valid: each IC slot must belong to exactly one bytecode site/op + union { + ant_value_t receiver_proto; + struct { + ant_shape_t *from_shape; + ant_shape_t *to_shape; + uint32_t slot; + uint32_t epoch; + } add; + } guard; bool cached_is_own; } sv_ic_entry_t; diff --git a/src/silver/ops/property.h b/src/silver/ops/property.h index ed51791..ad7c105 100644 --- a/src/silver/ops/property.h +++ b/src/silver/ops/property.h @@ -169,6 +169,7 @@ static inline bool sv_ic_try_get_hit( } else { ant_object_t *holder = ic->cached_holder; if (!holder || holder->is_exotic || !holder->shape) return false; + if (receiver->proto != ic->guard.receiver_proto) return false; source = holder; prop_shape = holder->shape; } @@ -423,6 +424,7 @@ static inline ant_value_t sv_prop_get_field_ic( if (sv_ic_probe_get_chain(obj, a->str, &holder, &prop_idx, &out)) { ic->cached_shape = ptr->shape; ic->cached_holder = holder; + ic->guard.receiver_proto = ptr->proto; ic->cached_index = prop_idx; ic->cached_is_own = (holder == ptr); ic->epoch = ant_ic_epoch_counter; @@ -504,26 +506,26 @@ static inline void sv_ic_set_add_transition( ) { if (!ic) return; - if (ic->add_from_shape != from) { - if (ic->add_from_shape) ant_shape_release(ic->add_from_shape); - ic->add_from_shape = NULL; + if (ic->guard.add.from_shape != from) { + if (ic->guard.add.from_shape) ant_shape_release(ic->guard.add.from_shape); + ic->guard.add.from_shape = NULL; if (from) { ant_shape_retain(from); - ic->add_from_shape = from; + ic->guard.add.from_shape = from; } } - if (ic->add_to_shape != to) { - if (ic->add_to_shape) ant_shape_release(ic->add_to_shape); - ic->add_to_shape = NULL; + if (ic->guard.add.to_shape != to) { + if (ic->guard.add.to_shape) ant_shape_release(ic->guard.add.to_shape); + ic->guard.add.to_shape = NULL; if (to) { ant_shape_retain(to); - ic->add_to_shape = to; + ic->guard.add.to_shape = to; } } - ic->add_slot = slot; - ic->add_epoch = epoch; + ic->guard.add.slot = slot; + ic->guard.add.epoch = epoch; } static inline ant_value_t sv_op_put_field( @@ -557,23 +559,23 @@ static inline ant_value_t sv_op_put_field( !ptr->frozen && !ptr->sealed && ptr->extensible && ptr->type_tag != T_ARR && !sv_is_proto_atom(a) && - ic->add_epoch == ant_ic_epoch_counter && - ic->add_from_shape == ptr->shape && - ic->add_to_shape) { + ic->guard.add.epoch == ant_ic_epoch_counter && + ic->guard.add.from_shape == ptr->shape && + ic->guard.add.to_shape) { if (ant_shape_lookup_interned(ptr->shape, a->str) < 0) { ant_shape_t *old_shape = ptr->shape; - ant_shape_retain(ic->add_to_shape); - ptr->shape = ic->add_to_shape; + ant_shape_retain(ic->guard.add.to_shape); + ptr->shape = ic->guard.add.to_shape; ant_shape_release(old_shape); - if (ic->add_slot >= ptr->prop_count && - !js_obj_ensure_prop_capacity(ptr, ic->add_slot + 1)) { + if (ic->guard.add.slot >= ptr->prop_count && + !js_obj_ensure_prop_capacity(ptr, ic->guard.add.slot + 1)) { return js_mkerr(js, "oom"); } - ant_object_prop_set_unchecked(ptr, ic->add_slot, val); + ant_object_prop_set_unchecked(ptr, ic->guard.add.slot, val); gc_write_barrier(js, ptr, val); ic->cached_shape = ptr->shape; ic->cached_holder = ptr; - ic->cached_index = ic->add_slot; + ic->cached_index = ic->guard.add.slot; ic->epoch = ant_ic_epoch_counter; return val; } diff --git a/src/silver/swarm.c b/src/silver/swarm.c index 259cb72..3291288 100644 --- a/src/silver/swarm.c +++ b/src/silver/swarm.c @@ -761,7 +761,7 @@ static bool mir_emit_get_field_ic_fastpath( char gf_ot_name[32], gf_op_name[32], gf_os_name[32], gf_ics_name[32]; char gf_h_name[32], gf_hs_name[32], gf_idx_name[32], gf_pc_name[32]; char gf_ica_name[32], gf_il_name[32], gf_ovf_name[32], gf_ovi_name[32]; - char gf_io_name[32], gf_src_name[32]; + char gf_io_name[32], gf_src_name[32], gf_op_proto_name[32], gf_ic_proto_name[32]; snprintf(gf_ic_name, sizeof(gf_ic_name), "gf_ic_%d_%u", bc_off, (unsigned)ic_idx); snprintf(gf_ice_name, sizeof(gf_ice_name), "gf_ice_%d_%u", bc_off, (unsigned)ic_idx); snprintf(gf_ot_name, sizeof(gf_ot_name), "gf_ot_%d_%u", bc_off, (unsigned)ic_idx); @@ -778,6 +778,8 @@ static bool mir_emit_get_field_ic_fastpath( snprintf(gf_ovi_name, sizeof(gf_ovi_name), "gf_ovi_%d_%u", bc_off, (unsigned)ic_idx); snprintf(gf_io_name, sizeof(gf_io_name), "gf_io_%d_%u", bc_off, (unsigned)ic_idx); snprintf(gf_src_name, sizeof(gf_src_name), "gf_src_%d_%u", bc_off, (unsigned)ic_idx); + snprintf(gf_op_proto_name, sizeof(gf_op_proto_name), "gf_opp_%d_%u", bc_off, (unsigned)ic_idx); + snprintf(gf_ic_proto_name, sizeof(gf_ic_proto_name), "gf_icp_%d_%u", bc_off, (unsigned)ic_idx); MIR_reg_t r_ic = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_ic_name); MIR_reg_t r_ic_epoch = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_ice_name); @@ -795,6 +797,8 @@ static bool mir_emit_get_field_ic_fastpath( MIR_reg_t r_overflow_idx = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_ovi_name); MIR_reg_t r_is_own = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_io_name); MIR_reg_t r_source = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_src_name); + MIR_reg_t r_obj_proto = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_op_proto_name); + MIR_reg_t r_ic_proto = MIR_new_func_reg(ctx, fn->u.func, MIR_T_I64, gf_ic_proto_name); MIR_label_t load_overflow = MIR_new_label(ctx); MIR_label_t fast_done = MIR_new_label(ctx); @@ -869,6 +873,21 @@ static bool mir_emit_get_field_ic_fastpath( MIR_new_reg_op(ctx, r_is_own), MIR_new_int_op(ctx, 0))); + MIR_append_insn(ctx, fn, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, r_obj_proto), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)offsetof(ant_object_t, proto), r_obj_ptr, 0, 1))); + MIR_append_insn(ctx, fn, + MIR_new_insn(ctx, MIR_MOV, + MIR_new_reg_op(ctx, r_ic_proto), + MIR_new_mem_op(ctx, MIR_T_I64, + (MIR_disp_t)offsetof(sv_ic_entry_t, guard.receiver_proto), r_ic, 0, 1))); + MIR_append_insn(ctx, fn, + MIR_new_insn(ctx, MIR_BNE, + MIR_new_label_op(ctx, slow), + MIR_new_reg_op(ctx, r_obj_proto), + MIR_new_reg_op(ctx, r_ic_proto))); MIR_append_insn(ctx, fn, MIR_new_insn(ctx, MIR_MOV, MIR_new_reg_op(ctx, r_holder), diff --git a/tests/test_property_ic_proto_guard.cjs b/tests/test_property_ic_proto_guard.cjs new file mode 100644 index 0000000..a6c84fd --- /dev/null +++ b/tests/test_property_ic_proto_guard.cjs @@ -0,0 +1,20 @@ +const assert = require('assert'); + +const protoA = { marker: 11 }; +const protoB = { marker: 29 }; + +function make(proto) { + return { __proto__: proto, own: 1 }; +} + +function marker(obj) { + return obj.marker; +} + +for (let i = 0; i < 64; i++) { + assert.strictEqual(marker(make(protoA)), 11); +} + +assert.strictEqual(marker(make(protoB)), 29); + +console.log('property-ic-proto-guard:ok');