1INTERNALS(1)          User Contributed Perl Documentation         INTERNALS(1)
2
3
4

NAME

6       PDL::Internals - description of some aspects of the current internals
7

SYNOPSIS

9         # let PDL tell you what it's doing
10         use PDL;
11         PDL::Core::set_debugging(1);
12         $pa = sequence(6, 3, 2);
13         $pb = $pa->slice('1:3');
14         $pc = $pb->matmult($pb);
15         $pd = $pc->dsumover;
16         print "pb=$pb\npc=$pc\npd=$pd";
17

DESCRIPTION

19   Intro
20       This document explains various aspects of the current implementation of
21       PDL. If you just want to use PDL for something, you definitely do not
22       need to read this. Even if you want to interface your C routines to PDL
23       or create new PDL::PP functions, you do not need to read this man page
24       (though it may be informative). This document is primarily intended for
25       people interested in debugging or changing the internals of PDL. To
26       read this, a good understanding of the C language and programming and
27       data structures in general is required, as well as some Perl
28       understanding. If you read through this document and understand all of
29       it and are able to point what any part of this document refers to in
30       the PDL core sources and additionally struggle to understand PDL::PP,
31       you will be awarded the title "PDL Guru".
32
33       Warning: If it seems that this document has gotten out of date, please
34       inform the PDL porters email list (pdl-devel@lists.sourceforge.net).
35       This may well happen.
36
37   ndarrays
38       The pdl data object is generally an opaque scalar reference into a pdl
39       structure in memory. Alternatively, it may be a hash reference with the
40       "PDL" field containing the scalar reference (this makes overloading
41       ndarrays easy, see PDL::Objects). You can easily find out at the Perl
42       level which type of ndarray you are dealing with. The example code
43       below demonstrates how to do it:
44
45          # check if this an ndarray
46          die "not an ndarray" unless UNIVERSAL::isa($pdl, 'PDL');
47          # is it a scalar ref or a hash ref?
48          if (UNIVERSAL::isa($pdl, "HASH")) {
49            die "not a valid PDL" unless exists $pdl->{PDL} &&
50               UNIVERSAL::isa($pdl->{PDL},'PDL');
51            print "This is a hash reference,",
52               " the PDL field contains the scalar ref\n";
53          } else {
54            print "This is a scalar ref that points to address $$pdl in memory\n";
55          }
56
57       The scalar reference points to the numeric address of a C structure of
58       type "pdl" which is defined in pdl.h. The mapping between the object at
59       the Perl level and the C structure containing the actual data and
60       structural that makes up an ndarray is done by the PDL typemap.  The
61       functions used in the PDL typemap are defined pretty much at the top of
62       the file pdlcore.h. So what does the structure look like:
63
64               struct pdl {
65                  unsigned long magicno; /* Always stores PDL_MAGICNO as a sanity check */
66                    /* This is first so most pointer accesses to wrong type are caught */
67                  int state;        /* What's in this pdl */
68
69                  pdl_trans *trans_parent; /* Opaque pointer to internals of transformation from
70                                       parent */
71
72                  pdl_vaffine *vafftrans;
73
74                  void*    sv;      /* (optional) pointer back to original sv.
75                                         ALWAYS check for non-null before use.
76                                         We cannot inc refcnt on this one or we'd
77                                         never get destroyed */
78
79                  void *datasv;        /* Pointer to SV containing data. Refcnt inced */
80                  void *data;            /* Null: no data alloced for this one */
81                  PDL_Indx nvals;           /* How many values allocated */
82                  int datatype;
83                  PDL_Indx   *dims;      /* Array of data dimensions */
84                  PDL_Indx   *dimincs;   /* Array of data default increments */
85                  short    ndims;     /* Number of data dimensions */
86
87                  unsigned char *broadcastids;  /* Starting index of the broadcast index set n */
88                  unsigned char nbroadcastids;
89
90                  pdl_trans_children trans_children;
91
92                  PDL_Indx   def_dims[PDL_NDIMS];   /* Preallocated space for efficiency */
93                  PDL_Indx   def_dimincs[PDL_NDIMS];   /* Preallocated space for efficiency */
94                  unsigned char def_broadcastids[PDL_NBROADCASTIDS];
95
96                  struct pdl_magic *magic;
97
98                  void *hdrsv; /* "header", settable from outside */
99                  PDL_Value value; /* to store at least one value */
100               };
101
102       This is quite a structure for just storing some data in - what is going
103       on?
104
105       Data storage
106
107       We are going to start with some of the simpler members: first of all,
108       there are the members (as of 2.078)
109
110               void *datasv;
111               PDL_Value value; /* to store at least one value */
112
113       which are a pointer to a Perl SV structure ("SV *"), and a union value
114       of all possible single PDL values. If the ndarray's whole data will fit
115       in the "value", the "datasv" will not be used except by "get_dataref"
116       in PDL::Core for temporary use by Perl code. It will then be destroyed
117       by the call to "upd_data" in PDL::Core method unless that is passed a
118       true value to keep the "datasv" around (largely used by the memory-
119       mapping implementation).
120
121       Otherwise, the SV is expected to be representing a string, in which the
122       data of the ndarray is stored in a tightly packed form. This pointer
123       counts as a reference to the SV so the reference count has been
124       incremented when the "SV *" was placed here (this reference count
125       business has to do with Perl's garbage collection mechanism -- don't
126       worry if this doesn't mean much to you). This pointer is allowed to
127       have the value "NULL" which means that there is no actual Perl SV for
128       this data, as alluded above.  Note the use of an SV* was purely for
129       convenience, it allows easy transformation of packed data from files
130       into ndarrays. Other implementations are not excluded.
131
132       The actual pointer to data is stored in the member
133
134               void *data;
135
136       which contains a pointer to a memory area with space for
137
138               PDL_Indx nvals;
139
140       data items of the data type of this ndarray.  PDL_Indx is either 'long'
141       or 'long long' depending on whether your perl is 64bit or not.
142
143       The data type of the data is stored in the variable
144
145               int datatype;
146
147       the values for this member are given in the enum "pdl_datatypes" (see
148       pdl.h). Currently we have byte, short, unsigned short, long, index
149       (either long or long long), long long, float and double (plus complex
150       equivalents) types, see also PDL::Types.
151
152       Dimensions
153
154       The number of dimensions in the ndarray is given by the member
155
156               int ndims;
157
158       which shows how many entries there are in the arrays
159
160               PDL_Indx   *dims;
161               PDL_Indx   *dimincs;
162
163       These arrays are intimately related: "dims" gives the sizes of the
164       dimensions and "dimincs" is always calculated by the code
165
166               PDL_Indx inc = 1;
167               for(i=0; i<it->ndims; i++) {
168                       it->dimincs[i] = inc; inc *= it->dims[i];
169               }
170
171       in the routine "pdl_resize_defaultincs" in "pdlapi.c".  What this means
172       is that the dimincs can be used to calculate the offset by code like
173
174               PDL_Indx offs = 0;
175               for(i=0; i<it->ndims; i++) {
176                       offs += it->dimincs[i] * index[i];
177               }
178
179       but this is not always the right thing to do, at least without checking
180       for certain things first.
181
182       Default storage
183
184       Since the vast majority of ndarrays don't have more than 6 dimensions,
185       it is more efficient to have default storage for the dimensions and
186       dimincs inside the PDL struct.
187
188               PDL_Indx   def_dims[PDL_NDIMS];
189               PDL_Indx   def_dimincs[PDL_NDIMS];
190
191       The "dims" and "dimincs" may be set to point to the beginning of these
192       arrays if "ndims" is smaller than or equal to the compile-time constant
193       "PDL_NDIMS". This is important to note when freeing an ndarray struct.
194       The same applies for the broadcastids:
195
196               unsigned char def_broadcastids[PDL_NBROADCASTIDS];
197
198       Magic
199
200       It is possible to attach magic to ndarrays, much like Perl's own magic
201       mechanism. If the member pointer
202
203                  struct pdl_magic *magic;
204
205       is nonzero, the PDL has some magic attached to it. The implementation
206       of magic can be gleaned from the file pdlmagic.c in the distribution.
207
208       State
209
210       One of the first members of the structure is
211
212               int state;
213
214       The possible flags and their meanings are given in "pdl.h".  These are
215       mainly used to implement the lazy evaluation mechanism and keep track
216       of ndarrays in these operations.
217
218       Transformations and virtual affine transformations
219
220       As you should already know, ndarrays often carry information about
221       where they come from. For example, the code
222
223               $y = $x->slice("2:5");
224               $y .= 1;
225
226       will alter $x. So $y and $x know that they are connected via a
227       "slice"-transformation. This information is stored in the members
228
229               pdl_trans *trans_parent;
230               pdl_vaffine *vafftrans;
231
232       Both $x (the parent) and $y (the child) store this information about
233       the transformation in appropriate slots of the "pdl" structure.
234
235       "pdl_trans" and "pdl_vaffine" are structures that we will look at in
236       more detail below.
237
238       The Perl SVs
239
240       When ndarrays are referred to through Perl SVs, we store an additional
241       reference to it in the member
242
243               void*    sv;
244
245       in order to be able to return a reference to the user when they want to
246       inspect the transformation structure on the Perl side.
247
248       Also, we store an opaque
249
250               void *hdrsv;
251
252       which is just for use by the user to hook up arbitrary data with this
253       sv.  This one is generally manipulated through sethdr and gethdr calls.
254
255   Smart references and transformations: slicing and dicing
256       Smart references and most other fundamental functions operating on
257       ndarrays are implemented via transformations (as mentioned above) which
258       are represented by the type "pdl_trans" in PDL.
259
260       A transformation links input and output ndarrays and contains all the
261       infrastructure that defines how:
262
263       •   output ndarrays are obtained from input ndarrays;
264
265       •   changes in smart-linked output ndarrays (e.g. the child of a sliced
266           parent ndarray) are flowed back to the input ndarray in
267           transformations where this is supported (the most often used
268           example being "slice" here);
269
270       •   datatype and size of output ndarrays that need to be created are
271           obtained.
272
273       In general, executing a PDL function on a group of ndarrays results in
274       creation of a transformation of the requested type that links all input
275       and output arguments (at least those that are ndarrays). In PDL
276       functions that support data flow between input and output args (e.g.
277       "slice", "index") this transformation links parent (input) and child
278       (output) ndarrays permanently until either the link is explicitly
279       broken by user request ("sever" at the Perl level) or all parents and
280       children have been destroyed. In those cases the transformation is
281       lazy-evaluated, e.g. only executed when ndarray values are actually
282       accessed.
283
284       In non-flowing functions, for example addition ("+") and inner products
285       ("inner"), the transformation is installed just as in flowing functions
286       but then the transformation is immediately executed and destroyed
287       (breaking the link between input and output args) before the function
288       returns.
289
290       It should be noted that the close link between input and output args of
291       a flowing function (like slice) requires that ndarray objects that are
292       linked in such a way be kept alive beyond the point where they have
293       gone out of scope from the point of view of Perl:
294
295         $x = zeroes(20);
296         $y = $x->slice('2:4');
297         undef $x;    # last reference to $x is now destroyed
298
299       Although $x should now be destroyed according to Perl's rules the
300       underlying "pdl" structure must actually only be freed when $y also
301       goes out of scope (since it still references internally some of $x's
302       data). This example demonstrates that such a dataflow paradigm between
303       PDL objects necessitates a special destruction algorithm that takes the
304       links between ndarrays into account and couples the lifespan of those
305       objects. The non-trivial algorithm is implemented in the function
306       "pdl_destroy" in pdlapi.c. In fact, most of the code in pdlapi.c is
307       concerned with making sure that ndarrays ("pdl *"s) are created,
308       updated and freed at the right times depending on interactions with
309       other ndarrays via PDL transformations (remember, "pdl_trans").
310
311   Accessing children and parents of an ndarray
312       When ndarrays are dynamically linked via transformations as suggested
313       above input and output ndarrays are referred to as parents and
314       children, respectively.
315
316       An example of processing the children of an ndarray is provided by the
317       method "badflag" in PDL::Bad (before 2.079, it only operated on the
318       children, though now it propagates to parents too).
319
320       Consider the following situation:
321
322        pdl> $x = rvals(7,7,{Centre=>[3,4]});
323        pdl> $y = $x->slice('2:4,3:5');
324        pdl> ? vars
325        PDL variables in package main::
326
327        Name         Type   Dimension       Flow  State          Mem
328        ----------------------------------------------------------------
329        $x           Double D [7,7]                P            0.38Kb
330        $y           Double D [3,3]                -C           0.00Kb
331
332       Now, if I suddenly decide that $x should be flagged as possibly
333       containing bad values, using
334
335        pdl> $x->badflag(1)
336
337       then I want the state of $y - its child - to be changed as well (since
338       it will either share or inherit some of $x's data and so be also bad),
339       so that I get a 'B' in the State field:
340
341        pdl> ? vars
342        PDL variables in package main::
343
344        Name         Type   Dimension       Flow  State          Mem
345        ----------------------------------------------------------------
346        $x           Double D [7,7]                PB           0.38Kb
347        $y           Double D [3,3]                -CB          0.00Kb
348
349       This bit of magic is performed by the "propagate_badflag" function,
350       which is in pdlapi.c.  Given an ndarray ("pdl *it"), the routine loops
351       through each "pdl_trans" structure, where access to this structure is
352       provided by the "PDL_CHILDLOOP_THISCHILD" macro.  The children of the
353       ndarray are stored in the "pdls" array, after the parents, hence the
354       loop from "i = ...nparents" to "i = ...npdls - 1".  Once we have the
355       pointer to the child ndarray, we can do what we want to it; here we
356       change the value of the "state" variable, but the details are
357       unimportant).  What is important is that we call "propagate_badflag" on
358       this ndarray, to ensure we loop through its children. This recursion
359       ensures we get to all the offspring of a particular ndarray.
360
361       Access to parents is similar, with the "for" loop replaced by:
362
363               for( i = 0;
364                    i < trans->vtable->nparents;
365                    i++ ) {
366                  /* do stuff with parent #i: trans->pdls[i] */
367               }
368
369   What's in a transformation ("pdl_trans")
370       All transformations are implemented as structures
371
372         struct pdl_trans {
373               int magicno; /* to detect memory overwrites */
374               short flags; /* state of the trans */
375               pdl_transvtable *vtable;   /* the all important vtable */
376               int __datatype; /* the type of the transformation */
377               void *params; /* Opaque pointer to "compiled representation" of transformation */
378               pdl *pdls[]; /* The pdls involved in the transformation */
379         };
380
381       The "params" member is an opaque pointer, typically to a C struct that
382       holds the "compiled representation" (generated by PDL::PP), and is the
383       way that information like "OtherPars" etc get communicated from
384       invoking code to the "redodims" function - effectively a closure, in
385       Perl/LISP terms. This is necessary because "redodims" is called by a
386       PDL-internal function, and therefore must have a fixed parameter list.
387
388       The transformation identifies all "pdl"s involved in the trans
389
390         pdl *pdls[];
391
392       This is a C99 "incomplete array type", and works because it is at the
393       end of the struct - PDL allocates the correct amount of memory based on
394       the "npdls" member of the "vtable". The trans records the state
395
396         short flags;
397
398       and the datatype
399
400         int __datatype;
401
402       of the trans (to which all ndarrays must be converted unless they are
403       explicitly typed, PDL functions created with PDL::PP make sure that
404       these conversions are done as necessary). Most important is the pointer
405       to the vtable (virtual table) that contains the actual functionality
406
407        pdl_transvtable *vtable;
408
409       The vtable structure in turn looks something like (slightly simplified
410       from pdl.h for clarity)
411
412         typedef struct pdl_transvtable {
413               int flags;
414               int nparents;   /* number of parent pdls (input) */
415               int npdls;      /* number of child pdls (output) */
416               char *per_pdl_flags;  /* optimization flags */
417               pdl_error (*redodims)(pdl_trans *tr);  /* figure out dims of children */
418               pdl_error (*readdata)(pdl_trans *tr);  /* flow parents to children  */
419               pdl_error (*writebackdata)(pdl_trans *tr); /* flow backwards */
420               pdl_error (*freetrans)(pdl_trans *tr, char);
421               int structsize;
422               char *name; /* the function's name */
423         } pdl_transvtable;
424
425       The transformation and vtable code is hardly ever written by hand but
426       rather generated by PDL::PP from concise descriptions.
427
428       Certain types of transformations can be optimized very efficiently
429       obviating the need for explicit "readdata" and "writebackdata" methods.
430       Those transformations are called pdl_vaffine. Most dimension
431       manipulating functions (e.g., "slice", "xchg") belong to this class.
432
433       The basic trick is that parent and child of such a transformation work
434       on the same (shared) block of data which they just choose to interpret
435       differently (by using different "dims", "dimincs" and "offs" on the
436       same data, compare the "pdl" structure above).  Each operation on an
437       ndarray sharing data with another one in this way is therefore
438       automatically flowed from child to parent and back -- after all they
439       are reading and writing the same block of memory. This is currently not
440       Perl thread safe -- no big loss since the whole PDL core is not
441       reentrant.
442
443   Callback functions
444       redodims
445
446       Works out the dimensions of ndarrays that need to be created and is
447       called from within the API function that should be called to ensure
448       that the dimensions of an ndarray are accessible (pdlapi.c):
449
450          pdl_error pdl_make_physdims(pdl *it)
451
452       readdata and writebackdata
453
454       Responsible for the actual computations of the child data from the
455       parents or parent data from those of the children, respectively (the
456       dataflow aspect).  "readdata" populates the children from the parents,
457       and "writebackdata" implements updating the parent(s) from the
458       child(ren) if dataflow is part of that transformation.  The PDL core
459       makes sure that these are called as needed when ndarray data is
460       accessed (lazy-evaluation). The general API function to ensure that an
461       ndarray is up-to-date is
462
463         pdl_error pdl_make_physvaffine(pdl *it)
464
465       which should be called before accessing ndarray data from XS/C (see
466       Core.xs for some examples).
467
468       freetrans
469
470       Frees dynamically allocated memory associated with the trans as needed.
471       If "redodims" has previously been called, it will free any vaffine-
472       associated memory. If the "destroy" parameter is true, it will also
473       free any bespoke "params"-connected memory - this will not be the case
474       if called before doing another "redodims".  Again, functions built with
475       PDL::PP make sure that freeing via these callbacks happens at the right
476       times.
477
478   Signatures: broadcasting over elementary operations
479       Most of that functionality of PDL broadcasting (automatic iteration of
480       elementary operations over multi-dim ndarrays) is implemented in the
481       file pdlbroadcast.c.
482
483       The PDL::PP generated functions (in particular the "readdata" and
484       "writebackdata" callbacks) use this infrastructure to make sure that
485       the fundamental operation implemented by the trans is performed in
486       agreement with PDL's broadcasting semantics.
487
488   Defining new PDL functions -- Glue code generation
489       Please, see PDL::PP and examples in the PDL distribution.
490       Implementation and syntax are currently far from perfect but it does a
491       good job!
492
493   The Core struct
494       As discussed in PDL::API, PDL uses a pointer to a structure to allow
495       PDL modules access to its core routines. The definition of this
496       structure (the "Core" struct) is in pdlcore.h (created by pdlcore.h in
497       Basic/Core) and looks something like
498
499        /* Structure to hold pointers core PDL routines so as to be used by
500         * many modules
501         */
502        struct Core {
503           I32    Version;
504           pdl*   (*SvPDLV)      ( SV*  );
505           void   (*SetSV_PDL)   ( SV *sv, pdl *it );
506           pdl*   (*pdlnew)      ( );
507           pdl*   (*tmp)         ( );
508           pdl*   (*create)      (int type);
509           pdl_error (*destroy)     (pdl *it);
510           ...
511        }
512        typedef struct Core Core;
513
514       The first field of the structure ("Version") is used to ensure
515       consistency between modules at run time; the following code is placed
516       in the BOOT section of the generated xs code:
517
518        if (PDL->Version != PDL_CORE_VERSION)
519          Perl_croak(aTHX_ "Foo needs to be recompiled against the newly installed PDL");
520
521       If you add a new field to the Core struct you should:
522
523       •    discuss it on the pdl porters email list
524            (pdl-devel@lists.sourceforge.net) and use the techniques in
525            PDL::FAQ 4.11.
526
527       •    increase by 1 the value of the "PDL_CORE_VERSION" C macro used to
528            populate the Version field, in pdlcore.h.
529
530       •    add documentation (e.g. to PDL::API) if it's a "useful" function
531            for external module writers (as well as ensuring the code is as
532            well documented as the rest of PDL ;)
533

BUGS

535       This description is far from perfect. If you need more details or
536       something is still unclear please ask on the pdl-devel mailing list
537       (pdl-devel@lists.sourceforge.net).
538

AUTHOR

540       Copyright(C) 1997 Tuomas J. Lukka (lukka@fas.harvard.edu), 2000 Doug
541       Burke (djburke@cpan.org), 2002 Christian Soeller & Doug Burke, 2013
542       Chris Marshall.
543
544
545
546perl v5.38.0                      2023-07-21                      INTERNALS(1)
Impressum