Refactor JS API amalgamation such that the bootstrapping/configuration is deferred until the whole amalgamation is available, to facilitate providing clients with a way to initialize the API with their own config (noting that we're still one small level of refactoring away from being able to actually do that).

FossilOrigin-Name: 9dbe9a6aecec43b51057375ef1d2d632db0d17eac8b7552c20cc91fc2f1a55d1
This commit is contained in:
stephan 2022-08-22 13:34:13 +00:00
parent 64d04a8d9f
commit e3cd67603d
9 changed files with 206 additions and 107 deletions

View File

@ -16,29 +16,45 @@
various subsystems.
*/
'use strict';
self.sqlite3.postInit.forEach(
self.importScripts/*global is a Worker*/
? function(f){
/** We try/catch/report for the sake of failures which happen in
a Worker, as those exceptions can otherwise get completely
swallowed, leading to confusing downstream errors which have
nothing to do with this failure. */
try{ f(self, self.sqlite3) }
catch(e){
console.error("Error in postInit() function:",e);
throw e;
}
}
: (f)=>f(self, self.sqlite3)
);
delete self.sqlite3.postInit;
if(self.location && +self.location.port > 1024){
console.warn("Installing sqlite3 bits as global S for dev-testing purposes.");
self.S = self.sqlite3;
}
/* Clean up temporary global-scope references to our APIs... */
self.sqlite3.config.Module.sqlite3 = self.sqlite3
/* ^^^^ Currently needed by test code and Worker API setup */;
delete self.sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
delete self.sqlite3 /* clean up our global-scope reference */;
//console.warn("Module.sqlite3 =",Module.sqlite3);
(function(){
/**
Replace sqlite3ApiBootstrap() with a variant which plugs in the
Emscripten-based config for all config options which the client
does not provide.
*/
const SAB = self.sqlite3ApiBootstrap;
self.sqlite3ApiBootstrap = function(apiConfig){
apiConfig = apiConfig||{};
const configDefaults = {
Module: Module /* ==> Emscripten-style Module object. Currently
needs to be exposed here for test code. NOT part
of the public API. */,
exports: Module['asm'],
memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */
};
const config = {};
Object.keys(configDefaults).forEach(function(k){
config[k] = Object.prototype.hasOwnProperty.call(apiConfig, k)
? apiConfig[k] : configDefaults[k];
});
return SAB(config);
};
/**
For current (2022-08-22) purposes, automatically call sqlite3ApiBootstrap().
That decision will be revisited at some point, as we really want client code
to be able to call this to configure certain parts.
*/
const sqlite3 = self.sqlite3ApiBootstrap();
if(self.location && +self.location.port > 1024){
console.warn("Installing sqlite3 bits as global S for dev-testing purposes.");
self.S = sqlite3;
}
/* Clean up temporary references to our APIs... */
delete self.sqlite3ApiBootstrap;
Module.sqlite3 = sqlite3 /* Currently needed by test code */;
delete sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
//console.warn("Module.sqlite3 =",Module.sqlite3);
})();

View File

@ -16,23 +16,9 @@
initializes the main API pieces so that the downstream components
(e.g. sqlite3-api-oo1.js) have all that they need.
*/
(function(self){
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))};
self.sqlite3 = self.sqlite3ApiBootstrap({
Module: Module /* ==> Emscripten-style Module object. Currently
needs to be exposed here for test code. NOT part
of the public API. */,
exports: Module['asm'],
memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */,
bigIntEnabled: !!self.BigInt64Array,
allocExportName: 'malloc',
deallocExportName: 'free'
});
delete self.sqlite3ApiBootstrap;
const sqlite3 = self.sqlite3;
const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util;
self.WhWasmUtilInstaller(capi.wasm);
delete self.WhWasmUtilInstaller;
@ -57,7 +43,7 @@
return oldP(v);
};
wasm.xWrap.argAdapter('.pointer', adapter);
}
} /* ".pointer" xWrap() argument adapter */
// WhWasmUtil.xWrap() bindings...
{
@ -78,7 +64,7 @@
capi[e[0]] = wasm.xWrap.apply(null, e);
}
/* For functions which cannot work properly unless
/* For C API functions which cannot work properly unless
wasm.bigIntEnabled is true, install a bogus impl which
throws if called when bigIntEnabled is false. */
const fI64Disabled = function(fname){
@ -198,5 +184,4 @@
capi[s.name] = sqlite3.StructBinder(s);
}
}
})(self);
});

