From 6b0c2c05544049ee6e5eb0c094d73f875d14491d Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Thu, 11 Jan 2024 02:18:12 +0000 Subject: [PATCH] Optimize. (#51) --- embed/exports.txt | 1 + embed/sqlite3.wasm | Bin 1473918 -> 1474310 bytes error.go | 6 ++-- ext/stats/stats.go | 8 +++--- func.go | 63 ++++++++++++++---------------------------- internal/util/func.go | 39 ++++++++++++++++++++++++++ sqlite.go | 10 +++---- sqlite3/func.c | 61 ++++++++++++++++++++++++++++++---------- sqlite3/log.c | 4 +-- sqlite3/vtab.c | 2 +- value.go | 10 ++++++- vtab.go | 28 ++++++++++--------- 12 files changed, 147 insertions(+), 85 deletions(-) diff --git a/embed/exports.txt b/embed/exports.txt index 2d84667..67c0306 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -89,6 +89,7 @@ sqlite3_value_dup sqlite3_value_free sqlite3_value_int64 sqlite3_value_nochange +sqlite3_value_numeric_type sqlite3_value_pointer_go sqlite3_value_text sqlite3_value_type diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index c5a61d98f1b05e6defeb28771efcd4175972fd2b..2e67f810ed865682168efab8de408049e8178a1d 100755 GIT binary patch delta 1260 zcma)*U1$_n7>3U|Gdo!`Cpy_ZGwx4*Rs#}LVq0l@S%Zl6q6K?V8U=^R6{%5`jJ%R#m60UMx>c@mq(o&m zxFx5~J)fiQ#xU8`JVw5eq+jS<^EKsFB_T-|OAVX8E?r2Mn#bvviGiFySuO>9yBUu$ zomz!^CEE<9svMo`Y7VEqC-_$Qon`b9v=Cmjj7=nmDz6x($U zmWpQCEt*9a-3|AS8(D(xHBRV#_>aa}J&*rvT+#>d{l=2M1wUwPFtV9d+FP;8W!E+* zxF2{mJ8(S-ulc(9o-u<*63Xv%p5CBsdXsh~4V|S^OY|XqM0+38Cv=&v&pf3ov@f5_ z4i5A`Ym&@$B47D=@7&RLZl;~XAOey=GAIF3KwY3DXbq?vq=G1@2ecNn4y1v4K`BtW z)y^^heLIB;@C*}qm?R6hKs2Q8!FalW2nc!`r4g)+qIcyT1vDya%L>J6K>|bFC_zM$ zcZ{N_Mm;#Eia}~T39Gx}N#Rc;Jwrnf#&H<`JFF3P>x&@UDK>#zQiZH0sjvLM6G1rM zSPu71vh7%DeKpBWVXTB#53z0GM?TBLdc&CZb~spPnby3|PIlZk!}B%9I&KTxA3DZ6 z|9Inzk6Al1{7=|tic-MpeiWYB+>>r;pR!W~HavHjU4_8R>@;i5PV3j~4;Gq4cB~4= zXO58P6bTftpn$vpK#ErJrIla0_K9e1yOpZtaV1*ESIV7g{ra0eclf|M=tO9lhdirV z<=#AvIuTx?bAxJ8CPGZK+Hvb|K05E+q$x;sP!qXZ;jCr!d8(E?Z&SBR)(=NL{ z7bkg0)19_k<&Nk+3X`_cmw!yxgIN!(arjVo`i4NMy4N_5`oUw6M?6+zwtbw mzai=snia{C6Y!!}3Z{hjjIIT*;!cQPunMqeT9<9(+w5Pjahj0; delta 886 zcmZ{iOK1~87{_;Jlk_#VX)>9n*+g3v!77Lq@#?LJ^x#G8LDzM6+6L0yxR1o*gM!6g zDhP`%hzCKhg0Rp%cu=WD^x(mpASj}Jzu)blI-6u$J$U%ReBbx~hTZx9@9lK$YIUU__aS`D z^Y77^+a5U?v3t>Jek9)OK1KJ#?)lg&2!1(dWs-*6xN})1M<6$0cP0)ClZY7Cb6Jy~ z5)EpKl66CoOiAp~xGCqYOqw#>G-?VI*RrZ2=C!P9Qr(x#*l!YPFzYlZBn7!Sr%fRl z$j#eHa!gp*ySQLV*F;%UyR$w1)|P#R5HRdoWC$GP0OUjANL4jS;CO5iEum%9xPn&E z8fr?eqYac6Qq2eUXExooKkX8-^v+>cKs`zqXXC*9Y}nWFAcje zq)L4hd>(+?i1RI#wZ6QhQ{bN!YM8p#4}*~KK|Vse(FpIRBJx)tyzQTfkGJh9oCf11 zQX*W^(7Rqw6#1{gYL5PKp|*hbOl`J#> diff --git a/error.go b/error.go index 838f1aa..71238ef 100644 --- a/error.go +++ b/error.go @@ -138,14 +138,14 @@ func (e ExtendedErrorCode) Timeout() bool { func errorCode(err error, def ErrorCode) (msg string, code uint32) { switch code := err.(type) { + case nil: + return "", _OK case ErrorCode: return "", uint32(code) - case ExtendedErrorCode: + case xErrorCode: return "", uint32(code) case *Error: return code.msg, uint32(code.code) - case nil: - return "", _OK } var ecode ErrorCode diff --git a/ext/stats/stats.go b/ext/stats/stats.go index eb29a78..6a566bb 100644 --- a/ext/stats/stats.go +++ b/ext/stats/stats.go @@ -96,13 +96,13 @@ func (fn *variance) Value(ctx sqlite3.Context) { } func (fn *variance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) { - if a := arg[0]; a.Type() != sqlite3.NULL { + if a := arg[0]; a.NumericType() != sqlite3.NULL { fn.enqueue(a.Float()) } } func (fn *variance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) { - if a := arg[0]; a.Type() != sqlite3.NULL { + if a := arg[0]; a.NumericType() != sqlite3.NULL { fn.dequeue(a.Float()) } } @@ -150,14 +150,14 @@ func (fn *covariance) Value(ctx sqlite3.Context) { func (fn *covariance) Step(ctx sqlite3.Context, arg ...sqlite3.Value) { a, b := arg[0], arg[1] - if a.Type() != sqlite3.NULL && b.Type() != sqlite3.NULL { + if a.NumericType() != sqlite3.NULL && b.NumericType() != sqlite3.NULL { fn.enqueue(a.Float(), b.Float()) } } func (fn *covariance) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) { a, b := arg[0], arg[1] - if a.Type() != sqlite3.NULL && b.Type() != sqlite3.NULL { + if a.NumericType() != sqlite3.NULL && b.NumericType() != sqlite3.NULL { fn.dequeue(a.Float(), b.Float()) } } diff --git a/func.go b/func.go index 72253c4..9544ecc 100644 --- a/func.go +++ b/func.go @@ -111,80 +111,59 @@ func compareCallback(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nK return uint32(fn(util.View(mod, pKey1, uint64(nKey1)), util.View(mod, pKey2, uint64(nKey2)))) } -func funcCallback(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) { +func funcCallback(ctx context.Context, mod api.Module, pCtx, pApp, nArg, pArg uint32) { args := getFuncArgs() defer putFuncArgs(args) db := ctx.Value(connKey{}).(*Conn) - fn := userDataHandle(db, pCtx).(ScalarFunction) + fn := util.GetHandle(db.ctx, pApp).(ScalarFunction) callbackArgs(db, args[:nArg], pArg) fn(Context{db, pCtx}, args[:nArg]...) } -func stepCallback(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) { +func stepCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp, nArg, pArg uint32) { args := getFuncArgs() defer putFuncArgs(args) db := ctx.Value(connKey{}).(*Conn) - fn := aggregateCtxHandle(db, pCtx, nil) callbackArgs(db, args[:nArg], pArg) + fn, _ := callbackAggregate(db, pAgg, pApp) fn.Step(Context{db, pCtx}, args[:nArg]...) } -func finalCallback(ctx context.Context, mod api.Module, pCtx uint32) { - var handle uint32 +func finalCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp uint32) { db := ctx.Value(connKey{}).(*Conn) - fn := aggregateCtxHandle(db, pCtx, &handle) + fn, handle := callbackAggregate(db, pAgg, pApp) fn.Value(Context{db, pCtx}) - if err := util.DelHandle(ctx, handle); err != nil { - Context{db, pCtx}.ResultError(err) - } + util.DelHandle(ctx, handle) } -func valueCallback(ctx context.Context, mod api.Module, pCtx uint32) { +func valueCallback(ctx context.Context, mod api.Module, pCtx, pAgg uint32) { db := ctx.Value(connKey{}).(*Conn) - fn := aggregateCtxHandle(db, pCtx, nil) + fn := util.GetHandle(db.ctx, pAgg).(AggregateFunction) fn.Value(Context{db, pCtx}) } -func inverseCallback(ctx context.Context, mod api.Module, pCtx, nArg, pArg uint32) { +func inverseCallback(ctx context.Context, mod api.Module, pCtx, pAgg, nArg, pArg uint32) { args := getFuncArgs() defer putFuncArgs(args) db := ctx.Value(connKey{}).(*Conn) - fn := aggregateCtxHandle(db, pCtx, nil).(WindowFunction) callbackArgs(db, args[:nArg], pArg) + fn := util.GetHandle(db.ctx, pAgg).(WindowFunction) fn.Inverse(Context{db, pCtx}, args[:nArg]...) } -func userDataHandle(db *Conn, pCtx uint32) any { - pApp := uint32(db.call("sqlite3_user_data", uint64(pCtx))) - return util.GetHandle(db.ctx, pApp) -} - -func aggregateCtxHandle(db *Conn, pCtx uint32, close *uint32) AggregateFunction { - // On close, we're getting rid of the aggregate. - // Don't allocate space to store it. - var size uint64 - if close == nil { - size = ptrlen - } - ptr := uint32(db.call("sqlite3_aggregate_context", uint64(pCtx), size)) - - // If we already have an aggregate, return it. - if ptr != 0 { - if handle := util.ReadUint32(db.mod, ptr); handle != 0 { - fn := util.GetHandle(db.ctx, handle).(AggregateFunction) - if close != nil { - *close = handle - } - return fn - } +func callbackAggregate(db *Conn, pAgg, pApp uint32) (AggregateFunction, uint32) { + if pApp == 0 { + handle := util.ReadUint32(db.mod, pAgg) + return util.GetHandle(db.ctx, handle).(AggregateFunction), handle } - // Create a new aggregate, and store it if needed. - fn := userDataHandle(db, pCtx).(func() AggregateFunction)() - if ptr != 0 { - util.WriteUint32(db.mod, ptr, util.AddHandle(db.ctx, fn)) + // We need to create the aggregate. + fn := util.GetHandle(db.ctx, pApp).(func() AggregateFunction)() + handle := util.AddHandle(db.ctx, fn) + if pAgg != 0 { + util.WriteUint32(db.mod, pAgg, handle) } - return fn + return fn, handle } func callbackArgs(db *Conn, arg []Value, pArg uint32) { diff --git a/internal/util/func.go b/internal/util/func.go index 9ff7757..47dc890 100644 --- a/internal/util/func.go +++ b/internal/util/func.go @@ -23,6 +23,19 @@ func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(con Export(name) } +type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1) + +func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1])) +} + +func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVII[T0, T1](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) { @@ -36,6 +49,32 @@ func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, f Export(name) } +type funcVIIII[T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) + +func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])) +} + +func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVIIII[T0, T1, T2, T3](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + +type funcVIIIII[T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) + +func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { + fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])) +} + +func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) { + mod.NewFunctionBuilder(). + WithGoModuleFunction(funcVIIIII[T0, T1, T2, T3, T4](fn), + []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). + Export(name) +} + type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) { diff --git a/sqlite.go b/sqlite.go index 79b25c4..4ef1454 100644 --- a/sqlite.go +++ b/sqlite.go @@ -284,11 +284,11 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { util.ExportFuncII(env, "go_progress", progressCallback) util.ExportFuncVIII(env, "go_log", logCallback) util.ExportFuncVI(env, "go_destroy", destroyCallback) - util.ExportFuncVIII(env, "go_func", funcCallback) - util.ExportFuncVIII(env, "go_step", stepCallback) - util.ExportFuncVI(env, "go_final", finalCallback) - util.ExportFuncVI(env, "go_value", valueCallback) - util.ExportFuncVIII(env, "go_inverse", inverseCallback) + util.ExportFuncVIIII(env, "go_func", funcCallback) + util.ExportFuncVIIIII(env, "go_step", stepCallback) + util.ExportFuncVIII(env, "go_final", finalCallback) + util.ExportFuncVII(env, "go_value", valueCallback) + util.ExportFuncVIIII(env, "go_inverse", inverseCallback) util.ExportFuncIIIIII(env, "go_compare", compareCallback) util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(0)) util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(1)) diff --git a/sqlite3/func.c b/sqlite3/func.c index 573371f..adff527 100644 --- a/sqlite3/func.c +++ b/sqlite3/func.c @@ -3,14 +3,47 @@ #include "include.h" #include "sqlite3.h" -void go_func(sqlite3_context *, int, sqlite3_value **); -void go_step(sqlite3_context *, int, sqlite3_value **); -void go_final(sqlite3_context *); -void go_value(sqlite3_context *); -void go_inverse(sqlite3_context *, int, sqlite3_value **); - int go_compare(go_handle, int, const void *, int, const void *); +void go_func(sqlite3_context *, go_handle, int, sqlite3_value **); + +void go_step(sqlite3_context *, go_handle *, go_handle, int, sqlite3_value **); +void go_final(sqlite3_context *, go_handle, go_handle); +void go_value(sqlite3_context *, go_handle); +void go_inverse(sqlite3_context *, go_handle *, int, sqlite3_value **); + +void go_func_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) { + go_func(ctx, sqlite3_user_data(ctx), nArg, pArg); +} + +void go_step_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) { + go_handle *agg = sqlite3_aggregate_context(ctx, 4); + go_handle data = NULL; + if (agg == NULL || *agg == NULL) { + data = sqlite3_user_data(ctx); + } + go_step(ctx, agg, data, nArg, pArg); +} + +void go_final_wrapper(sqlite3_context *ctx) { + go_handle *agg = sqlite3_aggregate_context(ctx, 0); + go_handle data = NULL; + if (agg == NULL || *agg == NULL) { + data = sqlite3_user_data(ctx); + } + go_final(ctx, agg, data); +} + +void go_value_wrapper(sqlite3_context *ctx) { + go_handle *agg = sqlite3_aggregate_context(ctx, 4); + go_value(ctx, *agg); +} + +void go_inverse_wrapper(sqlite3_context *ctx, int nArg, sqlite3_value **pArg) { + go_handle *agg = sqlite3_aggregate_context(ctx, 4); + go_inverse(ctx, *agg, nArg, pArg); +} + int sqlite3_create_collation_go(sqlite3 *db, const char *name, go_handle app) { int rc = sqlite3_create_collation_v2(db, name, SQLITE_UTF8, app, go_compare, go_destroy); @@ -21,22 +54,22 @@ int sqlite3_create_collation_go(sqlite3 *db, const char *name, go_handle app) { int sqlite3_create_function_go(sqlite3 *db, const char *name, int argc, int flags, go_handle app) { return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, app, - go_func, /*step=*/NULL, /*final=*/NULL, - go_destroy); + go_func_wrapper, /*step=*/NULL, + /*final=*/NULL, go_destroy); } int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *name, int argc, int flags, go_handle app) { - return sqlite3_create_window_function(db, name, argc, SQLITE_UTF8 | flags, - app, go_step, go_final, /*value=*/NULL, - /*inverse=*/NULL, go_destroy); + return sqlite3_create_function_v2(db, name, argc, SQLITE_UTF8 | flags, app, + /*func=*/NULL, go_step_wrapper, + go_final_wrapper, go_destroy); } int sqlite3_create_window_function_go(sqlite3 *db, const char *name, int argc, int flags, go_handle app) { - return sqlite3_create_window_function(db, name, argc, SQLITE_UTF8 | flags, - app, go_step, go_final, go_value, - go_inverse, go_destroy); + return sqlite3_create_window_function( + db, name, argc, SQLITE_UTF8 | flags, app, go_step_wrapper, + go_final_wrapper, go_value_wrapper, go_inverse_wrapper, go_destroy); } void sqlite3_set_auxdata_go(sqlite3_context *ctx, int i, go_handle aux) { diff --git a/sqlite3/log.c b/sqlite3/log.c index 1f66cab..bfcb66e 100644 --- a/sqlite3/log.c +++ b/sqlite3/log.c @@ -2,8 +2,8 @@ #include "sqlite3.h" -void go_log(void*, int, const char*); +void go_log(void *, int, const char *); int sqlite3_config_log_go(bool enable) { return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL, NULL); -} +} \ No newline at end of file diff --git a/sqlite3/vtab.c b/sqlite3/vtab.c index 56e9529..073402b 100644 --- a/sqlite3/vtab.c +++ b/sqlite3/vtab.c @@ -141,7 +141,7 @@ static int go_vtab_find_function_wrapper( go_handle handle; int rc = go_vtab_find_function(pVTab, nArg, zName, &handle); if (rc) { - *pxFunc = go_func; + *pxFunc = go_func_wrapper; *ppArg = handle; } return rc; diff --git a/value.go b/value.go index fda7f4a..0ae2ed4 100644 --- a/value.go +++ b/value.go @@ -50,7 +50,7 @@ func (dup *Value) Close() error { return nil } -// Type returns the initial [Datatype] of the value. +// Type returns the initial datatype of the value. // // https://sqlite.org/c3ref/value_blob.html func (v Value) Type() Datatype { @@ -58,6 +58,14 @@ func (v Value) Type() Datatype { return Datatype(r) } +// Type returns the numeric datatype of the value. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) NumericType() Datatype { + r := v.c.call("sqlite3_value_numeric_type", v.protected()) + return Datatype(r) +} + // Bool returns the value as a bool. // SQLite does not have a separate boolean storage class. // Instead, boolean values are retrieved as integers, diff --git a/vtab.go b/vtab.go index 3d46450..b9a52fe 100644 --- a/vtab.go +++ b/vtab.go @@ -168,7 +168,7 @@ type VTabOverloader interface { } // A VTabChecker allows a virtual table to report errors -// to the PRAGMA integrity_check PRAGMA quick_check commands. +// to the PRAGMA integrity_check and PRAGMA quick_check commands. // // Integrity should return an error if it finds problems in the content of the virtual table, // but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode], @@ -283,8 +283,8 @@ type IndexConstraintUsage struct { func (idx *IndexInfo) RHSValue(column int) (Value, error) { defer idx.c.arena.mark()() valPtr := idx.c.arena.new(ptrlen) - r := idx.c.call("sqlite3_vtab_rhs_value", - uint64(idx.handle), uint64(column), uint64(valPtr)) + r := idx.c.call("sqlite3_vtab_rhs_value", uint64(idx.handle), + uint64(column), uint64(valPtr)) if err := idx.c.error(r); err != nil { return Value{}, err } @@ -298,8 +298,8 @@ func (idx *IndexInfo) RHSValue(column int) (Value, error) { // // https://sqlite.org/c3ref/vtab_collation.html func (idx *IndexInfo) Collation(column int) string { - r := idx.c.call("sqlite3_vtab_collation", - uint64(idx.handle), uint64(column)) + r := idx.c.call("sqlite3_vtab_collation", uint64(idx.handle), + uint64(column)) return util.ReadString(idx.c.mod, uint32(r), _MAX_NAME) } @@ -315,7 +315,8 @@ func (idx *IndexInfo) Distinct() int { // // https://sqlite.org/c3ref/vtab_in.html func (idx *IndexInfo) In(column, handle int) bool { - r := idx.c.call("sqlite3_vtab_in", uint64(idx.handle), uint64(column), uint64(handle)) + r := idx.c.call("sqlite3_vtab_in", uint64(idx.handle), + uint64(column), uint64(handle)) return r != 0 } @@ -483,13 +484,14 @@ func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32) func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab, nArg, zName, pxFunc uint32) uint32 { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader) - fn, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME)) - if fn != nil { - handle := util.AddHandle(ctx, fn) - util.WriteUint32(mod, pxFunc, handle) - if op == 0 { - op = 1 - } + f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME)) + if op != 0 { + var wrapper uint32 + wrapper = util.AddHandle(ctx, func(c Context, arg ...Value) { + defer util.DelHandle(ctx, wrapper) + f(c, arg...) + }) + util.WriteUint32(mod, pxFunc, wrapper) } return uint32(op) }