home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: SysTools / SysTools.zip / ft-beta.zip / freetype / docs / memory.txt < prev    next >
Text File  |  1997-10-06  |  23KB  |  576 lines

  1.  
  2. ----------------------------------------------------------------------------
  3.  
  4.  
  5. Proposal for a coherent and extensible object memory management model for
  6. the FreeType engine. Draft 0.
  7.  
  8. -------------------------------------------------------------------------
  9.  
  10. Introduction :
  11.  
  12.   FreeType's engine's current implementation uses a  simple  growing
  13.   heap, called the  "Font  Pool".   This  pool  allows easy but very
  14.   limited memory management.  Now that we have  introduced  features
  15.   to  support  multiple  opened fonts and even re-entrant builds, it
  16.   becomes important to be able  to  manage several objects with much
  17.   more flexibility.   This  text  is  an  overview  of  the  current
  18.   problems  that  we're  now faced with, as well as a proposal for a
  19.   new memory model.  This document  is  named 'draft 0', which means
  20.   that you're welcome to add your comments and suggestions...
  21.  
  22. I. The problems:
  23.  
  24.   A TrueType font file  is  reflected  in  memory  by a large set of
  25.   structures of various kinds and extents.  Each of these structures
  26.   can  be seen as an 'object' which must be managed according to its
  27.   function and some choices we may make in the process of developing
  28.   the library.  Because  the  FreeType  library  should be as easily
  29.   extended as possible, we must provide an object management  scheme
  30.   that is both simple and extensible.
  31.  
  32.   We'll start by classifying the data that we  manage  into  several
  33.   groups  to present many problems introduced by the requirements of
  34.   extensibility.
  35.  
  36.   a. According to domain:
  37.   
  38.     It is important first to separate our data depending on some few
  39.     high-level abstractions that  reflect  the  goal of the library.
  40.  
  41.     - font/face data (a.k.a resident data):
  42.  
  43.       This  label  groups  all the data  that is relative to a given
  44.       face, regardless  of  device  resolutions  or specific  sizes.
  45.  
  46.       In our current implementation,  this data is a tree of objects
  47.       which  root is  a  'TResident_Record'.   The  name  'resident'
  48.       comes from the fact that  I thought, initially, that face data
  49.       could  be  created and loaded  in a single step, and would not
  50.       require  additional  i/o  operations  later  (unlike  instance
  51.       data, which  needs much  'on-the-fly loading').   Though  this
  52.       is  still the case, some new introductions  could require more
  53.       flexibility.
  54.  
  55.       Examples:    Font Header, Maximum Profile,  OS/2  Table,  Font
  56.                    Program,   CVT   Program,   Control  Value  Table
  57.                    (unscaled),   Kerning,   horizontal   &  vertical
  58.                    headers, etc.
  59.  
  60.     - pointsize data (a.k.a instance data):
  61.  
  62.       This data is  specific  to  a  given device  resolution and/or
  63.       point size.  This regroups a lot of data  that must be  loaded
  64.       on  demand,  depending  on some attributes  like glyph number,
  65.       character  mapping table,  pixels  per  EM, etc.  The sizes of
  66.       the required instance objects  can  change  greatly  from  one
  67.       rendering/loading/computation to another.
  68.        
  69.       A  very  straightforward implementation  would lead to a great
  70.       number of calls  of the  'malloc' and 'free' functions, with a
  71.       wide distribution of memory  blocks sizes.  This usually leads
  72.       to  early  fragmentation,   slower  performance,   and  memory
  73.       'spreading'.
  74.  
  75.       However, one very nice  thing in the TrueType spec is that the
  76.       'Maximum  Profile' table gives a lot of information that helps
  77.       in computing the maximum  size  of a  face's instance objects.
  78.       It is  thus possible to  pre-allocate  enough  space  for  all
  79.       instance   objects  and   re-use  it  for  each  new  instance
  80.       operation.
  81.  
  82.       In our  current  implementation,  instance  data  is a tree of
  83.       objects which root is a 'TInstance_Record'. We  currently  use
  84.       the  growing  heap to  allocate all instance objects after the
  85.       record, in what is called the 'instance segment'.
  86.  
  87.       Examples:    scaled CVT,  coordinates and flag arrays, contour
  88.                    arrays, instruction  coderange,  graphics  state,
  89.                    device  resolutions, compensations, pointsize and
  90.                    ppem dependent tables, ...
  91.  
  92.     - transient data (like execution contexts):
  93.  
  94.       Some data is only  necessary  for some specific operation, and
  95.       doesn't need  to be kept with other font or instance  objects.
  96.  
  97.       For  example,  the execution  stack used to interpret TrueType
  98.       opcodes  during  hinting  typically  needs  about  7  Kb.  Now
  99.       imagine  a  desktop  publishing   application,   or   a   word
  100.       processor, that can easily keep more  than a dozen opened font
  101.       instances  for  its  work.   If we give  each instance its own
  102.       execution stack, we'll take  more  than  80 Kb of  memory that
  103.       will be mainly unused, as  hinting on all  instances  will  be
  104.       very  rarely,  if  ever,  requested at the  same moment  (even
  105.       assuming a completely re-entrant build of the library).
  106.  
  107.       It is  much better  in this case to be  able  to  ask  for  an
  108.       on-demand  execution  stack  when a  new  hinting operation is
  109.       queried.
  110.  
  111.       On a larger scope,  this  sort  of data is called 'transient',
  112.       though   transience  doesn't  represent  a  domain   entirely.
  113.       Transient  data  is used only on  specific purpose.  It should
  114.       be possible  to  manage  it   efficiently,  with  schemes like
  115.       caching.
  116.  
  117.       That's the reason why  the 'execution context' record has been
  118.       recently   introduced  in  the  implementation.   Though   all
  119.       transient data  is currently  allocated with the instance one,
  120.       it should be later possible to change its management.
  121.  
  122.  
  123.   b. According to size/storage:
  124.  
  125.     - static data:
  126.  
  127.       These are structs usually  defined  in  the TrueType specs, or
  128.       for our own purposes.  Their size and layout is fixed  and  is
  129.       the same for all font objects that use them.  They may contain
  130.       pointers to some  other  tables,  or  simply some other static
  131.       structures...
  132.  
  133.       Examples:  Font Header,  Maximum Profile,  Horizontal  Header,
  134.                  Graphics State, Execution Context, Resident Record,
  135.                  Instance Record, ...
  136.  
  137.  
  138.     - linear data:
  139.  
  140.       These  are  arrays  of  simple  static  data.   Their  size is
  141.       proportional to one of the font's properties, like the  number
  142.       of glyphs, the maximum number of points, etc.
  143.  
  144.       Examples:  Glyph  Locations,  Coordinates  and  Flags  arrays,
  145.                  Contour array, ...
  146.  
  147.  
  148.     - attribute data:
  149.  
  150.       Data  found  in  the TrueType file in a simple form, but which
  151.       size varies with the nature and origin of the font.  Attribute
  152.       data  is   usually  put  in  the   resident  data.   Note:  It
  153.       *doesn't* contain pointers!   Attribute data  can be shared by
  154.       instances, or  replicated (e.g. the CVT:  while  the  original
  155.       is  kept  in  the resident segment, scaled tables are found in
  156.       the instance data).
  157.  
  158.       Note that the objects that are created to store all iterations
  159.       of  instance data operations (like glyph  instructions arrays,
  160.       execution stacks, etc.) are classified as 'attribute'  objects
  161.       too.  However, the  data that  usually  comes  from  the  font
  162.       file  and  is stored  in  the  attribute  objects  (e.g. glyph
  163.       instructions) belongs not to this class.
  164.  
  165.       'Attribute' is a common label for all properties shared by all
  166.       instances of a font, though their size isn't either static  or
  167.       linear.   A glyph's  instruction set is not a  common property
  168.       (it's even glyph dependent,  not instance dependent), however,
  169.       the maximum size of the glyph code range _is_ ...
  170.  
  171.       Example:  Font program, CVT program, Control Value Table, ...
  172.  
  173.  
  174.     - hybrid data:
  175.  
  176.       All data that doesn't come in one of the three previous forms.
  177.       Usually, hybrid data are made of a static part and one or more
  178.       linear parts of varying sizes.
  179.  
  180.       Examples: HMTX Table, Character Mappings, Embedded bitmaps,...
  181.  
  182.       It is sometimes difficult to pre-compute the maximum  size  of
  183.       some   of   the  hybrid  data.   The  memory  structures  that
  184.       manipulate them must then be coded with greater care.
  185.  
  186.  
  187.   c. According to management:
  188.      
  189.     Several different management  issues  arise,  depending  on  the
  190.     nature and use of some of our objects:
  191.  
  192.     - Shared objects:
  193.  
  194.       We need a scheme that let us share some objects  easily.   For
  195.       example, all instances of a same face point to the same parent
  196.       resident  record.   It  is important to see that we don't know
  197.       completely how the FreeType engine  is going to be extended in
  198.       the  future; this means  that  among  all  of  the  well-known
  199.       techniques  used  to manage sharing, one should focus on these
  200.       that can provide a  good separation between implementation and
  201.       semantics.
  202.       
  203.     - Cached objects:
  204.  
  205.       As  seen  previously  in the description of transient data, it
  206.       may be  necessary  or  useful  to  cache  some  important data
  207.       structures.  As for sharing, it should be noted that the  most
  208.       interesting  techniques  are those that allow us to extend and
  209.       change the management of various objects in the simplest way.
  210.  
  211.  
  212. II. Some proposed solutions:
  213.  
  214.   1. Separating implementation from use whenever possible:
  215.  
  216.     It is, IMHO, very important  to  be  able to separate the way we
  217.     use our objects  from  the  way  they  are  indeed  implemented.
  218.  
  219.     For example, as we're heading towards  multiple  builds  of  the
  220.     same  library  (singly-threaded,  thread-safe,  and  re-entrant)
  221.     through the use of macros, it is  much better to be able to have
  222.     as few #ifdefs as possible.  Not only  do  they  make  the  code
  223.     difficult  to read and understand, they also make it much harder
  224.     to maintain and extend.  Only by  carefully selecting our macros
  225.     and their use can we craft  a convenient engine.  It probably is
  226.     a  bad  thing to cast details about sharing and caching within a
  227.     lot of routines when it's  perfectly  possible to gather them in
  228.     specific portions of the code that are called by the rest of the
  229.     library. The following proposals are inspired by this idea:
  230.  
  231.   a. Implementing sharing and caching of objects:
  232.  
  233.     Consider the case of tracking the instances of a given face.  We
  234.     could be happy by managing a simple reference  counter  in  each
  235.     TResident_Record.   The counter would be incremented on instance
  236.     creation,  and  decremented  on  instance  deletion.   A  zeroed
  237.     counter would imply an  unused  face  object  that could then be
  238.     discarded.  A re-entrant build  would  require  the  use  of  an
  239.     additionnal mutex to protect the counter, but this wouldn't need
  240.     that much work.
  241.  
  242.     However, we may also need to maintain a list of these instances,
  243.     to be able to close all  of  them  at once when  desired (either
  244.     through an API call  like  "Close_Face",  or  when  the  library
  245.     detects  a  broken  or corrupt font file).   This is a different
  246.     management choice. Both choices have their benefit and cost, and
  247.     could be required by the nature of the system we want to write a
  248.     font server for.
  249.  
  250.     The idea is to be able to change the implemented scheme  easily.
  251.  
  252.     In order to do  that, I recommend  that we  define  some  simple
  253.     specific  components,  called  'managers', that could be used to
  254.     perform the  low-level  tracking  operation,  while  providing a
  255.     simple  API  to  the  rest of  the  library,  e.g. an 'instance'
  256.     manager could provide the following functions:
  257.  
  258.       PInstance_Record  New_Instance( PResident_Record face );
  259.       // Return a new instance record for a given face
  260.       // The record is the root of a tree of instance objects
  261.       // that are allocated/recycled by the manager.
  262.  
  263.       void  Done_Instance( PInstance_Record  this );
  264.       // Discards or recycles an instance's data. The owner
  265.       // resident data can be discarded when zeroed depending
  266.       // on the chosen implementation within the manager.
  267.  
  268.       PExecution_Context New_Execution( PInstance_Record  instance );
  269.       // Queries a new execution context for a given instance object
  270.       // This should be called before a new execution. The execution
  271.       // context could be allocated on the fly.
  272.  
  273.       void  Done_Execution( PInstance_Record  instance );
  274.       // Discards or recycles an execution context previously
  275.       // queried through 'New_Execution'.
  276.  
  277.    The implementation of these functions could  deal  with  all  the
  278.    details  regarding  sharing  and  caching,  while the rest of the
  279.    library would be freed from these considerations.
  280.  
  281.    Note that these functions have nothing to do with the loading  or
  282.    interpretation  of  rueType  data.  The managers' functionalities
  283.    should  focus  on   the   sole   purpose   of  object  management
  284.    (allocation/sharing/caching/recycling).
  285.  
  286.  
  287.   b. Using generic lists:
  288.  
  289.     The previous example has shown that  we  may not be able to know
  290.     whether  we'll need to  track  closely  some structures or  not.
  291.     That's why  I recommend  the use of  generic containers whenever
  292.     possible.
  293.  
  294.     When putting objects in lists, either singly  or  doubly  linked
  295.     ones,  one  can  either  insert  some  link  pointers within the
  296.     objects, or use generic  link  elements.  The latter solution is
  297.     preferred, as it eases the extension of the library.   Moreover,
  298.     it  becomes possible to use a single list manager, independently
  299.     from the linked objects' types and functions.
  300.  
  301.            ______      ______      ______
  302.           |      |    |      |    |      |
  303.        -->| Next----->| Next----->| Next----->0 (NULL)
  304.           |      |    |      |    |      |
  305.           | Data |    | Data |    | Data |
  306.           |__|___|    |__|___|    |__|___|
  307.              |           |           |
  308.              |           |           |
  309.              v           v           v
  310.         __________   __________    __________
  311.        |          | |          |  |          |
  312.        | Object   | | Object   |  | Object   |
  313.        |          | |          |  |          |
  314.        |__________| |__________|  |__________|
  315.  
  316.      Example:  Use of a generic list. The list is made of simple
  317.                linked 'List_Element' structures, containing fields
  318.                like a 'Next' link, and a 'Data' field used to point
  319.                to the actual object.
  320.  
  321.      It  is  important that only the manager modifies or even parses
  322.      list elements (this issue is critical for reentrant builds).
  323.  
  324.      The objects do not  necessarily  need  a  pointer to their list
  325.      element (which  could  be  added  in  the  object's   structure
  326.      definitions,   hough  modified   by  the  manager  only).   For
  327.      example, these objects could be instance records, which already
  328.      contain a pointer to their  common resident record.  The latter
  329.      could then contain the head of the linked list.
  330.  
  331.      And finally it should  be noted  that  we  may  introduce  some
  332.      additional  fields  into  the  list elements to hold some extra
  333.      information,  like  flags   or  optional  destructor  functions
  334.      pointers for the pointed objects etc.
  335.  
  336.      One  should  easily  alternate  between singly or doubly linked
  337.      lists if  required  without  touching  anything  else  than the
  338.      manager (and eventually the objects' type declaration to add or
  339.      remove some fields).
  340.  
  341.    c. Add your own suggestions here...
  342.  
  343.  
  344.   2. Memory Allocation and Object Initialisation/Destruction:
  345.  
  346.     The data that  is created and  used to manage a  specific domain
  347.     concept (like a face, or an instance),  is represented by a tree
  348.     of objects.   For example,  the resident data  is made of a root
  349.     TResident_Record, which contains pointers to  some other tables,
  350.     which  may in  turn contain  pointers to  some sub-tables  (like
  351.     TResident_Record->CMap_Directory->CMap_Table).
  352.     
  353.     When we  want to delete a  face, we have to  parse the tree in a
  354.     depth-first  order  to delete  all sub-tables,  which means that
  355.     destruction  is  not  a trivial  operation.   One must  know the
  356.     structure and layout of the  implemented hierarchy of objects to
  357.     do it right.
  358.  
  359.     If we want to  be able to extend the  hierarchy easily,  it is a
  360.     good idea to isolate 'destructor' functions.
  361.  
  362.     Now, consider the fact that, as we may load a lot of information
  363.     on-demand,  some tables may,  or may not,  be present in memory.
  364.     Consider also  the case when  we discover  that a  font  file is
  365.     broken, while loading  one table and then must decide to destroy
  366.     what was previously built.  There are many ways to do that. Here
  367.     is one:
  368.  
  369.     (Note: this is fictious code)
  370.  
  371.     Bool  Load_Resident_Table( TT_Stream          stream,
  372.                                PResident_Record*  res )
  373.     {
  374.       ...
  375.  
  376.       if ( !Load_TrueType_Directory( resident ) )
  377.           goto Error_1;
  378.  
  379.       if ( !Load_TrueType_Header( resident ) )
  380.           goto Error_2;
  381.  
  382.       if ( !Load_TrueType_MaxProfile( resident ) )
  383.           goto Error_3;
  384.  
  385.       if ( !Load_TrueType_CMaps( resident ) )
  386.           goto Error_4;
  387.  
  388.       ...
  389.  
  390.       // success, do the following work
  391.  
  392.       res = resident;
  393.       return SUCCESS;
  394.  
  395.     Error_n:
  396.        ...
  397.  
  398.     Error_5:
  399.        Free_CMaps( resident );
  400.  
  401.     Error_4:
  402.        Free_MaxProfile( resident );
  403.  
  404.     Error_3:
  405.        Free_Header( resident );
  406.  
  407.     Error_2:
  408.        Free_Directory( resident );
  409.  
  410.     Error_1:
  411.        // nothing to free
  412.  
  413.        res = NULL;
  414.        return FAILURE;
  415.     }       
  416.    
  417.     One can see that, in this example, allocations are 'unrolled' in
  418.     the case  of an error.  Though this  is elegant,  it requires an
  419.     additional function to destroy the resident data normally when a
  420.     face's life-cycle ends.  This means another routine, that should
  421.     look like:
  422.  
  423.     void  Free_Resident( PResident_Record  res )
  424.     {
  425.       ....
  426.       Free_CMaps     ( resident );
  427.       Free_MaxProfile( resident );
  428.       Free_Header    ( resident );
  429.       Free_Directory ( resident );
  430.     }
  431.  
  432.     This means that we now have two iterations of a similar code. If
  433.     we now decide to  add a new table to the resident record,  we'll
  434.     have to  add a call to its loader in 'Load_Resident_Table',  and
  435.     two  calls  to  its  destructor  in  both  the  loader  and  the
  436.     destructor. That isn't really a good idea.
  437.  
  438.     Now imagine the case of more dynamic data, where some sub-tables
  439.     may, or may not be there, depending on various and unpredictable
  440.     factors (font file contents, user calls, etc.).  This introduces
  441.     some  complexities in  the destructor  that are not  necessarily
  442.     duplicated  in the  loader.  Now,  we  would  have two different
  443.     destruction  schemes  for the  same  data.  This is  still a bad
  444.     thing.
  445.  
  446.     That's why I think that  each table/structure that is not static
  447.     needs a single destructor, that must be able to handle partially
  448.     built data.
  449.    
  450.     Something that should look like:
  451.  
  452.     void  Free_Resident( PResident_Record  res )
  453.     {
  454.       ...
  455.  
  456.       if ( res->cmaps_table )
  457.       {
  458.         Free_CMaps( res->cmaps_table );
  459.         res->cmaps_table = NULL;
  460.         res->numCMaps    = 0;
  461.       }
  462.  
  463.       if ( res->maxProfile )
  464.       {
  465.         Free_MaxProfile( res->maxProfile );
  466.         res->maxProfile = NULL;
  467.       }
  468.  
  469.       if ( res->fontHeader )
  470.       {
  471.         Free_Header( res->fontHeader );
  472.         res->fontHeader = NULL;
  473.       }
  474.  
  475.       ...
  476.  
  477.     }
  478.  
  479.    Using a simple convention: 
  480.  
  481.      a NULL pointer indicates that the table isn't there. Otherwise,
  482.      it should  be destroyed,  and other  fields  associated to  the
  483.      table should be set to 0 (like the 'res->numCMaps').
  484.  
  485.    The Loader could also become simpler:
  486.  
  487.    {
  488.      ...
  489.  
  490.      resident = New_Resident_Header();
  491.      // Get a new (fresh or recycled) resident header
  492.  
  493.      Init_Resident_Header( resident );
  494.      // Inits the resident header
  495.  
  496.      if ( ! Load_Table1( resident )  ||  // try to load the sub-tables
  497.           ! Load_Table2( resident )  ||
  498.           ! Load_Table3( resident ) )
  499.      {
  500.        // an error occured
  501.        Free_Resident_Header( resident );
  502.        *res = NULL;
  503.        return FAILURE;
  504.      }
  505.  
  506.      // success
  507.  
  508.      *res = resident;
  509.      return SUCCESS;
  510.    }
  511.  
  512.    Here, we can see that:
  513.  
  514.      1. We obtain the a new resident header through a  function call
  515.         (or a macro),  which is better  than using ALLOC or (malloc)
  516.         directly.
  517.  
  518.      2. We  used  a  function  'Init_xx',   that  I   would  call  a
  519.         'constructor' which main  role is to clear all fields of the
  520.         resident record, especially those that contain pointers. The
  521.         constructor  can also  contain  some other  functionalities,
  522.         like  creating  a new mutex  for the  object in a  reentrant
  523.         build.   These are important  tasks,  but I don't think that
  524.         they should be performed by the loader function directly.
  525.  
  526.    So the idea  is to declare,  for every  table that  might contain
  527.    pointers to an owned subtable, several functions:
  528.  
  529.    - an  Init_xxxxxx  constructor,  used to  set all  fields to 0 or
  530.      NULL.
  531.  
  532.      The constructor can be a macro that casts a simple
  533.  
  534.        memset( table, sizeof( *table ), 0 )
  535.  
  536.      But it's good to have the  ability to change it for the sake of
  537.      extensibility.
  538.  
  539.      In some cases,  it's  simply  better to  allocate and  load the
  540.      tables at the same time, in the same routine (that's what we're
  541.      currently doing).  If we decide to keep this technique for some
  542.      of our functions, we'll need however a decent destructor...
  543.  
  544.    - a Free_xxxxx destructor,  that should test  the availability of
  545.      each  sub-table  pointer before  freeing  them  (this  issue is
  546.      important in the cases of partial and dynamic allocations).
  547.  
  548.    and possibly:
  549.  
  550.    - some  life-cycle  functions like New_xxxxx  and Done_xxxxx that
  551.      would allow  later changes in  the way we manage  some of these
  552.      tables.  They are not a requirement  for all tables  but can be
  553.      very useful.
  554.  
  555.    I think that the names of  these functions are quite  expressive,
  556.    but we could also find some other ones.
  557.  
  558.    Talking about  migration,  I think  it should  be fairly  easy to
  559.    begin writing the  Init_xxx and Free_xxx functions for our common
  560.    non-static  tables.  That would  probably be a great  step in the
  561.    right direction.
  562.  
  563.    I don't think we'll need to touch a lot of code.  We already have
  564.    some ALLOC  macros that can be changed  very easily to get rid of
  565.    the growing heap.
  566.  
  567.  
  568. To be continued ...
  569.  
  570.  
  571.  
  572.  
  573.  
  574.     
  575.  
  576.