diff --git a/src/silver/engine.c b/src/silver/engine.c index 01e65d4..46e14ce 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -195,6 +195,13 @@ ant_value_t sv_execute_entry_tla(ant_t *js, sv_func_t *func, ant_value_t this_va return sv_start_tla(js, func, this_val); } +static inline void sv_sync_frame_locals( + sv_vm_t *vm, sv_frame_t **frame, sv_func_t **func, + ant_value_t **bp, ant_value_t **lp +) { + *frame = &vm->frames[vm->fp]; *func = (*frame)->func; + *bp = (*frame)->bp; *lp = (*frame)->lp; +} static inline ant_value_t sv_stage_frame_args( sv_vm_t *vm, ant_t *js, sv_func_t *func, ant_value_t *args, int argc, @@ -449,15 +456,16 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant }; ant_value_t sv_err; - #define VM_CHECK(expr) do { \ - frame->ip = ip; \ - sv_err = (expr); \ - if (is_err(sv_err)) goto sv_throw; \ - } while (0) ant_value_t tc_this = js_mkundef(); + + #define VM_CHECK(expr) ({ \ + frame->ip = ip; \ + sv_err = (expr); \ + if (is_err(sv_err)) goto sv_throw; \ + }) - #define DISPATCH() goto *dispatch[*ip] - #define NEXT(n) do { ip += (n); DISPATCH(); } while (0) + #define DISPATCH() goto *dispatch[*ip] + #define NEXT(n) ({ ip += (n); DISPATCH(); }) #ifdef ANT_JIT #define JIT_OSR_BACK_EDGE() do { \ @@ -848,6 +856,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant ant_value_t call_result = sv_vm_call( vm, js, call_func, call_this, call_args, call_argc, is_super_call ? &super_this_c : NULL, is_super_call); + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); vm->sp -= call_argc + 1; if (is_err(call_result)) { sv_err = call_result; goto sv_throw; } if (is_super_call) @@ -937,6 +946,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant ant_value_t call_result = sv_vm_call( vm, js, call_func, call_this, call_args, call_argc, is_super_call ? &super_this_cm : NULL, is_super_call); + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); vm->sp -= call_argc + 2; if (is_err(call_result)) { sv_err = call_result; goto sv_throw; } if (is_super_call) @@ -960,6 +970,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant ant_value_t call_args[1] = { call_arg }; frame->ip = ip; call_result = sv_vm_call(vm, js, call_func, call_this, call_args, 1, NULL, false); + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); } vm->sp -= 3; @@ -990,6 +1001,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant frame->ip = ip; ant_value_t call_result = sv_vm_call( vm, js, call_func, tc_this, call_args, call_argc, NULL, false); + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); vm->sp -= call_argc + 1; if (is_err(call_result)) { sv_err = call_result; goto sv_throw; } vm->stack[vm->sp++] = call_result; @@ -1017,6 +1029,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant ant_value_t *call_args = &vm->stack[vm->sp - call_argc]; ant_value_t call_result = sv_vm_call( vm, js, call_func, tc_this, call_args, call_argc, NULL, false); + sv_sync_frame_locals(vm, &frame, &func, &bp, &lp); vm->sp -= call_argc + 2; if (is_err(call_result)) { sv_err = call_result; goto sv_throw; } vm->stack[vm->sp++] = call_result; diff --git a/tests/bench_call_fallback.js b/tests/bench_call_fallback.js new file mode 100644 index 0000000..2110ed0 --- /dev/null +++ b/tests/bench_call_fallback.js @@ -0,0 +1,88 @@ +// Benchmark: interpreter fallback-call overhead. +// +// This targets the paths in silver/engine.c that call sv_vm_call(...) and then +// continue executing in the current frame. It is useful for tracking the cost +// of refreshing cached frame/stack locals after re-entrant native calls. + +const now = + typeof performance !== "undefined" && performance && typeof performance.now === "function" + ? () => performance.now() + : () => Date.now(); + +let sink = 0; + +function bench(name, iterations, fn) { + for (let i = 0; i < 3; i++) fn(iterations >> 3 || 1); + + const start = now(); + const ops = fn(iterations); + const elapsed = now() - start; + const nsPerOp = (elapsed * 1e6) / ops; + const opsPerSec = (ops * 1000) / elapsed; + + console.log( + name + + ": " + + elapsed.toFixed(2) + + "ms (" + + ops + + " ops, " + + nsPerOp.toFixed(2) + + " ns/op, " + + opsPerSec.toFixed(0) + + " ops/s)" + ); +} + +function jsAdd(a, b) { + return a + b; +} + +bench("js direct call", 5000000, function(n) { + let sum = 0; + for (let i = 0; i < n; i++) sum += jsAdd(i, 1); + sink = sum; + return n; +}); + +bench("native method Math.abs", 5000000, function(n) { + let sum = 0; + for (let i = 0; i < n; i++) sum += Math.abs(-i); + sink = sum; + return n; +}); + +bench("native method Math.imul", 5000000, function(n) { + let sum = 0; + for (let i = 0; i < n; i++) sum += Math.imul(i, 3); + sink = sum; + return n; +}); + +bench("native method array push/pop", 3000000, function(n) { + const arr = []; + let sum = 0; + for (let i = 0; i < n; i++) { + arr.push(i); + sum += arr.pop(); + } + sink = sum + arr.length; + return n * 2; +}); + +bench("native method string charCodeAt", 5000000, function(n) { + const str = "fallback"; + let sum = 0; + for (let i = 0; i < n; i++) sum += str.charCodeAt(i & 7); + sink = sum; + return n; +}); + +bench("native method Date.now", 1000000, function(n) { + let sum = 0; + for (let i = 0; i < n; i++) sum += Date.now() & 1; + sink = sum; + return n; +}); + +console.log("sink:", sink);