diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index f6d2a50c93..cf0e8b1081 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -68,14 +68,10 @@ SQLITE_OPT = \ -DSQLITE_TEMP_STORE=3 \ -DSQLITE_OS_KV_OPTIONAL=1 \ '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \ - -DSQLITE_USE_URI=1 -#SQLITE_OPT += -DSQLITE_ENABLE_MEMSYS5 -# ^^^ MEMSYS5 is hypothetically useful for non-Emscripten builds but -# requires adding more infrastructure and fixing one spot in the -# sqlite3 internals which calls malloc() early on. - -# SQLITE_OMIT_LOAD_EXTENSION: if this is true, sqlite3_vfs::xDlOpen -# and friends may be NULL. + -DSQLITE_USE_URI=1 \ + -DSQLITE_WASM_ENABLE_C_TESTS +# ^^^ most flags are set in sqlite3-wasm.c but we need them +# made explicit here for building speedtest1.c. ifneq (,$(filter release,$(MAKECMDGOALS))) emcc_opt ?= -Oz -flto @@ -128,8 +124,7 @@ else $(info Development build. Use '$(MAKE) release' for a smaller release build.) endif -EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api \ - $(dir.jacc)/jaccwabyt_test.exports +EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api EXPORTED_FUNCTIONS.api: $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE) cat $(EXPORTED_FUNCTIONS.api.in) > $@ @@ -294,12 +289,6 @@ sqlite3-wasm.o := $(dir.api)/sqlite3-wasm.o $(sqlite3-wasm.o): emcc.cflags += $(SQLITE_OPT) $(sqlite3-wasm.o): $(dir.top)/sqlite3.c sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c -jaccwabyt_test.c := $(dir.jacc)/jaccwabyt_test.c -# ^^^ FIXME (how?): jaccwabyt_test.c is only needed for the test apps, -# so we don't really want to include it in release builds. However, we -# want to test the release builds with those apps, so we cannot simply -# elide that file in release builds. That component is critical to the -# VFS bindings so needs to be tested along with the core APIs. ######################################################################## # call-wasm-c-compile sets up build rules # for $1.o. $1 must be the name of a C file (with extension). @@ -310,7 +299,7 @@ $$($(1).o): $$(MAKEFILE) $(1) $$(emcc.bin) $$(emcc_opt_full) $$(emcc.flags) $$(emcc.cflags) -c $(1) -o $$@ CLEAN_FILES += $$($(1).o) endef -$(foreach c,$(sqlite3-wasm.c) $(jaccwabyt_test.c),$(eval $(call call-wasm-c-compile,$(c)))) +$(foreach c,$(sqlite3-wasm.c),$(eval $(call call-wasm-c-compile,$(c)))) $(eval $(call call-make-pre-js,sqlite3)) $(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \ EXPORTED_FUNCTIONS.api \ @@ -401,7 +390,7 @@ speedtest1.wasm := $(subst .js,.wasm,$(speedtest1.js)) speedtest1.cflags := \ -I. -I.. -I$(dir.top) \ -DSQLITE_SPEEDTEST1_WASM -speedtest1.cs := $(speedtest1.c) $(sqlite3-wasm.c) $(jaccwabyt_test.c) +speedtest1.cs := $(speedtest1.c) $(sqlite3-wasm.c) $(speedtest1.js): emcc.cflags+= # speedtest1 notes re. sqlite3-wasm.o vs sqlite3-wasm.c: building against # the latter (predictably) results in a slightly faster binary, but we're diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 6f7301ece6..fd1f0c3e02 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -592,8 +592,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc]; /* Bind all registered C-side structs... */ + const notThese = Object.assign(Object.create(null),{ + // Structs NOT to register + WasmTestStruct: true + }); for(const s of wasm.ctype.structs){ - capi[s.name] = sqlite3.StructBinder(s); + if(!notThese[s.name]){ + capi[s.name] = sqlite3.StructBinder(s); + } } }/*end C constant imports*/ diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index b78c9b68ef..fa6c6d8e5f 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -806,12 +806,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - (optionsObject) In the final two cases, the function must be defined as the - 'callback' property of the options object. In the final + `callback` property of the options object (optionally called + `xFunc` to align with the C API documentation). In the final case, the function's name must be the 'name' property. - This can only be used to create scalar functions, not - aggregate or window functions. UDFs cannot be removed from - a DB handle after they're added. + This can currently only be used to create scalar functions, not + aggregate or window functions (requires only a bit of + refactoring to support aggregates and window functions). + + UDFs cannot currently be removed from a DB handle after they're + added. On success, returns this object. Throws on error. @@ -848,18 +852,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - .deterministic = SQLITE_DETERMINISTIC - .directOnly = SQLITE_DIRECTONLY - .innocuous = SQLITE_INNOCUOUS + + TODO: for the (optionsObject) form, accept callbacks for + aggregate and window functions. + */ - createFunction: function f(name, callback,opt){ + createFunction: function f(name, callback, opt){ switch(arguments.length){ case 1: /* (optionsObject) */ opt = name; name = opt.name; - callback = opt.callback; + callback = opt.xFunc || opt.callback; break; case 2: /* (name, callback|optionsObject) */ if(!(callback instanceof Function)){ opt = callback; - callback = opt.callback; + callback = opt.xFunc || opt.callback; } break; default: break; diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 10d09d7404..a267e93454 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -949,7 +949,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return rc; }, /** - A convenience wrapper for allocChunks() which sizes each chunks + A convenience wrapper for allocChunks() which sizes each chunk as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if safePtrSize is falsy). diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index d57dc0562d..26771d5c9a 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -14,6 +14,16 @@ */ #define SQLITE_WASM +#ifdef SQLITE_WASM_ENABLE_C_TESTS +/* +** Functions blocked off by SQLITE_WASM_TESTS are intended solely for +** use in unit/regression testing. They may be safely omitted from +** client-side builds. +*/ +# define SQLITE_WASM_TESTS 1 +#else +# define SQLITE_WASM_TESTS 0 +#endif /* ** Threading and file locking: JS is single-threaded. Each Worker @@ -32,34 +42,20 @@ ** locking, and any similar future filesystems, threading and file ** locking support are unnecessary in the wasm build. */ + +/* +** Undefine any SQLITE_... config flags which we specifically do not +** want undefined. Please keep these alphabetized. +*/ #undef SQLITE_OMIT_DESERIALIZE -#ifndef SQLITE_DEFAULT_UNIX_VFS -# define SQLITE_DEFAULT_UNIX_VFS "unix-none" -#endif -#ifndef SQLITE_OMIT_DEPRECATED -# define SQLITE_OMIT_DEPRECATED -#endif -#ifndef SQLITE_OMIT_LOAD_EXTENSION -# define SQLITE_OMIT_LOAD_EXTENSION -#endif -#ifndef SQLITE_OMIT_SHARED_CACHE -# define SQLITE_OMIT_SHARED_CACHE -#endif -#ifndef SQLITE_OMIT_UTF16 -# define SQLITE_OMIT_UTF16 -#endif -#ifndef SQLITE_OS_KV_OPTIONAL -# define SQLITE_OS_KV_OPTIONAL 1 -#endif -#ifndef SQLITE_TEMP_STORE -# define SQLITE_TEMP_STORE 3 -#endif -#ifndef SQLITE_THREADSAFE -# define SQLITE_THREADSAFE 0 -#endif -#ifndef SQLITE_OMIT_WAL -# define SQLITE_OMIT_WAL -#endif + +/* +** Define any SQLITE_... config defaults we want if they aren't +** overridden by the builder. Please keep these alphabetized. +*/ + +/**********************************************************************/ +/* SQLITE_DEFAULT_... */ #ifndef SQLITE_DEFAULT_CACHE_SIZE /* ** The OPFS impls benefit tremendously from an increased cache size @@ -71,14 +67,78 @@ */ # define SQLITE_DEFAULT_CACHE_SIZE -16777216 #endif - -#if 0 -/* -** TODO: experiment with this when back on the opfs-capable machine. -*/ -#ifndef SQLITE_DEFAULT_PAGE_SIZE +#if 0 && !defined(SQLITE_DEFAULT_PAGE_SIZE) +/* TODO: experiment with this. */ # define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/ #endif +#ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix-none" +#endif + +/**********************************************************************/ +/* SQLITE_ENABLE_... */ +#ifndef SQLITE_ENABLE_BYTECODE_VTAB +# define SQLITE_ENABLE_BYTECODE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBPAGE_VTAB +# define SQLITE_ENABLE_DBPAGE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBSTAT_VTAB +# define SQLITE_ENABLE_DBSTAT_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif +#ifndef SQLITE_ENABLE_FTS4 +# define SQLITE_ENABLE_FTS4 1 +#endif +#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC +# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 +#endif +#ifndef SQLITE_ENABLE_RTREE +# define SQLITE_ENABLE_RTREE 1 +#endif +#ifndef SQLITE_ENABLE_STMTVTAB +# define SQLITE_ENABLE_STMTVTAB 1 +#endif +#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +#endif + +/**********************************************************************/ +/* SQLITE_O... */ +#ifndef SQLITE_OMIT_DEPRECATED +# define SQLITE_OMIT_DEPRECATED 1 +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE +# define SQLITE_OMIT_SHARED_CACHE 1 +#endif +#ifndef SQLITE_OMIT_UTF16 +# define SQLITE_OMIT_UTF16 1 +#endif +#ifndef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL 1 +#endif +#ifndef SQLITE_OS_KV_OPTIONAL +# define SQLITE_OS_KV_OPTIONAL 1 +#endif + +/**********************************************************************/ +/* SQLITE_T... */ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 3 +#endif +#ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +#endif + +/**********************************************************************/ +/* SQLITE_USE_... */ +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 1 #endif #include @@ -89,11 +149,11 @@ #endif /* -** SQLITE_WASM_KEEP is identical to EMSCRIPTEN_KEEPALIVE but is not -** Emscripten-specific. It explicitly marks functions for export into -** the target wasm file without requiring explicit listing of those -** functions in Emscripten's -sEXPORTED_FUNCTIONS=... list (or -** equivalent in other build platforms). Any function with neither +** SQLITE_WASM_KEEP is functionally identical to EMSCRIPTEN_KEEPALIVE +** but is not Emscripten-specific. It explicitly marks functions for +** export into the target wasm file without requiring explicit listing +** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list +** (or equivalent in other build platforms). Any function with neither ** this attribute nor which is listed as an explicit export will not ** be exported from the wasm file (but may still be used internally ** within the wasm file). @@ -256,6 +316,28 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ return err_code; } +#if SQLITE_WASM_TESTS +struct WasmTestStruct { + int v4; + void * ppV; + const char * cstr; + int64_t v8; + void (*xFunc)(void*); +}; +typedef struct WasmTestStruct WasmTestStruct; +SQLITE_WASM_KEEP +void sqlite3_wasm_test_struct(WasmTestStruct * s){ + if(s){ + s->v4 *= 2; + s->v8 = s->v4 * 2; + s->ppV = s; + s->cstr = __FILE__; + if(s->xFunc) s->xFunc(s); + } + return; +} +#endif /* SQLITE_WASM_TESTS */ + /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. Unlike the @@ -692,6 +774,19 @@ const char * sqlite3_wasm_enum_json(void){ M(xDelete,"i(ss)"); M(nKeySize,"i"); } _StructBinder; +#undef CurrentStruct + +#if SQLITE_WASM_TESTS +#define CurrentStruct WasmTestStruct + StructBinder { + M(v4,"i"); + M(cstr,"s"); + M(ppV,"p"); + M(v8,"j"); + M(xFunc,"v(p)"); + } _StructBinder; +#undef CurrentStruct +#endif } out( "]"/*structs*/); @@ -926,4 +1021,56 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){ } #endif /* __EMSCRIPTEN__ && SQLITE_WASM_WASMFS */ +#if SQLITE_WASM_TESTS + +SQLITE_WASM_KEEP +int sqlite3_wasm_test_intptr(int * p){ + return *p = *p * 2; +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64_max(void){ + return (int64_t)0x7fffffffffffffff; +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64_min(void){ + return ~sqlite3_wasm_test_int64_max(); +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64_times2(int64_t x){ + return x * 2; +} + +SQLITE_WASM_KEEP +void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){ + *max = sqlite3_wasm_test_int64_max(); + *min = sqlite3_wasm_test_int64_min(); + /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/ +} + +SQLITE_WASM_KEEP +int64_t sqlite3_wasm_test_int64ptr(int64_t * p){ + /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/ + return *p = *p * 2; +} + +SQLITE_WASM_KEEP +void sqlite3_wasm_test_stack_overflow(int recurse){ + if(recurse) sqlite3_wasm_test_stack_overflow(recurse); +} + +/* For testing the 'string-free' whwasmutil.xWrap() conversion. */ +SQLITE_WASM_KEEP +char * sqlite3_wasm_test_str_hello(int fail){ + char * s = fail ? 0 : (char *)malloc(6); + if(s){ + memcpy(s, "hello", 5); + s[5] = 0; + } + return s; +} +#endif /* SQLITE_WASM_TESTS */ + #undef SQLITE_WASM_KEEP diff --git a/ext/wasm/common/testing.css b/ext/wasm/common/testing.css index c8629a2c62..27f77e45c7 100644 --- a/ext/wasm/common/testing.css +++ b/ext/wasm/common/testing.css @@ -2,7 +2,6 @@ body { display: flex; flex-direction: column; flex-wrap: wrap; - white-space: break-spaces; } textarea { font-family: monospace; diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index c98b7cea4d..e04886de8a 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -1255,12 +1255,13 @@ self.WhWasmUtilInstaller = function(target){ Would that be too much magic concentrated in one place, ready to backfire? */ - xcv.arg.string = xcv.arg['pointer'] = xcv.arg['*'] = function(v){ - if('string'===typeof v) return target.scopedAllocCString(v); - return v ? xcv.arg[ptrIR](v) : null; - }; - xcv.result.string = (i)=>target.cstringToJs(i); - xcv.result['string:free'] = (i)=>{ + xcv.arg.string = xcv.arg.utf8 = xcv.arg['pointer'] = xcv.arg['*'] + = function(v){ + if('string'===typeof v) return target.scopedAllocCString(v); + return v ? xcv.arg[ptrIR](v) : null; + }; + xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i); + xcv.result['string:free'] = xcv.result['utf8:free'] = (i)=>{ try { return i ? target.cstringToJs(i) : null } finally{ target.dealloc(i) } }; @@ -1374,31 +1375,33 @@ self.WhWasmUtilInstaller = function(target){ Non-numeric conversions include: - - `string` (args): has two different semantics in order to - accommodate various uses of certain C APIs (e.g. output-style - strings)... + - `string` or `utf8` (args): has two different semantics in order + to accommodate various uses of certain C APIs + (e.g. output-style strings)... - - If the arg is a string, it creates a _temporary_ C-string to - pass to the exported function, cleaning it up before the - wrapper returns. If a long-lived C-string pointer is - required, that requires client-side code to create the - string, then pass its pointer to the function. + - If the arg is a string, it creates a _temporary_ + UTF-8-encoded C-string to pass to the exported function, + cleaning it up before the wrapper returns. If a long-lived + C-string pointer is required, that requires client-side code + to create the string, then pass its pointer to the function. - Else the arg is assumed to be a pointer to a string the client has already allocated and it's passed on as a WASM pointer. - - `string` (results): treats the result value as a const C-string, - copies it to a JS string, and returns that JS string. + - `string` or `utf8` (results): treats the result value as a + const C-string, encoded as UTF-8, copies it to a JS string, + and returns that JS string. - - `string:free` (results): treats the result value as a non-const - C-string, ownership of which has just been transfered to the - caller. It copies the C-string to a JS string, frees the - C-string, and returns the JS string. If such a result value is - NULL, the JS result is `null`. Achtung: when using an API which - returns results from a specific allocator, e.g. `my_malloc()`, - this conversion _is not legal_. Instead, an equivalent conversion - which uses the appropriate deallocator is required. For example: + - `string:free` or `utf8:free) (results): treats the result value + as a non-const UTF-8 C-string, ownership of which has just been + transfered to the caller. It copies the C-string to a JS + string, frees the C-string, and returns the JS string. If such + a result value is NULL, the JS result is `null`. Achtung: when + using an API which returns results from a specific allocator, + e.g. `my_malloc()`, this conversion _is not legal_. Instead, an + equivalent conversion which uses the appropriate deallocator is + required. For example: ```js target.xWrap.resultAdaptor('string:my_free',(i)=>{ diff --git a/ext/wasm/index.html b/ext/wasm/index.html index 20668db20e..01d178bb7a 100644 --- a/ext/wasm/index.html +++ b/ext/wasm/index.html @@ -41,6 +41,15 @@
The tests and demos...