My project needs to load many modules at runtime, and each one contains many functions with a form similar to the below pseudo code:
void someFunction(Context &ctx) {
bool result;
result = ctx.call("someFunction2")(ctx.arg["arg1"], ctx.arg["arg2"])
&& ctx.call("someFunction3")(ctx.arg["arg1"], ctx.arg["arg3"]);
ctx.result(result);
}
where ctx.arg["arg1"]
, ctx.arg["arg2"]
, ctx.arg["arg3"]
are arguments passed to someFunction
at runtime. someFunction2
and someFunction3
could not be statically resolved at compile time, but will be known (whether they have been defined in other modules) at runtime when all modules are loaded.
Now, a naive implementation would be using a hash map to store a function handle to all of these functions, but hashing would be slow as there are typically 10k functions to search for and each function will be called many times in other functions (eg: arguments are enumerated to find a correct combination which will produce a desired result).
Therefore, I am looking for some kind of solution, which will perform a one time replacement on these "ctx.call" when all modules are loaded, and not perform a "hash-and-probe" every time. Currently the main problem is the "replacing" action. I have come up with some ideas, but they are not perfect:
1st solution: create a inner function inner_func(func_handle1, func_handle2, arg1, arg2, arg3)
, and use std::bind
to create a outer wrapper outer_wrapper()
.
problem: not user friendly, must explicitly tell the context which functions and args to find.
2nd solution: use metaprogramming + constexpr + macros to automatically count function and argument name references, then create a reference table, then let the context fill each table at runtime.
problem: I cannot work it out, need some help. I have read documents of the Fatal library from facebook, mpl and hana from boost, but there doesn't seem to be a clean way to do this.
3rd solution: use a JIT compiler
problem: c++ JIT compiler choices are limited. NativeJIT is not powerful enough, easy::JIT doesn't seem to be customizable and isn't easy to distribute. asmjit is not usable.
PS: Problem context is "automated planners", and these functions are used to construct predicates. Context ctx
is just an example, you may use other appropriate syntaxes if necessary, as long as they are easy to be used to represent the following lisp expression:
(and (at ?p ?c1)
(aircraft ?a)
(at ?a ?c3)
(different ?c1 ?c3))
PPS: more specifically I am thinking about something look like this:
User will define a function looking like this:
void module_init() {
FUNC ("someFunction")("p", "a", "c1", "c3") (
bool result;
result = CALL("at")("p", "c1")
&& CALL("aircraft")("a")
&& CALL("at")("a", "c3")
&& CALL("different")("c1", "c3")
/// Users should also be able to access arguments as a "Variable"
/// class using ARG["p"]
return result;
)
}
Then by some way, FUNC()
will be converted to a functor similar to:
struct func_someFunction {
some_vector<std::function<bool()>> functions;
some_vector<Variable*> args;
some_vector<std::string> func_name;
some_vector<std::string> arg_name;
bool operator()() {
/// above representation of Func(), but function and args are pointers in "functions" and "args"
}
}
Then when all modules are loaded, the system will read func_name
and arg_name
, and fill appropriate function pointers and variable pointers to functions
and args
respectively.
Status: Using hashmap first, I will post updates once completed.
Status: Figured out a solution myself, also tested hash implementation, posted below.
Any idea would be appreciated. Thank you!