#include "executor/tuptable.h"
#include "utils/expandeddatum.h"
+#include "lib/llvmjit.h"
/* Does att's datatype allow packing into the 1-byte-header varlena format? */
#define ATT_IS_PACKABLE(att) \
static void * slot_deform_tuple(TupleTableSlot *slot, int natts);
+#ifdef USE_LLVM
+bool jit_tuple_deforming = false;
+
+static void *jit_compile_deform(TupleDesc desc);
+
+/* define to verify that the JITed deform returns the correct results */
+#undef VERIFY_LLVM_RESULT
+
+#endif /* USE_LLVM */
/* FIXME: use computed goto only on gcc / clang */
#define USE_COMPUTED_GOTO
}
+#ifdef USE_LLVM
+ if (!slot->tts_tupleDescriptor->tddeform && jit_tuple_deforming)
+ {
+ slot->tts_tupleDescriptor->tddeform =
+ jit_compile_deform(slot->tts_tupleDescriptor);
+ }
+
+ /* if we're verifying only run the compiled version after plain deforming */
+#ifndef VERIFY_LLVM_RESULT
+ if (slot->tts_tupleDescriptor->tddeform)
+ {
+ PartiallyJittedDeform deform = slot->tts_tupleDescriptor->tddeform;
+ deform(slot, (char *) tup + tup->t_hoff, slot->tts_off, natts);
+ return NULL;
+ }
+#endif
+#endif
+
/*
* To avoid checking "progress" in every loop iteration, temporarily set
* the last step + 1 to DONE. That'll cause the loop below to exit.
slot->tts_dp[natts].attopcode_p = SD_OPCODE(oldop);
#endif
+#if defined(USE_LLVM) && defined(VERIFY_LLVM_RESULT)
+ /* verify results */
+ if (slot->tts_tupleDescriptor->tddeform)
+ {
+ Datum *save_values = slot->tts_values;
+ bool *save_isnull = slot->tts_isnull;
+ int save_nvalid = slot->tts_nvalid;
+ int save_off = slot->tts_off;
+ Datum checkvalues[MaxTupleAttributeNumber];
+ bool checknulls[MaxTupleAttributeNumber];
+ int i;
+ PartiallyJittedDeform deform = slot->tts_tupleDescriptor->tddeform;
+
+ memcpy(checknulls, slot->tts_isnull, sizeof(bool) * slot->tts_tupleDescriptor->natts);
+ memset(checkvalues, 0xef, sizeof(Datum) * natts);
+ //memcpy(checkvalues, slot->tts_values, sizeof(bool) * slot->tts_nvalid);
+
+ slot->tts_values = checkvalues;
+ slot->tts_isnull = checknulls;
+ //slot->tts_nvalid = 0;
+ //slot->tts_off = 0;
+
+ deform(slot, (char *) tup + tup->t_hoff, slot->tts_off, natts);
+
+ if (slot->tts_nvalid != attnum)
+ {
+ elog(WARNING, "nvalid differs: %d %d prev: %d, natts: %d",
+ slot->tts_nvalid, attnum, save_nvalid, natts);
+ }
+
+ if (slot->tts_off != -1 && slot->tts_off != tpc - ((char *) tup + tup->t_hoff))
+ {
+ elog(WARNING, "offs differ: %d %zd",
+ slot->tts_off,
+ tpc - ((char *) tup + tup->t_hoff));
+ }
+
+ slot->tts_off = save_off;
+ slot->tts_nvalid = save_nvalid;
+ slot->tts_values = save_values;
+ slot->tts_isnull = save_isnull;
+
+ for (i = slot->tts_nvalid; i < natts; i++)
+ {
+ if (slot->tts_isnull[i] != checknulls[i])
+ {
+ elog(ERROR, "att[%u] has differing nulls: is %c shouldbe %c", i, checknulls[i], slot->tts_isnull[i]);
+ }
+ if (checknulls[i])
+ {
+ if (checkvalues[i] != 0)
+ elog(ERROR, "att[%u] should be 0", i);
+ continue;
+ }
+ if(checkvalues[i] != slot->tts_values[i])
+ {
+ elog(ERROR, "att[%u] values differ is %lu should be %lu", i, checkvalues[i], slot->tts_values[i]);
+ }
+ }
+ }
+#endif
+
+
/*
* Save state for next execution
*/
result->t_len = len;
return result;
}
+
+
+extern size_t varsize_any(void *p);
+
+size_t
+varsize_any(void *p)
+{
+ return VARSIZE_ANY(p);
+}
+
+#ifdef USE_LLVM
+
+/* build extern reference for varsize_any */
+static LLVMValueRef
+create_varsize_any(LLVMModuleRef mod)
+{
+ LLVMTypeRef *param_types = palloc(sizeof(LLVMTypeRef) * 1);
+ LLVMTypeRef sig;
+ LLVMValueRef fn;
+
+ param_types[0] = LLVMPointerType(LLVMInt8Type(), 0);
+ sig = LLVMFunctionType(LLVMInt64Type(), param_types, 1, 0);
+ fn = LLVMAddFunction(mod, "varsize_any", sig);
+
+ {
+ char argname[] = "readonly";
+ LLVMAttributeRef ref =
+ LLVMCreateStringAttribute(LLVMGetGlobalContext(), argname, strlen(argname), NULL, 0);
+ LLVMAddAttributeAtIndex(fn, LLVMAttributeFunctionIndex, ref);
+ }
+ {
+ char argname[] = "argmemonly";
+ LLVMAttributeRef ref =
+ LLVMCreateStringAttribute(LLVMGetGlobalContext(), argname, strlen(argname), NULL, 0);
+ LLVMAddAttributeAtIndex(fn, LLVMAttributeFunctionIndex, ref);
+ }
+
+ return fn;
+}
+
+/* build extern reference for strlen */
+static LLVMValueRef
+create_strlen(LLVMModuleRef mod)
+{
+ LLVMTypeRef *param_types = palloc(sizeof(LLVMTypeRef) * 1);
+ LLVMTypeRef sig;
+ LLVMValueRef fn;
+
+ param_types[0] = LLVMPointerType(LLVMInt8Type(), 0);
+ sig = LLVMFunctionType(TypeSizeT, param_types, 1, 0);
+ fn = LLVMAddFunction(mod, "strlen", sig);
+
+ return fn;
+}
+
+static void*
+jit_compile_deform(TupleDesc desc)
+{
+ static int deformcounter = 0;
+ char *funcname;
+ void *funcptr = NULL;
+
+ LLVMModuleRef mod;
+ LLVMTypeRef deform_sig;
+ LLVMValueRef deform_fn;
+ LLVMBuilderRef builder;
+ LLVMBasicBlockRef entry;
+ LLVMBasicBlockRef outblock;
+ LLVMBasicBlockRef deadblock;
+ LLVMBasicBlockRef *attcheckattnoblocks;
+ LLVMBasicBlockRef *attstartblocks;
+ LLVMBasicBlockRef *attcheckalignblocks;
+ LLVMBasicBlockRef *attalignblocks;
+ LLVMBasicBlockRef *attstoreblocks;
+ LLVMBasicBlockRef *attoutblocks;
+ LLVMValueRef tupdata_base;
+ LLVMValueRef v_off, v_off_inc, v_off_start;
+ LLVMValueRef resval;
+ LLVMValueRef resnull;
+ LLVMValueRef v_slotoffp;
+ LLVMValueRef v_nvalidp, v_nvalid;
+ LLVMValueRef l_varsize_any;
+ LLVMValueRef l_strlen;
+ LLVMValueRef v_maxatt;
+ int attnum;
+ int attcuralign = 0;
+ bool lastcouldbenull = false;
+
+ llvm_initialize();
+
+ funcname = psprintf("deform%d", deformcounter);
+ deformcounter++;
+
+ /* Create the signature and function */
+ mod = LLVMModuleCreateWithName(funcname);
+ {
+ LLVMTypeRef param_types[] = {
+ LLVMPointerType(StructTupleTableSlot, 0),
+ LLVMPointerType(LLVMInt8Type(), 0),
+ LLVMInt32Type(),
+ LLVMInt16Type()};
+ deform_sig = LLVMFunctionType(LLVMVoidType(), param_types,
+ lengthof(param_types), 0);
+ }
+ deform_fn = LLVMAddFunction(mod, funcname, deform_sig);
+ entry = LLVMAppendBasicBlock(deform_fn, "entry");
+ outblock = LLVMAppendBasicBlock(deform_fn, "out");
+ deadblock = LLVMAppendBasicBlock(deform_fn, "deadblock");
+ builder = LLVMCreateBuilder();
+ attcheckattnoblocks = palloc(sizeof(LLVMBasicBlockRef) * desc->natts);
+ attstartblocks = palloc(sizeof(LLVMBasicBlockRef) * desc->natts);
+ attcheckalignblocks = palloc(sizeof(LLVMBasicBlockRef) * desc->natts);
+ attalignblocks = palloc(sizeof(LLVMBasicBlockRef) * desc->natts);
+ attstoreblocks = palloc(sizeof(LLVMBasicBlockRef) * desc->natts);
+ attoutblocks = palloc(sizeof(LLVMBasicBlockRef) * desc->natts);
+
+ l_varsize_any = create_varsize_any(mod);
+ l_strlen = create_strlen(mod);
+
+ attcuralign = 0;
+ lastcouldbenull = false;
+
+ LLVMAddModule(llvm_engine, mod);
+
+ LLVMSetLinkage(deform_fn, LLVMInternalLinkage);
+ LLVMPositionBuilderAtEnd(builder, entry);
+
+ LLVMSetParamAlignment(LLVMGetParam(deform_fn, 0), MAXIMUM_ALIGNOF);
+
+ resval = LLVMBuildLoad(builder,
+ LLVMBuildStructGEP(builder, LLVMGetParam(deform_fn, 0), 10, ""),
+ "tts_values");
+ resnull = LLVMBuildLoad(builder,
+ LLVMBuildStructGEP(builder, LLVMGetParam(deform_fn, 0), 11, ""),
+ "tts_isnull");
+ v_slotoffp = LLVMBuildStructGEP(builder, LLVMGetParam(deform_fn, 0), 15, "");
+ v_nvalidp = LLVMBuildStructGEP(builder, LLVMGetParam(deform_fn, 0), 9, "");
+
+ tupdata_base = LLVMGetParam(deform_fn, 1);
+ v_off_inc = v_off = v_off_start = LLVMGetParam(deform_fn, 2);
+ v_maxatt = LLVMGetParam(deform_fn, 3);
+
+ /* build the basic block for each attribute, need them as jump target */
+ for (attnum = 0; attnum < desc->natts; attnum++)
+ {
+ char *blockname;
+
+ blockname = psprintf("block.attr.%d.attcheckattno", attnum);
+ attcheckattnoblocks[attnum] = LLVMAppendBasicBlock(deform_fn, blockname);
+ pfree(blockname);
+ blockname = psprintf("block.attr.%d.start", attnum);
+ attstartblocks[attnum] = LLVMAppendBasicBlock(deform_fn, blockname);
+ pfree(blockname);
+ blockname = psprintf("block.attr.%d.attcheckalign", attnum);
+ attcheckalignblocks[attnum] = LLVMAppendBasicBlock(deform_fn, blockname);
+ pfree(blockname);
+ blockname = psprintf("block.attr.%d.align", attnum);
+ attalignblocks[attnum] = LLVMAppendBasicBlock(deform_fn, blockname);
+ pfree(blockname);
+ blockname = psprintf("block.attr.%d.store", attnum);
+ attstoreblocks[attnum] = LLVMAppendBasicBlock(deform_fn, blockname);
+ pfree(blockname);
+ blockname = psprintf("block.attr.%d.out", attnum);
+ attoutblocks[attnum] = LLVMAppendBasicBlock(deform_fn, blockname);
+ pfree(blockname);
+ }
+
+ v_nvalid = LLVMBuildLoad(builder, v_nvalidp, "");
+
+ /* build switch to go from nvalid to the right startblock */
+ if (true)
+ {
+ LLVMValueRef v_switch = LLVMBuildSwitch(builder, v_nvalid,
+ deadblock, desc->natts);
+ for (attnum = 0; attnum < desc->natts; attnum++)
+ {
+ LLVMValueRef v_attno = LLVMConstInt(LLVMInt32Type(), attnum, false);
+ LLVMAddCase(v_switch, v_attno, attstartblocks[attnum]);
+ }
+
+ }
+ else
+ {
+ /* jump from entry block to first block */
+ LLVMBuildBr(builder, attstartblocks[0]);
+ }
+
+ LLVMPositionBuilderAtEnd(builder, deadblock);
+ LLVMBuildUnreachable(builder);
+
+ for (attnum = 0; attnum < desc->natts; attnum++)
+ {
+ Form_pg_attribute att = desc->attrs[attnum];
+ LLVMValueRef incby;
+ int alignto;
+ LLVMValueRef l_attno = LLVMConstInt(LLVMInt32Type(), attnum, false);
+ LLVMValueRef v_attdatap;
+ LLVMValueRef v_resultp;
+ LLVMValueRef v_islast;
+
+ /* build block checking whether we did all the necessary attributes */
+ LLVMPositionBuilderAtEnd(builder, attcheckattnoblocks[attnum]);
+
+ /*
+ * Build phi node, unless first block. This can be reached from:
+ * - store block of last attribute
+ * - start block of last attribute if null
+ */
+ if (lastcouldbenull)
+ {
+ LLVMValueRef incoming_values[] =
+ {v_off, v_off_inc};
+ LLVMBasicBlockRef incoming_blocks[] =
+ {attstartblocks[attnum - 1], attstoreblocks[attnum - 1]};
+ v_off = LLVMBuildPhi(builder, LLVMInt32Type(), "off");
+ LLVMAddIncoming(v_off,
+ incoming_values, incoming_blocks,
+ lengthof(incoming_blocks));
+ }
+ else
+ {
+ v_off = v_off_inc;
+ }
+
+ /* check if done */
+ v_islast = LLVMBuildICmp(builder, LLVMIntEQ,
+ LLVMConstInt(LLVMInt16Type(), attnum, false),
+ v_maxatt, "");
+ LLVMBuildCondBr(
+ builder,
+ v_islast,
+ attoutblocks[attnum], attstartblocks[attnum]);
+
+ /* bould block to jump out */
+ LLVMPositionBuilderAtEnd(builder, attoutblocks[attnum]);
+ LLVMBuildStore(builder, LLVMConstInt(LLVMInt32Type(), attnum, false), v_nvalidp);
+ LLVMBuildStore(builder, v_off, v_slotoffp);
+ LLVMBuildRetVoid(builder);
+
+ LLVMPositionBuilderAtEnd(builder, attstartblocks[attnum]);
+
+ /*
+ * This block can be reached because
+ * - we've been directly jumped through to continue deforming
+ * - this attribute's checkattno block
+ * Build the appropriate phi node.
+ */
+ {
+ LLVMValueRef incoming_values[] =
+ {v_off_start, v_off};
+ LLVMBasicBlockRef incoming_blocks[] =
+ {entry, attcheckattnoblocks[attnum]};
+
+ v_off = LLVMBuildPhi(builder, LLVMInt32Type(), "off");
+ LLVMAddIncoming(v_off,
+ incoming_values, incoming_blocks,
+ lengthof(incoming_blocks));
+ }
+
+ /* check for nulls if necessary */
+ if (!att->attnotnull)
+ {
+ LLVMBasicBlockRef blockifnotnull;
+ LLVMBasicBlockRef blockifnull;
+ LLVMValueRef attisnull;
+
+ blockifnotnull = attcheckalignblocks[attnum];
+ if (attnum + 1 == desc->natts)
+ blockifnull = outblock;
+ else
+ blockifnull = attcheckattnoblocks[attnum + 1];
+
+ /* zero Datum, FIXME: this should better only be done in the NULL case */
+ v_resultp = LLVMBuildGEP(builder, resval, &l_attno, 1, "");
+ LLVMBuildStore(builder, LLVMConstInt(TypeSizeT, 0, false), v_resultp);
+
+ attisnull = LLVMBuildLoad(builder,
+ LLVMBuildGEP(builder, resnull, &l_attno, 1, ""),
+ "");
+ attisnull = LLVMBuildICmp(builder, LLVMIntEQ, attisnull,
+ LLVMConstInt(LLVMInt8Type(), 1, false), "attisnull");
+ LLVMBuildCondBr(builder, attisnull, blockifnull, blockifnotnull);
+ lastcouldbenull = true;
+ }
+ else
+ {
+ LLVMBuildBr(builder, attcheckalignblocks[attnum]);
+ lastcouldbenull = false;
+ }
+ LLVMPositionBuilderAtEnd(builder, attcheckalignblocks[attnum]);
+
+ /* perform alignment */
+ if (att->attalign == 'i')
+ {
+ alignto = ALIGNOF_INT;
+ }
+ else if (att->attalign == 'c')
+ {
+ alignto = 1;
+ }
+ else if (att->attalign == 'd')
+ {
+ alignto = ALIGNOF_DOUBLE;
+ }
+ else if (att->attalign == 's')
+ {
+ alignto = ALIGNOF_SHORT;
+ }
+ else
+ {
+ elog(ERROR, "unknown alignment");
+ alignto = 0;
+ }
+
+ if ((alignto > 1 &&
+ (attcuralign < 0 || attcuralign != TYPEALIGN(alignto, attcuralign))))
+ {
+ LLVMValueRef v_off_aligned;
+ bool conditional_alignment;
+
+ /*
+ * If varlena, do only alignment if not short varlena. Check if
+ * the byte is padding for that.
+ */
+ if (att->attlen == -1)
+ {
+ LLVMValueRef possible_padbyte;
+ LLVMValueRef ispad;
+ possible_padbyte =
+ LLVMBuildLoad(builder,
+ LLVMBuildGEP(builder, tupdata_base, &v_off, 1, ""),
+ "padbyte");
+ ispad =
+ LLVMBuildICmp(builder, LLVMIntEQ, possible_padbyte,
+ LLVMConstInt(LLVMInt8Type(), 0, false),
+ "ispadbyte");
+ LLVMBuildCondBr(builder, ispad,
+ attalignblocks[attnum],
+ attstoreblocks[attnum]);
+ conditional_alignment = true;
+ }
+ else
+ {
+ LLVMBuildBr(builder, attalignblocks[attnum]);
+ conditional_alignment = false;
+ }
+
+ LLVMPositionBuilderAtEnd(builder, attalignblocks[attnum]);
+
+ {
+ /* translation of alignment code */
+#if 0
+#define TYPEALIGN(ALIGNVAL,LEN) \
+ (((uintptr_t) (LEN) + ((ALIGNVAL) - 1)) & ~((uintptr_t) ((ALIGNVAL) - 1)))
+/* used like */
+ ptr = (char *) TYPEALIGN(ALIGNOF_DOUBLE, ptr);
+#endif
+ /* ((ALIGNVAL) - 1) */
+ LLVMValueRef alignval = LLVMConstInt(LLVMInt32Type(), alignto - 1, false);
+ /* ((uintptr_t) (LEN) + ((ALIGNVAL) - 1)) */
+ LLVMValueRef lh = LLVMBuildAdd(builder, v_off, alignval, "");
+ /* ~((uintptr_t) ((ALIGNVAL) - 1))*/
+ LLVMValueRef rh = LLVMConstInt(LLVMInt32Type(), ~(alignto - 1), false);
+
+ v_off_aligned = LLVMBuildAnd(builder, lh, rh, "aligned_offset");
+ }
+
+ LLVMBuildBr(builder, attstoreblocks[attnum]);
+ LLVMPositionBuilderAtEnd(builder, attstoreblocks[attnum]);
+
+ if (conditional_alignment)
+ {
+ LLVMValueRef incoming_values[] =
+ {v_off, v_off_aligned};
+ LLVMBasicBlockRef incoming_blocks[] =
+ {attcheckalignblocks[attnum], attalignblocks[attnum]};
+ v_off_inc = LLVMBuildPhi(builder, LLVMInt32Type(), "");
+ LLVMAddIncoming(v_off_inc,
+ incoming_values, incoming_blocks,
+ lengthof(incoming_values));
+ }
+ else
+ {
+ v_off_inc = v_off_aligned;
+ }
+ }
+ else
+ {
+ LLVMPositionBuilderAtEnd(builder, attcheckalignblocks[attnum]);
+ LLVMBuildBr(builder, attalignblocks[attnum]);
+ LLVMPositionBuilderAtEnd(builder, attalignblocks[attnum]);
+ LLVMBuildBr(builder, attstoreblocks[attnum]);
+ v_off_inc = v_off;
+ }
+ LLVMPositionBuilderAtEnd(builder, attstoreblocks[attnum]);
+
+
+ /* compute what following columns are aligned to */
+ if (att->attlen < 0)
+ {
+ /* can't guarantee any alignment after varlen field */
+ attcuralign = -1;
+ }
+ else if (att->attnotnull && attcuralign >= 0)
+ {
+ Assert(att->attlen > 0);
+ attcuralign += att->attlen;
+ }
+ else if (att->attnotnull)
+ {
+ /*
+ * After a NOT NULL fixed-width column, alignment is
+ * guaranteed to be the minimum of the forced alignment and
+ * length. XXX
+ */
+ attcuralign = alignto + att->attlen;
+ Assert(attcuralign > 0);
+ }
+ else
+ {
+ //elog(LOG, "attnotnullreset: %d", attnum);
+ attcuralign = -1;
+ }
+
+ /* compute address to load data from */
+ v_attdatap =
+ LLVMBuildGEP(builder, tupdata_base, &v_off_inc, 1, "");
+ /* compute address to store value at */
+ v_resultp = LLVMBuildGEP(builder, resval, &l_attno, 1, "");
+
+ if (att->attbyval)
+ {
+ LLVMValueRef tmp_loaddata;
+ LLVMTypeRef vartypep =
+ LLVMPointerType(LLVMIntType(att->attlen*8), 0);
+ tmp_loaddata =
+ LLVMBuildPointerCast(builder, v_attdatap, vartypep, "");
+ tmp_loaddata = LLVMBuildLoad(builder, tmp_loaddata, "attr_byval");
+ tmp_loaddata = LLVMBuildZExt(builder, tmp_loaddata, TypeSizeT, "");
+
+ LLVMBuildStore(builder, tmp_loaddata, v_resultp);
+ }
+ else
+ {
+ LLVMValueRef tmp_loaddata;
+
+ /* store pointer */
+ tmp_loaddata =
+ LLVMBuildPtrToInt(builder,
+ v_attdatap,
+ TypeSizeT,
+ "attr_ptr");
+ LLVMBuildStore(builder, tmp_loaddata, v_resultp);
+ }
+
+ /* increment data pointer */
+ if (att->attlen > 0)
+ {
+ incby = LLVMConstInt(LLVMInt32Type(), att->attlen, false);
+ }
+ else if (att->attlen == -1)
+ {
+ incby =
+ LLVMBuildCall(builder, l_varsize_any,
+ &v_attdatap, 1,
+ "varsize_any");
+ {
+ char argname[] = "readonly";
+ LLVMAttributeRef ref =
+ LLVMCreateStringAttribute(LLVMGetGlobalContext(), argname, strlen(argname), NULL, 0);
+ LLVMAddCallSiteAttribute(incby, LLVMAttributeFunctionIndex, ref);
+ }
+ incby = LLVMBuildTrunc(builder, incby,
+ LLVMInt32Type(), "");
+ }
+ else if (att->attlen == -2)
+ {
+ incby = LLVMBuildCall(builder, l_strlen, &v_attdatap, 1, "strlen");
+ incby = LLVMBuildTrunc(builder, incby,
+ LLVMInt32Type(), "");
+ /* add 1 for NULL byte */
+ incby =
+ LLVMBuildAdd(builder, incby,
+ LLVMConstInt(LLVMInt32Type(), 1, false), "");
+ }
+ else
+ {
+ Assert(false);
+ incby = NULL; /* silence compiler */
+ }
+
+ v_off_inc = LLVMBuildAdd(builder, v_off_inc, incby, "increment_offset");
+
+ /*
+ * jump to next block, unless last possible column, or all desired
+ * (available) attributes have been fetched.
+ */
+ if (attnum + 1 == desc->natts)
+ {
+ LLVMBuildBr(builder, outblock);
+ }
+ else
+ {
+ LLVMBuildBr(builder, attcheckattnoblocks[attnum + 1]);
+ }
+ }
+
+ /* jump out */
+ LLVMPositionBuilderAtEnd(builder, outblock);
+ LLVMBuildStore(builder, LLVMBuildZExt(builder, v_maxatt, LLVMInt32Type(), ""), v_nvalidp);
+ LLVMBuildStore(builder, LLVMConstInt(LLVMInt32Type(), -1, false), v_slotoffp);
+ LLVMBuildRetVoid(builder);
+
+ llvm_add_module(mod, funcname);
+
+ funcptr = llvm_get_function(funcname);
+
+ LLVMDisposeBuilder(builder);
+ llvm_dispose_module(mod, funcname);
+
+ return funcptr;
+}
+#endif