]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Cherry-pick [c4dab53b8ea3401abd] for sqlite3.wasm.xWrap() optimizations.
authorstephan <stephan@noemail.net>
Fri, 23 Dec 2022 18:19:28 +0000 (18:19 +0000)
committerstephan <stephan@noemail.net>
Fri, 23 Dec 2022 18:19:28 +0000 (18:19 +0000)
FossilOrigin-Name: 9b97412d3aa791870016ab3c6f565b6a6afa1764f98e969833aec093b9b29919

ext/wasm/common/whwasmutil.js
ext/wasm/tester1.c-pp.js
manifest
manifest.uuid

index 87cc100f330c73e5120452d92abd7e551be0cc72..ecd08327f43a2025b5c2f38f8a54609ddca25f97 100644 (file)
@@ -359,7 +359,8 @@ self.WhWasmUtilInstaller = function(target){
 
   /**
      Given a function pointer, returns the WASM function table entry
-     if found, else returns a falsy value.
+     if found, else returns a falsy value: undefined if fptr is out of
+     range or null if it's in range but the table entry is empty.
   */
   target.functionEntry = function(fptr){
     const ft = target.functionTable();
@@ -552,13 +553,12 @@ self.WhWasmUtilInstaller = function(target){
         cache.scopedAlloc[cache.scopedAlloc.length-1].push(ptr);
       }
     }catch(e){
-      if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen); 
-     throw e;
+      if(ptr===oldLen) cache.freeFuncIndexes.push(oldLen);
+      throw e;
     }
-    return ptr;      
+    return ptr;
   };
 
