summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2020-11-04 16:12:56 -0800
committerVito Caputo <vcaputo@pengaru.com>2020-11-04 16:12:56 -0800
commit71d53baee273ab397533c2585293457a620a66df (patch)
treea3327f2be93171f4b23e77d4ddaac1ab7024560f
parent18d2918a02028bf649607bb9959b926d10f184f6 (diff)
thunk: introduce split instantiation and payloads
The existing THUNK() instantiation macro has really nice ergonomics taking the form of the bare function call wrapped by THUNK(). It's desirable however to sometimes enlarge the instance's allocation to accomodate some user-specified data with an instance-boound life cycle. There didn't seem to be a clean way to provide that with the existing THUNK() interface. This commit introduces a split THUNK() variant for such scenarios: THUNK_ALLOC(function_name, payload_pointer, payload_size) and THUNK_INIT(function_name(thunk_instance, [function_args] ...)) It's expected that these be used in pairs, like so: char *buf; thunk_t *t = THUNK_ALLOC(foo_func, &buf, BUFSIZ); THUNK_INIT(foo_func(t, foo_arg1, foo_arg2)); Note the THUNK_INIT's foo_func call has the thunk_t * passed as the first parameter. This isn't part of foo_func's THUNK_DEFINE signature, it's only part of the initializer for the function, followed by the real parameters from the THUNK_DEFINE's signature if there are any. This differs from the simpler THUNK() variant, where you'd do: THUNK(foo_func(foo_arg1, foo_arg2)); Also note the THUNK_ALLOC() above has stored a pointer to BUFSIZ bytes of payload memory @ *buf. This could then be supplied into foo_func, which is sometimes convenient: THUNK_INIT(foo_func(t, foo_arg1, buf)); The payloads are particularly useful when chaining up multiple thunks, and there's a need for some shared state across them for inputs/outputs, that you'd like automatically cleaned up when the instance the payload belongs to executes and returns freeing itself.
-rw-r--r--thunk.h195
1 files changed, 190 insertions, 5 deletions
diff --git a/thunk.h b/thunk.h
index d2ec683..8bcc6d7 100644
--- a/thunk.h
+++ b/thunk.h
@@ -3,7 +3,7 @@
/*
* Machinery for generating simple C thunks using the C preprocessor.
- * Copyright (C) 2016 Vito Caputo <vcaputo@pengaru.com>
+ * Copyright (C) 2016,2020 Vito Caputo <vcaputo@pengaru.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -370,6 +370,66 @@ struct thunk_t {
_THUNK_GEN_PROTO_(_name, _C99_NARGS(__VA_ARGS__), __VA_ARGS__)
+/* XXX: I'm unclear on why using the existing _THUNK_GEN_PROTO() macros refuse
+ * to work from the init case. The only thing I'm doing differently there is
+ * inserting `thunk_t *, thunk` parameters between the ##_name, and
+ * __VA_ARGS__. I expected the inserted pair to just combine with the
+ * __VA_ARGS__ pasted afterwards, but it kept spewing errors until I resorted
+ * to this variant. There may be some stupidity hiding in plain sight, maybe I
+ * needed to do another double evaluation dance, but this internal duplication
+ * works for now. TODO: investigate when time permits
+ */
+/* Macros for prototyping the initializer for the thunk being instantiated,
+ * we need a variant for each supported arity. */
+#define _THUNK_GEN_INIT_PROTO_0(_name, _thunk, _nil) \
+ _name(_thunk)
+
+#define _THUNK_GEN_INIT_PROTO_2(_name, _thunk, _t_a, _n_a) \
+ _name(_thunk, _t_a _n_a)
+
+#define _THUNK_GEN_INIT_PROTO_4(_name, _thunk, _t_a, _n_a, _t_b, _n_b) \
+ _name(_thunk, _t_a _n_a, _t_b _n_b)
+
+#define _THUNK_GEN_INIT_PROTO_6(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c) \
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c)
+
+#define _THUNK_GEN_INIT_PROTO_8(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d)
+
+#define _THUNK_GEN_INIT_PROTO_10(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e)
+
+#define _THUNK_GEN_INIT_PROTO_12(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f)
+
+#define _THUNK_GEN_INIT_PROTO_14(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f, _t_g, _n_g)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f, _t_g _n_g)
+
+#define _THUNK_GEN_INIT_PROTO_16(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f, _t_g, _n_g, _t_h, _n_h)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f, _t_g _n_g, _t_h _n_h)
+
+#define _THUNK_GEN_INIT_PROTO_18(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f, _t_g, _n_g, _t_h, _n_h, _t_i, _n_i)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f, _t_g _n_g, _t_h _n_h, _t_i _n_i)
+
+#define _THUNK_GEN_INIT_PROTO_20(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f, _t_g, _n_g, _t_h, _n_h, _t_i, _n_i, _t_j, _n_j)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f, _t_g _n_g, _t_h _n_h, _t_i _n_i, _t_j _n_j)
+
+#define _THUNK_GEN_INIT_PROTO_22(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f, _t_g, _n_g, _t_h, _n_h, _t_i, _n_i, _t_j, _n_j, _t_k, _n_k)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f, _t_g _n_g, _t_h _n_h, _t_i _n_i, _t_j _n_j, _t_k _n_k)
+
+#define _THUNK_GEN_INIT_PROTO_24(_name, _thunk, _t_a, _n_a, _t_b, _n_b, _t_c, _n_c, _t_d, _n_d, _t_e, _n_e, _t_f, _n_f, _t_g, _n_g, _t_h, _n_h, _t_i, _n_i, _t_j, _n_j, _t_k, _n_k, _t_l, _n_l)\
+ _name(_thunk, _t_a _n_a, _t_b _n_b, _t_c _n_c, _t_d _n_d, _t_e _n_e, _t_f _n_f, _t_g _n_g, _t_h _n_h, _t_i _n_i, _t_j _n_j, _t_k _n_k, _t_l _n_l)
+
+#define _THUNK_GEN_INIT_PROTO__(_name, _thunk, _count, ...) \
+ _THUNK_GEN_INIT_PROTO_##_count(_name, _thunk, __VA_ARGS__)
+
+#define _THUNK_GEN_INIT_PROTO_(_name, _thunk, _count, ...) \
+ _THUNK_GEN_INIT_PROTO__(_name, _thunk, _count, __VA_ARGS__)
+
+#define _THUNK_GEN_INIT_PROTO(_name, _thunk, ...) \
+ _THUNK_GEN_INIT_PROTO_(_name, _thunk, _C99_NARGS(__VA_ARGS__), __VA_ARGS__)
+
+
/* Define a function for use with a thunk. This generates the scaffolding
* functions and structure for (un)packing the args with an appropriately
* wrapped thunk_t. Calls to this function may then be supplied to anything
@@ -385,6 +445,7 @@ struct thunk_t {
/* struct for encapsulating the calling environment */ \
thunk_t __thunk; \
_THUNK_GEN_STRUCT (__VA_ARGS__); \
+ char __payload[]; \
} __thunk_environment_##_name; \
\
static int __thunk_dispatch_##_name(thunk_t *thunk) { \
@@ -416,6 +477,33 @@ struct thunk_t {
return &env->__thunk; \
} \
\
+ static thunk_t * __thunk_alloc_##_name(void **payload_ptr, size_t payload_size) {\
+ /* allocate a thunk with an optional extra payload ptr, return it */\
+ __thunk_environment_##_name *env; \
+ \
+ env = malloc(sizeof(*env) + payload_size); \
+ assert(env); \
+ \
+ if (payload_ptr) \
+ *payload_ptr = env->__payload; \
+ \
+ env->__thunk.dispatch = __thunk_dispatch_##_name; \
+ \
+ return &env->__thunk; \
+ } \
+ \
+ static thunk_t * _THUNK_GEN_INIT_PROTO(__thunk_init_##_name, thunk_t *_thunk, __VA_ARGS__) {\
+ /* initialize environment provided as a pre-allocd thunk, return it */\
+ __thunk_environment_##_name *env = (__thunk_environment_##_name *)_thunk;\
+ \
+ assert(env); \
+ assert(env->__thunk.dispatch == __thunk_dispatch_##_name); \
+ \
+ _THUNK_GEN_INITIALIZE(env, __VA_ARGS__); \
+ \
+ return &env->__thunk; \
+ } \
+ \
static int _THUNK_GEN_PROTO(_name, __VA_ARGS__)
@@ -429,9 +517,12 @@ struct thunk_t {
/* struct for encapsulating the calling environment */ \
thunk_t __thunk; \
_THUNK_GEN_STRUCT (__VA_ARGS__); \
+ char __payload[]; \
} __thunk_environment_##_name; \
\
int __thunk_dispatch_##_name(thunk_t *thunk); \
+ thunk_t * _THUNK_GEN_INIT_PROTO(__thunk_init_##_name, thunk_t *_thunk, __VA_ARGS__);\
+ thunk_t * __thunk_alloc_##_name(void **payload_ptr, size_t payload_size);\
thunk_t * _THUNK_GEN_PROTO(__thunk_instantiate_##_name, __VA_ARGS__);
@@ -465,19 +556,113 @@ struct thunk_t {
return &env->__thunk; \
} \
\
+ thunk_t * __thunk_alloc_##_name(void **payload_ptr, size_t payload_size) {\
+ /* allocate a thunk with an optional extra payload ptr, return it */\
+ __thunk_environment_##_name *env; \
+ \
+ env = malloc(sizeof(*env) + payload_size); \
+ assert(env); \
+ \
+ if (payload_ptr) \
+ *payload_ptr = env->__payload; \
+ \
+ env->__thunk.dispatch = __thunk_dispatch_##_name; \
+ \
+ return &env->__thunk; \
+ } \
+ \
+ thunk_t * _THUNK_GEN_INIT_PROTO(__thunk_init_##_name, thunk_t *_thunk, __VA_ARGS__) {\
+ /* initialize environment provided as a pre-allocd thunk, return it */\
+ __thunk_environment_##_name *env = (__thunk_environment_##_name *)_thunk;\
+ \
+ assert(env); \
+ assert(env->__thunk.dispatch == __thunk_dispatch_##_name); \
+ \
+ _THUNK_GEN_INITIALIZE(env, __VA_ARGS__); \
+ \
+ return &env->__thunk; \
+ } \
+ \
int _THUNK_GEN_PROTO(_name, __VA_ARGS__)
-/* Call the appropriate instantiate function which returns an embedded thunk_t,
- * this is how you prepare the thunks established via THUNK_DEFINE(). */
+/* The following macros wrap the appropriate thunk-specific functions for
+ * instantiating a pre-defined thunk.
+ */
+
+/* This is the simplest form where you just write a call to the
+ * THUNK_DEFINE()'d function as usual, but wrapped in THUNK(). Most of the
+ * time this is sufficient, but in more complicated scenarios you may need the
+ * split THUNK_ALLOC() and THUNK_INIT() pair further below.
+ */
#define _THUNK(_call) \
__thunk_instantiate_##_call
#define THUNK(_call) \
_THUNK(_call)
-/* Call the function associated with a prepared thunk, supplying the bound environment
- * as arguments. This is how code receiving generic thunks executes them. */
+
+/* This is for separately allocating the thunk instance for the named
+ * THUNK_DEFINE()'d function, with an optional payload of the specified size
+ * allocated as part of the instance, with the payload's pointer stored @
+ * _payload_ptr if non-NULL. There is no way to retrieve this payload pointer
+ * after this point, it's completely opaque to everything, provided purely as a
+ * place to externally stow some additional instance-bound state.
+ *
+ * The instance is not ready to dispatch yet at this point, it still needs
+ * initializing to bind any variables to the thunk's calling environment.
+ * Those variables may reference indirectly to state stowed in the payload
+ * pointer returned here, which is the main reason this became a two-step
+ * process.
+ */
+#define _THUNK_ALLOC(_func, _payload_ptr, _payload_size) \
+ __thunk_alloc_##_func(_payload_ptr, _payload_size)
+
+#define THUNK_ALLOC(_func, _payload_ptr, _payload_size) \
+ _THUNK_ALLOC(_func, _payload_ptr, _payload_size)
+
+
+/* This is for initializing a distinctly allocated instance from THUNK_ALLOC().
+ * You only should use the split ALLOC+INIT method when using the embedded
+ * payload feature, otherwise the simpler THUNK() should suffice.
+ *
+ * Use is basically identical to THUNK() except the parameters for the function
+ * as established by THUNK_DEFINE() must be prefixed by the (thunk_t *)
+ * returned by THUNK_ALLOC() as the first parameter, so the initializer can
+ * find the pre-allocated instance to initialize.
+ *
+ * i.e. for the function foo_func: THUNK_DEFINE(foo_func, char *, bar):
+ *
+ * Allocating the instance foo_thunk w/4096 byte payload, accessed via buf:
+ *
+ * char *buf;
+ * thunk_t foo_thunk = THUNK_ALLOC(foo_func, &buf, 4096);
+ *
+ * Initializing foo_thunk with "bar-str" as the (char *bar) parameter:
+ *
+ * THUNK_INIT(foo_func(foo_thunk, "bar-str"));
+ * ^^^^ note the thunk is supplied, vs. w/THUNK():
+ * THUNK(foo_func("bar-str"));
+ *
+ * XXX: note that you cannot mix functions across THUNK_ALLOC and THUNK_INIT
+ * on a given instance. You must initialize an instance allocated for a given
+ * function with the same function. There is an assert in place to help
+ * prevent this type of mistake.
+ */
+#define _THUNK_INIT(_call) \
+ __thunk_init_##_call
+
+#define THUNK_INIT(_call) \
+ _THUNK_INIT(_call)
+
+
+/* Call the function associated with a thunk instance, supplying the bound
+ * environment as arguments. This is how code receiving generic thunks
+ * executes them.
+ *
+ * After the thunked function returns, the instance is automatically freed,
+ * including any payload if THUNK_ALLOC() was used.
+ */
static inline int thunk_dispatch(thunk_t *thunk) {
return thunk->dispatch(thunk);
}
© 2021 All Rights Reserved