aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm')
-rw-r--r--ext/wasm/GNUmakefile8
-rw-r--r--ext/wasm/api/sqlite3-opfs-async-proxy.js78
-rw-r--r--ext/wasm/api/sqlite3-vfs-opfs.c-pp.js10
-rw-r--r--ext/wasm/common/whwasmutil.js26
-rw-r--r--ext/wasm/fiddle.make63
-rw-r--r--ext/wasm/fiddle/emscripten.css24
-rw-r--r--ext/wasm/fiddle/index.html25
7 files changed, 139 insertions, 95 deletions
diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile
index 115374e62..d9508e8d7 100644
--- a/ext/wasm/GNUmakefile
+++ b/ext/wasm/GNUmakefile
@@ -139,8 +139,10 @@ dir.api := api
dir.jacc := jaccwabyt
dir.common := common
dir.fiddle := fiddle
+dir.fiddle-debug := fiddle-debug
dir.tool := $(dir.top)/tool
-CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ $(dir.fiddle)/*~
+CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ $(dir.fiddle)/*~ \
+ $(dir.fiddle-debug)/*
########################################################################
# dir.dout = output dir for deliverables.
@@ -386,8 +388,8 @@ emcc_opt_full := $(emcc_opt) -g3
# ^^^ -flto improves runtime speed at -O0 considerably but doubles
# build time.
#
-# ^^^^ -O3, -Oz, -Os minify symbol names and there appears to be no
-# way around that except to use -g3, but -g3 causes the binary file
+# ^^^^ (-O3, -Oz, -Os) all minify symbol names and there appears to be
+# no way around that except to use -g3, but -g3 causes the binary file
# size to absolutely explode (approx. 5x larger). This minification
# utterly breaks the resulting module, making it unsable except as
# self-contained/self-referential-only code, as ALL of the exported
diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js
index e671094f0..a2c052d32 100644
--- a/ext/wasm/api/sqlite3-opfs-async-proxy.js
+++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js
@@ -173,10 +173,10 @@ const installAsyncProxy = function(){
/**
If the given file-holding object has a sync handle attached to it,
- that handle is remove and asynchronously closed. Though it may
+ that handle is removed and asynchronously closed. Though it may
sound sensible to continue work as soon as the close() returns
(noting that it's asynchronous), doing so can cause operations
- performed soon afterwards, e.g. a call to getSyncHandle() to fail
+ performed soon afterwards, e.g. a call to getSyncHandle(), to fail
because they may happen out of order from the close(). OPFS does
not guaranty that the actual order of operations is retained in
such cases. i.e. always "await" on the result of this function.
@@ -265,23 +265,34 @@ const installAsyncProxy = function(){
this.name = 'GetSyncHandleError';
}
};
+
+ /**
+ Attempts to find a suitable SQLITE_xyz result code for Error
+ object e. Returns either such a translation or rc if if it does
+ not know how to translate the exception.
+ */
GetSyncHandleError.convertRc = (e,rc)=>{
- if(1){
- return (
- e instanceof GetSyncHandleError
- && ((e.cause.name==='NoModificationAllowedError')
- /* Inconsistent exception.name from Chrome/ium with the
- same exception.message text: */
- || (e.cause.name==='DOMException'
- && 0===e.cause.message.indexOf('Access Handles cannot')))
- ) ? (
- /*console.warn("SQLITE_BUSY",e),*/
- state.sq3Codes.SQLITE_BUSY
- ) : rc;
- }else{
- return rc;
+ if( e instanceof GetSyncHandleError ){
+ if( e.cause.name==='NoModificationAllowedError'
+ /* Inconsistent exception.name from Chrome/ium with the
+ same exception.message text: */
+ || (e.cause.name==='DOMException'
+ && 0===e.cause.message.indexOf('Access Handles cannot')) ){
+ return state.sq3Codes.SQLITE_BUSY;
+ }else if( 'NotFoundError'===e.cause.name ){
+ /**
+ Maintenance reminder: SQLITE_NOTFOUND, though it looks like
+ a good match, has different semantics than NotFoundError
+ and is not suitable here.
+ */
+ return state.sq3Codes.SQLITE_CANTOPEN;
+ }
+ }else if( 'NotFoundError'===e?.name ){
+ return state.sq3Codes.SQLITE_CANTOPEN;
}
- }
+ return rc;
+ };
+
/**
Returns the sync access handle associated with the given file
handle object (which must be a valid handle object, as created by
@@ -293,6 +304,20 @@ const installAsyncProxy = function(){
times. If acquisition still fails at that point it will give up
and propagate the exception. Client-level code will see that as
an I/O error.
+
+ 2024-06-12: there is a rare race condition here which has been
+ reported a single time:
+
+ https://sqlite.org/forum/forumpost/9ee7f5340802d600
+
+ What appears to be happening is that file we're waiting for a
+ lock on is deleted while we wait. What currently happens here is
+ that a locking exception is thrown but the exception type is
+ NotFoundError. In such cases, we very probably should attempt to
+ re-open/re-create the file an obtain the lock on it (noting that
+ there's another race condition there). That's easy to say but
+ creating a viable test for that condition has proven challenging
+ so far.
*/
const getSyncHandle = async (fh,opName)=>{
if(!fh.syncHandle){
@@ -586,19 +611,6 @@ const installAsyncProxy = function(){
fh.releaseImplicitLocks =
(opfsFlags & state.opfsFlags.OPFS_UNLOCK_ASAP)
|| state.opfsFlags.defaultUnlockAsap;
- if(0 /* this block is modelled after something wa-sqlite
- does but it leads to immediate contention on journal files.
- Update: this approach reportedly only works for DELETE journal
- mode. */
- && (0===(flags & state.sq3Codes.SQLITE_OPEN_MAIN_DB))){
- /* sqlite does not lock these files, so go ahead and grab an OPFS
- lock. */
- fh.xLock = "xOpen"/* Truthy value to keep entry from getting
- flagged as auto-locked. String value so
- that we can easily distinguish is later
- if needed. */;
- await getSyncHandle(fh,'xOpen');
- }
__openFiles[fid] = fh;
storeAndNotify(opName, 0);
}catch(e){
@@ -674,8 +686,10 @@ const installAsyncProxy = function(){
mTimeStart('xUnlock');
let rc = 0;
const fh = __openFiles[fid];
- if( state.sq3Codes.SQLITE_LOCK_NONE===lockType
- && fh.syncHandle ){
+ if( fh.syncHandle
+ && state.sq3Codes.SQLITE_LOCK_NONE===lockType
+ /* Note that we do not differentiate between lock types in
+ this VFS. We're either locked or unlocked. */ ){
wTimeStart('xUnlock');
try { await closeSyncHandle(fh) }
catch(e){
diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
index a2ad6ad3e..fc0fb9db9 100644
--- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
+++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
@@ -390,6 +390,7 @@ const installOpfsVfs = function callee(options){
'SQLITE_ACCESS_EXISTS',
'SQLITE_ACCESS_READWRITE',
'SQLITE_BUSY',
+ 'SQLITE_CANTOPEN',
'SQLITE_ERROR',
'SQLITE_IOERR',
'SQLITE_IOERR_ACCESS',
@@ -718,9 +719,13 @@ const installOpfsVfs = function callee(options){
involve an inherent race condition. For the time being,
pending a better solution, we simply report whether the
given pFile is open.
+
+ Update 2024-06-12: based on forum discussions, this
+ function now always sets pOut to 0 (false):
+
+ https://sqlite.org/forum/forumpost/a2f573b00cda1372
*/
- const f = __openFiles[pFile];
- wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
+ wasm.poke(pOut, 0, 'i32');
return 0;
},
xClose: function(pFile){
@@ -736,7 +741,6 @@ const installOpfsVfs = function callee(options){
return rc;
},
xDeviceCharacteristics: function(pFile){
- //debug("xDeviceCharacteristics(",pFile,")");
return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
},
xFileControl: function(pFile, opId, pArg){
diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js
index b8a2a8774..25400d48e 100644
--- a/ext/wasm/common/whwasmutil.js
+++ b/ext/wasm/common/whwasmutil.js
@@ -156,8 +156,7 @@
- 'dealloc()` must behave like C's `free()`, accepting either a
pointer returned from its allocation counterpart or the values
- null/0 (for which it must be a no-op). allocating N bytes of
- memory and returning its pointer. In Emscripten this is
+ null/0 (for which it must be a no-op). In Emscripten this is
conventionally made available via `Module['_free']`.
APIs which require allocation routines are explicitly documented as
@@ -730,22 +729,23 @@ globalThis.WhWasmUtilInstaller = function(target){
};
/**
- The counterpart of peek(), this sets a numeric value at
- the given WASM heap address, using the type to define how many
- bytes are written. Throws if given an invalid type. See
- peek() for details about the type argument. If the 3rd
- argument ends with `*` then it is treated as a pointer type and
- this function behaves as if the 3rd argument were `i32`.
+ The counterpart of peek(), this sets a numeric value at the given
+ WASM heap address, using the 3rd argument to define how many
+ bytes are written. Throws if given an invalid type. See peek()
+ for details about the `type` argument. If the 3rd argument ends
+ with `*` then it is treated as a pointer type and this function
+ behaves as if the 3rd argument were `i32`.
If the first argument is an array, it is treated like a list
of pointers and the given value is written to each one.
- Returns `this`. (Prior to 2022-12-09 it returns this function.)
+ Returns `this`. (Prior to 2022-12-09 it returned this function.)
- ACHTUNG: calling this often, e.g. in a loop, can have a noticably
- painful impact on performance. Rather than doing so, use
- heapForSize() to fetch the heap object and assign directly to it
- or use the heap's set() method.
+ ACHTUNG: calling this often, e.g. in a loop to populate a large
+ chunk of memory, can have a noticably painful impact on
+ performance. Rather than doing so, use heapForSize() to fetch the
+ heap object and assign directly to it or use the heap's set()
+ method.
*/
target.poke = function(ptr, value, type='i8'){
if (type.endsWith('*')) type = ptrIR;
diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make
index 496e518de..97cf189b0 100644
--- a/ext/wasm/fiddle.make
+++ b/ext/wasm/fiddle.make
@@ -45,6 +45,13 @@ fiddle.emcc-flags = \
-DSQLITE_SHELL_FIDDLE
# -D_POSIX_C_SOURCE is needed for strdup() with emcc
+# Flags specifically for debug builds of fiddle. Performance suffers
+# greatly in debug builds.
+fiddle.emcc-flags.debug := $(fiddle.emcc-flags) \
+ -DSQLITE_DEBUG \
+ -DSQLITE_ENABLE_SELECTTRACE \
+ -DSQLITE_ENABLE_WHERETRACE
+
fiddle.EXPORTED_FUNCTIONS.in := \
EXPORTED_FUNCTIONS.fiddle.in \
$(EXPORTED_FUNCTIONS.api)
@@ -52,27 +59,43 @@ fiddle.EXPORTED_FUNCTIONS.in := \
$(EXPORTED_FUNCTIONS.fiddle): $(fiddle.EXPORTED_FUNCTIONS.in) $(MAKEFILE.fiddle)
sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@
-fiddle-module.js := $(dir.fiddle)/fiddle-module.js
-fiddle-module.wasm := $(subst .js,.wasm,$(fiddle-module.js))
fiddle.cses := $(dir.top)/shell.c $(sqlite3-wasm.c)
-
-fiddle.SOAP.js := $(dir.fiddle)/$(notdir $(SOAP.js))
-$(fiddle.SOAP.js): $(SOAP.js)
- cp $< $@
-
$(eval $(call call-make-pre-post,fiddle-module,vanilla))
-$(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \
- $(EXPORTED_FUNCTIONS.fiddle) \
- $(fiddle.cses) $(pre-post-fiddle-module-vanilla.deps) $(fiddle.SOAP.js)
- $(emcc.bin) -o $@ $(fiddle.emcc-flags) \
- $(pre-post-fiddle-module-vanilla.flags) \
- $(fiddle.cses)
- $(maybe-wasm-strip) $(fiddle-module.wasm)
- gzip < $@ > $@.gz
- gzip < $(fiddle-module.wasm) > $(fiddle-module.wasm).gz
-$(dir.fiddle)/fiddle.js.gz: $(dir.fiddle)/fiddle.js
- gzip < $< > $@
+########################################################################
+# emit rules for one of the two fiddle builds. $1 must be
+# either $(dir.fiddle) or $(dir.fiddle-debug). $2 must be empty
+# in the former case and .debug in the latter.
+define make-fiddle-rules
+fiddle-module.js$(2) := $(1)/fiddle-module.js
+fiddle-module.wasm$(2) := $$(subst .js,.wasm,$$(fiddle-module.js$(2)))
+$(1):
+ @test -d "$$@" || mkdir -p "$$@"
+$$(fiddle-module.js$(2)): $(1) $$(MAKEFILE) $$(MAKEFILE.fiddle) \
+ $$(EXPORTED_FUNCTIONS.fiddle) \
+ $$(fiddle.cses) $$(pre-post-fiddle-module-vanilla.deps) $$(fiddle.SOAP.js$(2))
+ $$(emcc.bin) -o $$@ $$(fiddle.emcc-flags$(2)) \
+ $$(pre-post-fiddle-module-vanilla.flags) \
+ $$(fiddle.cses)
+ $$(maybe-wasm-strip) $$(fiddle-module.wasm$(2))
+ @cp -p $$(SOAP.js) $$(dir $$@)
+ @if [[ x.debug = x$(2) ]]; then \
+ cp -p $$(dir.fiddle)/index.html \
+ $$(dir.fiddle)/fiddle.js \
+ $$(dir.fiddle)/fiddle-worker.js \
+ $$(dir $$@)/.; \
+ fi
+ @for i in $(1)/*.*js $(1)/*.html $(1)/*.wasm; do \
+ test -f $$$${i} || continue; \
+ gzip < $$$${i} > $$$${i}.gz; \
+ done
+fiddle$(2): $$(fiddle-module.js$(2)) $(1)/fiddle.js.gz
+endef
+
+$(eval $(call make-fiddle-rules,$(dir.fiddle)))
+$(eval $(call make-fiddle-rules,$(dir.fiddle-debug),.debug))
+fiddle: $(fiddle-module.js) $(fiddle-module.js.debug)
+fiddle.debug: $(fiddle-module.js.debug)
clean: clean-fiddle
clean-fiddle:
@@ -81,8 +104,8 @@ clean-fiddle:
$(dir.fiddle)/$(SOAP.js) \
$(dir.fiddle)/fiddle-module.worker.js \
EXPORTED_FUNCTIONS.fiddle
-.PHONY: fiddle
-fiddle: $(fiddle-module.js) $(dir.fiddle)/fiddle.js.gz
+ rm -fr $(dir.fiddle-debug)
+.PHONY: fiddle fiddle.debug
all: fiddle
########################################################################
diff --git a/ext/wasm/fiddle/emscripten.css b/ext/wasm/fiddle/emscripten.css
deleted file mode 100644
index 7e3dc811d..000000000
--- a/ext/wasm/fiddle/emscripten.css
+++ /dev/null
@@ -1,24 +0,0 @@
-/* emcscript-related styling, used during the module load/intialization processes... */
-.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
-div.emscripten { text-align: center; }
-div.emscripten_border { border: 1px solid black; }
-#module-spinner { overflow: visible; }
-#module-spinner > * {
- margin-top: 1em;
-}
-.spinner {
- height: 50px;
- width: 50px;
- margin: 0px auto;
- animation: rotation 0.8s linear infinite;
- border-left: 10px solid rgb(0,150,240);
- border-right: 10px solid rgb(0,150,240);
- border-bottom: 10px solid rgb(0,150,240);
- border-top: 10px solid rgb(100,0,200);
- border-radius: 100%;
- background-color: rgb(200,100,250);
-}
-@keyframes rotation {
- from {transform: rotate(0deg);}
- to {transform: rotate(360deg);}
-}
diff --git a/ext/wasm/fiddle/index.html b/ext/wasm/fiddle/index.html
index 272f1aca7..5f8647b03 100644
--- a/ext/wasm/fiddle/index.html
+++ b/ext/wasm/fiddle/index.html
@@ -171,6 +171,31 @@
display: flex;
flex-direction: column-reverse;
}
+
+ /* emcscript-related styling, used during the module load/intialization processes... */
+ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
+ div.emscripten { text-align: center; }
+ div.emscripten_border { border: 1px solid black; }
+ #module-spinner { overflow: visible; }
+ #module-spinner > * {
+ margin-top: 1em;
+ }
+ .spinner {
+ height: 50px;
+ width: 50px;
+ margin: 0px auto;
+ animation: rotation 0.8s linear infinite;
+ border-left: 10px solid rgb(0,150,240);
+ border-right: 10px solid rgb(0,150,240);
+ border-bottom: 10px solid rgb(0,150,240);
+ border-top: 10px solid rgb(100,0,200);
+ border-radius: 100%;
+ background-color: rgb(200,100,250);
+ }
+ @keyframes rotation {
+ from {transform: rotate(0deg);}
+ to {transform: rotate(360deg);}
+ }
</style>
</head>
<body>