-  
   /**
      Expects a JS function and signature, exactly as for
      this.jsFuncToWasm(). It uses that function to create a
@@ -612,8 +612,13 @@ self.WhWasmUtilInstaller = function(target){
      installFunction() has been called and results are undefined if
      ptr was not returned by that function. The returned function
      may be passed back to installFunction() to reinstall it.
+
+     To simplify certain use cases, if passed a falsy non-0 value
+     (noting that 0 is a valid function table index), this function
+     has no side effects and returns undefined.
   */
   target.uninstallFunction = function(ptr){
+    if(!ptr && 0!==ptr) return undefined;
     const fi = cache.freeFuncIndexes;
     const ft = target.functionTable();
     fi.push(ptr);
@@ -730,7 +735,7 @@ self.WhWasmUtilInstaller = function(target){
           ? cache : heapWrappers();
     for(const p of (Array.isArray(ptr) ? ptr : [ptr])){
       switch (type) {
-          case 'i1': 
+          case 'i1':
           case 'i8': c.HEAP8[p>>0] = value; continue;
           case 'i16': c.HEAP16[p>>1] = value; continue;
           case 'i32': c.HEAP32[p>>2] = value; continue;
@@ -803,7 +808,6 @@ self.WhWasmUtilInstaller = function(target){
   /** f64 variant of poke8(). */
   target.poke64f = (ptr, value)=>target.poke(ptr, value, 'f64');
 
-  
   /** Deprecated alias for getMemValue() */
   target.getMemValue = target.peek;
   /** Deprecated alias for peekPtr() */
@@ -1351,7 +1355,7 @@ self.WhWasmUtilInstaller = function(target){
 
   const __argcMismatch =
         (f,n)=>toss(f+"() requires",n,"argument(s).");
-  
+
   /**
      Looks up a WASM-exported function named fname from
      target.exports. If found, it is called, passed all remaining
@@ -1390,18 +1394,25 @@ self.WhWasmUtilInstaller = function(target){
   if(target.bigIntEnabled){
     xArg.set('i64', (i)=>BigInt(i));
   }
-  xArg.set('i32', (i)=>(i | 0));
-  xArg.set('i16', (i)=>((i | 0) & 0xFFFF));
-  xArg.set('i8', (i)=>((i | 0) & 0xFF));
-  xArg.set('f32', (i)=>Number(i).valueOf());
-  xArg.set('float', xArg.get('f32'));
-  xArg.set('f64', xArg.get('f32'));
-  xArg.set('double', xArg.get('f64'));
-  xArg.set('int', xArg.get('i32'));
-  xResult.set('*', xArg.get(ptrIR));
-  xResult.set('pointer', xArg.get(ptrIR));
-  xArg.set('**', xArg.get(ptrIR));
-  xResult.set('number', (v)=>Number(v));
+  const __xArgPtr = 'i32' === ptrIR
+        ? ((i)=>(i | 0)) : ((i)=>(BigInt(i) | BigInt(0)));
+  xArg.set('i32', __xArgPtr )
+    .set('i16', (i)=>((i | 0) & 0xFFFF))
+    .set('i8', (i)=>((i | 0) & 0xFF))
+    .set('f32', (i)=>Number(i).valueOf())
+    .set('float', xArg.get('f32'))
+    .set('f64', xArg.get('f32'))
+    .set('double', xArg.get('f64'))
+    .set('int', xArg.get('i32'))
+    .set('null', (i)=>i)
+    .set(null, xArg.get('null'))
+    .set('**', __xArgPtr);
+  xResult.set('*', __xArgPtr)
+    .set('pointer', __xArgPtr)
+    .set('number', (v)=>Number(v))
+    .set('void', (v)=>undefined)
+    .set('null', (v)=>v)
+    .set(null, xResult.get('null'));
 
   { /* Copy certain xArg[...] handlers to xResult[...] and
        add pointer-style variants of them. */
@@ -1430,15 +1441,17 @@ self.WhWasmUtilInstaller = function(target){
 
      TODO? Permit an Int8Array/Uint8Array and convert it to a string?
      Would that be too much magic concentrated in one place, ready to
-     backfire?
+     backfire? We handle that at the client level in sqlite3 with a
+     custom argument converter.
   */
-  xArg.set('string', function(v){
+  const __xArgString = function(v){
     if('string'===typeof v) return target.scopedAllocCString(v);
-    return v ? xArg.get(ptrIR)(v) : null;
-  });
-  xArg.set('utf8', xArg.get('string'));
-  xArg.set('pointer', xArg.get('string'));
-  xArg.set('*', xArg.get('string'));
+    return v ? __xArgPtr(v) : null;
+  };
+  xArg.set('string', __xArgString);
+  xArg.set('utf8', __xArgString);
+  xArg.set('pointer', __xArgString);
+  xArg.set('*', __xArgString);
 
   xResult.set('string', (i)=>target.cstrToJs(i));
   xResult.set('utf8', xResult.get('string'));
@@ -1452,25 +1465,6 @@ self.WhWasmUtilInstaller = function(target){
     try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
     finally{ target.dealloc(i) }
   });
-  xResult.set('void', (v)=>undefined);
-  xResult.set('null', (v)=>v);
-
-  if(0){
-    /***
-        This idea can't currently work because we don't know the
-        signature for the func and don't have a way for the user to
-        convey it. To do this we likely need to be able to match
-        arg/result handlers by a regex, but that would incur an O(N)
-        cost as we check the regex one at a time. Another use case for
-        such a thing would be pseudotypes like "int:-1" to say that
-        the value will always be treated like -1 (which has a useful
-        case in the sqlite3 bindings).
-    */
-    xArg.set('func-ptr', function(v){
-      if(!(v instanceof Function)) return xArg.get(ptrIR);
-      const f = target.jsFuncToWasm(v, WHAT_SIGNATURE);
-    });
-  }
 
   /**
      Internal-use-only base class for FuncPtrAdapter and potentially
@@ -1537,8 +1531,8 @@ self.WhWasmUtilInstaller = function(target){
          value. This is only useful for use with "global" functions
          which do not rely on any state other than this function
          pointer. If the being-converted function pointer is intended
-         to be mapped to some sort of state object (e.g. an sqlite3*)
-         then "context" (see below) is the proper mode.
+         to be mapped to some sort of state object (e.g. an
+         `sqlite3*`) then "context" (see below) is the proper mode.
 
        - 'context': similar to singleton mode but for a given
          "context", where the context is a key provided by the user
@@ -1688,35 +1682,41 @@ self.WhWasmUtilInstaller = function(target){
         (t)=>xResult.get(t) || toss("Result adapter not found:",t);
 
   cache.xWrap.convertArg = (t,...args)=>__xArgAdapterCheck(t)(...args);
+  cache.xWrap.convertArgNoCheck = (t,...args)=>xArg.get(t)(...args);
+
   cache.xWrap.convertResult =
     (t,v)=>(null===t ? v : (t ? __xResultAdapterCheck(t)(v) : undefined));
+  cache.xWrap.convertResultNoCheck =
+    (t,v)=>(null===t ? v : (t ? xResult.get(t)(v) : undefined));
 
   /**
-     Creates a wrapper for the WASM-exported function fname. Uses
-     xGet() to fetch the exported function (which throws on
-     error) and returns either that function or a wrapper for that
-     function which converts the JS-side argument types into WASM-side
-     types and converts the result type. If the function takes no
-     arguments and resultType is `null` then the function is returned
-     as-is, else a wrapper is created for it to adapt its arguments
-     and result value, as described below.
+     Creates a wrapper for another function which converts the arguments
+     of the wrapper to argument types accepted by the wrapped function,
+     then converts the wrapped function's result to another form
+     for the wrapper.
 
-     (If you're familiar with Emscripten's ccall() and cwrap(), this
-     function is essentially cwrap() on steroids.)
+     The first argument must be one of:
 
-     This function's arguments are:
+     - A JavaScript function.
+     - The name of a WASM-exported function. In the latter case xGet()
+       is used to fetch the exported function, which throws if it's not
+       found.
+     - A pointer into the indirect function table. e.g. a pointer
+       returned from target.installFunction().
 
-     - fname: the exported function's name. xGet() is used to fetch
-       this, so will throw if no exported function is found with that
-       name.
-
-     - resultType: the name of the result type. A literal `null` means
-       to return the original function's value as-is (mnemonic: there
-       is "null" conversion going on). Literal `undefined` or the
-       string `"void"` mean to ignore the function's result and return
-       `undefined`. Aside from those two special cases, it may be one
-       of the values described below or any mapping installed by the
-       client using xWrap.resultAdapter().
+     It returns either the passed-in function or a wrapper for that
+     function which converts the JS-side argument types into WASM-side
+     types and converts the result type.
+
+     The second argument, `resultType`, describes the conversion for
+     the wrapped functions result. A literal `null` or the string
+     `'null'` both mean to return the original function's value as-is
+     (mnemonic: there is "null" conversion going on). Literal
+     `undefined` or the string `"void"` both mean to ignore the
+     function's result and return `undefined`. Aside from those two
+     special cases, the `resultType` value may be one of the values
+     described below or any mapping installed by the client using
+     xWrap.resultAdapter().
 
      If passed 3 arguments and the final one is an array, that array
      must contain a list of type names (see below) for adapting the
@@ -1730,6 +1730,12 @@ self.WhWasmUtilInstaller = function(target){
      xWrap('funcname', 'i32', ['string', 'f64']);
      ```
 
+     This function enforces that the given list of arguments has the
+     same arity as the being-wrapped function (as defined by its
+     `length` property) and it will throw if that is not the case.
+     Similarly, the created wrapper will throw if passed a differing
+     argument count.
+
      Type names are symbolic names which map the arguments to an
      adapter function to convert, if needed, the value before passing
      it on to WASM or to convert a return result from WASM. The list
@@ -1766,6 +1772,10 @@ self.WhWasmUtilInstaller = function(target){
 
      Non-numeric conversions include:
 
+     - `null` literal or `"null"` string (args and results): perform
+       no translation and pass the arg on as-is. This is primarily
+       useful for results but may have a use or two for arguments.
+
      - `string` or `utf8` (args): has two different semantics in order
        to accommodate various uses of certain C APIs
        (e.g. output-style strings)...
@@ -1780,9 +1790,9 @@ self.WhWasmUtilInstaller = function(target){
          client has already allocated and it's passed on as
          a WASM pointer.
 
-       - `string` or `utf8` (results): treats the result value as a
-         const C-string, encoded as UTF-8, copies it to a JS string,
-         and returns that JS string.
+     - `string` or `utf8` (results): treats the result value as a
+       const C-string, encoded as UTF-8, copies it to a JS string,
+       and returns that JS string.
 
      - `string:dealloc` or `utf8:dealloc) (results): treats the result value
        as a non-const UTF-8 C-string, ownership of which has just been
@@ -1819,6 +1829,11 @@ self.WhWasmUtilInstaller = function(target){
      type conversions are valid for both arguments _and_ result types
      as they often have different memory ownership requirements.
 
+     Design note: the ability to pass in a JS function as the first
+     argument is of relatively limited use, primarily for testing
+     argument and result converters. JS functions, by and large, will
+     not want to deal with C-type arguments.
+
      TODOs:
 
      - Figure out how/whether we can (semi-)transparently handle
@@ -1839,14 +1854,21 @@ self.WhWasmUtilInstaller = function(target){
        abstracting it into this API (and taking on the associated
        costs) may well not make good sense.
   */
-  target.xWrap = function(fname, resultType, ...argTypes){
+  target.xWrap = function(fArg, resultType, ...argTypes){
     if(3===arguments.length && Array.isArray(arguments[2])){
       argTypes = arguments[2];
     }
-    const xf = target.xGet(fname);
-    if(argTypes.length!==xf.length) __argcMismatch(fname, xf.length);
+    if(target.isPtr(fArg)){
+      fArg = target.functionEntry(fArg)
+        || toss("Function pointer not found in WASM function table.");
+    }
+    const fIsFunc = (fArg instanceof Function);
+    const xf = fIsFunc ? fArg : target.xGet(fArg);
+    if(fIsFunc) fArg = xf.name || 'unnamed function';
+    if(argTypes.length!==xf.length) __argcMismatch(fArg, xf.length);
     if((null===resultType) && 0===xf.length){
-      /* Func taking no args with an as-is return. We don't need a wrapper. */
+      /* Func taking no args with an as-is return. We don't need a wrapper.
+         We forego the argc check here, though. */
       return xf;
     }
     /*Verify the arg type conversions are valid...*/;
@@ -1859,11 +1881,11 @@ self.WhWasmUtilInstaller = function(target){
     if(0===xf.length){
       // No args to convert, so we can create a simpler wrapper...
       return (...args)=>(args.length
-                         ? __argcMismatch(fname, xf.length)
+                         ? __argcMismatch(fArg, xf.length)
                          : cxw.convertResult(resultType, xf.call(null)));
     }
     return function(...args){
-      if(args.length!==xf.length) __argcMismatch(fname, xf.length);
+      if(args.length!==xf.length) __argcMismatch(fArg, xf.length);
       const scope = target.scopedAllocPush();
       try{
         /*
@@ -1881,8 +1903,8 @@ self.WhWasmUtilInstaller = function(target){
           and what those arguments are, is _not_ part of the public
           interface and is _not_ stable.
         */
-        for(const i in args) args[i] = cxw.convertArg(argTypes[i], args[i], i, args);
-        return cxw.convertResult(resultType, xf.apply(null,args));
+        for(const i in args) args[i] = cxw.convertArgNoCheck(argTypes[i], args[i], i, args);
+        return cxw.convertResultNoCheck(resultType, xf.apply(null,args));
       }finally{
         target.scopedAllocPop(scope);
       }
@@ -1974,14 +1996,13 @@ self.WhWasmUtilInstaller = function(target){
 
   /**
      Functions like xCall() but performs argument and result type
-     conversions as for xWrap(). The first argument is the name of the
-     exported function to call. The 2nd its the name of its result
-     type, as documented for xWrap(). The 3rd is an array of argument
-     type name, as documented for xWrap() (use a falsy value or an
-     empty array for nullary functions). The 4th+ arguments are
-     arguments for the call, with the special case that if the 4th
-     argument is an array, it is used as the arguments for the
-     call. Returns the converted result of the call.
+     conversions as for xWrap(). The first, second, and third
+     arguments are as documented for xWrap(), except that the 3rd
+     argument may be either a falsy value or empty array to represent
+     nullary functions. The 4th+ arguments are arguments for the call,
+     with the special case that if the 4th argument is an array, it is
+     used as the arguments for the call. Returns the converted result
+     of the call.
 
      This is just a thin wrapper around xWrap(). If the given function
      is to be called more than once, it's more efficient to use
@@ -1990,9 +2011,9 @@ self.WhWasmUtilInstaller = function(target){
      arguably more efficient because it will hypothetically free the
      wrapper function quickly.
   */
-  target.xCallWrapped = function(fname, resultType, argTypes, ...args){
+  target.xCallWrapped = function(fArg, resultType, argTypes, ...args){
     if(Array.isArray(arguments[3])) args = arguments[3];
-    return target.xWrap(fname, resultType, argTypes||[]).apply(null, args||[]);
+    return target.xWrap(fArg, resultType, argTypes||[]).apply(null, args||[]);
   };
 
   /**
index bef34a0ba54a7ec93fd41cdb226c89ae08010e73..eb014d7b51a0755c6eb20873ddb67c38ed47daad 100644 (file)
@@ -539,7 +539,7 @@ self.sqlite3InitModule = sqlite3InitModule;
         }
         w.dealloc(m);
       }
-      
+
       // isPtr32()
       {
         const ip = w.isPtr32;
@@ -743,6 +743,65 @@ self.sqlite3InitModule = sqlite3InitModule;
             .assert('HI' === cj(new Uint8Array([72, 73])));
         });
 
+        // jsFuncToWasm()
+        {
+          const fsum3 = (x,y,z)=>x+y+z;
+          fw = w.jsFuncToWasm('i(iii)', fsum3);
+          T.assert(fw instanceof Function)
+            .assert( fsum3 !== fw )
+            .assert( 3 === fw.length )
+            .assert( 6 === fw(1,2,3) );
+          T.mustThrowMatching( ()=>w.jsFuncToWasm('x()', function(){}),
+                               'Invalid signature letter: x');
+        }
+
+        // xWrap(Function,...)
+        {
+          let fp;
+          try {
+            const fmy = function fmy(i,s,d){
+              if(fmy.debug) log("fmy(",...arguments,")");
+              T.assert( 3 === i )
+                .assert( w.isPtr(s) )
+                .assert( w.cstrToJs(s) === 'a string' )
+                .assert( T.eqApprox(1.2, d) );
+              return w.allocCString("hi");
+            };
+            fmy.debug = false;
+            const xwArgs = ['string:dealloc', ['i32', 'string', 'f64']];
+            fw = w.xWrap(fmy, ...xwArgs);
+            const fmyArgs = [3, 'a string', 1.2];
+            let rc = fw(...fmyArgs);
+            T.assert( 'hi' === rc );
+            if(0){
+              /* Retain this as a "reminder to self"...
+
+                 This extra level of indirection does not work: the
+                 string argument is ending up as a null in fmy() but
+                 the numeric arguments are making their ways through
+
+                 What's happening is: installFunction() is creating a
+                 WASM-compatible function instance. When we pass a JS string
+                 into there it's getting coerced into `null` before being passed
+                 on to the lower-level wrapper.
+              */
+              fmy.debug = true;
+              fp = wasm.installFunction('i(isd)', fw);
+              fw = w.functionEntry(fp);
+              rc = fw(...fmyArgs);
+              log("rc =",rc);
+              T.assert( 'hi' === rc );
+              // Similarly, this does not work:
+              //let fpw = w.xWrap(fp, null, [null,null,null]);
+              //rc = fpw(...fmyArgs);
+              //log("rc =",rc);
+              //T.assert( 'hi' === rc );
+            }
+          }finally{
+            wasm.uninstallFunction(fp);
+          }
+        }
+
         if(haveWasmCTests()){
           if(!sqlite3.config.useStdAlloc){
             fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']);
@@ -768,7 +827,7 @@ self.sqlite3InitModule = sqlite3InitModule;
             });
           }
         }
-      }
+      }/*xWrap()*/
     }/*WhWasmUtil*/)
 
   ////////////////////////////////////////////////////////////////////
@@ -997,7 +1056,6 @@ self.sqlite3InitModule = sqlite3InitModule;
         P.restore(stack);
       }
     }/*pstack tests*/)
