aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api
diff options
context:
space:
mode:
authorstephan <stephan@noemail.net>2022-10-02 03:11:13 +0000
committerstephan <stephan@noemail.net>2022-10-02 03:11:13 +0000
commit63e9ec2f9c7042fc8fb3f858144ee9ebe5408f69 (patch)
tree1e85e4eef656f867b1a220388cf0563f715223ac /ext/wasm/api
parent6479c5a359e932a76225a903f1a6655cda8c277d (diff)
downloadsqlite-63e9ec2f9c7042fc8fb3f858144ee9ebe5408f69.tar.gz
sqlite-63e9ec2f9c7042fc8fb3f858144ee9ebe5408f69.zip
More fleshing out of sqlite3.capi.wasm.pstack.
FossilOrigin-Name: eb5726677a727a958df11f1fba078d30c7c0ba2a9bdb158e8641b35b5f971af3
Diffstat (limited to 'ext/wasm/api')
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js59
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js88
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js22
-rw-r--r--ext/wasm/api/sqlite3-wasm.c12
4 files changed, 125 insertions, 56 deletions
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
index ab9424aca..b6ec1695e 100644
--- a/ext/wasm/api/sqlite3-api-glue.js
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -55,10 +55,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(1){// WhWasmUtil.xWrap() bindings...
/**
- Add some descriptive xWrap() aliases for '*' intended to
- (A) initially improve readability/correctness of capi.signatures
- and (B) eventually perhaps provide some sort of type-safety
- in their conversions.
+ Add some descriptive xWrap() aliases for '*' intended to (A)
+ initially improve readability/correctness of capi.signatures
+ and (B) eventually perhaps provide automatic conversion from
+ higher-level representations, e.g. capi.sqlite3_vfs to
+ `sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/
const aPtr = wasm.xWrap.argAdapter('*');
wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr);
@@ -248,6 +249,56 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
}/*sqlite3_prepare_v2/v3()*/;
+ if(1){// Extend wasm.pstack, now that the wasm utils are installed
+ /**
+ Allocates n chunks, each sz bytes, as a single memory block and
+ returns the addresses as an array of n element, each holding
+ the address of one chunk.
+
+ Throws a WasmAllocError if allocation fails.
+
+ Example:
+
+ ```
+ const [p1, p2, p3] = wasm.pstack.allocChunks(3,4);
+ ```
+ */
+ wasm.pstack.allocChunks = (n,sz)=>{
+ const mem = wasm.pstack.alloc(n * sz);
+ const rc = [];
+ let i = 0, offset = 0;
+ for(; i < n; offset = (sz * ++i)){
+ rc.push(mem + offset);
+ }
+ return rc;
+ };
+
+ /**
+ A convenience wrapper for allocChunks() which sizes each chunks
+ as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if
+ safePtrSize is truthy).
+
+ How it returns its result differs depending on its first
+ argument: if it's 1, it returns a single pointer value. If it's
+ more than 1, it returns the same as allocChunks().
+
+ When one of the pointers refers to a 64-bit value, e.g. a
+ double or int64, and that value must be written or fetch,
+ e.g. using wasm.setMemValue() or wasm.getMemValue(), it is
+ important that the pointer in question be aligned to an 8-byte
+ boundary or else it will not be fetched or written properly and
+ will corrupt or read neighboring memory.
+
+ However, when all pointers involved are "small", it is safe to
+ pass a falsy value to save to memory.
+ */
+ wasm.pstack.allocPtr = (n=1,safePtrSize=true) =>{
+ return 1===n
+ ? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof)
+ : wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof);
+ };
+ }/*wasm.pstack filler*/
+
/**
Install JS<->C struct bindings for the non-opaque struct types we
need... */
diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js
index 627af2e8a..07f0657fa 100644
--- a/ext/wasm/api/sqlite3-api-oo1.js
+++ b/ext/wasm/api/sqlite3-api-oo1.js
@@ -18,7 +18,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const toss = (...args)=>{throw new Error(args.join(' '))};
const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)};
- const capi = sqlite3.capi, util = capi.util;
+ const capi = sqlite3.capi, wasm = capi.wasm, 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
the same thread (main or worker) as the one in which the
@@ -33,7 +33,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
accessor and store their real values in this map. Keys = DB/Stmt
objects, values = pointer values. This also unifies how those are
accessed, for potential use downstream via custom
- capi.wasm.xWrap() function signatures which know how to extract
+ wasm.xWrap() function signatures which know how to extract
it.
*/
const __ptrMap = new WeakMap();
@@ -72,7 +72,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
being-construct DB object as its "this". See the DB constructor
for the argument docs. This is split into a separate function
in order to enable simple creation of special-case DB constructors,
- e.g. a hypothetical LocalStorageDB or OpfsDB.
+ e.g. JsStorageDB and OpfsDB.
Expects to be passed a configuration object with the following
properties:
@@ -123,7 +123,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
console.error("Invalid DB ctor args",opt,arguments);
toss3("Invalid arguments for DB constructor.");
}
- let fnJs = ('number'===typeof fn) ? capi.wasm.cstringToJs(fn) : fn;
+ let fnJs = ('number'===typeof fn) ? wasm.cstringToJs(fn) : fn;
const vfsCheck = ctor._name2vfs[fnJs];
if(vfsCheck){
vfsName = vfsCheck.vfs;
@@ -136,20 +136,20 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE;
- const stack = capi.wasm.scopedAllocPush();
+ const scope = wasm.scopedAllocPush();
try {
- const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */;
+ const ppDb = wasm.allocPtr() /* output (sqlite3**) arg */;
const pVfsName = vfsName ? (
- ('number'===typeof vfsName ? vfsName : capi.wasm.scopedAllocCString(vfsName))
+ ('number'===typeof vfsName ? vfsName : wasm.scopedAllocCString(vfsName))
): 0;
const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, pVfsName);
- ptr = capi.wasm.getPtrValue(ppDb);
+ ptr = wasm.getPtrValue(ppDb);
checkSqlite3Rc(ptr, rc);
}catch( e ){
if( ptr ) capi.sqlite3_close_v2(ptr);
throw e;
}finally{
- capi.wasm.scopedAllocPop(stack);
+ wasm.scopedAllocPop(scope);
}
this.filename = fnJs;
__ptrMap.set(this, ptr);
@@ -265,7 +265,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
blob: 5
};
BindTypes['undefined'] == BindTypes.null;
- if(capi.wasm.bigIntEnabled){
+ if(wasm.bigIntEnabled){
BindTypes.bigint = BindTypes.number;
}
@@ -454,7 +454,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(s && s.pointer) s.finalize();
});
Object.values(__udfMap.get(this)).forEach(
- capi.wasm.uninstallFunction.bind(capi.wasm)
+ wasm.uninstallFunction.bind(capi.wasm)
);
__ptrMap.delete(this);
__stmtMap.delete(this);
@@ -539,15 +539,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
prepare: function(sql){
affirmDbOpen(this);
- const stack = capi.wasm.pstack.pointer;
+ const stack = wasm.pstack.pointer;
let ppStmt, pStmt;
try{
- ppStmt = capi.wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
+ ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null));
- pStmt = capi.wasm.getPtrValue(ppStmt);
+ pStmt = wasm.getPtrValue(ppStmt);
}
finally {
- capi.wasm.pstack.restore(stack);
+ wasm.pstack.restore(stack);
}
if(!pStmt) toss3("Cannot prepare empty SQL.");
const stmt = new Stmt(this, pStmt, BindTypes);
@@ -846,7 +846,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
let i, pVal, valType, arg;
const tgt = [];
for(i = 0; i < argc; ++i){
- pVal = capi.wasm.getPtrValue(pArgv + (capi.wasm.ptrSizeof * i));
+ pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i));
/**
Curiously: despite ostensibly requiring 8-byte
alignment, the pArgv array is parcelled into chunks of
@@ -868,7 +868,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const pBlob = capi.sqlite3_value_blob(pVal);
arg = new Uint8Array(n);
let i;
- const heap = n ? capi.wasm.heap8() : false;
+ const heap = n ? wasm.heap8() : false;
for(i = 0; i < n; ++i) arg[i] = heap[pBlob+i];
break;
}
@@ -902,10 +902,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
capi.sqlite3_result_null(pCx);
break;
}else if(util.isBindableTypedArray(val)){
- const pBlob = capi.wasm.allocFromTypedArray(val);
+ const pBlob = wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob(pCx, pBlob, val.byteLength,
capi.SQLITE_TRANSIENT);
- capi.wasm.dealloc(pBlob);
+ wasm.dealloc(pBlob);
break;
}
// else fall through
@@ -925,7 +925,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
}
};
- const pUdf = capi.wasm.installFunction(wrapper, "v(iii)");
+ const pUdf = wasm.installFunction(wrapper, "v(iii)");
let fFlags = 0 /*flags for sqlite3_create_function_v2()*/;
if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC;
if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY;
@@ -938,12 +938,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
capi.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf,
null/*xStep*/, null/*xFinal*/, null/*xDestroy*/));
}catch(e){
- capi.wasm.uninstallFunction(pUdf);
+ wasm.uninstallFunction(pUdf);
throw e;
}
const udfMap = __udfMap.get(this);
if(udfMap[name]){
- try{capi.wasm.uninstallFunction(udfMap[name])}
+ try{wasm.uninstallFunction(udfMap[name])}
catch(e){/*ignore*/}
}
udfMap[name] = pUdf;
@@ -1049,7 +1049,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case BindTypes.string:
return t;
case BindTypes.bigint:
- if(capi.wasm.bigIntEnabled) return t;
+ if(wasm.bigIntEnabled) return t;
/* else fall through */
default:
//console.log("isSupportedBindType",t,v);
@@ -1109,7 +1109,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const bindOne = function f(stmt,ndx,bindType,val){
affirmUnlocked(stmt, 'bind()');
if(!f._){
- if(capi.wasm.bigIntEnabled){
+ if(wasm.bigIntEnabled){
f._maxInt = BigInt("0x7fffffffffffffff");
f._minInt = ~f._maxInt;
}
@@ -1120,25 +1120,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
string: function(stmt, ndx, val, asBlob){
if(1){
/* _Hypothetically_ more efficient than the impl in the 'else' block. */
- const stack = capi.wasm.scopedAllocPush();
+ const stack = wasm.scopedAllocPush();
try{
- const n = capi.wasm.jstrlen(val);
- const pStr = capi.wasm.scopedAlloc(n);
- capi.wasm.jstrcpy(val, capi.wasm.heap8u(), pStr, n, false);
+ const n = wasm.jstrlen(val);
+ const pStr = wasm.scopedAlloc(n);
+ wasm.jstrcpy(val, wasm.heap8u(), pStr, n, false);
const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_TRANSIENT);
}finally{
- capi.wasm.scopedAllocPop(stack);
+ wasm.scopedAllocPop(stack);
}
}else{
- const bytes = capi.wasm.jstrToUintArray(val,false);
- const pStr = capi.wasm.alloc(bytes.length || 1);
- capi.wasm.heap8u().set(bytes.length ? bytes : [0], pStr);
+ const bytes = wasm.jstrToUintArray(val,false);
+ const pStr = wasm.alloc(bytes.length || 1);
+ wasm.heap8u().set(bytes.length ? bytes : [0], pStr);
try{
const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
return f(stmt.pointer, ndx, pStr, bytes.length, capi.SQLITE_TRANSIENT);
}finally{
- capi.wasm.dealloc(pStr);
+ wasm.dealloc(pStr);
}
}
}
@@ -1160,7 +1160,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
else if('bigint'===typeof val){
if(val<f._minInt || val>f._maxInt){
toss3("BigInt value is out of range for storing as int64: "+val);
- }else if(capi.wasm.bigIntEnabled){
+ }else if(wasm.bigIntEnabled){
m = capi.sqlite3_bind_int64;
}else if(val >= Number.MIN_SAFE_INTEGER && val <= Number.MAX_SAFE_INTEGER){
val = Number(val);
@@ -1170,7 +1170,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
}else{ // !int32, !bigint
val = Number(val);
- if(capi.wasm.bigIntEnabled && Number.isInteger(val)){
+ if(wasm.bigIntEnabled && Number.isInteger(val)){
m = capi.sqlite3_bind_int64;
}else{
m = capi.sqlite3_bind_double;
@@ -1190,22 +1190,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"that it be a string, Uint8Array, or Int8Array.");
}else if(1){
/* _Hypothetically_ more efficient than the impl in the 'else' block. */
- const stack = capi.wasm.scopedAllocPush();
+ const stack = wasm.scopedAllocPush();
try{
- const pBlob = capi.wasm.scopedAlloc(val.byteLength || 1);
- capi.wasm.heap8().set(val.byteLength ? val : [0], pBlob)
+ const pBlob = wasm.scopedAlloc(val.byteLength || 1);
+ wasm.heap8().set(val.byteLength ? val : [0], pBlob)
rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
capi.SQLITE_TRANSIENT);
}finally{
- capi.wasm.scopedAllocPop(stack);
+ wasm.scopedAllocPop(stack);
}
}else{
- const pBlob = capi.wasm.allocFromTypedArray(val);
+ const pBlob = wasm.allocFromTypedArray(val);
try{
rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
capi.SQLITE_TRANSIENT);
}finally{
- capi.wasm.dealloc(pBlob);
+ wasm.dealloc(pBlob);
}
}
break;
@@ -1518,7 +1518,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
: asType){
case capi.SQLITE_NULL: return null;
case capi.SQLITE_INTEGER:{
- if(capi.wasm.bigIntEnabled){
+ if(wasm.bigIntEnabled){
const rc = capi.sqlite3_column_int64(this.pointer, ndx);
if(rc>=Number.MIN_SAFE_INTEGER && rc<=Number.MAX_SAFE_INTEGER){
/* Coerce "normal" number ranges to normal number values,
@@ -1549,8 +1549,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const n = capi.sqlite3_column_bytes(this.pointer, ndx),
ptr = capi.sqlite3_column_blob(this.pointer, ndx),
rc = new Uint8Array(n);
- //heap = n ? capi.wasm.heap8() : false;
- if(n) rc.set(capi.wasm.heap8u().slice(ptr, ptr+n), 0);
+ //heap = n ? wasm.heap8() : false;
+ if(n) rc.set(wasm.heap8u().slice(ptr, ptr+n), 0);
//for(let i = 0; i < n; ++i) rc[i] = heap[ptr + i];
if(n && this.db._blobXfer instanceof Array){
/* This is an optimization soley for the
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index 97376be6c..59533815f 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -265,6 +265,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
this.name = 'WasmAllocError';
}
};
+ WasmAllocError.toss = (...args)=>{
+ throw new WasmAllocError(args.join(' '));
+ };
/**
The main sqlite3 binding API gets installed into this object,
@@ -733,6 +736,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
Functions which are intended solely for API-internal use by the
WASM components, not client code. These get installed into
capi.wasm.
+
+ TODO: get rid of sqlite3_wasm_vfs_unlink(). It is ill-conceived
+ and only rarely actually useful.
*/
capi.wasm.bindingSignatures.wasm = [
["sqlite3_wasm_vfs_unlink", "int", "string"]
@@ -781,15 +787,21 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
Attempts to allocate the given number of bytes from the
pstack. On success, it zeroes out a block of memory of the
given size, adjusts the pstack pointer, and returns a pointer
- to the memory. On error, returns 0. The memory must eventually
- be released using restore().
+ to the memory. On error, returns throws a WasmAllocError. The
+ memory must eventually be released using restore().
This method always adjusts the given value to be a multiple
of 8 bytes because failing to do so can lead to incorrect
results when reading and writing 64-bit values from/to the WASM
heap.
*/
- alloc: capi.wasm.exports.sqlite3_wasm_pstack_alloc
+ alloc: (n)=>{
+ return capi.wasm.exports.sqlite3_wasm_pstack_alloc(n)
+ || WasmAllocError.toss("Could not allocate",n,
+ "bytes from the pstack.");
+ }
+ // More methods get added after the capi.wasm object is populated
+ // by WhWasmUtilInstaller.
});
/**
sqlite3.capi.wasm.pstack.pointer resolves to the current pstack
@@ -828,7 +840,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
this.name = 'SQLite3Error';
}
};
-
+ SQLite3Error.toss = (...args)=>{
+ throw new SQLite3Error(args.join(' '));
+ };
/** State for sqlite3_wasmfs_opfs_dir(). */
let __persistentDir = undefined;
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
index a2de8ca71..b9454155d 100644
--- a/ext/wasm/api/sqlite3-wasm.c
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -59,6 +59,10 @@
#include <assert.h>
#include "sqlite3.c" /* yes, .c instead of .h. */
+#if defined(__EMSCRIPTEN__)
+# include <emscripten/console.h>
+#endif
+
/*
** WASM_KEEP is identical to EMSCRIPTEN_KEEPALIVE but is not
** Emscripten-specific. It explicitly marks functions for export into
@@ -667,6 +671,9 @@ WASM_KEEP
int sqlite3_wasm_vfs_unlink(const char * zName){
int rc = SQLITE_MISUSE /* ??? */;
sqlite3_vfs * const pVfs = sqlite3_vfs_find(0);
+#if defined(__EMSCRIPTEN__)
+ emscripten_console_warn("sqlite3_wasm_vfs_unlink() will be removed.");
+#endif
if( zName && pVfs && pVfs->xDelete ){
rc = pVfs->xDelete(pVfs, zName, 1);
}
@@ -750,9 +757,7 @@ int sqlite3_wasm_db_serialize( sqlite3* pDb, unsigned char **pOut, sqlite3_int64
}
-#if defined(__EMSCRIPTEN__)
-#include <emscripten/console.h>
-#if defined(SQLITE_WASM_WASMFS)
+#if defined(__EMSCRIPTEN__) && defined(SQLITE_WASM_WASMFS)
#include <emscripten/wasmfs.h>
/*
@@ -809,6 +814,5 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){
return SQLITE_NOTFOUND;
}
#endif /* __EMSCRIPTEN__ && SQLITE_WASM_WASMFS */
-#endif
#undef WASM_KEEP