LCOV - code coverage report
Current view: top level - monetdb5/modules/mal - manifold.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 144 182 79.1 %
Date: 2024-12-20 21:24:02 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         802 : MANIFOLDjob(MULTItask *mut)
     157             : {
     158         802 :         int i;
     159         802 :         char **args;
     160         802 :         str y = NULL, msg = MAL_SUCCEED;
     161         802 :         oid oo = 0, olimit = mut->args[mut->fvar].cnt;
     162             : 
     163         802 :         if (olimit == 0)
     164             :                 return msg;                             /* nothing to do */
     165             : 
     166         719 :         args = (char **) GDKzalloc(sizeof(char *) * mut->pci->argc);
     167         726 :         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        2135 :         for (i = mut->pci->retc + 2; i < mut->pci->argc; i++) {
     172        1422 :                 if (mut->args[i].b) {
     173         945 :                         if (ATOMstorage(mut->args[i].type) < TYPE_str) {
     174          67 :                                 args[i] = (char *) mut->args[i].first;
     175         878 :                         } else if (ATOMvarsized(mut->args[i].type)) {
     176         860 :                                 mut->args[i].s = BUNtvar(mut->args[i].bi, mut->args[i].o);
     177         860 :                                 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         477 :                         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         713 :         switch (mut->pci->argc) {
     191         170 :         case 4:
     192      220638 :                 Manifoldbody(1, args[3]);
     193         173 :                 break;
     194         420 :         case 5:
     195        1744 :                 Manifoldbody(2, args[3], args[4]);
     196         421 :                 break;
     197          93 :         case 6:
     198       59829 :                 Manifoldbody(3, args[3], args[4], args[5]);
     199          92 :                 break;
     200          27 :         case 7:
     201         474 :                 Manifoldbody(4, args[3], args[4], args[5], args[6]);
     202          27 :                 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         716 :         if (ATOMextern(mut->args[0].type) && y)
     210           0 :                 GDKfree(y);
     211         716 :   bunins_failed:
     212         716 :         GDKfree(args);
     213         716 :         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       12969 : MANIFOLDtypecheck(Client cntxt, MalBlkPtr mb, InstrPtr pci, int checkprops)
     221             : {
     222       12969 :         int i, k, tpe = 0;
     223       12969 :         InstrPtr q = 0;
     224       12969 :         MalBlkPtr nmb;
     225       12969 :         MALfcn fcn;
     226             : 
     227       12969 :         if (mb->errors)
     228             :                 return NULL;
     229       12969 :         if (getArgType(mb, pci, pci->retc) == TYPE_lng) {
     230             :                 // TODO: trivial case
     231             :                 return NULL;
     232             :         }
     233             : 
     234       12969 :         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       12927 :         nmb = newMalBlk(2);
     238       12932 :         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       25865 :         q = newStmt(nmb,
     244       12932 :                                 getVarConstant(mb, getArg(pci, pci->retc)).val.sval,
     245       12932 :                                 getVarConstant(mb, getArg(pci, pci->retc + 1)).val.sval);
     246       12933 :         if (q == NULL) {
     247           0 :                 goto bailout;
     248             :         }
     249             :         // Prepare the single result variable
     250       12933 :         tpe = getBatType(getArgType(mb, pci, 0));
     251       12933 :         k = getArg(q, 0);
     252       12933 :         setVarType(nmb, k, tpe);
     253       12933 :         if (isVarFixed(nmb, k))
     254           0 :                 setVarFixed(nmb, k);
     255             : 
     256             :         // extract their scalar argument type
     257       38226 :         for (i = pci->retc + 2; i < pci->argc; i++) {
     258       25293 :                 tpe = getBatType(getArgType(mb, pci, i));
     259       25293 :                 k = newTmpVariable(nmb, tpe);
     260       25292 :                 if (k < 0) {
     261           0 :                         freeInstruction(q);
     262           0 :                         goto bailout;
     263             :                 }
     264       25292 :                 q = pushArgument(nmb, q, k);
     265       25293 :                 setVarFixed(nmb, k);
     266             :         }
     267       12933 :         pushInstruction(nmb, q);
     268             : 
     269       12932 :         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       12932 :         typeChecker(cntxt->usermodule, nmb, q, getPC(nmb, q), TRUE);
     279       12920 :         if (nmb->errors)
     280           0 :                 goto bailout;
     281       12920 :         if (q->fcn == NULL || q->token != CMDcall ||
     282        1528 :                 (checkprops && q->blk && q->blk->unsafeProp))
     283             :                 fcn = NULL;
     284             :         else {
     285        3864 :                 fcn = q->fcn;
     286             :                 // retain the type detected
     287        3864 :                 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       12920 :         freeMalBlk(nmb);
     297       12920 :         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         804 : MANIFOLDevaluate(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
     314             : {
     315         804 :         MULTItask mut;
     316         804 :         MULTIarg *mat;
     317         804 :         int i, tpe = 0;
     318         804 :         BUN cnt = 0;
     319         804 :         oid o = 0;
     320         804 :         str msg = MAL_SUCCEED;
     321         804 :         MALfcn fcn;
     322             : 
     323         804 :         fcn = MANIFOLDtypecheck(cntxt, mb, pci, 0);
     324         809 :         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         809 :         mat = (MULTIarg *) GDKzalloc(sizeof(MULTIarg) * pci->argc);
     334         809 :         if (mat == NULL)
     335           0 :                 throw(MAL, "mal.manifold", SQLSTATE(HY013) MAL_MALLOC_FAIL);
     336             : 
     337             :         // mr-job structure preparation
     338         809 :         mut = (MULTItask) {
     339             :                 .cntxt = cntxt,
     340             :                 .mb = mb,
     341             :                 .stk = stk,
     342             :                 .args = mat,
     343             :                 .pci = pci,
     344             :         };
     345             : 
     346             :         // prepare iterators
     347        2367 :         for (i = pci->retc + 2; i < pci->argc; i++) {
     348        1559 :                 if (isaBatType(getArgType(mb, pci, i))) {
     349        1058 :                         mat[i].b = BATdescriptor(*getArgReference_bat(stk, pci, i));
     350        1058 :                         if (mat[i].b == NULL) {
     351           0 :                                 msg = createException(MAL, "mal.manifold",
     352             :                                                                           SQLSTATE(HY013) MAL_MALLOC_FAIL);
     353           0 :                                 goto wrapup;
     354             :                         }
     355        1058 :                         mat[i].bi = bat_iterator(mat[i].b);
     356        1057 :                         mat[i].type = tpe = getBatType(getArgType(mb, pci, i));
     357        1057 :                         if (mut.fvar == 0) {
     358         809 :                                 mut.fvar = i;
     359         809 :                                 cnt = BATcount(mat[i].b);
     360         248 :                         } else if (BATcount(mat[i].b) != cnt) {
     361           0 :                                 msg = createException(MAL, "mal.manifold",
     362             :                                                                           "Columns must be of same length");
     363           0 :                                 goto wrapup;
     364             :                         }
     365        1057 :                         mut.lvar = i;
     366        1057 :                         mat[i].size = mat[i].bi.width;
     367        1057 :                         mat[i].cnt = cnt;
     368        1057 :                         if (mat[i].b->ttype == TYPE_void) {
     369           0 :                                 o = mat[i].b->tseqbase;
     370           0 :                                 mat[i].first = mat[i].last = (void *) &o;
     371             :                         } else {
     372        1057 :                                 mat[i].first = (void *) mat[i].bi.base;
     373        1057 :                                 mat[i].last = (void *) ((char *) mat[i].bi.base + (BATcount(mat[i].b) << mat[i].bi.shift));
     374             :                         }
     375        1057 :                         mat[i].o = 0;
     376        1057 :                         mat[i].q = BATcount(mat[i].b);
     377             :                 } else {
     378         501 :                         mat[i].last = mat[i].first = (void *) getArgReference(stk, pci, i);
     379         501 :                         mat[i].type = getArgType(mb, pci, i);
     380             :                 }
     381             :         }
     382             : 
     383             :         // Then iterator over all BATs
     384         808 :         if (mut.fvar == 0) {
     385           0 :                 msg = createException(MAL, "mal.manifold",
     386             :                                                           "At least one column required");
     387           0 :                 goto wrapup;
     388             :         }
     389             :         // prepare result variable
     390        1609 :         mat[0].b = COLnew(mat[mut.fvar].b->hseqbase,
     391         808 :                                           getBatType(getArgType(mb, pci, 0)), cnt, TRANSIENT);
     392         801 :         if (mat[0].b == NULL) {
     393           0 :                 msg = createException(MAL, "mal.manifold",
     394             :                                                           SQLSTATE(HY013) MAL_MALLOC_FAIL);
     395           0 :                 goto wrapup;
     396             :         }
     397         801 :         mat[0].b->tnonil = false;
     398         801 :         mat[0].b->tsorted = false;
     399         801 :         mat[0].b->trevsorted = false;
     400         801 :         mat[0].b->tkey = false;
     401         801 :         mat[0].bi = (BATiter) {.b = NULL, };
     402         801 :         mat[0].first = (void *) Tloc(mat[0].b, 0);
     403         801 :         mat[0].last = (void *) Tloc(mat[0].b, BATcount(mat[0].b));
     404             : 
     405         801 :         mut.pci = copyInstruction(pci);
     406         810 :         if (mut.pci == NULL) {
     407           0 :                 msg = createException(MAL, "mal.manifold",
     408             :                                                           SQLSTATE(HY013) MAL_MALLOC_FAIL);
     409           0 :                 goto wrapup;
     410             :         }
     411         810 :         mut.pci->fcn = fcn;
     412         810 :         msg = MANIFOLDjob(&mut);
     413         803 :         freeInstruction(mut.pci);
     414             : 
     415         809 :   wrapup:
     416             :         // restore the argument types
     417        3970 :         for (i = pci->retc; i < pci->argc; i++) {
     418        3164 :                 if (mat[i].b) {
     419        1057 :                         bat_iterator_end(&mat[i].bi);
     420        1057 :                         BBPunfix(mat[i].b->batCacheid);
     421             :                 }
     422             :         }
     423         806 :         if (msg) {
     424           6 :                 BBPreclaim(mat[0].b);
     425         800 :         } else if (!msg) {
     426             :                 // consolidate the properties
     427         800 :                 if (ATOMstorage(mat[0].b->ttype) < TYPE_str)
     428         506 :                         BATsetcount(mat[0].b, cnt);
     429         802 :                 BATsettrivprop(mat[0].b);
     430         789 :                 *getArgReference_bat(stk, pci, 0) = mat[0].b->batCacheid;
     431         789 :                 BBPkeepref(mat[0].b);
     432             :         }
     433         795 :         GDKfree(mat);
     434         807 :         return msg;
     435             : }
     436             : 
     437             : // The old code
     438             : static str
     439           1 : MANIFOLDremapMultiplex(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr p)
     440             : {
     441           1 :         (void) mb;
     442           1 :         (void) cntxt;
     443           4 :         throw(MAL, "mal.multiplex", "Function '%s.%s' not defined",
     444           1 :                   *getArgReference_str(stk, p, p->retc),
     445           1 :                   *getArgReference_str(stk, p, p->retc + 1));
     446             : }
     447             : 
     448             : #include "mel.h"
     449             : mel_func manifold_init_funcs[] = {
     450             :  pattern("mal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,4, varargany("",0),arg("mod",str),arg("fcn",str),varargany("a",0))),
     451             :  pattern("mal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,4, varargany("",0),arg("card", lng), arg("mod",str),arg("fcn",str))),
     452             :  pattern("mal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,5, varargany("",0),arg("card", lng), arg("mod",str),arg("fcn",str),varargany("a",0))),
     453             :  pattern("batmal", "multiplex", MANIFOLDremapMultiplex, false, "", args(1,4, varargany("",0),arg("mod",str),arg("fcn",str),varargany("a",0))),
     454             :  pattern("mal", "manifold", MANIFOLDevaluate, false, "", args(1,4, batargany("",0),arg("mod",str),arg("fcn",str),varargany("a",0))),
     455             :  { .imp=NULL }
     456             : };
     457             : #include "mal_import.h"
     458             : #ifdef _MSC_VER
     459             : #undef read
     460             : #pragma section(".CRT$XCU",read)
     461             : #endif
     462         345 : LIB_STARTUP_FUNC(init_manifold_mal)
     463         345 : { mal_module("manifold", NULL, manifold_init_funcs); }

Generated by: LCOV version 1.14