-
   ////////////////////////////////////////////////////////////////////
   ;/*end of C/WASM utils checks*/
 
index 9863dbab45dd5b86dcedff6ace7cb03f42c44db8..bc2050511c117ba056987d2cbf53e59a5d40ae01 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\slots\sof\sharmless,\snuisance\scompiler\swarnings,\smostly\sunused\sparameter\nwarnings\sin\sextensions.
-D 2022-12-23T14:49:24.143
+C Cherry-pick\s[c4dab53b8ea3401abd]\sfor\ssqlite3.wasm.xWrap()\soptimizations.
+D 2022-12-23T18:19:28.093
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -521,7 +521,7 @@ F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c07
 F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b
 F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
 F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f
-F ext/wasm/common/whwasmutil.js ba1a8db1f32124e43e24b3d890102b6552b2c0b5a202185041a55887692df328
+F ext/wasm/common/whwasmutil.js ff43d28d04e60068ecb3e9a2550581f36be5867ad5ffc00d8479580a12cf9b0f
 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
 F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508
 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6
@@ -555,7 +555,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b
 F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9
 F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406
-F ext/wasm/tester1.c-pp.js c45c46cdae1949d426ee12a736087ab180beacc2a20cd829f9052b957adf9ac9
+F ext/wasm/tester1.c-pp.js 4202e7ec525445386f29612ddf1365348edd1f6002b8b21721c954b9569b756a
 F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70
 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@@ -2067,8 +2067,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 7d3772f0bd0e2602fe919573b49001da4e2b9f3874cb0183dea675204afa7abd
-R 16338e89b5ec3a31b67202fe13704742
-U drh
-Z 80509b64f86f8701ef91d19f5ff36290
+P c14bbe1606c1450b709970f922b94a641dfc8f9bd09126501d7dc4db99ea4772
+Q +c4dab53b8ea3401abd57671b8f3cb39fa4431b864d4c4e14ae24592f8d4cba0a
+R 584560ad77984d30d6316ac32176cf60
+U stephan
+Z ff71cfe8de9cd6a3b2f8b5e767f940f3
 # Remove this line to create a well-formed Fossil manifest.
index f42d26d9c507c325f7d444fc9dde2c1139e6c7a4..a270ba73053c434414c73bbff4fbad80275ccc2d 100644 (file)
@@ -1 +1 @@
-c14bbe1606c1450b709970f922b94a641dfc8f9bd09126501d7dc4db99ea4772
\ No newline at end of file
+9b97412d3aa791870016ab3c6f565b6a6afa1764f98e969833aec093b9b29919
\ No newline at end of file