View File

@ -14,10 +14,9 @@
WASM build. It requires that sqlite3-api-glue.js has already run
and it installs its deliverable as self.sqlite3.oo1.
*/
(function(self){
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const toss = (...args)=>{throw new Error(args.join(' '))};
const sqlite3 = self.sqlite3 || toss("Missing main sqlite3 object.");
const capi = sqlite3.capi, util = capi.util;
/* What follows is colloquially known as "OO API #1". It is a
binding of the sqlite3 API which is designed to be run within
@ -1547,5 +1546,6 @@
},
DB,
Stmt
}/*SQLite3 object*/;
})(self);
}/*oo1 object*/;
});

View File

@ -31,12 +31,13 @@
// FileSystemDirectoryHandle
// FileSystemFileHandle
// FileSystemFileHandle.prototype.createSyncAccessHandle
self.sqlite3.postInit.push(function(self, sqlite3){
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const warn = console.warn.bind(console),
error = console.error.bind(console);
if(!self.importScripts || !self.FileSystemFileHandle
|| !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
warn("OPFS not found or its sync API is not available in this environment.");
if(!self.importScripts || !self.FileSystemFileHandle){
//|| !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
// ^^^ sync API is not required with WASMFS/OPFS backend.
warn("OPFS is not available in this environment.");
return;
}else if(!sqlite3.capi.wasm.bigIntEnabled){
error("OPFS requires BigInt support but sqlite3.capi.wasm.bigIntEnabled is false.");

View File

@ -78,25 +78,88 @@
*/
/**
This global symbol is is only a temporary measure: the JS-side
post-processing will remove that object from the global scope when
setup is complete. We require it there temporarily in order to glue
disparate parts together during the loading of the API (which spans
several components).
sqlite3ApiBootstrap() is the only global symbol exposed by this
API. It is intended to be called one time at the end of the API
amalgamation process, passed configuration details for the current
environment, and then optionally be removed from the global object
using `delete self.sqlite3ApiBootstrap`.
This function requires a configuration object intended to abstract
This function expects a configuration object, intended to abstract
away details specific to any given WASM environment, primarily so
that it can be used without any _direct_ dependency on Emscripten.
(That said, OO API #1 requires, as of this writing, Emscripten's
virtual filesystem API. Baby steps.)
that it can be used without any _direct_ dependency on
Emscripten. The config object is only honored the first time this
is called. Subsequent calls ignore the argument and return the same
(configured) object which gets initialized by the first call.
The config object properties include:
- `Module`: Emscripten-style module object. Currently only required
by certain test code and is _not_ part of the public interface.
(TODO: rename this to EmscriptenModule to be more explicit.)
- `exports`: the "exports" object for the current WASM
environment. In an Emscripten build, this should be set to
`Module['asm']`.
- `memory`: optional WebAssembly.Memory object, defaulting to
`exports.memory`. In Emscripten environments this should be set
to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be
left undefined/falsy to default to `exports.memory` when using
WASM-exported memory.
- `bigIntEnabled`: true if BigInt support is enabled. Defaults to
true if self.BigInt64Array is available, else false. Some APIs
will throw exceptions if called without BigInt support, as BigInt
is required for marshalling C-side int64 into and out of JS.
- `allocExportName`: the name of the function, in `exports`, of the
`malloc(3)`-compatible routine for the WASM environment. Defaults
to `"malloc"`.
- `deallocExportName`: the name of the function, in `exports`, of
the `free(3)`-compatible routine for the WASM
environment. Defaults to `"free"`.
- `persistentDirName`: if the environment supports persistent storage, this
directory names the "mount point" for that directory. It must be prefixed
by `/` and may currently contain only a single directory-name part. Using
the root directory name is not supported by any current persistent backend.
*/
self.sqlite3ApiBootstrap = function(config){
self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(apiConfig){
'use strict';
if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
console.warn("sqlite3ApiBootstrap() called multiple times.",
"Config and external initializers are ignored on calls after the first.");
return sqlite3ApiBootstrap.sqlite3;
}
apiConfig = apiConfig||{};
const config = Object.create(null);
{
const configDefaults = {
Module: undefined/*needed for some test code, not part of the public API*/,
exports: undefined,
memory: undefined,
bigIntEnabled: !!self.BigInt64Array,
allocExportName: 'malloc',
deallocExportName: 'free',
persistentDirName: '/persistent'
};
Object.keys(configDefaults).forEach(function(k){
config[k] = Object.prototype.hasOwnProperty.call(apiConfig, k)
? apiConfig[k] : configDefaults[k];
});
}
/** Throws a new Error, the message of which is the concatenation
all args with a space between each. */
const toss = (...args)=>{throw new Error(args.join(' '))};
if(config.persistentDirName && !/^\/[^/]+$/.test(config.persistentDirName)){
toss("config.persistentDirName must be falsy or in the form '/dir-name'.");
}
/**
Returns true if n is a 32-bit (signed) integer, else
false. This is used for determining when we need to switch to
@ -143,7 +206,18 @@ self.sqlite3ApiBootstrap = function(config){
};
const utf8Decoder = new TextDecoder('utf-8');
const typedArrayToString = (str)=>utf8Decoder.decode(str);
/** Internal helper to use in operations which need to distinguish
between SharedArrayBuffer heap memory and non-shared heap. */
const __SAB = ('undefined'===typeof SharedArrayBuffer)
? function(){} : SharedArrayBuffer;
const typedArrayToString = function(arrayBuffer, begin, end){
return utf8Decoder.decode(
(arrayBuffer.buffer instanceof __SAB)
? arrayBuffer.slice(begin, end)
: arrayBuffer.subarray(begin, end)
);
};
/**
An Error subclass specifically for reporting Wasm-level malloc()
@ -591,9 +665,6 @@ self.sqlite3ApiBootstrap = function(config){
TODOs and caveats:
- The directory name (mount point) for persistent storage is
currently hard-coded. It needs to be configurable.
- If persistent storage is available at the root of the virtual
filesystem, this interface cannot currently distinguish that
from the lack of persistence. That case cannot currently (with
@ -604,12 +675,17 @@ self.sqlite3ApiBootstrap = function(config){
capi.sqlite3_web_persistent_dir = function(){
if(undefined !== __persistentDir) return __persistentDir;
// If we have no OPFS, there is no persistent dir
if(!self.FileSystemHandle || !self.FileSystemDirectoryHandle
const pdir = config.persistentDirName;
if(!pdir
|| !self.FileSystemHandle
|| !self.FileSystemDirectoryHandle
|| !self.FileSystemFileHandle){
return __persistentDir = "";
}
try{
if(0===this.wasm.xCall('sqlite3_wasm_init_opfs')){
if(pdir && 0===this.wasm.xCallWrapped(
'sqlite3_wasm_init_opfs', 'i32', ['string'], pdir
)){
/** OPFS does not support locking and will trigger errors if
we try to lock. We don't _really_ want to
_unconditionally_ install a non-locking sqlite3 VFS as the
@ -617,13 +693,12 @@ self.sqlite3ApiBootstrap = function(config){
time being. That said: locking is a no-op on all of the
current WASM storage, so this isn't (currently) as bad as
it may initially seem. */
const pVfs = this.sqlite3_vfs_find("unix-none");
const pVfs = sqlite3.capi.sqlite3_vfs_find("unix-none");
if(pVfs){
this.sqlite3_vfs_register(pVfs,1);
//warn("Installed 'unix-none' as the default sqlite3 VFS.");
capi.sqlite3_vfs_register(pVfs,1);
console.warn("Installed 'unix-none' as the default sqlite3 VFS.");
}
return __persistentDir =
"/persistent" /* name is hard-coded in sqlite3_wasm_init_opfs()!*/;
return __persistentDir = pdir;
}else{
return __persistentDir = "";
}
@ -644,19 +719,29 @@ self.sqlite3ApiBootstrap = function(config){
}.bind(capi);
/* The remainder of the API will be set up in later steps. */
return {
/**
An Error subclass which is thrown by this.wasm.alloc() on OOM.
*/
const sqlite3 = {
WasmAllocError: WasmAllocError,
capi,
postInit: [
/* some pieces of the API may install functions into this array,
and each such function will be called, passed (self,sqlite3),
at the very end of the API load/init process, where self is
the current global object and sqlite3 is the object returned
from sqlite3ApiBootstrap(). This array will be removed at the
end of the API setup process. */],
config
};
sqlite3ApiBootstrap.initializers.forEach((f)=>f(sqlite3));
delete sqlite3ApiBootstrap.initializers;
sqlite3ApiBootstrap.sqlite3 = sqlite3;
return sqlite3;
}/*sqlite3ApiBootstrap()*/;
/**
self.sqlite3ApiBootstrap.initializers is an internal detail used by
the various pieces of the sqlite3 API's amalgamation process. It
must not be modified by client code except when plugging such code
into the amalgamation process.
Each component of the amalgamation is expected to append a function
to this array. When sqlite3ApiBootstrap() is called for the first
time, each such function will be called (in their appended order)
and passed the sqlite3 namespace object, into which they can install
their features (noting that most will also require that certain
features alread have been installed). At the end of that process,
this array is deleted.
*/
self.sqlite3ApiBootstrap.initializers = [];
self.sqlite3ApiBootstrap.sqlite3 = undefined /* installed at first call */;

View File

@ -43,7 +43,8 @@
In some contexts, however, listening for the above message is
a better fit.
*/
self.sqlite3.initWorker1API = function(){
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sqlite3.initWorker1API = function(){
'use strict';
/**
UNDER CONSTRUCTION
@ -418,4 +419,6 @@ self.sqlite3.initWorker1API = function(){
wState.post(evType, response, wMsgHandler.xfer);
};
setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'worker1-ready'}), 0);
}.bind({self, sqlite3: self.sqlite3});
}.bind({self, sqlite3});
});

View File

@ -435,7 +435,8 @@ int sqlite3_wasm_vfs_unlink(const char * zName){
#include <emscripten/console.h>
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
** for use by the sqlite project's own JS/WASM bindings, specifically
** only when building with Emscripten's WASMFS support.
**
** This function should only be called if the JS side detects the
** existence of the Origin-Private FileSystem (OPFS) APIs in the
@ -443,14 +444,19 @@ int sqlite3_wasm_vfs_unlink(const char * zName){
** WASMFS backend impl for OPFS. On success, subsequent calls are
** no-ops.
**
** This function may be passed a "mount point" name, which must have a
** leading "/" and is currently restricted to a single path component,
** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is
** NULL or empty, it defaults to "/persistent".
**
** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend
** object fails, SQLITE_IOERR if mkdir() of the "/persistent" dir in
** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in
** the virtual FS fails. In builds compiled without SQLITE_WASM_OPFS
** defined, SQLITE_NOTFOUND is returned without side effects.
*/
int sqlite3_wasm_init_opfs(void){
int sqlite3_wasm_init_opfs(const char *zMountPoint){
static backend_t pOpfs = 0;
static const char * zDir = "/persistent";
if( !zMountPoint || !*zMountPoint ) zMountPoint = "/persistent";
if( !pOpfs ){
pOpfs = wasmfs_create_opfs_backend();
if( pOpfs ){
@ -459,12 +465,15 @@ int sqlite3_wasm_init_opfs(void){
}
/** It's not enough to instantiate the backend. We have to create a
mountpoint in the VFS and attach the backend to it. */
if( pOpfs && 0!=access(zDir, F_OK) ){
if( pOpfs && 0!=access(zMountPoint, F_OK) ){
/* mkdir() simply hangs when called from fiddle app. Cause is
not yet determined but the hypothesis is an init-order
issue. */
const int rc = wasmfs_create_directory(zDir, 0777, pOpfs);
emscripten_console_log(rc ? "OPFS mkdir failed." : "OPFS mkdir ok.");
/* Note that this check and is not robust but it will
hypothetically suffice for the transient wasm-based virtual
filesystem we're currently running in. */
const int rc = wasmfs_create_directory(zMountPoint, 0777, pOpfs);
emscripten_console_logf("OPFS mkdir rc=%d", rc);
if(rc) return SQLITE_IOERR;
}
return pOpfs ? 0 : SQLITE_NOMEM;

View File

@ -1,5 +1,5 @@
C wasm:\saccommodated\sa\sJS\sAPI\srename.
D 2022-08-22T08:55:10.642
C Refactor\sJS\sAPI\samalgamation\ssuch\sthat\sthe\sbootstrapping/configuration\sis\sdeferred\suntil\sthe\swhole\samalgamation\sis\savailable,\sto\sfacilitate\sproviding\sclients\swith\sa\sway\sto\sinitialize\sthe\sAPI\swith\stheir\sown\sconfig\s(noting\sthat\swe're\sstill\sone\ssmall\slevel\sof\srefactoring\saway\sfrom\sbeing\sable\sto\sactually\sdo\sthat).
D 2022-08-22T13:34:13.519
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -481,14 +481,14 @@ F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de
F ext/wasm/api/README.md d876597edd2b9542b6ea031adaaff1c042076fde7b670b1dc6d8a87b28a6631b
F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba81456260a713ed04900c
F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
F ext/wasm/api/sqlite3-api-cleanup.js 149fd63a0400cd1d69548887ffde2ed89c13283384a63c2e9fcfc695e38a9e11
F ext/wasm/api/sqlite3-api-glue.js 4a09dd1153874f7a716cf953329bc1e78bf24e0870a9aad15214ffd51ac913bc
F ext/wasm/api/sqlite3-api-oo1.js 6c14e97987fafdd5f63ffa8a086e5316d49c115743add4dabfacb302a7c76b45
F ext/wasm/api/sqlite3-api-opfs.js c93cdd14f81a26b3a64990515ee05c7e29827fbc8fba4e4c2fef3a37a984db89
F ext/wasm/api/sqlite3-api-prologue.js 96997e411b41ff3d4024c2ad625c5cdb7b6a619ba8beece4662ad190191b1119
F ext/wasm/api/sqlite3-api-worker1.js 74130ec4979baeaf3e909c7619b99e9f466e2d816cd07370987ff639d168ef45
F ext/wasm/api/sqlite3-api-cleanup.js eee5ac931fa0aee2cace52a0dff0cf22ae56a1993a88d911bd0dd384fb380c9a
F ext/wasm/api/sqlite3-api-glue.js 67ca83974410961953eeaa1dfed3518530d68381729ed1d27f95122f5baeabd3
F ext/wasm/api/sqlite3-api-oo1.js f6dcaac3270182471f97efcfda25bd4a4ac1777b8ec52ebd1c6846721160e54c
F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0
F ext/wasm/api/sqlite3-api-prologue.js 5d1b13b23af48ce952e30a0f2d6dff4bc4b33f2dc36fdcaf69c164fd9a72b60f
F ext/wasm/api/sqlite3-api-worker1.js ceb1fc88d8a3742c069632e88fd05c14d5a79eb86bdb9e12969ec37f64fbf42b
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 0e78035045e3328fb050ec9580c6bfb714c756a1d3b917259e58baf9b0332c98
F ext/wasm/api/sqlite3-wasm.c 0d81282eaeff2a6e9fc5c28a388c5c5b45cf25a9393992fa511ac009b27df982
F ext/wasm/common/SqliteTestUtil.js e41a1406f18da9224523fad0c48885caf995b56956a5b9852909c0989e687e90
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/wasm/common/testing.css 572cf1ffae0b6eb7ca63684d3392bf350217a07b90e7a896e4fa850700c989b0
@ -2006,8 +2006,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 7a3c444fb515413254b426908e4d3528ccc664a629628c23b7b85bd21c060d0e
R 763c38437f86fddfab452c17546273d5
P 00991335c4dae56232e999398e5e82d8161903ba7d084b16a73a150e83f1f782
R d8e7cfc53479bc2a5e5d34dd2cc26aa3
U stephan
Z 9e445c1e5e3ac9504605b874a5870c61
Z dd954bd42edcc1a06b3b31bdffb215a4
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
00991335c4dae56232e999398e5e82d8161903ba7d084b16a73a150e83f1f782
9dbe9a6aecec43b51057375ef1d2d632db0d17eac8b7552c20cc91fc2f1a55d1