commit 9b255f3ad8946e46bb8a42f730218db921b2fe29
parent 9e241f5866aa5e493af8641ade54df66eba4bfcd
Author: Florian Dold <florian@dold.me>
Date: Wed, 14 Dec 2022 15:18:17 +0100
perf map support
Diffstat:
| M | quickjs.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