summaryrefslogtreecommitdiff
path: root/upb/pb/compile_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'upb/pb/compile_decoder.c')
-rw-r--r--upb/pb/compile_decoder.c385
1 files changed, 228 insertions, 157 deletions
diff --git a/upb/pb/compile_decoder.c b/upb/pb/compile_decoder.c
index 200eef5..f96f07a 100644
--- a/upb/pb/compile_decoder.c
+++ b/upb/pb/compile_decoder.c
@@ -11,7 +11,6 @@
#include <stdarg.h>
#include "upb/pb/decoder.int.h"
#include "upb/pb/varint.int.h"
-#include "upb/bytestream.h"
#ifdef UPB_DUMP_BYTECODE
#include <stdio.h>
@@ -20,76 +19,140 @@
#define MAXLABEL 5
#define EMPTYLABEL -1
+static const void *methodkey(const upb_msgdef *md, const upb_handlers *h) {
+ const void *ret = h ? (const void*)h : (const void*)md;
+ assert(ret);
+ return ret;
+}
+
+
+/* mgroup *********************************************************************/
+
+static void freegroup(upb_refcounted *r) {
+ mgroup *g = (mgroup*)r;
+ upb_inttable_uninit(&g->methods);
+#ifdef UPB_USE_JIT_X64
+ upb_pbdecoder_freejit(g);
+#endif
+ free(g->bytecode);
+ free(g);
+}
+
+static void visitgroup(const upb_refcounted *r, upb_refcounted_visit *visit,
+ void *closure) {
+ const mgroup *g = (const mgroup*)r;
+ upb_inttable_iter i;
+ upb_inttable_begin(&i, &g->methods);
+ for(; !upb_inttable_done(&i); upb_inttable_next(&i)) {
+ upb_pbdecodermethod *method = upb_value_getptr(upb_inttable_iter_value(&i));
+ visit(r, UPB_UPCAST(method), closure);
+ }
+}
+
+mgroup *newgroup(const void *owner) {
+ mgroup *g = malloc(sizeof(*g));
+ static const struct upb_refcounted_vtbl vtbl = {visitgroup, freegroup};
+ upb_refcounted_init(UPB_UPCAST(g), &vtbl, owner);
+ upb_inttable_init(&g->methods, UPB_CTYPE_PTR);
+ g->bytecode = NULL;
+ g->bytecode_end = NULL;
+ return g;
+}
+
+
/* upb_pbdecodermethod ********************************************************/
+static void freemethod(upb_refcounted *r) {
+ upb_pbdecodermethod *method = (upb_pbdecodermethod*)r;
+ upb_byteshandler_uninit(&method->input_handler_);
+
+ if (method->dest_handlers_) {
+ upb_handlers_unref(method->dest_handlers_, method);
+ }
+
+ upb_inttable_uninit(&method->dispatch);
+ free(method);
+}
+
+static void visitmethod(const upb_refcounted *r, upb_refcounted_visit *visit,
+ void *closure) {
+ const upb_pbdecodermethod *m = (const upb_pbdecodermethod*)r;
+ visit(r, m->group, closure);
+}
+
static upb_pbdecodermethod *newmethod(const upb_msgdef *msg,
- const upb_handlers *dest_handlers) {
- upb_pbdecodermethod *ret = malloc(sizeof(upb_pbdecodermethod));
- ret->msg = msg;
- ret->dest_handlers = dest_handlers;
- ret->native_code = false; // If we JIT, it will update this later.
+ const upb_handlers *dest_handlers,
+ mgroup *group,
+ const void *key) {
+ static const struct upb_refcounted_vtbl vtbl = {visitmethod, freemethod};
+ upb_pbdecodermethod *ret = malloc(sizeof(*ret));
+ upb_refcounted_init(UPB_UPCAST(ret), &vtbl, &ret);
+ upb_byteshandler_init(&ret->input_handler_);
+
+ // The method references the group and vice-versa, in a circular reference.
+ upb_ref2(ret, group);
+ upb_ref2(group, ret);
+ upb_inttable_insertptr(&group->methods, key, upb_value_ptr(ret)); // Owns ref
+ upb_refcounted_unref(UPB_UPCAST(ret), &ret);
+
+ ret->group = UPB_UPCAST(group);
+ ret->schema_ = msg;
+ ret->dest_handlers_ = dest_handlers;
+ ret->is_native_ = false; // If we JIT, it will update this later.
upb_inttable_init(&ret->dispatch, UPB_CTYPE_UINT64);
- if (ret->dest_handlers) {
- upb_handlers_ref(ret->dest_handlers, ret);
+ if (ret->dest_handlers_) {
+ upb_handlers_ref(ret->dest_handlers_, ret);
}
return ret;
}
-static void freemethod(upb_pbdecodermethod *method) {
- if (method->dest_handlers) {
- upb_handlers_unref(method->dest_handlers, method);
- }
+void upb_pbdecodermethod_ref(const upb_pbdecodermethod *m, const void *owner) {
+ upb_refcounted_ref(UPB_UPCAST(m), owner);
+}
- upb_inttable_uninit(&method->dispatch);
- free(method);
+void upb_pbdecodermethod_unref(const upb_pbdecodermethod *m,
+ const void *owner) {
+ upb_refcounted_unref(UPB_UPCAST(m), owner);
}
+void upb_pbdecodermethod_donateref(const upb_pbdecodermethod *m,
+ const void *from, const void *to) {
+ upb_refcounted_donateref(UPB_UPCAST(m), from, to);
+}
-/* upb_pbdecoderplan **********************************************************/
+void upb_pbdecodermethod_checkref(const upb_pbdecodermethod *m,
+ const void *owner) {
+ upb_refcounted_checkref(UPB_UPCAST(m), owner);
+}
-upb_pbdecoderplan *newplan() {
- upb_pbdecoderplan *p = malloc(sizeof(*p));
- upb_inttable_init(&p->methods, UPB_CTYPE_PTR);
- p->code = NULL;
- p->code_end = NULL;
- return p;
+const upb_msgdef *upb_pbdecodermethod_schema(const upb_pbdecodermethod *m) {
+ return m->schema_;
}
-void freeplan(void *_p) {
- upb_pbdecoderplan *p = _p;
+const upb_handlers *upb_pbdecodermethod_desthandlers(
+ const upb_pbdecodermethod *m) {
+ return m->dest_handlers_;
+}
- upb_inttable_iter i;
- upb_inttable_begin(&i, &p->methods);
- for(; !upb_inttable_done(&i); upb_inttable_next(&i)) {
- upb_pbdecodermethod *method = upb_value_getptr(upb_inttable_iter_value(&i));
- freemethod(method);
- }
- upb_inttable_uninit(&p->methods);
- free(p->code);
-#ifdef UPB_USE_JIT_X64
- upb_pbdecoder_freejit(p);
-#endif
- free(p);
+const upb_byteshandler *upb_pbdecodermethod_inputhandler(
+ const upb_pbdecodermethod *m) {
+ return &m->input_handler_;
}
-void set_bytecode_handlers(upb_pbdecoderplan *p, upb_handlers *h) {
- upb_handlers_setstartstr(h, UPB_BYTESTREAM_BYTES, upb_pbdecoder_start, p,
- NULL);
- upb_handlers_setstring(h, UPB_BYTESTREAM_BYTES, upb_pbdecoder_decode, p,
- freeplan);
- upb_handlers_setendstr(h, UPB_BYTESTREAM_BYTES, upb_pbdecoder_end, p, NULL);
+bool upb_pbdecodermethod_isnative(const upb_pbdecodermethod *m) {
+ return m->is_native_;
}
-static const upb_pbdecoderplan *getdecoderplan(const upb_handlers *h) {
- if (upb_handlers_frametype(h) != &upb_pbdecoder_frametype)
- return NULL;
- upb_selector_t sel;
- if (!upb_handlers_getselector(UPB_BYTESTREAM_BYTES, UPB_HANDLER_STARTSTR,
- &sel)) {
- return NULL;
- }
- return upb_handlers_gethandlerdata(h, sel);
+const upb_pbdecodermethod *upb_pbdecodermethod_newfordesthandlers(
+ const upb_handlers *dest, const void *owner) {
+ upb_pbcodecache cache;
+ upb_pbcodecache_init(&cache);
+ const upb_pbdecodermethod *ret =
+ upb_pbcodecache_getdecodermethodfordesthandlers(&cache, dest);
+ upb_pbdecodermethod_ref(ret, owner);
+ upb_pbcodecache_uninit(&cache);
+ return ret;
}
@@ -97,16 +160,16 @@ static const upb_pbdecoderplan *getdecoderplan(const upb_handlers *h) {
// Data used only at compilation time.
typedef struct {
- upb_pbdecoderplan *plan;
+ mgroup *group;
uint32_t *pc;
int fwd_labels[MAXLABEL];
int back_labels[MAXLABEL];
} compiler;
-static compiler *newcompiler(upb_pbdecoderplan *plan) {
- compiler *ret = malloc(sizeof(compiler));
- ret->plan = plan;
+static compiler *newcompiler(mgroup *group) {
+ compiler *ret = malloc(sizeof(*ret));
+ ret->group = group;
for (int i = 0; i < MAXLABEL; i++) {
ret->fwd_labels[i] = EMPTYLABEL;
ret->back_labels[i] = EMPTYLABEL;
@@ -165,7 +228,7 @@ static void setofs(uint32_t *instruction, int32_t ofs) {
assert(getofs(*instruction) == ofs); // Would fail in cases of overflow.
}
-static uint32_t pcofs(compiler *c) { return c->pc - c->plan->code; }
+static uint32_t pcofs(compiler *c) { return c->pc - c->group->bytecode; }
// Defines a local label at the current PC location. All previous forward
// references are updated to point to this location. The location is noted
@@ -173,7 +236,7 @@ static uint32_t pcofs(compiler *c) { return c->pc - c->plan->code; }
static void label(compiler *c, unsigned int label) {
assert(label < MAXLABEL);
int val = c->fwd_labels[label];
- uint32_t *codep = (val == EMPTYLABEL) ? NULL : c->plan->code + val;
+ uint32_t *codep = (val == EMPTYLABEL) ? NULL : c->group->bytecode + val;
while (codep) {
int ofs = getofs(*codep);
setofs(codep, c->pc - codep - instruction_len(*codep));
@@ -197,7 +260,7 @@ static int32_t labelref(compiler *c, int label) {
return 0;
} else if (label < 0) {
// Backward local label. Relative to the next instruction.
- uint32_t from = (c->pc + 1) - c->plan->code;
+ uint32_t from = (c->pc + 1) - c->group->bytecode;
return c->back_labels[-label] - from;
} else {
// Forward local label: prepend to (possibly-empty) linked list.
@@ -209,14 +272,15 @@ static int32_t labelref(compiler *c, int label) {
}
static void put32(compiler *c, uint32_t v) {
- if (c->pc == c->plan->code_end) {
+ mgroup *g = c->group;
+ if (c->pc == g->bytecode_end) {
int ofs = pcofs(c);
- size_t oldsize = c->plan->code_end - c->plan->code;
+ size_t oldsize = g->bytecode_end - g->bytecode;
size_t newsize = UPB_MAX(oldsize * 2, 64);
// TODO(haberman): handle OOM.
- c->plan->code = realloc(c->plan->code, newsize * sizeof(uint32_t));
- c->plan->code_end = c->plan->code + newsize;
- c->pc = c->plan->code + ofs;
+ g->bytecode = realloc(g->bytecode, newsize * sizeof(uint32_t));
+ g->bytecode_end = g->bytecode + newsize;
+ c->pc = g->bytecode + ofs;
}
*c->pc++ = v;
}
@@ -272,7 +336,7 @@ static void putop(compiler *c, opcode op, ...) {
break;
case OP_CALL: {
const upb_pbdecodermethod *method = va_arg(ap, upb_pbdecodermethod *);
- put32(c, op | (method->base.ofs - (pcofs(c) + 1)) << 8);
+ put32(c, op | (method->code_base.ofs - (pcofs(c) + 1)) << 8);
break;
}
case OP_CHECKDELIM:
@@ -349,7 +413,7 @@ static void dumpbc(uint32_t *p, uint32_t *end, FILE *f) {
const upb_pbdecodermethod *method =
(void *)((char *)dispatch -
offsetof(upb_pbdecodermethod, dispatch));
- fprintf(f, " %s", upb_msgdef_fullname(method->msg));
+ fprintf(f, " %s", upb_msgdef_fullname(method->schema_));
break;
}
case OP_STARTMSG:
@@ -453,7 +517,7 @@ static upb_selector_t getsel(const upb_fielddef *f, upb_handlertype_t type) {
static void dispatchtarget(compiler *c, upb_pbdecodermethod *method,
const upb_fielddef *f, int wire_type) {
// Offset is relative to msg base.
- uint64_t ofs = pcofs(c) - method->base.ofs;
+ uint64_t ofs = pcofs(c) - method->code_base.ofs;
uint32_t fn = upb_fielddef_number(f);
upb_inttable *d = &method->dispatch;
upb_value v;
@@ -485,11 +549,11 @@ static void putpush(compiler *c, const upb_fielddef *f) {
static upb_pbdecodermethod *find_submethod(const compiler *c,
const upb_pbdecodermethod *method,
const upb_fielddef *f) {
- const void *key = method->dest_handlers ?
- (const void*)upb_handlers_getsubhandlers(method->dest_handlers, f) :
- (const void*)upb_downcast_msgdef(upb_fielddef_subdef(f));
+ const upb_handlers *sub = method->dest_handlers_ ?
+ upb_handlers_getsubhandlers(method->dest_handlers_, f) : NULL;
+ const void *key = methodkey(upb_downcast_msgdef(upb_fielddef_subdef(f)), sub);
upb_value v;
- bool ok = upb_inttable_lookupptr(&c->plan->methods, key, &v);
+ bool ok = upb_inttable_lookupptr(&c->group->methods, key, &v);
UPB_ASSERT_VAR(ok, ok);
return upb_value_getptr(v);
}
@@ -532,12 +596,12 @@ static void compile_method(compiler *c, upb_pbdecodermethod *method) {
upb_inttable_uninit(&method->dispatch);
upb_inttable_init(&method->dispatch, UPB_CTYPE_UINT64);
- method->base.ofs = pcofs(c);
+ method->code_base.ofs = pcofs(c);
putop(c, OP_SETDISPATCH, &method->dispatch);
putop(c, OP_STARTMSG);
label(c, LABEL_FIELD);
upb_msg_iter i;
- for(upb_msg_begin(&i, method->msg); !upb_msg_done(&i); upb_msg_next(&i)) {
+ for(upb_msg_begin(&i, method->schema_); !upb_msg_done(&i); upb_msg_next(&i)) {
const upb_fielddef *f = upb_msg_iter_field(&i);
upb_descriptortype_t type = upb_fielddef_descriptortype(f);
@@ -680,17 +744,15 @@ static void compile_method(compiler *c, upb_pbdecodermethod *method) {
// On the other hand, if/when the optimization mentioned below is implemented,
// binding to a upb_handlers can result in *fewer* methods being generated if
// many of the submessages have no handlers bound to them.
-static upb_pbdecodermethod *find_methods(compiler *c,
- const upb_msgdef *md,
- const upb_handlers *h) {
- const void *key = h ? (const void*)h : (const void*)md;
+static void find_methods(compiler *c, const upb_msgdef *md,
+ const upb_handlers *h) {
+ const void *key = methodkey(md, h);
upb_value v;
- if (upb_inttable_lookupptr(&c->plan->methods, key, &v))
- return upb_value_getptr(v);
- upb_pbdecodermethod *method = newmethod(md, h);
- // Takes ownership of method.
- upb_inttable_insertptr(&c->plan->methods, key, upb_value_ptr(method));
+ if (upb_inttable_lookupptr(&c->group->methods, key, &v))
+ return;
+ newmethod(md, h, c->group, key);
+ // Find submethods.
upb_msg_iter i;
for(upb_msg_begin(&i, md); !upb_msg_done(&i); upb_msg_next(&i)) {
const upb_fielddef *f = upb_msg_iter_field(&i);
@@ -706,24 +768,34 @@ static upb_pbdecodermethod *find_methods(compiler *c,
find_methods(c, upb_downcast_msgdef(upb_fielddef_subdef(f)), sub_h);
}
-
- return method;
}
-// (Re-)compile bytecode for all messages in "msgs", ensuring that the code
-// for "md" is emitted first. Overwrites any existing bytecode in "c".
+// (Re-)compile bytecode for all messages in "msgs."
+// Overwrites any existing bytecode in "c".
static void compile_methods(compiler *c) {
// Start over at the beginning of the bytecode.
- c->pc = c->plan->code;
- compile_method(c, c->plan->topmethod);
+ c->pc = c->group->bytecode;
upb_inttable_iter i;
- upb_inttable_begin(&i, &c->plan->methods);
+ upb_inttable_begin(&i, &c->group->methods);
for(; !upb_inttable_done(&i); upb_inttable_next(&i)) {
upb_pbdecodermethod *method = upb_value_getptr(upb_inttable_iter_value(&i));
- if (method != c->plan->topmethod) {
- compile_method(c, method);
- }
+ compile_method(c, method);
+ }
+}
+
+static void set_bytecode_handlers(mgroup *g) {
+ upb_inttable_iter i;
+ upb_inttable_begin(&i, &g->methods);
+ for(; !upb_inttable_done(&i); upb_inttable_next(&i)) {
+ upb_pbdecodermethod *m = upb_value_getptr(upb_inttable_iter_value(&i));
+
+ m->code_base.ptr = g->bytecode + m->code_base.ofs;
+
+ upb_byteshandler *h = &m->input_handler_;
+ upb_byteshandler_setstartstr(h, upb_pbdecoder_startbc, m->code_base.ptr);
+ upb_byteshandler_setstring(h, upb_pbdecoder_decode, g);
+ upb_byteshandler_setendstr(h, upb_pbdecoder_end, m);
}
}
@@ -732,17 +804,13 @@ static void compile_methods(compiler *c) {
#ifdef UPB_USE_JIT_X64
-static void sethandlers(upb_pbdecoderplan *p, upb_handlers *h, bool allowjit) {
- p->jit_code = NULL;
-
+static void sethandlers(mgroup *g, bool allowjit) {
+ g->jit_code = NULL;
if (allowjit) {
- upb_pbdecoder_jit(p); // Compile byte-code into machine code.
- upb_handlers_setstartstr(h, UPB_BYTESTREAM_BYTES, upb_pbdecoder_start, p,
- freeplan);
- upb_handlers_setstring(h, UPB_BYTESTREAM_BYTES, p->jit_code, NULL, NULL);
- upb_handlers_setendstr(h, UPB_BYTESTREAM_BYTES, upb_pbdecoder_end, p, NULL);
+ // Compile byte-code into machine code, create handlers.
+ upb_pbdecoder_jit(g);
} else {
- set_bytecode_handlers(p, h);
+ set_bytecode_handlers(g);
}
}
@@ -754,10 +822,10 @@ static bool bind_dynamic(bool allowjit) {
#else // UPB_USE_JIT_X64
-static void sethandlers(upb_pbdecoderplan *p, upb_handlers *h, bool allowjit) {
+static void sethandlers(mgroup *g, bool allowjit) {
// No JIT compiled in; use bytecode handlers unconditionally.
UPB_UNUSED(allowjit);
- set_bytecode_handlers(p, h);
+ set_bytecode_handlers(g);
}
static bool bind_dynamic(bool allowjit) {
@@ -769,56 +837,16 @@ static bool bind_dynamic(bool allowjit) {
#endif // UPB_USE_JIT_X64
-/* Public interface ***********************************************************/
-
-bool upb_pbdecoder_isdecoder(const upb_handlers *h) {
- return getdecoderplan(h) != NULL;
-}
-
-bool upb_pbdecoderplan_hasjitcode(const upb_pbdecoderplan *p) {
-#ifdef UPB_USE_JIT_X64
- return p->jit_code != NULL;
-#else
- UPB_UNUSED(p);
- return false;
-#endif
-}
-
-bool upb_pbdecoder_hasjitcode(const upb_handlers *h) {
- const upb_pbdecoderplan *p = getdecoderplan(h);
- if (!p) return false;
- return upb_pbdecoderplan_hasjitcode(p);
-}
-
-uint32_t *upb_pbdecoderplan_codebase(const upb_pbdecoderplan *p) {
- return p->code;
-}
-
-upb_string_handler *upb_pbdecoderplan_jitcode(const upb_pbdecoderplan *p) {
-#ifdef UPB_USE_JIT_X64
- return p->jit_code;
-#else
- UPB_UNUSED(p);
- assert(false);
- return NULL;
-#endif
-}
-
-const upb_handlers *upb_pbdecoder_getdesthandlers(const upb_handlers *h) {
- const upb_pbdecoderplan *p = getdecoderplan(h);
- if (!p) return NULL;
- return p->topmethod->dest_handlers;
-}
-
-const upb_handlers *upb_pbdecoder_gethandlers(const upb_handlers *dest,
- bool allowjit,
- const void *owner) {
+// TODO(haberman): allow this to be constructed for an arbitrary set of dest
+// handlers and other mgroups (but verify we have a transitive closure).
+const mgroup *mgroup_new(const upb_handlers *dest, bool allowjit,
+ const void *owner) {
UPB_UNUSED(allowjit);
assert(upb_handlers_isfrozen(dest));
const upb_msgdef *md = upb_handlers_msgdef(dest);
- upb_pbdecoderplan *p = newplan();
- compiler *c = newcompiler(p);
+ mgroup *g = newgroup(owner);
+ compiler *c = newcompiler(g);
if (bind_dynamic(allowjit)) {
// If binding dynamically, remove the reference against destination
@@ -826,32 +854,75 @@ const upb_handlers *upb_pbdecoder_gethandlers(const upb_handlers *dest,
dest = NULL;
}
- p->topmethod = find_methods(c, md, dest);
+ find_methods(c, md, dest);
// We compile in two passes:
// 1. all messages are assigned relative offsets from the beginning of the
- // bytecode (saved in method->base).
+ // bytecode (saved in method->code_base).
// 2. forwards OP_CALL instructions can be correctly linked since message
// offsets have been previously assigned.
//
// Could avoid the second pass by linking OP_CALL instructions somehow.
compile_methods(c);
compile_methods(c);
- p->code_end = c->pc;
+ g->bytecode_end = c->pc;
+ freecompiler(c);
#ifdef UPB_DUMP_BYTECODE
FILE *f = fopen("/tmp/upb-bytecode", "wb");
assert(f);
- dumpbc(p->code, p->code_end, stderr);
- dumpbc(p->code, p->code_end, f);
+ dumpbc(g->bytecode, g->bytecode_end, stderr);
+ dumpbc(g->bytecode, g->bytecode_end, f);
fclose(f);
#endif
- upb_handlers *h = upb_handlers_new(
- UPB_BYTESTREAM, &upb_pbdecoder_frametype, owner);
- sethandlers(p, h, allowjit);
+ sethandlers(g, allowjit);
+ return g;
+}
- freecompiler(c);
- return h;
+/* upb_pbcodecache ************************************************************/
+
+void upb_pbcodecache_init(upb_pbcodecache *c) {
+ upb_inttable_init(&c->groups, UPB_CTYPE_CONSTPTR);
+ c->allow_jit_ = true;
+}
+
+void upb_pbcodecache_uninit(upb_pbcodecache *c) {
+ upb_inttable_iter i;
+ upb_inttable_begin(&i, &c->groups);
+ for(; !upb_inttable_done(&i); upb_inttable_next(&i)) {
+ const mgroup *group = upb_value_getconstptr(upb_inttable_iter_value(&i));
+ upb_refcounted_unref(UPB_UPCAST(group), c);
+ }
+ upb_inttable_uninit(&c->groups);
+}
+
+bool upb_pbcodecache_allowjit(const upb_pbcodecache *c) {
+ return c->allow_jit_;
+}
+
+bool upb_pbcodecache_setallowjit(upb_pbcodecache *c, bool allow) {
+ if (upb_inttable_count(&c->groups) > 0)
+ return false;
+ c->allow_jit_ = allow;
+ return true;
+}
+
+const upb_pbdecodermethod *upb_pbcodecache_getdecodermethodfordesthandlers(
+ upb_pbcodecache *c, const upb_handlers *handlers) {
+ // Right now we build a new DecoderMethod every time.
+ // TODO(haberman): properly cache methods by their true key.
+ const mgroup *g = mgroup_new(handlers, c->allow_jit_, c);
+ upb_inttable_push(&c->groups, upb_value_constptr(g));
+
+ const upb_msgdef *md = upb_handlers_msgdef(handlers);
+ if (bind_dynamic(c->allow_jit_)) {
+ handlers = NULL;
+ }
+
+ upb_value v;
+ bool ok = upb_inttable_lookupptr(&g->methods, methodkey(md, handlers), &v);
+ UPB_ASSERT_VAR(ok, ok);
+ return upb_value_getptr(v);
}
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback