]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
wasm: lots of doc additions and refactoring. Refactored the WASM memory heap usage...
authorstephan <stephan@noemail.net>
Sat, 25 Jun 2022 18:18:45 +0000 (18:18 +0000)
committerstephan <stephan@noemail.net>
Sat, 25 Jun 2022 18:18:45 +0000 (18:18 +0000)
FossilOrigin-Name: e10d57dfbaa672a3a4cbfd9a9209552c3bde15cc75af838690ca412fd182066a

Makefile.in
ext/fiddle/sqlite3-api.js
ext/fiddle/testing1.js
ext/fiddle/wasm_util.c
manifest
manifest.uuid

index 2e0768b829506652e1f172b7351801f9cb666c9b..2d90cc02000c928e15e27b7e75efbd79a18444f4 100644 (file)
@@ -1529,10 +1529,15 @@ sqlite3_wasm = $(fiddle_dir)/sqlite3.wasm
 #emcc_opt = -O2
 #emcc_opt = -O3
 emcc_opt = -Oz
-emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -sSTRICT_JS \
-        -sENVIRONMENT=web -sMODULARIZE \
+emcc_flags = $(emcc_opt) \
+        -sALLOW_TABLE_GROWTH \
+        -sABORTING_MALLOC \
+        -sSTRICT_JS \
+        -sENVIRONMENT=web \
+        -sMODULARIZE \
         -sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \
         -sDYNAMIC_EXECUTION=0 \
+        --minify 0 \
         -I. $(SHELL_OPT)
 $(fiddle_module_js): Makefile sqlite3.c shell.c \
     $(fiddle_dir)/EXPORTED_RUNTIME_METHODS \
@@ -1590,7 +1595,7 @@ wasm: fiddle sqlite3-wasm
 #
 # -sMODULARIZE: changes how the generated code is structured to avoid
 #  declaring a global Module object and instead installing a function
-#  which loads and initialized the module. The function is named...
+#  which loads and initializes the module. The function is named...
 #
 # -sEXPORT_NAME=jsFunctionName (see -sMODULARIZE)
 #
@@ -1610,7 +1615,15 @@ wasm: fiddle sqlite3-wasm
 #  results in build errors.
 #
 # -sALLOW_TABLE_GROWTH is required for (at a minimum) the UDF-binding
-#  feature.
+#  feature. Without it, JS functions cannot be made to proxy C-side
+#  callbacks.
+#
+# -sABORTING_MALLOC causes the JS-bound _malloc() to abort rather than
+#  return 0 on OOM. If set to 0 then all code which uses _malloc()
+#  must, just like in C, check the result before using it, else
+#  they're likely to corrupt the JS/WASM heap by writing to its
+#  address of 0. It is, as of this writing, enabled in Emscripten by
+#  default but we enable it explicitly in case that default changes.
 #
 # -sDYNAMIC_EXECUTION=0 disables eval() and the Function constructor.
 #  If the build runs without these, it's preferable to use this flag
@@ -1642,4 +1655,28 @@ wasm: fiddle sqlite3-wasm
 #  primarily because -Oz will shrink the wasm file notably. JS-side
 #  minification makes little difference in terms of overall
 #  distributable size.
+#
+# --minify 0: disables minification of the generated JS code,
+#  regardless of optimization level. Minification of the JS has
+#  minimal overall effect in the larger scheme of things and results
+#  in JS files which can neither be edited nor viewed as text files in
+#  Fossil (which flags them as binary because of their extreme line
+#  lengths). Interestingly, whether or not the comments in the
+#  generated JS file get stripped is unaffected by this setting and
+#  depends entirely on the optimization level. Higher optimization
+#  levels reduce the size of the JS considerably even without
+#  minification.
+#
+# ACHTUNG...
+#
+# -sALLOW_MEMORY_GROWTH is very tempting but has side-effects which
+#  may break the higher-level code. Specifically, growning the memory
+#  region beyong the default size requires that the app-visible arrays
+#  which act as views for that memory buffer have to be replaced,
+#  which inherently leaves any client-side references to those arrays
+#  with stale state, effectively resulting in memory corruption.
+#  Until/unless that can be well-encapsulated, such that the higher-level
+#  bindings which access those memory heaps are known to be using the
+#  proper references, DO NOT build with this option.
+#
 ########################################################################
index d4a5993ba574940ad9dd60f84a3f16c44bd8b45a..5350464e90745d76d458e2024054a626b8e1a660 100644 (file)
 
   Because using certain parts of the low-level API properly requires
   some degree of WASM-related magic, it is not recommended that that
-  API be used as-is in client-level code. Rather, client code should
-  use the higher-level OO API or write a custom wrapper on top of the
-  lower-level API. In short, most of the C-style API is used in an
-  intuitive manner from JS but any C-style APIs which take
+  API be used as-is in client-level code. Rather, client code is
+  encouraged use the higher-level OO API or write a custom wrapper on
+  top of the lower-level API. In short, most of the C-style API is
+  used in an intuitive manner from JS but any C-style APIs which take
   pointers-to-pointer arguments require WASM-specific interfaces
-  installed by emcscripten-generated code. Those which take or return
+  installed by Emscripten-generated code. Those which take or return
   only integers, doubles, strings, or "plain" pointers to db or
   statement objects can be used in "as normal," noting that "pointers"
-  in wasm are simply 32-bit integers.
+  in WASM are simply 32-bit integers.
 
-  # Goals and Non-goals of this API
 
-  Goals:
+  Specific goals of this project:
 
   - Except where noted in the non-goals, provide a more-or-less
     complete wrapper to the sqlite3 C API, insofar as WASM feature
     parity with C allows for. In fact, provide at least 3...
 
-  - (1) The aforementioned C-style API. (2) An OO-style API on
-    top of that, designed to run in the same thread (main window or
-    Web Worker) as the C API. (3) A less-capable wrapper which can
-    work across the main window/worker boundary, where the sqlite3 API
-    is one of those and this wrapper is in the other. That
-    constellation places some considerable limitations on how the API
-    can be interacted with, but keeping the DB operations out of the
-    UI thread is generally desirable.
+    1) Bind a low-level sqlite3 API which is as close to the native
+       one as feasible in terms of usage.
+
+    2) A higher-level API, more akin to sql.js and node.js-style
+       implementations. This one speaks directly to the low-level
+       API. This API must be used from the same thread as the
+       low-level API.
+
+    3) A second higher-level API which speaks to the previous APIs via
+       worker messages. This one is intended for use in the main
+       thread, with the lower-level APIs installed in a Worker thread,
+       and talking to them via Worker messages. Because Workers are
+       asynchronouns and have only a single message channel, some
+       acrobatics are needed here to feed async work results back to
+       the client (as we cannot simply pass around callbacks between
+       the main and Worker threads).
 
   - Insofar as possible, support client-side storage using JS
     filesystem APIs. As of this writing, such things are still very
     much TODO.
 
-  Non-goals:
+
+  Specific non-goals of this project:
 
   - As WASM is a web-centric technology and UTF-8 is the King of
-    Encodings in that realm, there are no current plans to support the
-    UTF16-related APIs. They would add a complication to the bindings
-    for no appreciable benefit.
+    Encodings in that realm, there are no currently plans to support
+    the UTF16-related sqlite3 APIs. They would add a complication to
+    the bindings for no appreciable benefit.
 
   - Supporting old or niche-market platforms. WASM is built for a
     modern web and requires modern platforms.
 
