LCOV - code coverage report
Current view: top level - monetdb5/modules/mal - manifold.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 149 187 79.7 %
Date: 2024-10-04 20:04:04 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /*
       2             :  * SPDX-License-Identifier: MPL-2.0
       3             :  *
       4             :  * This Source Code Form is subject to the terms of the Mozilla Public
       5             :  * License, v. 2.0.  If a copy of the MPL was not distributed with this
       6             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       7             :  *
       8             :  * Copyright 2024 MonetDB Foundation;
       9             :  * Copyright August 2008 - 2023 MonetDB B.V.;
      10             :  * Copyright 1997 - July 2008 CWI.
      11             :  */
      12             : 
      13             : /*
      14             :  * M.L. Kersten
      15             :  */
      16             : #include "monetdb_config.h"
      17             : #include "manifold.h"
      18             : #include "mal_resolve.h"
      19             : #include "mal_builder.h"
      20             : 
      21             : /* The default iterator over known scalar commands.
      22             :  * It can be less efficient then the vector based implementations,
      23             :  * but saves quite some hacking in non-essential cases or
      24             :  * expensive user defined functions.
      25             :  *
      26             :  * To keep things simple and reasonably performant we limit the
      27             :  * implementation to those cases where a single BAT is returned.
      28             :  * Arguments may be of any type. The MAL signature should be a COMMAND.
      29             :  *
      30             :  * The functionality has been extended to also perform the manifold
      31             :  * over aligned BATs, provided the underlying scalar function carries
      32             :  * the 'manifold' property.
      33             :  */
      34             : 
      35             : typedef struct {
      36             :         BAT *b;
      37             :         void *first;
      38             :         void *last;
      39             :         int size;
      40             :         int type;
      41             :         BUN cnt;
      42             :         BATiter bi;
      43             :         BUN o;
      44             :         BUN q;
      45             :         str *s;
      46             : } MULTIarg;
      47             : 
      48             : typedef struct {
      49             :         Client cntxt;
      50             :         MalBlkPtr mb;
      51             :         MalStkPtr stk;
      52             :         InstrPtr pci;
      53             :         int fvar, lvar;
      54             :         MULTIarg *args;
      55             : } MULTItask;
      56             : 
      57             : 
      58             : // Loop through the first BAT
      59             : // keep the last error message received
      60             : #define MALfcn1(Type) (str (*) (Type *, void *))
      61             : #define MALfcn2(Type) (str (*) (Type *, void *, void *))
      62             : #define MALfcn3(Type) (str (*) (Type *, void *, void *, void *))
      63             : #define MALfcn4(Type) (str (*) (Type *, void *, void *, void *, void *))
      64             : #define MALfcn5(Type) (str (*) (Type *, void *, void *, void *, void *, void *))
      65             : #define ManifoldLoop(N, Type, ...)                                                                              \
      66             :         do {                                                                                                                            \
      67             :                 Type *v = (Type *) mut->args[0].first;                                                       \
      68             :                 for (;;) {                                                                                                              \
      69             :                         msg = (*(MALfcn##N(Type) mut->pci->fcn))(v, __VA_ARGS__); \
      70             :                         if (msg)                                                                                                        \
      71             :                                 break;                                                                                                  \
      72             :                         if (++oo == olimit)                                                                                     \
      73             :                                 break;                                                                                                  \
      74             :                         for (i = mut->fvar; i <= mut->lvar; i++) {                                     \
      75             :                                 if (ATOMstorage(mut->args[i].type) == TYPE_void) {           \
      76             :                                         args[i] = (void *) &mut->args[i].o;                                      \
      77             :                                         mut->args[i].o++;                                                                    \
      78             :                                 } else if (mut->args[i].size == 0) {                                 \
      79             :                                         ;                                                                                                       \
      80             :                                 } else if (ATOMstorage(mut->args[i].type) < TYPE_str) {   \
      81             :                                         args[i] += mut->args[i].size;                                                \
      82             :                                 } else if (ATOMvarsized(mut->args[i].type)) {                        \
      83             :                                         mut->args[i].o++;                                                                    \
      84             :                                         mut->args[i].s = (str *) BUNtvar(mut->args[i].bi, mut->args[i].o); \
      85             :                                         args[i] = (void *) &mut->args[i].s;                                      \
      86             :                                 } else {                                                                                                \
      87             :                                         mut->args[i].o++;                                                                    \
      88             :                                         mut->args[i].s = (str *) BUNtloc(mut->args[i].bi, mut->args[i].o); \
      89             :                                         args[i] = (void *) &mut->args[i].s;                                      \
      90             :                                 }                                                                                                               \
      91             :                         }                                                                                                                       \
      92             :                         v++;                                                                                                            \
      93             :                 }                                                                                                                               \
      94             :         } while (0)
      95             : 
      96             : // The target BAT tail type determines the result variable
      97             : #ifdef HAVE_HGE
      98             : #define Manifoldbody_hge(N,...)                                                         \
      99             :         case TYPE_hge: ManifoldLoop(N,hge,__VA_ARGS__); break
     100             : #else
     101             : #define Manifoldbody_hge(N,...)
     102             : #endif
     103             : #define Manifoldbody(N,...)                                                                                             \
     104             :         do {                                                                                                                            \
     105             :                 switch (ATOMstorage(mut->args[0].b->ttype)) {                                     \
     106             :                 case TYPE_bte: ManifoldLoop(N,bte,__VA_ARGS__); break;                  \
     107             :                 case TYPE_sht: ManifoldLoop(N,sht,__VA_ARGS__); break;                  \
     108             :                 case TYPE_int: ManifoldLoop(N,int,__VA_ARGS__); break;                  \
     109             :                 case TYPE_lng: ManifoldLoop(N,lng,__VA_ARGS__); break;                  \
     110             :                 Manifoldbody_hge(N,__VA_ARGS__);                                                                \
     111             :                 case TYPE_oid: ManifoldLoop(N,oid,__VA_ARGS__); break;                  \
     112             :                 case TYPE_flt: ManifoldLoop(N,flt,__VA_ARGS__); break;                  \
     113             :                 case TYPE_dbl: ManifoldLoop(N,dbl,__VA_ARGS__); break;                  \
     114             :                 case TYPE_uuid: ManifoldLoop(N,uuid,__VA_ARGS__); break;                \
     115             :                 case TYPE_str:                                                                                                  \
     116             :                 default: {                                                                                                              \
     117             :                         for (;;) {                                                                                                      \
     118             :                             msg = (*(MALfcn##N(str) mut->pci->fcn))(&y, __VA_ARGS__); \
     119             :                                 if (msg)                                                                                                \
     120             :                                         break;                                                                                          \
     121             :                                 if (bunfastapp(mut->args[0].b, (void*) y) != GDK_SUCCEED) \
     122             :                                         goto bunins_failed;                                                                     \
     123             :                                 GDKfree(y);                                                                                             \
     124             :                                 y = NULL;                                                                                               \
     125             :                                 if (++oo == olimit)                                                                             \
     126             :                                         break;                                                                                          \
     127             :                                 for (i = mut->fvar; i <= mut->lvar; i++) {                             \
     128             :                                         if (ATOMstorage(mut->args[i].type) == TYPE_void) {   \
     129             :                                                 args[i] = (void *) &mut->args[i].o;                              \
     130             :                                                 mut->args[i].o++;                                                            \
     131             :                                         } else if(mut->args[i].size == 0) {                                  \
     132             :                                                 ;                                                                                               \
     133             :                                         } else if (ATOMstorage(mut->args[i].type) < TYPE_str) { \
     134             :                                                 args[i] += mut->args[i].size;                                        \
     135             :                                         } else if (ATOMvarsized(mut->args[i].type)) {                \
     136             :                                                 mut->args[i].o++;                                                            \
     137             :                                                 mut->args[i].s = (str *) BUNtvar(mut->args[i].bi, mut->args[i].o); \
     138             :                                                 args[i] = (void *) &mut->args[i].s;                              \
     139             :                                         } else {                                                                                        \
     140             :                                                 mut->args[i].o++;                                                            \
     141             :                                                 mut->args[i].s = (str *) BUNtloc(mut->args[i].bi, mut->args[i].o); \
     142             :                                                 args[i] = (void*) &mut->args[i].s;                               \
     143             :                                         }                                                                                                       \
     144             :                                 }                                                                                                               \
     145             :                         }                                                                                                                       \
     146             :                         break;                                                                                                          \
     147             :                 }                                                                                                                               \
     148             :                 }                                                                                                                               \
     149             :                 mut->args[0].b->theap->dirty = true;                                                   \
     150             :         } while (0)
     151             : 
     152             : // single argument is preparatory step for GDK_mapreduce
     153             : // Only the last error message is returned, the value of
     154             : // an erroneous call depends on the operator itself.
     155             : static str
     156         662 : MANIFOLDjob(MULTItask *mut)
     157             : {
     158         662 :         int i;
     159         662 :         char **args;
     160         662 :         str y = NULL, msg = MAL_SUCCEED;
     161         662 :         oid oo = 0, olimit = mut->args[mut->fvar].cnt;
     162             : 
     163         662 :         if (olimit == 0)
     164             :                 return msg;                             /* nothing to do */
     165             : 
     166         613 :         args = (char **) GDKzalloc(sizeof(char *) * mut->pci->argc);
     167         613 :         if (args == NULL)
     168           0 :                 throw(MAL, "mal.manifold", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     169             : 
     170             :         // the mod.fcn arguments are ignored from the call
     171        1844 :         for (i = mut->pci->retc + 2; i < mut->pci->argc; i++) {
     172        1230 :                 if (mut->args[i].b) {
     173         815 :                         if (ATOMstorage(mut->args[i].type) < TYPE_str) {
     174          60 :                                 args[i] = (char *) mut->args[i].first;
     175         755 :                         } else if (ATOMvarsized(mut->args[i].type)) {
     176         737 :                                 mut->args[i].s = BUNtvar(mut->args[i].bi, mut->args[i].o);
     177         738 :                                 args[i] = (void *) &mut->args[i].s;
     178             :                         } else {
     179          18 :                                 mut->args[i].s = BUNtloc(mut->args[i].bi, mut->args[i].o);
     180          18 :                                 args[i] = (void *) &mut->args[i].s;
     181             :                         }
     182             :                 } else {
     183         415 :                         args[i] = (char *) getArgReference(mut->stk, mut->pci, i);
     184             :                 }
     185             :         }
     186             : 
     187             :         /* TRC_DEBUG(MAL_SERVER, "fvar %d lvar %d type %d\n", mut->fvar,mut->lvar, ATOMstorage(mut->args[mut->fvar].b->ttype)); */
     188             : 
     189             :         // use limited argument list expansion.
     190         614 :         switch (mut->pci->argc) {
     191         145 :         case 4:
     192      220673 :                 Manifoldbody(1, args[3]);
     193         145 :                 break;
     194         352 :         case 5:
     195        1866 :                 Manifoldbody(2, args[3], args[4]);
     196         351 :                 break;
     197          88 :         case 6:
     198       57578 :                 Manifoldbody(3, args[3], args[4], args[5]);
     199          88 :                 break;
     200          26 :         case 7:
     201         476 :                 Manifoldbody(4, args[3], args[4], args[5], args[6]);
     202          26 :                 break;
     203           3 :         case 8:
     204          45 :                 Manifoldbody(5, args[3], args[4], args[5], args[6], args[7]);
     205           3 :                 break;
     206           0 :         default:
     207           0 :                 msg = createException(MAL, "mal.manifold", "manifold call limitation ");
     208             :         }
     209         613 :         if (ATOMextern(mut->args[0].type) && y)
     210           0 :                 GDKfree(y);
     211         613 :   bunins_failed:
     212         613 :         GDKfree(args);
     213         613 :         return msg;
     214             : }
     215             : 
     216             : /* The manifold optimizer should check for the possibility
     217             :  * to use this implementation instead of the MAL loop.
     218             :  */
     219             : MALfcn
     220        8314 : MANIFOLDtypecheck(Client cntxt, MalBlkPtr mb, InstrPtr pci, int checkprops)
     221             : {
     222        8314 :         int i, k, tpe = 0;
     223        8314 :         InstrPtr q = 0;
     224        8314 :         MalBlkPtr nmb;
     225        8314 :         MALfcn fcn;
     226             : 
     227        8314 :         if (mb->errors)
     228             :                 return NULL;
     229        8314 :         if (getArgType(mb, pci, pci->retc) == TYPE_lng) {
     230             :                 // TODO: trivial case
     231             :                 return NULL;
     232             :         }
     233             : 
     234        8314 :         if (pci->retc > 1 || pci->argc > 8 || getModuleId(pci) == NULL)     // limitation on MANIFOLDjob
     235             :                 return NULL;
     236             :         // We need a private MAL context to resolve the function call
     237        8272 :         nmb = newMalBlk(2);
     238        8272 :         if (nmb == NULL) {
     239           0 :                 mb->errors = createException(MAL, "mal.manifold", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     240           0 :                 return NULL;
     241             :         }
     242             :         // the scalar function
     243       16544 :         q = newStmt(nmb,
     244        8272 :                                 getVarConstant(mb, getArg(pci, pci->retc)).val.sval,
     245        8272 :                                 getVarConstant(mb, getArg(pci, pci->retc + 1)).val.sval);
     246        8272 :         if (q == NULL) {
     247           0 :                 goto bailout;
     248             :         }
     249             :         // Prepare the single result variable
     250        8272 :         tpe = getBatType(getArgType(mb, pci, 0));
     251        8272 :         k = getArg(q, 0);
     252        8272 :         setVarType(nmb, k, tpe);
     253        8272 :         if (isVarFixed(nmb, k))
     254           0 :                 setVarFixed(nmb, k);
     255             : 
     256             :         // extract their scalar argument type
     257       24716 :         for (i = pci->retc + 2; i < pci->argc; i++) {
     258       16444 :                 tpe = getBatType(getArgType(mb, pci, i));
     259       16444 :                 k = newTmpVariable(nmb, tpe);
     260       16444 :                 if (k < 0) {
     261           0 :                         freeInstruction(q);
     262           0 :                         goto bailout;
     263             :                 }
     264       16444 :                 q = pushArgument(nmb, q, k);
     265       16444 :                 setVarFixed(nmb, k);
     266             :         }
     267        8272 :         pushInstruction(nmb, q);
     268             : 
     269        8272 :         if (nmb->errors)
     270           0 :                 goto bailout;
     271             : 
     272             : /*
     273             :         TRC_DEBUG(MAL_SERVER, "Manifold operation\n");
     274             :         traceInstruction(MAL_SERVER, mb, 0, pci, LIST_MAL_ALL);
     275             :         traceInstruction(MAL_SERVER, nmb, 0, q, LIST_MAL_ALL);
     276             : */
     277             :         // Localize the underlying scalar operator
     278        8272 :         typeChecker(cntxt->usermodule, nmb, q, getPC(nmb, q), TRUE);
     279        8272 :         if (nmb->errors)
     280           0 :                 goto bailout;
     281        8272 :         if (q->fcn == NULL || q->token != CMDcall ||
     282        1254 :                 (checkprops && q->blk && q->blk->unsafeProp))
     283             :                 fcn = NULL;
     284             :         else {
     285        3155 :                 fcn = q->fcn;
     286             :                 // retain the type detected
     287        3155 :                 if (!isVarFixed(mb, getArg(pci, 0)))
     288           0 :                         setVarType(mb, getArg(pci, 0), newBatType(getArgType(nmb, q, 0)));
     289             :         }
     290             : 
     291             : /*
     292             :         TRC_DEBUG(MAL_SERVER, "Success? %s\n", (fcn == NULL? "no":"yes"));
     293             :         traceInstruction(MAL_SERVER, nmb, 0, q, LIST_MAL_ALL);
     294             : */
     295             : 
     296        8272 :         freeMalBlk(nmb);
     297        8272 :         return fcn;
     298             : 
     299           0 :   bailout:
     300             :         /* there was an error, perhaps it's in nmb->errors */
     301           0 :         assert(mb->errors == NULL);
     302           0 :         if ((mb->errors = nmb->errors) == NULL)
     303           0 :                 mb->errors = createException(MAL, "mal.manifold", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     304           0 :         nmb->errors = NULL;
     305           0 :         freeMalBlk(nmb);
     306           0 :         return NULL;
     307             : }
     308             : 
     309             : /*
     310             :  * The manifold should support aligned BATs as well
     311             :  */
     312             : static str
     313         663 : MANIFOLDevaluate(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
     314             : {
     315         663 :         MULTItask mut;
     316         663 :         MULTIarg *mat;
     317         663 :         int i, tpe = 0;
     318         663 :         BUN cnt = 0;
     319         663 :         oid o = 0;
     320         663 :         str msg = MAL_SUCCEED;
     321         663 :         MALfcn fcn;
     322             : 
     323         663 :         fcn = MANIFOLDtypecheck(cntxt, mb, pci, 0);
     324         663 :         if (fcn == NULL) {
     325           0 :                 if (mb->errors) {
     326           0 :                         msg = mb->errors;
     327           0 :                         mb->errors = NULL;
     328           0 :                         return msg;
     329             :                 }
     330           0 :                 throw(MAL, "mal.manifold", "Illegal manifold function call");
     331             :         }
     332             : 
     333         663 :         mat = (MULTIarg *) GDKzalloc(sizeof(MULTIarg) * pci->argc);
     334         663 :         if (mat == NULL)
     335           0 :                 throw(MAL, "mal.manifold", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     336             : 
     337             :         // mr-job structure preparation
     338         663 :         mut.fvar = mut.lvar = 0;
     339         663 :         mut.cntxt = cntxt;
     340         663 :         mut.mb = mb;
     341         663 :         mut.stk = stk;
     342         663 :         mut.args = mat;
     343         663 :         mut.pci = pci;
     344             : 
     345             :         // prepare iterators
     346        1977 :         for (i = pci->retc + 2; i < pci->argc; i++) {
     347        1314 :                 if (isaBatType(getArgType(mb, pci, i))) {
     348         882 :                         mat[i].b = BATdescriptor(*getArgReference_bat(stk, pci, i));
     349         882 :                         if (mat[i].b == NULL) {
     350           0 :                                 msg = createException(MAL, "mal.manifold",
     351             :                                                                           SQLSTATE(HY013) MAL_MALLOC_FAIL);
     352           0 :                                 goto wrapup;
     353             :                         }
     354         882 :                         mat[i].bi = bat_iterator(mat[i].b);
     355         882 :                         mat[i].type = tpe = getBatType(getArgType(mb, pci, i));
     356         882 :                         if (mut.fvar == 0) {
     357         662 :                                 mut.fvar = i;
     358         662 :                                 cnt = BATcount(mat[i].b);
     359         220 :                         } else if (BATcount(mat[i].b) != cnt) {
     360           0 :                                 msg = createException(MAL, "mal.manifold",
     361             :                                                                           "Columns must be of same length");
     362           0 :                                 goto wrapup;
     363             :                         }
     364         882 :                         mut.lvar = i;
     365         882 :                         mat[i].size = mat[i].bi.width;
     366         882 :                         mat[i].cnt = cnt;
     367         882 :                         if (mat[i].b->ttype == TYPE_void) {
     368           0 :                                 o = mat[i].b->tseqbase;
     369           0 :                                 mat[i].first = mat[i].last = (void *) &o;
     370             :                         } else {
     371         882 :                                 mat[i].first = (void *) mat[i].bi.base;
     372         882 :                                 mat[i].last = (void *) ((char *) mat[i].bi.base + (BATcount(mat[i].b) << mat[i].bi.shift));
     373             :                         }
     374         882 :                         mat[i].o = 0;
     375         882 :                         mat[i].q = BATcount(mat[i].b);
     376             :                 } else {
     377         432 :                         mat[i].last = mat[i].first = (void *) getArgReference(stk, pci, i);
     378         432 :                         mat[i].type = getArgType(mb, pci, i);
     379             :                 }
     380             :         }
     381             : 
     382             :         // Then iterator over all BATs
     383         663 :         if (mut.fvar == 0) {
     384           0 :                 msg = createException(MAL, "mal.manifold",
     385             :                                                           "At least one column required");
     386           0 :                 goto wrapup;
     387             :         }
     388             :         // prepare result variable
     389        1326 :         mat[0].b = COLnew(mat[mut.fvar].b->hseqbase,
     390         663 :                                           getBatType(getArgType(mb, pci, 0)), cnt, TRANSIENT);
     391         663 :         if (mat[0].b == NULL) {
     392           0 :                 msg = createException(MAL, "mal.manifold",
     393             :                                                           SQLSTATE(HY013) MAL_MALLOC_FAIL);
     394           0 :                 goto wrapup;
     395             :         }
     396         663 :         mat[0].b->tnonil = false;
     397         663 :         mat[0].b->tsorted = false;
     398         663 :         mat[0].b->trevsorted = false;
     399         663 :         mat[0].b->tkey = false;
     400         663 :         mat[0].bi = (BATiter) {.b = NULL, };
     401         663 :         mat[0].first = (void *) Tloc(mat[0].b, 0);
     402         663 :         mat[0].last = (void *) Tloc(mat[0].b, BATcount(mat[0].b));
     403             : 
     404         663 :         mut.pci = copyInstruction(pci);
     405         662 :         if (mut.pci == NULL) {
     406           0 :                 msg = createException(MAL, "mal.manifold",
     407             :                                                           SQLSTATE(HY013) MAL_MALLOC_FAIL);
     408           0 :                 goto wrapup;
     409             :         }
     410         662 :         mut.pci->fcn = fcn;
     411         662 :         msg = MANIFOLDjob(&mut);
     412         663 :         freeInstruction(mut.pci);
     413             : 
     414         663 :   wrapup:
     415             :         // restore the argument types
     416        3303 :         for (i = pci->retc; i < pci->argc; i++) {
     417        2640 :                 if (mat[i].b) {
     418         882 :                         bat_iterator_end(&mat[i].bi);
     419         883 :                         BBPunfix(mat[i].b->batCacheid);
     420             :                 }
     421             :         }
     422         663 :         if (msg) {
     423           6 :                 BBPreclaim(mat[0].b);
     424         657 :         } else if (!msg) {
     425             :                 // consolidate the properties
     426         657 :                 if (ATOMstorage(mat[0].b->ttype) < TYPE_str)
     427         421 :                         BATsetcount(mat[0].b, cnt);
     428         657 :                 BATsettrivprop(mat[0].b);
     429         657 :                 *getArgReference_bat(stk, pci, 0) = mat[0].b->batCacheid;
     430         657 :                 BBPkeepref(mat[0].b);
     431             :         }
     432         663 :         GDKfree(mat);
     433         663 :         return msg;
     434             : }
     435             : 
     436             : // The old code
     437             : static str
     438           1 : MANIFOLDremapMultiplex(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr p)
     439             : {
     440           1 :         (void) mb;
     441           1 :         (void) cntxt;
     442           4 :         throw(MAL, "mal.multiplex", "Function '%s.%s' not defined",
     443           1 :                   *getArgReference_str(stk, p, p->retc),
     444           1 :                   *getArgReference_str(stk, p, p->retc + 1));
     445             : }
     446             : 
     447             : #include "mel.h"
     448             : mel_func manifold_init_funcs[] = {
     449             :  pattern("mal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,4, varargany("",0),arg("mod",str),arg("fcn",str),varargany("a",0))),
     450             :  pattern("mal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,4, varargany("",0),arg("card", lng), arg("mod",str),arg("fcn",str))),
     451             :  pattern("mal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,5, varargany("",0),arg("card", lng), arg("mod",str),arg("fcn",str),varargany("a",0))),
     452             :  pattern("batmal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,4, varargany("",0),arg("mod",str),arg("fcn",str),varargany("a",0))),
     453             :  pattern("mal", "manifold", MANIFOLDevaluate, false, "", args(1,4, batargany("",0),arg("mod",str),arg("fcn",str),varargany("a",0))),
     454             :  { .imp=NULL }
     455             : };
     456             : #include "mal_import.h"
     457             : #ifdef _MSC_VER
     458             : #undef read
     459             : #pragma section(".CRT$XCU",read)
     460             : #endif
     461         323 : LIB_STARTUP_FUNC(init_manifold_mal)
     462         323 : { mal_module("manifold", NULL, manifold_init_funcs); }

Generated by: LCOV version 1.14