quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit 9b255f3ad8946e46bb8a42f730218db921b2fe29
parent 9e241f5866aa5e493af8641ade54df66eba4bfcd
Author: Florian Dold <florian@dold.me>
Date:   Wed, 14 Dec 2022 15:18:17 +0100

perf map support

Diffstat:
Mquickjs.c | 179++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 178 insertions(+), 1 deletion(-)

diff --git a/quickjs.c b/quickjs.c @@ -109,6 +109,8 @@ /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC +#define PERF_TRAMPOLINE 1 + #ifdef CONFIG_ATOMICS #include <pthread.h> #include <stdatomic.h> @@ -612,6 +614,7 @@ typedef struct JSFunctionBytecode { JSValue *cpool; /* constant pool (self pointer) */ int cpool_count; int closure_var_count; + void *perf_trampoline; struct { /* debug info, move to separate structure to save memory? */ JSAtom filename; @@ -16195,7 +16198,7 @@ typedef enum { #define FUNC_RET_YIELD_STAR 2 /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ -static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, +static JSValue __JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags) { @@ -18709,6 +18712,178 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, return ret_val; } +#if PERF_TRAMPOLINE + +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> + +static FILE * +perf_map_get_file(void) +{ + static FILE *perf_map_file = NULL; + if (perf_map_file) { + return perf_map_file; + } + char filename[100]; + pid_t pid = getpid(); + // Location and file name of perf map is hard-coded in perf tool. + // Use exclusive create flag wit nofollow to prevent symlink attacks. + int flags = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC; + snprintf(filename, sizeof(filename) - 1, "/tmp/perf-%jd.map", + (intmax_t)pid); + int fd = open(filename, flags, 0600); + if (fd == -1) { + return NULL; + } + perf_map_file = fdopen(fd, "w"); + if (!perf_map_file) { + close(fd); + return NULL; + } + return perf_map_file; +} + +static void +perf_map_write_entry(JSContext *ctx, const void *code_addr, unsigned int code_size, JSFunctionBytecode *b) +{ + FILE *method_file = perf_map_get_file(); + const char *atom_entry = NULL; + const char *atom_filename = NULL; + const char *filename = NULL; + int line = 0; + if (b->has_debug) { + line = b->debug.line_num; + } + if (b->func_name != JS_ATOM_NULL) { + atom_entry = JS_AtomToCString(ctx, b->func_name); + } + if (b->has_debug && b->debug.filename != JS_ATOM_NULL) { + atom_filename = JS_AtomToCString(ctx, b->debug.filename); + } + if (NULL == atom_filename) { + filename = "<unknown>"; + } else { + filename = atom_filename; + } + if (NULL == atom_entry) { + fprintf(method_file, "%p %x js@%s:%u\n", code_addr, code_size, filename, line); + } else { + fprintf(method_file, "%p %x js::%s@%s:%u\n", code_addr, code_size, atom_entry, filename, line); + } + fflush(method_file); + JS_FreeCString(ctx, atom_entry); + JS_FreeCString(ctx, atom_filename); +} + +typedef struct { + JSContext *caller_ctx; + JSValueConst func_obj; + JSValueConst this_obj; + JSValueConst new_target; + int argc; + JSValue *argv; + int flags; +} CallInternalArgs; + +typedef JSValue CallFn(CallInternalArgs *args); +typedef JSValue TrampolineFn(CallInternalArgs *args, CallFn fn); + +/** + * For x86-64: + * push %rbp; mov %rsp,%rbp; call *%rsi; pop %rbp; ret; +*/ +char perf_trampoline_code[] = {0x55, 0x48, 0x89, 0xe5, 0xff, 0xd6, 0x5d, 0xc3}; + +void *compile_trampoline() +{ + size_t mem_size = 4096 * 16; + char *memory = + mmap(NULL, // address + mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, // fd (not used here) + 0); // offset (not used here) + memcpy(memory, perf_trampoline_code, 8); + mprotect(memory, mem_size, PROT_READ | PROT_EXEC); + return memory; +} + +static JSValue fallback_trampoline(CallInternalArgs *ci_args, CallFn fn) +{ + JSValue value; + value = fn(ci_args); + return value; +} + +static JSValue JS_CallInternalStruct(CallInternalArgs *ci_args) +{ + return __JS_CallInternal(ci_args->caller_ctx, ci_args->func_obj, + ci_args->this_obj, ci_args->new_target, + ci_args->argc, ci_args->argv, ci_args->flags); +} + +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags) +{ + CallInternalArgs ci_args; + JSObject *p; + JSFunctionBytecode *b = NULL; + + ci_args.caller_ctx = caller_ctx; + ci_args.func_obj = func_obj; + ci_args.this_obj = this_obj; + ci_args.new_target = new_target; + ci_args.argc = argc; + ci_args.argv = argv; + ci_args.flags = flags; + + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { + if (flags & JS_CALL_FLAG_GENERATOR) { + JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); + JSStackFrame *sf; + /* func_obj get contains a pointer to JSFuncAsyncState */ + /* the stack frame is already allocated */ + sf = &s->frame; + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + } + } else { + p = JS_VALUE_GET_OBJ(func_obj); + if (p->class_id == JS_CLASS_BYTECODE_FUNCTION) { + b = p->u.func.function_bytecode; + } + } + + if (b) { + TrampolineFn *fn; + if (!b->perf_trampoline) { + b->perf_trampoline = compile_trampoline(); + if (b->perf_trampoline) { + perf_map_write_entry(caller_ctx, b->perf_trampoline, 8, b); + } + } + fn = b->perf_trampoline; + if (fn) { + return fn(&ci_args, JS_CallInternalStruct); + } + } + + return fallback_trampoline(&ci_args, JS_CallInternalStruct); + //return __JS_CallInternal(caller_ctx, func_obj, this_obj, new_target, argc, argv, flags); +} + +#else + +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags) +{ + return __JS_CallInternal(caller_ctx, func_obj, this_obj, new_target, argc, argv, flags); +} + +#endif /* PERF_TRAMPOLINE */ + JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv) { @@ -32612,6 +32787,8 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) b->stack_size = stack_size; + b->perf_trampoline = NULL; + if (fd->js_mode & JS_MODE_STRIP) { JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); // probably useless