Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
12 KiB
397 lines
12 KiB
/* |
|
* upb - a minimalist implementation of protocol buffers. |
|
* |
|
* Copyright (c) 2013 Google Inc. See LICENSE for details. |
|
* Author: Josh Haberman <jhaberman@gmail.com> |
|
* |
|
* Driver code for the x64 JIT compiler. |
|
*/ |
|
|
|
#include <dlfcn.h> |
|
#include <stdio.h> |
|
#include <sys/mman.h> |
|
#include "upb/pb/decoder.h" |
|
#include "upb/pb/decoder.int.h" |
|
#include "upb/pb/varint.int.h" |
|
#include "upb/shim/shim.h" |
|
|
|
// These defines are necessary for DynASM codegen. |
|
// See dynasm/dasm_proto.h for more info. |
|
#define Dst_DECL jitcompiler *jc |
|
#define Dst_REF (jc->dynasm) |
|
#define Dst (jc) |
|
|
|
// In debug mode, make DynASM do internal checks (must be defined before any |
|
// dasm header is included. |
|
#ifndef NDEBUG |
|
#define DASM_CHECKS |
|
#endif |
|
|
|
#ifndef MAP_ANONYMOUS |
|
#define MAP_ANONYMOUS MAP_ANON |
|
#endif |
|
|
|
#define DECODE_EOF -3 |
|
|
|
typedef struct { |
|
mgroup *group; |
|
uint32_t *pc; |
|
|
|
// This pointer is allocated by dasm_init() and freed by dasm_free(). |
|
struct dasm_State *dynasm; |
|
|
|
// Maps arbitrary void* -> pclabel. |
|
upb_inttable pclabels; |
|
upb_inttable pcdefined; |
|
|
|
// For marking labels that should go into the generated code. |
|
// Maps pclabel -> char* label (string is owned by the table). |
|
upb_inttable asmlabels; |
|
|
|
// For checking that two asmlabels aren't defined for the same byte. |
|
int lastlabelofs; |
|
|
|
// The total number of pclabels currently defined. |
|
uint32_t pclabel_count; |
|
|
|
// Used by DynASM to store globals. |
|
void **globals; |
|
} jitcompiler; |
|
|
|
// Functions called by codegen. |
|
static int pclabel(jitcompiler *jc, const void *here); |
|
static int define_pclabel(jitcompiler *jc, const void *here); |
|
static void asmlabel(jitcompiler *jc, const char *fmt, ...); |
|
static int pcofs(jitcompiler* jc); |
|
|
|
#include "dynasm/dasm_proto.h" |
|
#include "dynasm/dasm_x86.h" |
|
#include "upb/pb/compile_decoder_x64.h" |
|
|
|
static jitcompiler *newjitcompiler(mgroup *group) { |
|
jitcompiler *jc = malloc(sizeof(jitcompiler)); |
|
jc->group = group; |
|
jc->pclabel_count = 0; |
|
jc->lastlabelofs = -1; |
|
upb_inttable_init(&jc->pclabels, UPB_CTYPE_UINT32); |
|
upb_inttable_init(&jc->pcdefined, UPB_CTYPE_BOOL); |
|
upb_inttable_init(&jc->asmlabels, UPB_CTYPE_PTR); |
|
jc->globals = malloc(UPB_JIT_GLOBAL__MAX * sizeof(*jc->globals)); |
|
|
|
dasm_init(jc, 1); |
|
dasm_setupglobal(jc, jc->globals, UPB_JIT_GLOBAL__MAX); |
|
dasm_setup(jc, upb_jit_actionlist); |
|
|
|
return jc; |
|
} |
|
|
|
static void freejitcompiler(jitcompiler *jc) { |
|
upb_inttable_iter i; |
|
upb_inttable_begin(&i, &jc->asmlabels); |
|
for (; !upb_inttable_done(&i); upb_inttable_next(&i)) { |
|
free(upb_value_getptr(upb_inttable_iter_value(&i))); |
|
} |
|
upb_inttable_uninit(&jc->asmlabels); |
|
upb_inttable_uninit(&jc->pclabels); |
|
upb_inttable_uninit(&jc->pcdefined); |
|
dasm_free(jc); |
|
free(jc->globals); |
|
free(jc); |
|
} |
|
|
|
// Returns a pclabel associated with the given arbitrary pointer. |
|
static int pclabel(jitcompiler *jc, const void *here) { |
|
upb_value v; |
|
bool found = upb_inttable_lookupptr(&jc->pclabels, here, &v); |
|
if (!found) { |
|
upb_value_setuint32(&v, jc->pclabel_count++); |
|
dasm_growpc(jc, jc->pclabel_count); |
|
upb_inttable_insertptr(&jc->pclabels, here, v); |
|
} |
|
return upb_value_getuint32(v); |
|
} |
|
|
|
// Defines a pclabel associated with the given arbitrary pointer. |
|
// May only be called once (to avoid redefining the pclabel). |
|
static int define_pclabel(jitcompiler *jc, const void *here) { |
|
// Will assert-fail if it already exists. |
|
upb_inttable_insertptr(&jc->pcdefined, here, upb_value_bool(true)); |
|
return pclabel(jc, here); |
|
} |
|
|
|
// Returns a bytecode pc offset relative to the beginning of the group's code. |
|
static int pcofs(jitcompiler *jc) { |
|
return jc->pc - jc->group->bytecode; |
|
} |
|
|
|
static void upb_reg_jit_gdb(jitcompiler *jc); |
|
|
|
static int getpclabel(jitcompiler *jc, const void *target) { |
|
return dasm_getpclabel(jc, pclabel(jc, target)); |
|
} |
|
|
|
// Given a pcofs relative to method, returns the machine code offset for it |
|
// (relative to the beginning of the machine code). |
|
int nativeofs(jitcompiler *jc, const upb_pbdecodermethod *method, int pcofs) { |
|
void *target = jc->group->bytecode + method->code_base.ofs + pcofs; |
|
return getpclabel(jc, target); |
|
} |
|
|
|
// Given a pcofs relative to this method's base, returns a machine code offset |
|
// relative to pclabel(dispatch->array) (which is used in jitdispatch as the |
|
// machine code base for dispatch table lookups). |
|
uint32_t dispatchofs(jitcompiler *jc, const upb_pbdecodermethod *method, |
|
int pcofs) { |
|
int ofs1 = getpclabel(jc, method->dispatch.array); |
|
int ofs2 = nativeofs(jc, method, pcofs); |
|
assert(ofs1 > 0); |
|
assert(ofs2 > 0); |
|
int ret = ofs2 - ofs1; |
|
assert(ret > 0); |
|
return ret; |
|
} |
|
|
|
// Rewrites the dispatch tables into machine code offsets. |
|
static void patchdispatch(jitcompiler *jc) { |
|
upb_inttable_iter i; |
|
upb_inttable_begin(&i, &jc->group->methods); |
|
for (; !upb_inttable_done(&i); upb_inttable_next(&i)) { |
|
upb_pbdecodermethod *method = upb_value_getptr(upb_inttable_iter_value(&i)); |
|
method->is_native_ = true; |
|
|
|
upb_inttable *dispatch = &method->dispatch; |
|
upb_inttable_iter i2; |
|
upb_inttable_begin(&i2, dispatch); |
|
for (; !upb_inttable_done(&i2); upb_inttable_next(&i2)) { |
|
uintptr_t key = upb_inttable_iter_key(&i2); |
|
if (key == 0) continue; |
|
uint64_t val = upb_value_getuint64(upb_inttable_iter_value(&i2)); |
|
uint64_t newval; |
|
if (key <= UPB_MAX_FIELDNUMBER) { |
|
// Primary slot. |
|
uint64_t oldofs = val >> 16; |
|
uint64_t newofs = dispatchofs(jc, method, oldofs); |
|
newval = (val & 0xffff) | (newofs << 16); |
|
assert((int64_t)newval > 0); |
|
} else { |
|
// Secondary slot. Since we have 64 bits for the value, we use an |
|
// absolute offset. |
|
newval = (uint64_t)(jc->group->jit_code + nativeofs(jc, method, val)); |
|
} |
|
bool ok = upb_inttable_replace(dispatch, key, upb_value_uint64(newval)); |
|
UPB_ASSERT_VAR(ok, ok); |
|
} |
|
|
|
// Set this only *after* we have patched the offsets (nativeofs() above |
|
// reads this). |
|
method->code_base.ptr = jc->group->jit_code + getpclabel(jc, method); |
|
|
|
upb_byteshandler *h = &method->input_handler_; |
|
upb_byteshandler_setstartstr(h, upb_pbdecoder_startjit, NULL); |
|
upb_byteshandler_setstring(h, jc->group->jit_code, method->code_base.ptr); |
|
upb_byteshandler_setendstr(h, upb_pbdecoder_end, method); |
|
} |
|
} |
|
|
|
// Define for JIT debugging. |
|
//#define UPB_JIT_LOAD_SO |
|
|
|
#ifdef UPB_JIT_LOAD_SO |
|
static void load_so(jitcompiler *jc) { |
|
// Dump to a .so file in /tmp and load that, so all the tooling works right |
|
// (for example, debuggers and profilers will see symbol names for the JIT-ted |
|
// code). This is the same goal of the GDB JIT code below, but the GDB JIT |
|
// interface is only used/understood by GDB. Hopefully a standard will |
|
// develop for registering JIT-ted code that all tools will recognize, |
|
// rendering this obsolete. |
|
// |
|
// Requires that gcc is available from the command-line. |
|
|
|
// Convert all asm labels from pclabel offsets to machine code offsets. |
|
upb_inttable_iter i; |
|
upb_inttable mclabels; |
|
upb_inttable_init(&mclabels, UPB_CTYPE_PTR); |
|
upb_inttable_begin(&i, &jc->asmlabels); |
|
for (; !upb_inttable_done(&i); upb_inttable_next(&i)) { |
|
upb_inttable_insert(&mclabels, |
|
dasm_getpclabel(jc, upb_inttable_iter_key(&i)), |
|
upb_inttable_iter_value(&i)); |
|
} |
|
|
|
FILE *f = fopen("/tmp/upb-jit-code.s", "w"); |
|
if (f) { |
|
uint8_t *jit_code = (uint8_t*)jc->group->jit_code; |
|
fputs(" .text\n\n", f); |
|
size_t linelen = 0; |
|
for (size_t i = 0; i < jc->group->jit_size; i++) { |
|
upb_value v; |
|
if (upb_inttable_lookup(&mclabels, i, &v)) { |
|
const char *label = upb_value_getptr(v); |
|
// "X." makes our JIT syms recognizable as such, which we build into |
|
// other tooling. |
|
fprintf(f, "\n\nX.%s:\n", label); |
|
fprintf(f, " .globl X.%s", label); |
|
linelen = 1000; |
|
} |
|
if (linelen >= 77) { |
|
linelen = fprintf(f, "\n .byte %u", jit_code[i]); |
|
} else { |
|
linelen += fprintf(f, ",%u", jit_code[i]); |
|
} |
|
} |
|
fputs("\n", f); |
|
fclose(f); |
|
} else { |
|
fprintf(stderr, "Couldn't open /tmp/upb-jit-code.s for writing\n"); |
|
abort(); |
|
} |
|
|
|
// TODO: racy |
|
if (system("gcc -shared -o /tmp/upb-jit-code.so /tmp/upb-jit-code.s") != 0) { |
|
fprintf(stderr, "Error compiling upb-jit-code.s\n"); |
|
abort(); |
|
} |
|
|
|
jc->group->dl = dlopen("/tmp/upb-jit-code.so", RTLD_LAZY); |
|
if (!jc->group->dl) { |
|
fprintf(stderr, "Couldn't dlopen(): %s\n", dlerror()); |
|
abort(); |
|
} |
|
|
|
munmap(jc->group->jit_code, jc->group->jit_size); |
|
jc->group->jit_code = dlsym(jc->group->dl, "X.enterjit"); |
|
if (!jc->group->jit_code) { |
|
fprintf(stderr, "Couldn't find enterjit sym\n"); |
|
abort(); |
|
} |
|
|
|
upb_inttable_uninit(&mclabels); |
|
} |
|
#endif |
|
|
|
void upb_pbdecoder_jit(mgroup *group) { |
|
group->debug_info = NULL; |
|
group->dl = NULL; |
|
|
|
assert(group->bytecode); |
|
jitcompiler *jc = newjitcompiler(group); |
|
emit_static_asm(jc); |
|
jitbytecode(jc); |
|
|
|
int dasm_status = dasm_link(jc, &jc->group->jit_size); |
|
if (dasm_status != DASM_S_OK) { |
|
fprintf(stderr, "DynASM error; returned status: 0x%08x\n", dasm_status); |
|
abort(); |
|
} |
|
|
|
char *jit_code = mmap(NULL, jc->group->jit_size, PROT_READ | PROT_WRITE, |
|
MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); |
|
dasm_encode(jc, jit_code); |
|
mprotect(jit_code, jc->group->jit_size, PROT_EXEC | PROT_READ); |
|
upb_reg_jit_gdb(jc); |
|
jc->group->jit_code = (upb_string_handlerfunc *)jit_code; |
|
|
|
#ifdef UPB_JIT_LOAD_SO |
|
load_so(jc); |
|
#endif |
|
|
|
patchdispatch(jc); |
|
|
|
freejitcompiler(jc); |
|
|
|
// Now the bytecode is no longer needed. |
|
free(group->bytecode); |
|
group->bytecode = NULL; |
|
} |
|
|
|
void upb_pbdecoder_freejit(mgroup *group) { |
|
if (!group->jit_code) return; |
|
if (group->dl) { |
|
#ifdef UPB_JIT_LOAD_SO |
|
dlclose(group->dl); |
|
#endif |
|
} else { |
|
munmap(group->jit_code, group->jit_size); |
|
} |
|
free(group->debug_info); |
|
// TODO: unregister GDB JIT interface. |
|
} |
|
|
|
// To debug JIT-ted code with GDB we need to tell GDB about the JIT-ted code |
|
// at runtime. GDB 7.x+ has defined an interface for doing this, and these |
|
// structure/function defintions are copied out of gdb/jit.h |
|
// |
|
// We need to give GDB an ELF file at runtime describing the symbols we have |
|
// generated. To avoid implementing the ELF format, we generate an ELF file |
|
// at compile-time and compile it in as a character string. We can replace |
|
// a few key constants (address of JIT-ted function and its size) by looking |
|
// for a few magic numbers and doing a dumb string replacement. |
|
// |
|
// Unfortunately this approach is showing its limits; we can only define one |
|
// symbol, and this approach only works with GDB. The .so approach above is |
|
// more reliable. |
|
|
|
#ifndef __APPLE__ |
|
const unsigned char upb_jit_debug_elf_file[] = { |
|
#include "upb/pb/jit_debug_elf_file.h" |
|
}; |
|
|
|
typedef enum { |
|
GDB_JIT_NOACTION = 0, |
|
GDB_JIT_REGISTER, |
|
GDB_JIT_UNREGISTER |
|
} jit_actions_t; |
|
|
|
typedef struct gdb_jit_entry { |
|
struct gdb_jit_entry *next_entry; |
|
struct gdb_jit_entry *prev_entry; |
|
const char *symfile_addr; |
|
uint64_t symfile_size; |
|
} gdb_jit_entry; |
|
|
|
typedef struct { |
|
uint32_t version; |
|
uint32_t action_flag; |
|
gdb_jit_entry *relevant_entry; |
|
gdb_jit_entry *first_entry; |
|
} gdb_jit_descriptor; |
|
|
|
gdb_jit_descriptor __jit_debug_descriptor = {1, GDB_JIT_NOACTION, NULL, NULL}; |
|
|
|
void __attribute__((noinline)) __jit_debug_register_code() { |
|
__asm__ __volatile__(""); |
|
} |
|
|
|
static void upb_reg_jit_gdb(jitcompiler *jc) { |
|
// Create debug info. |
|
size_t elf_len = sizeof(upb_jit_debug_elf_file); |
|
jc->group->debug_info = malloc(elf_len); |
|
memcpy(jc->group->debug_info, upb_jit_debug_elf_file, elf_len); |
|
uint64_t *p = (void *)jc->group->debug_info; |
|
for (; (void *)(p + 1) <= (void *)jc->group->debug_info + elf_len; ++p) { |
|
if (*p == 0x12345678) { |
|
*p = (uintptr_t)jc->group->jit_code; |
|
} |
|
if (*p == 0x321) { |
|
*p = jc->group->jit_size; |
|
} |
|
} |
|
|
|
// Register the JIT-ted code with GDB. |
|
gdb_jit_entry *e = malloc(sizeof(gdb_jit_entry)); |
|
e->next_entry = __jit_debug_descriptor.first_entry; |
|
e->prev_entry = NULL; |
|
if (e->next_entry) e->next_entry->prev_entry = e; |
|
e->symfile_addr = jc->group->debug_info; |
|
e->symfile_size = elf_len; |
|
__jit_debug_descriptor.first_entry = e; |
|
__jit_debug_descriptor.relevant_entry = e; |
|
__jit_debug_descriptor.action_flag = GDB_JIT_REGISTER; |
|
__jit_debug_register_code(); |
|
} |
|
|
|
#else |
|
|
|
static void upb_reg_jit_gdb(jitcompiler *jc) { (void)jc; } |
|
|
|
#endif
|
|
|