+
+  Attribution:
+
+  Though this code is not a direct copy/paste, much of the
+  functionality in this file is strongly influenced by the
+  corresponding features in sql.js:
+
+  https://github.com/sql-js/sql.js
+
+  sql.js was an essential stepping stone in this code's development as
+  it demonstrated how to handle some of the WASM-related voodoo (like
+  handling pointers-to-pointers and adding JS implementations of
+  C-bound callback functions). These APIs have a considerably
+  different shape than sql.js's, however.
 */
 if(!Module.postRun) Module.postRun = [];
 /* ^^^^ the name Module is, in this setup, scope-local in the generated
@@ -88,124 +110,208 @@ if(!Module.postRun) Module.postRun = [];
 Module.postRun.push(function(namespace/*the module object, the target for
                                         installed features*/){
     'use strict';
-    /* For reference: sql.js does essentially everything we want and
-       it solves much of the wasm-related voodoo, but we'll need a
-       different structure because we want the db connection to run in
-       a worker thread and feed data back into the main
-       thread. Regardless of those differences, it makes a great point
-       of reference:
-
-       https://github.com/sql-js/sql.js
-
-       Some of the specific design goals here:
-
-       - Bind a low-level sqlite3 API which is close to the native one
-         in terms of usage.
-
-       - Create a higher-level one, more akin to sql.js and
-         node.js-style implementations. This one would speak directly
-         to the low-level API. This API could be used by clients who
-         import the low-level API directly into their main thread
-         (which we don't want to recommend but also don't want to
-         outright forbid).
-
-       - Create a second higher-level one which speaks to the
-         low-level API via worker messages. This one would be intended
-         for use in the main thread, talking to the low-level UI via
-         worker messages. Because workers have only a single message
-         channel, some acrobatics will be needed here to feed async
-         work results back into client-side callbacks (as those
-         callbacks cannot simply be passed to the worker). Exactly
-         what those acrobatics should look like is not yet entirely
-         clear and much experimentation is pending.
+    /**
     */
-
-    const SQM = namespace/*the sqlite module object */;
+    const SQM/*interal-use convenience alias*/ = namespace/*the sqlite module object */;
 
     /** Throws a new Error, the message of which is the concatenation
         all args with a space between each. */
     const toss = function(){
         throw new Error(Array.prototype.join.call(arguments, ' '));
     };
+    
+    /** Returns true if n is a 32-bit (signed) integer,
+        else false. */
+    const isInt32 = function(n){
+        return (n===(n|0) && n<0xFFFFFFFF) ? true : undefined;
+    };
 
+    /** Returns v if v appears to be a TypedArray, else false. */
+    const isTypedArray = (v)=>{
+        return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false;
+    };
+    
     /**
-       Returns true if v appears to be one of our supported TypedArray types:
-       Uint8Array or Int8Array.
+       Returns true if v appears to be one of our bind()-able
+       TypedArray types: Uint8Array or Int8Array. Support for
+       TypedArrays with element sizes >1 is a potential TODO.
     */
-    const isSupportedTypedArray = function(v){
-        return v && (undefined!==v.byteLength) && (v.byteLength === v.length);
+    const isBindableTypedArray = (v)=>{
+        return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
     };
 
-    /** Returns true if isSupportedTypedArray(v) does, else throws with a message
+    /**
+       Returns true if v appears to be one of the TypedArray types
+       which is legal for holding SQL code (as opposed to binary blobs).
+
+       Currently this is the same as isBindableTypedArray() but it
+       seems likely that we'll eventually want to add Uint32Array
+       and friends to the isBindableTypedArray() list but not to the
+       isSQLableTypedArray() list.
+    */
+    const isSQLableTypedArray = isBindableTypedArray;
+
+    /** Returns true if isBindableTypedArray(v) does, else throws with a message
         that v is not a supported TypedArray value. */
-    const affirmSupportedTypedArray = function(v){
-        return isSupportedTypedArray(v)
+    const affirmBindableTypedArray = (v)=>{
+        return isBindableTypedArray(v)
             || toss("Value is not of a supported TypedArray type.");
     };
 
     /** 
-      Set up the main sqlite3 binding API here, mimicking the C API as
-      closely as we can.
-
-      Attribution: though not a direct copy/paste, much of what
-      follows is strongly influenced by the sql.js implementation.
+      The main sqlite3 binding API gets installed into this object,
+      mimicking the C API as closely as we can. The numerous members
+      names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
+      possible, identically to the C-native counterparts. A very few
+      exceptions may require an additional level of proxy function, as
+      documented in this object.
     */
     const api = {
+        /**
+           The sqlite3_prepare_v2() binding handles two different uses
+           with differing JS/WASM semantics:
+
+           1) sqlite3_prepare_v2(pDb, sqlString, -1, ppStmt [, null])
+
+           2) sqlite3_prepare_v2(pDb, sqlPointer, -1, ppStmt, sqlPointerToPointer)
+
+           Note that the SQL length argument (the 3rd argument) must
+           always be negative because it must be a byte length and
+           that value is expensive to calculate from JS (where only
+           the character length of strings is readily available). It
+           is retained in this API's interface for code/documentation
+           compatibility reasons but is currently _always_
+           ignored. When using the 2nd form of this call, it is
+           critical that the custom-allocated string be terminated
+           with a 0 byte. (Potential TODO: if the 3rd argument is >0,
+           assume the caller knows precisely what they're doing, vis a
+           vis WASM memory management, and pass it on as-is. That
+           approach currently seems fraught with peril.)
+
+           In usage (1), the 2nd argument must be of type string,
+           Uint8Array, or Int8Array (either of which is assumed to
+           hold SQL). If it is, this function assumes case (1) and
+           calls the underyling C function with:
+
+           (pDb, sqlAsString, -1, ppStmt, null)
+
+           The pzTail argument is ignored in this case because its result
+           is meaningless when a string-type value is passed through
+           (because the string goes through another level of internal
+           conversion for WASM's sake and the result pointer would refer
+           to that conversion's memory, not the passed-in string).
+
+           If sql is not a string or supported TypedArray, it must be
+           a _pointer_ to a string which was allocated via
+           api.wasm.allocateUTF8OnStack(), api.wasm._malloc(), or
+           equivalent. In that case,
+           the final argument may be 0/null/undefined or must be a
+           pointer to which the "tail" of the compiled SQL is written,
+           as documented for the C-side sqlite3_prepare_v2(). In case
+           (2), the underlying C function is called with:
+
+           (pDb, sqlAsPointer, -1, ppStmt, pzTail)
+
+           It returns its result and compiled statement as documented
+           in the C API. Fetching the output pointers (4th and 5th
+           parameters) requires using api.wasm.getValue() and the
+           pzTail will point to an address relative to the
+           sqlAsPointer value.
+
+           If passed an invalid 2nd argument type, this function will
+           throw. That behaviour is in strong constrast to all of the
+           other C-bound functions (which return a non-0 result code
+           on error) but is necessary because we have to way to set
+           the db's error state such that this function could return a
+           non-0 integer and the client could call sqlite3_errcode()
+           or sqlite3_errmsg() to fetch it.
+        */
+        sqlite3_prepare_v2: undefined/*installed later*/,
+
         /**
            Holds state which are specific to the WASM-related
            infrastructure and glue code. It is not expected that client
            code will normally need these, but they're exposed here in case it
            does.
+
+           Note that a number of members of this object are injected
+           dynamically after the api object is fully constructed, so
+           not all are documented inline here.
         */
         wasm: {
             /**
                api.wasm._malloc()'s srcTypedArray.byteLength bytes,
-               populates them with the values from the source array,
-               and returns the pointer to that memory. The pointer
-               must eventually be passed to api.wasm._free() to clean
-               it up.
+               populates them with the values from the source
+               TypedArray, and returns the pointer to that memory. The
+               returned pointer must eventually be passed to
+               api.wasm._free() to clean it up.
 
                As a special case, to avoid further special cases where
                this is used, if srcTypedArray.byteLength is 0, it
-               allocates a single byte and sets it to the value 0.
+               allocates a single byte and sets it to the value
+               0. Even in such cases, calls must behave as if the
+               allocated memory has exactly srcTypedArray.byteLength
+               bytes.
 
                ACHTUNG: this currently only works for Uint8Array and
-               Int8Array types.
+               Int8Array types and will throw if srcTypedArray is of
+               any other type.
             */
             mallocFromTypedArray: function(srcTypedArray){
-                affirmSupportedTypedArray(srcTypedArray);
+                affirmBindableTypedArray(srcTypedArray);
                 const pRet = api.wasm._malloc(srcTypedArray.byteLength || 1);
-                if(srcTypedArray.byteLength){
-                    api.wasm._malloc.HEAP.set(srcTypedArray, pRet);
-                    /* That unfortunately does not behave intuitively
-                       when copying, e.g., the contents of a
-                       Uint16Array, copying only 1 byte of each
-                       entry instead of blitting the whole array
-                       contents over the destination array. A potential TODO
-                       is handle that copying here so that we can support a wider
-                       array (haha) of bindable-as-blob types. */
-                }
-                else api.wasm._malloc.HEAP[pRet] = 0;
+                this.heapForSize(srcTypedArray).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet);
                 return pRet;
             },
+            /** Convenience form of this.heapForSize(8,false). */
+            HEAP8: ()=>SQM['HEAP8'],
+            /** Convenience form of this.heapForSize(8,true). */
+            HEAPU8: ()=>SQM['HEAPU8'],
             /**
-               The TypedArray buffer which holds the heap memory
-               managed by the emscripten-installed _malloc().
+               Requires n to be one of (8, 16, 32) or a TypedArray
+               instance of Int8Array, Int16Array, Int32Array, or their
+               Uint counterparts.
+
+               Returns the current integer-based TypedArray view of
+               the WASM heap memory buffer associated with the given
+               block size. If unsigned is truthy then the "U"
+               (unsigned) variant of that view is returned, else the
+               signed variant is returned. If passed a TypedArray
+               value and no 2nd argument then the 2nd argument
+               defaults to the signedness of that array. Note that
+               Float32Array and Float64Array views are not supported
+               by this function.
+
+               Note that growth of the heap will invalidate any
+               references to this heap, so do not hold a reference
+               longer than needed and do not use a reference
+               after any operation which may allocate.
+
+               Throws if passed an invalid n
             */
-            HEAP8: SQM.HEAP8
+            heapForSize: function(n,unsigned = true){
+                if(isTypedArray(n)){
+                    if(1===arguments.length){
+                        unsigned = n instanceof Uint8Array || n instanceof Uint16Array
+                            || n instanceof Uint32Array;
+                    }
+                    n = n.constructor.BYTES_PER_ELEMENT * 8;
+                }
+                switch(n){
+                    case 8:  return SQM[unsigned ? 'HEAPU8'  : 'HEAP8'];
+                    case 16: return SQM[unsigned ? 'HEAPU16' : 'HEAP16'];
+                    case 32: return SQM[unsigned ? 'HEAPU32' : 'HEAP32'];
+                }
+                toss("Invalid heapForSize() size: expecting 8, 16, or 32.");
+            }
         }
     };
-    [/* C-side functions to bind. Each entry is an array with 3 or 4
-        elements:
+    [/* C-side functions to bind. Each entry is an array with 3 elements:
         
         ["c-side name",
          "result type" (cwrap() syntax),
          [arg types in cwrap() syntax]
         ]
-
-        If it has 4 elements, the first one is an alternate name to
-        use for the JS-side binding. That's required when overloading
-        a binding for two different uses.
      */
         ["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]],
         ["sqlite3_bind_double","number",["number", "number", "number"]],
@@ -263,10 +369,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
         ["sqlite3_value_text", "string", ["number"]],
         ["sqlite3_value_type", "number", ["number"]]
         //["sqlite3_normalized_sql", "string", ["number"]]
-    ].forEach(function(a){
-        const k = (4==a.length) ? a.shift() : a[0];
-        api[k] = SQM.cwrap.apply(this, a);
-    });
+    ].forEach((a)=>api[a[0]] = SQM.cwrap.apply(this, a));
 
     /**
        Proxies for variants of sqlite3_prepare_v2() which have
@@ -314,54 +417,9 @@ Module.postRun.push(function(namespace/*the module object, the target for
     const typedArrayToString = (str)=>utf8Decoder.decode(str);
     //const stringToUint8 = (sql)=>new TextEncoder('utf-8').encode(sql);
 
-    /**
-       sqlite3_prepare_v2() binding which handles two different uses
-       with differing JS/WASM semantics:
-
-       1) sqlite3_prepare_v2(pDb, sqlString, -1, ppStmt [, null])
-
-       2) sqlite3_prepare_v2(pDb, sqlPointer, -1, ppStmt, sqlPointerToPointer)
-
-       Note that the SQL length argument (the 3rd argument) must
-       always be negative because it must be a byte length and that
-       value is expensive to calculate from JS (where we get the
-       character length of strings). It is retained in this API's
-       interface for code/documentation compatibility reasons but is
-       currently _always_ ignored. When using the 2nd form of this
-       call, it is critical that the custom-allocated string be
-       terminated with a 0 byte. (Potential TODO: if this value is >0,
-       assume the caller knows precisely what they're doing and pass
-       it on as-is. That approach currently seems fraught with peril.)
-
-       In usage (1), the 2nd argument must be of type string or
-       Uint8Array (which is assumed to hold SQL). If it is, this
-       function assumes case (1) and calls the underling C function
-       with:
-
-       (pDb, sqlAsString, -1, ppStmt, null)
-
-       The pzTail argument is ignored in this case because its result
-       is meaningless when a string-type value is passed through
-       (because the string goes through another level of internal
-       conversion for WASM's sake and the result pointer would refer
-       to that conversion's memory, not the passed-in string).
-
-       If sql is not a string or Uint8Array, it must be a _pointer_ to
-       a string which was allocated via api.wasm.allocateUTF8OnStack()
-       or equivalent (TODO: define "or equivalent"). In that case, the
-       final argument may be 0/null/undefined or must be a pointer to
-       which the "tail" of the compiled SQL is written, as documented
-       for the C-side sqlite3_prepare_v2(). In case (2), the
-       underlying C function is called with:
-
-       (pDb, sqlAsPointer, -1, ppStmt, pzTail)
-
-       It returns its result and compiled statement as documented in
-       the C API. Fetching the output pointers (4th and 5th
-       parameters) requires using api.wasm.getValue().
-    */
+    /* Documented inline in the api object. */
     api.sqlite3_prepare_v2 = function(pDb, sql, sqlLen, ppStmt, pzTail){
-        if(isSupportedTypedArray(sql)) sql = typedArrayToString(sql);
+        if(isSQLableTypedArray(sql)) sql = typedArrayToString(sql);
         switch(typeof sql){
             case 'string': return prepareMethods.basic(pDb, sql, -1, ppStmt, null);
             case 'number': return prepareMethods.full(pDb, sql, -1, ppStmt, pzTail);
@@ -369,23 +427,35 @@ Module.postRun.push(function(namespace/*the module object, the target for
         }
     };
 
-    /** Populate api.wasm with several members of the module object... */
-    ['getValue','setValue', 'stackSave', 'stackRestore', 'stackAlloc',
-     'allocateUTF8OnStack', '_malloc', '_free',
-     'addFunction', 'removeFunction',
-     'intArrayFromString', 'lengthBytesUTF8', 'stringToUTF8Array'
+    /**
+       Populate api.wasm with several members of the module object. Some of these
+       will be required by higher-level code. At a minimum:
+
+       getValue(), setValue(), stackSave(), stackRestore(), stackAlloc(), _malloc(),
+       _free(), addFunction(), removeFunction()
+
+       The rest are exposed primarily for internal use in this API but may well
+       be useful from higher-level client code.
+
+       All of the functions injected here are part of the
+       Emscripten-exposed APIs and are documented "elsewhere". Some
+       are documented in the Emscripten-generated `sqlite3.js` and
+       some are documented (if at all) in places unknown, possibly
+       even inaccessible, to us.
+    */
+    [
+        // Memory management:
+        'getValue','setValue', 'stackSave', 'stackRestore', 'stackAlloc',
+        'allocateUTF8OnStack', '_malloc', '_free',
+        // String utilities:
+        'intArrayFromString', 'lengthBytesUTF8', 'stringToUTF8Array',
+        // The obligatory "misc" category:
+        'addFunction', 'removeFunction'
     ].forEach(function(m){
         if(undefined === (api.wasm[m] = SQM[m])){
             toss("Internal init error: Module."+m+" not found.");
         }
     });
-    /**
-       The array object which holds the raw bytes managed by the
-       _malloc() binding. Side note: why on earth _malloc() manages
-       HEAP8 (an Int8Array), rather than HEAPU8 (a Uint8Array), is a
-       mystery.
-    */
-    api.wasm._malloc.HEAP = api.wasm.HEAP8;
 
     /* What follows is colloquially known as "OO API #1". It is a
        binding of the sqlite3 API which is designed to be run within
@@ -500,12 +570,6 @@ Module.postRun.push(function(namespace/*the module object, the target for
         return db;
     };
 
-    /** Returns true if n is a 32-bit (signed) integer,
-        else false. */
-    const isInt32 = function(n){
-        return (n===(n|0) && n<0xFFFFFFFF) ? true : undefined;
-    };
-
     /**
        Expects to be passed (arguments) from DB.exec() and
        DB.execMulti(). Does the argument processing/validation, throws
@@ -522,9 +586,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
         const out = {opt:{}};
         switch(args.length){
             case 1:
-                if('string'===typeof args[0]){
-                    out.sql = args[0];
-                }else if(isSupportedTypedArray(args[0])){
+                if('string'===typeof args[0] || isSQLableTypedArray(args[0])){
                     out.sql = args[0];
                 }else if(args[0] && 'object'===typeof args[0]){
                     out.opt = args[0];
@@ -537,7 +599,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
             break;
             default: toss("Invalid argument count for exec().");
         };
-        if(isSupportedTypedArray(out.sql)){
+        if(isSQLableTypedArray(out.sql)){
             out.sql = typedArrayToString(out.sql);
         }else if(Array.isArray(out.sql)){
             out.sql = out.sql.join('');
@@ -797,7 +859,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                 (opt.callback && opt.rowMode)
                     ? opt.rowMode : false);
             try{
-                const sql = isSupportedTypedArray(arg.sql)
+                const sql = isSQLableTypedArray(arg.sql)
                       ? typedArrayToString(arg.sql)
                       : arg.sql;
                 let pSql = api.wasm.allocateUTF8OnStack(sql)
@@ -939,7 +1001,8 @@ Module.postRun.push(function(namespace/*the module object, the target for
                                 const pBlob = api.sqlite3_value_blob(pVal);
                                 arg = new Uint8Array(n);
                                 let i;
-                                for(i = 0; i < n; ++i) arg[i] = api.wasm.HEAP8[pBlob+i];
+                                const heap = n ? api.wasm.HEAP8() : false;
+                                for(i = 0; i < n; ++i) arg[i] = heap[pBlob+i];
                                 break;
                             }
                             default:
@@ -967,7 +1030,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                             if(null===val) {
                                 api.sqlite3_result_null(pCx);
                                 break;
-                            }else if(isSupportedTypedArray(val)){
+                            }else if(isBindableTypedArray(val)){
                                 const pBlob = api.wasm.mallocFromTypedArray(val);
                                 api.sqlite3_result_blob(pCx, pBlob, val.byteLength,
                                                         api.SQLITE_TRANSIENT);
@@ -1081,7 +1144,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                 return t;
             default:
                 //console.log("isSupportedBindType",t,v);
-                return isSupportedTypedArray(v) ? BindTypes.blob : undefined;
+                return isBindableTypedArray(v) ? BindTypes.blob : undefined;
         }
     };
 
@@ -1159,7 +1222,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                         try{
                             const n = api.wasm.lengthBytesUTF8(val)+1/*required for NUL terminator*/;
                             const pStr = api.wasm.stackAlloc(n);
-                            api.wasm.stringToUTF8Array(val, api.wasm.HEAP8, pStr, n);
+                            api.wasm.stringToUTF8Array(val, api.wasm.HEAPU8(), pStr, n);
                             const f = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text;
                             return f(stmt._pStmt, ndx, pStr, n-1, api.SQLITE_TRANSIENT);
                         }finally{
@@ -1168,7 +1231,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                     }else{
                         const bytes = api.wasm.intArrayFromString(val,true);
                         const pStr = api.wasm._malloc(bytes.length || 1);
-                        api.wasm._malloc.HEAP.set(bytes.length ? bytes : [0], pStr);
+                        api.wasm.HEAPU8().set(bytes.length ? bytes : [0], pStr);
                         try{
                             const f = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text;
                             return f(stmt._pStmt, ndx, pStr, bytes.length, api.SQLITE_TRANSIENT);
@@ -1205,7 +1268,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
             case BindTypes.blob: {
                 if('string'===typeof val){
                     rc = f._.string(stmt, ndx, val, true);
-                }else if(!isSupportedTypedArray(val)){
+                }else if(!isBindableTypedArray(val)){
                     toss("Binding a value as a blob requires",
                          "that it be a string, Uint8Array, or Int8Array.");
                 }else if(1){
@@ -1213,7 +1276,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                     const stack = api.wasm.stackSave();
                     try{
                         const pBlob = api.wasm.stackAlloc(val.byteLength || 1);
-                        api.wasm.HEAP8.set(val.byteLength ? val : [0], pBlob)
+                        api.wasm.HEAP8().set(val.byteLength ? val : [0], pBlob)
                         rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, val.byteLength,
                                                    api.SQLITE_TRANSIENT);
                     }finally{
@@ -1379,7 +1442,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
                 return this;
             }
             else if('object'===typeof arg/*null was checked above*/
-                    && !isSupportedTypedArray(arg)){
+                    && !isBindableTypedArray(arg)){
                 /* Treat each property of arg as a named bound parameter. */
                 if(1!==arguments.length){
                     toss("When binding an object, an index argument is not permitted.");
@@ -1501,10 +1564,11 @@ Module.postRun.push(function(namespace/*the module object, the target for
                 case api.SQLITE_TEXT:
                     return api.sqlite3_column_text(this._pStmt, ndx);
                 case api.SQLITE_BLOB: {
-                    const n = api.sqlite3_column_bytes(this._pStmt, ndx);
-                    const ptr = api.sqlite3_column_blob(this._pStmt, ndx);
-                    const rc = new Uint8Array(n);
-                    for(let i = 0; i < n; ++i) rc[i] = api.wasm.HEAP8[ptr + i];
+                    const n = api.sqlite3_column_bytes(this._pStmt, ndx),
+                          ptr = api.sqlite3_column_blob(this._pStmt, ndx),
+                          rc = new Uint8Array(n),
+                          heap = n ? api.wasm.HEAP8() : false;
+                    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
                            Worker-based API. These values will be
index 44e285f73a1833ea6d58deba1b40ef099430a864..6cae6de8fab6e4f26f82b9cd81deb7dadf2a43f4 100644 (file)
     };
 
     const runTests = function(Module){
-        T.assert(Module._free instanceof Function).
-            assert(Module.allocate instanceof Function).
-            assert(Module.addFunction instanceof Function).
-            assert(Module.removeFunction instanceof Function);
         const sqlite3 = Module.sqlite3;
         const api = sqlite3.api;
         const oo = sqlite3.SQLite3;
         log("Loaded module:",api.sqlite3_libversion(),
                     api.sqlite3_sourceid());
         log("Build options:",oo.compileOptionUsed());
-        log("api.wasm.HEAP8 size =",api.wasm.HEAP8.length);
+        log("api.wasm.HEAP8 size =",api.wasm.HEAP8().length);
         log("wasmEnum",JSON.parse(Module.ccall('sqlite3_wasm_enum_json', 'string', [])));
         [ /* Spot-check a handful of constants to make sure they got installed... */
             'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8',
         ].forEach(function(k){
             T.assert('number' === typeof api[k]);
         });
+        [/* Spot-check a few of the WASM API methods. */
+            '_free', '_malloc', 'addFunction', 'stackRestore'
+        ].forEach(function(k){
+            T.assert(Module[k] instanceof Function).
+                assert(api.wasm[k] instanceof Function);
+        });
+
         const db = new oo.DB();
         try {
             log("DB:",db.filename);
index 54326b297779d0b0b58d30e23641f4c8760b0c19..91b89119341ea056cfb3d98c9c54a86ffdfa6bc1 100644 (file)
 **
 ***********************************************************************
 **
-** Utility functions for use with the emscripten/WASM bits.  These
+** Utility functions for use with the emscripten/WASM bits. These
 ** functions ARE NOT part of the sqlite3 public API. They are strictly
 ** for internal use by the JS/WASM bindings.
+**
+** This file is intended to be WASM-compiled together with sqlite3.c,
+** e.g.:
+**
+**  emcc ... sqlite3.c wasm_util.c
 */
 
 /** Result value of sqlite3_wasm_enum_json(). */
index 3697a203f137ab98acfef5d955792dace9dbd185..cfc2a5ac5b32bec59e98650c8c24cb8730dc7e33 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C wasm:\sadded\sutility\sC\scode\sto\sgenerate\sa\sJSON-format\s"enum"\sof\sthe\snumerous\sSQLITE_xyz\sconstants\sso\sthat\swe\sdo\snot\srisk\sthose\sgetting\sout\sof\ssync\sin\sthe\sJS\scode.\sRenamed\sinitSqlite3Module\sto\ssqlite3InitModule.\sCleanups\sin\sthe\sTypedArray\shandling.
-D 2022-06-25T10:30:24.409
+C wasm:\slots\sof\sdoc\sadditions\sand\srefactoring.\sRefactored\sthe\sWASM\smemory\sheap\susage\sto\shopefully\seventually\saccount\sfor\sa\sruntime-growable\sheap.\sDifferentiate\sbetween\ssupported\sTypedArray\stypes\sfor\sinput\sSQL\sstrings\svs\sbinding/fetching\sblobs.\sMight\s(untested)\shave\simplemented\sthe\sability\sto\sbind\sUtfNNArray\svalues\sas\sblobs,\swhere\sNN\sis\sone\sof\s16\sor\s32.
+D 2022-06-25T18:18:45.580
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in b3ccd1a79e6364d49c465b38cec5eccdc74dfdc06501be62ef8eb01dc1f93f43
+F Makefile.in fff0e19d74fe31d6c2960e72b79a1c82aeb971b32fa5002ace2ceb906b9917e5
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 F Makefile.msc de7cb3e095ce2fdc33513ccd76ebdaeda1483d0ddab0410fe65cbdeadd4c0ee1
 F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
@@ -65,14 +65,14 @@ F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba83
 F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
 F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8
 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
-F ext/fiddle/sqlite3-api.js 03ac065f4bc68eefd3b09cf3957a58891cbfcebe2a5ffe1c8a10f16a58dfc70b
+F ext/fiddle/sqlite3-api.js 690921c0daabde10f7eea1fd1d2d4b034ef0d66d68292931403be0b8e6fccf6a
 F ext/fiddle/sqlite3-worker.js 50b7a9ce14c8fae0af965e35605fe12cafb79c1e01e99216d8110d8b02fbf4b5
 F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a
 F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a
-F ext/fiddle/testing1.js f9615ff58b9de6879e4836618b34322085510ec44c6754e725a92a097c908a6f
+F ext/fiddle/testing1.js b7e34d83d6cb2f640311654266cbbe85f4144ef31fda7615d6e91c6486d3890f
 F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4
 F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee
-F ext/fiddle/wasm_util.c b63e00c2f264ab4a9c45c9f9727627cbc4d8aa2f93c5dd09e8105d63ff7e0872
+F ext/fiddle/wasm_util.c dc9f6e8882b0777037b01d2c8ef2dd9360306a37980f6908cfa3909bf6c25da7
 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
 F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
 F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@@ -1979,8 +1979,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 da1d3151a440567f34a2f6c0b2bfc2e9fab81c256cc361c9ce7b46f2c23a2aa8
-R ff53072845ce3be319c8a5687950f355
+P 778062e3b415dca5104eee398950741b6dbb9d4bdf7c998eef18371a42669946
+R 7173ecb48dc9d037c18c86459c1a9792
 U stephan
-Z 0ce9f393ac2f284b021257ae10338830
+Z d57bc6484b99c6c0083ee3c3d006ea5c
 # Remove this line to create a well-formed Fossil manifest.
index 7a1e3598fa4e82a4e13851f42bd7b2cf68274f02..94a04290769acab70ce9d9257c0d7eb0e3061b17 100644 (file)
@@ -1 +1 @@
-778062e3b415dca5104eee398950741b6dbb9d4bdf7c998eef18371a42669946
\ No newline at end of file
+e10d57dfbaa672a3a4cbfd9a9209552c3bde15cc75af838690ca412fd182066a
\ No newline at end of file