From 01fbb66d84a5c0aefc225a2066bdce31b76b0fd3 Mon Sep 17 00:00:00 2001
From: "dmichael@google.com"
 <dmichael@google.com@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Wed, 22 Dec 2010 15:42:15 +0000
Subject: Improve documentation for the tools for generating size checks, based
 on comments on: http://codereview.chromium.org/5730003/

Also automated handling of long and unsigned long as suggested in review comments for that CL.

BUG=None
TEST=Reran generate_ppapi_size_checks.py;  produced the same output.

Review URL: http://codereview.chromium.org/6014007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69950 0039d316-1c4b-4281-b951-d872f2087c98
---
 ppapi/generate_ppapi_size_checks.py | 190 ++++++++++++++++++++++++++----------
 1 file changed, 138 insertions(+), 52 deletions(-)

(limited to 'ppapi/generate_ppapi_size_checks.py')

diff --git a/ppapi/generate_ppapi_size_checks.py b/ppapi/generate_ppapi_size_checks.py
index ba55602e..e1ba8a4 100644
--- a/ppapi/generate_ppapi_size_checks.py
+++ b/ppapi/generate_ppapi_size_checks.py
@@ -15,20 +15,9 @@ import subprocess
 import sys
 
 
-# The string that the PrintNamesAndSizes plugin uses to indicate a type is or
-# contains a pointer.
-HAS_POINTER_STRING = "HasPointer"
-# These are types that don't include a pointer but nonetheless have
-# architecture-dependent size.  They all are ultimately typedefs to 'long' or
-# 'unsigned long'.  If there get to be too many types that use 'long' or
-# 'unsigned long', we can add detection of them to the plugin to automate this.
-ARCH_DEPENDENT_TYPES = set(["khronos_intptr_t",
-                            "khronos_uintptr_t",
-                            "khronos_ssize_t",
-                            "khronos_usize_t",
-                            "GLintptr",
-                            "GLsizeiptr"
-                           ])
+# The string that the PrintNamesAndSizes plugin uses to indicate a type is
+# expected to have architecture-dependent size.
+ARCH_DEPENDENT_STRING = "ArchDependentSize"
 
 
 
@@ -45,10 +34,42 @@ class SourceLocation:
 
 class TypeInfo:
 
-  """A class representing information about a C++ type."""
+  """A class representing information about a C++ type.  It contains the
+  following fields:
+   - kind:  The Clang TypeClassName (Record, Enum, Typedef, Union, etc)
+   - name:  The unmangled string name of the type.
+   - size:  The size in bytes of the type.
+   - arch_dependent:  True if the type may have architecture dependent size
+                      according to PrintNamesAndSizes.  False otherwise.  Types
+                      which are considered architecture-dependent from 32-bit
+                      to 64-bit are pointers, longs, unsigned longs, and any
+                      type that contains an architecture-dependent type.
+   - source_location:  A SourceLocation describing where the type is defined.
+   - target:  The target Clang was compiling when it found the type definition.
+              This is used only for diagnostic output.
+   - parsed_line:  The line which Clang output which was used to create this
+                   TypeInfo (as the info_string parameter to __init__).  This is
+                   used only for diagnostic output.
+  """
 
   def __init__(self, info_string, target):
-    [self.kind, self.name, self.size, has_pointer_string, source_file,
+    """Create a TypeInfo from a given info_string.  Also store the name of the
+    target for which the TypeInfo was first created just so we can print useful
+    error information.
+    info_string is a comma-delimited string of the following form:
+    kind,name,size,arch_dependent,source_file,start_line,end_line
+    Where:
+   - kind:  The Clang TypeClassName (Record, Enum, Typedef, Union, etc)
+   - name:  The unmangled string name of the type.
+   - size:  The size in bytes of the type.
+   - arch_dependent:  'ArchDependentSize' if the type has architecture-dependent
+                      size, NotArchDependentSize otherwise.
+   - source_file:  The source file in which the type is defined.
+   - first_line:  The first line of the definition (counting from 0).
+   - last_line:  The last line of the definition (counting from 0).
+   This should match the output of the PrintNamesAndSizes plugin.
+   """
+    [self.kind, self.name, self.size, arch_dependent_string, source_file,
         start_line, end_line] = info_string.split(',')
     self.target = target
     self.parsed_line = info_string
@@ -56,24 +77,13 @@ class TypeInfo:
     self.source_location = SourceLocation(source_file,
                                           int(start_line)-1,
                                           int(end_line)-1)
-    # If the type is one of our known special cases, mark it as architecture-
-    # dependent.
-    if self.name in ARCH_DEPENDENT_TYPES:
-      self.arch_dependent = True
-    # Otherwise, its size can be architecture dependent if it contains one or
-    # more pointers (or is a pointer).
-    else:
-      self.arch_dependent = (has_pointer_string == HAS_POINTER_STRING)
-    self.target = target
-    self.parsed_line = info_string
-
+    self.arch_dependent = (arch_dependent_string == ARCH_DEPENDENT_STRING)
 
 
 class FilePatch:
 
   """A class representing a set of line-by-line changes to a particular file.
-  None
-  of the changes are applied until Apply is called.  All line numbers are
+  None of the changes are applied until Apply is called.  All line numbers are
   counted from 0.
   """
 
@@ -85,7 +95,9 @@ class FilePatch:
     self.lines_to_add = {}
 
   def Delete(self, start_line, end_line):
-    """Make the patch delete the lines [start_line, end_line)."""
+    """Make the patch delete the lines starting with |start_line| up to but not
+    including |end_line|.
+    """
     self.linenums_to_delete |= set(range(start_line, end_line))
 
   def Add(self, text, line_number):
@@ -96,7 +108,7 @@ class FilePatch:
       self.lines_to_add[line_number] = [text]
 
   def Apply(self):
-    """Apply the patch by writing it to self.filename"""
+    """Apply the patch by writing it to self.filename."""
     # Read the lines of the existing file in to a list.
     sourcefile = open(self.filename, "r")
     file_lines = sourcefile.readlines()
@@ -163,14 +175,12 @@ def CheckAndInsert(typeinfo, typeinfo_map):
     typeinfo_map[typeinfo.name] = typeinfo
 
 
-def ProcessTarget(clang_command, target, arch_types, ind_types):
+def ProcessTarget(clang_command, target, types):
   """Run clang using the given clang_command for the given target string.  Parse
   the output to create TypeInfos for each discovered type.  Insert each type in
-  to the appropriate dictionary.  For each type that has architecture-dependent
-  size, insert it in to arch_types.  Types with independent size go in to
-  ind_types.  If the type already exists in the appropriate map, make sure that
-  the size matches what's already in the map.  If not, the script terminates
-  with an error message.
+  to the 'types' dictionary.  If the type already exists in the types
+  dictionary, make sure that the size matches what's already in the map.  If
+  not, exit with an error message.
   """
   p = subprocess.Popen(clang_command + " -triple " + target,
                        shell=True,
@@ -178,13 +188,7 @@ def ProcessTarget(clang_command, target, arch_types, ind_types):
   lines = p.communicate()[0].split()
   for line in lines:
     typeinfo = TypeInfo(line, target)
-    # Put types which have architecture-specific size in to arch_types.  All
-    # other types are 'architecture-independent' and get put in ind_types.
-    # in the appropraite dictionary.
-    if typeinfo.arch_dependent:
-      CheckAndInsert(typeinfo, arch_types)
-    else:
-      CheckAndInsert(typeinfo, ind_types)
+    CheckAndInsert(typeinfo, types)
 
 
 def ToAssertionCode(typeinfo):
@@ -245,6 +249,26 @@ def WriteArchSpecificCode(types, root, filename):
 
 
 def main(argv):
+  # See README file for example command-line invocation.  This script runs the
+  # PrintNamesAndSizes Clang plugin with 'test_struct_sizes.c' as input, which
+  # should include all C headers and all existing size checks.  It runs the
+  # plugin multiple times;  once for each of a set of targets, some 32-bit and
+  # some 64-bit.  It verifies that wherever possible, types have a consistent
+  # size on both platforms.  Types that can't easily have consistent size (e.g.
+  # ones that contain a pointer) are checked to make sure they are consistent
+  # for all 32-bit platforms and consistent on all 64-bit platforms, but the
+  # sizes on 32 vs 64 are allowed to differ.
+  #
+  # Then, if all the types have consistent size as expected, compile assertions
+  # are added to the source code.  Types whose size is independent of
+  # architectureacross have their compile assertions placed immediately after
+  # their definition in the C API header.  Types whose size differs on 32-bit
+  # vs 64-bit have a compile assertion placed in each of:
+  # ppapi/tests/arch_dependent_sizes_32.h and
+  # ppapi/tests/arch_dependent_sizes_64.h.
+  #
+  # Note that you should always check the results of the tool to make sure
+  # they are sane.
   parser = optparse.OptionParser()
   parser.add_option(
       '-c', '--clang-path', dest='clang_path',
@@ -293,19 +317,70 @@ def main(argv):
 
   # Now run clang for each target.  Along the way, make sure architecture-
   # dependent types are consistent sizes on all 32-bit platforms and consistent
-  # on all 64-bit platforms.  Any types in 'types_independent' are checked for
-  # all targets to make sure their size is consistent across all of them.
+  # on all 64-bit platforms.
   targets32 = options.targets32.split(',');
   for target in targets32:
-    ProcessTarget(clang_command, target, types32, types_independent)
+    # For each 32-bit target, run the PrintNamesAndSizes Clang plugin to get
+    # information about all types in the translation unit, and add a TypeInfo
+    # for each of them to types32.  If any size mismatches are found,
+    # ProcessTarget will spit out an error and exit.
+    ProcessTarget(clang_command, target, types32)
   targets64 = options.targets64.split(',');
   for target in targets64:
-    ProcessTarget(clang_command, target, types64, types_independent)
-
-  # This dictionary maps file names to FilePatch objects.
+    # Do the same as above for each 64-bit target;  put all types in types64.
+    ProcessTarget(clang_command, target, types64)
+
+  # Now for each dictionary, find types whose size are consistent regardless of
+  # architecture, and move those in to types_independent.  Anywhere sizes
+  # differ, make sure they are expected to be architecture-dependent based on
+  # their structure.  If we find types which could easily be consistent but
+  # aren't, spit out an error and exit.
+  types_independent = {}
+  for typename, typeinfo32 in types32.items():
+    if (typename in types64):
+      typeinfo64 = types64[typename]
+      if (typeinfo64.size == typeinfo32.size):
+        # The types are the same size, so we can treat it as arch-independent.
+        types_independent[typename] = typeinfo32
+        del types32[typename]
+        del types64[typename]
+      elif (typeinfo32.arch_dependent or typeinfo64.arch_dependent):
+        # The type is defined in such a way that it would be difficult to make
+        # its size consistent.  E.g., it has pointers.  We'll leave it in the
+        # arch-dependent maps so that we can put arch-dependent size checks in
+        # test code.
+        pass
+      else:
+        # The sizes don't match, but there's no reason they couldn't.  It's
+        # probably due to an alignment mismatch between Win32/NaCl vs Linux32/
+        # Mac32.
+        print "Error: '" + typename + "' is", typeinfo32.size, \
+            "bytes on target '" + typeinfo32.target + \
+            "', but", typeinfo64.size, "on target '" + typeinfo64.target + "'"
+        print typeinfo32.parsed_line
+        print typeinfo64.parsed_line
+        sys.exit(1)
+    else:
+      print "WARNING:  Type '", typename, "' was defined for target '",
+      print typeinfo32.target, ", but not for any 64-bit targets."
+
+  # Now we have all the information we need to generate our static assertions.
+  # Types that have consistent size across architectures will have the static
+  # assertion placed immediately after their definition.  Types whose size
+  # depends on 32-bit vs 64-bit architecture will have checks placed in
+  # tests/arch_dependent_sizes_32/64.h.
+
+  # This dictionary maps file names to FilePatch objects.  We will add items
+  # to it as needed.  Each FilePatch represents a set of changes to make to the
+  # associated file (additions and deletions).
   file_patches = {}
 
-  # Find locations of existing macros, and just delete them all.
+  # Find locations of existing macros, and just delete them all.  Note that
+  # normally, only things in 'types_independent' need to be deleted, as arch-
+  # dependent checks exist in tests/arch_dependent_sizes_32/64.h, which are
+  # always completely over-ridden.  However, it's possible that a type that used
+  # to be arch-independent has changed to now be arch-dependent (e.g., because
+  # a pointer was added), and we want to delete the old check in that case.
   for name, typeinfo in \
       types_independent.items() + types32.items() + types64.items():
     if IsMacroDefinedName(name):
@@ -318,18 +393,29 @@ def main(argv):
   # Add a compile-time assertion for each type whose size is independent of
   # architecture.  These assertions go immediately after the class definition.
   for name, typeinfo in types_independent.items():
-    # Ignore macros and types that are 0 bytes (i.e., typedefs to void)
+    # Ignore dummy types that were defined by macros and also ignore types that
+    # are 0 bytes (i.e., typedefs to void).
     if not IsMacroDefinedName(name) and typeinfo.size > 0:
       sourcefile = typeinfo.source_location.filename
       if sourcefile not in file_patches:
         file_patches[sourcefile] = FilePatch(sourcefile)
       # Add the assertion code just after the definition of the type.
+      # E.g.:
+      # struct Foo {
+      #   int32_t x;
+      # };
+      # PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(Foo, 4); <---Add this line
       file_patches[sourcefile].Add(ToAssertionCode(typeinfo),
                                    typeinfo.source_location.end_line+1)
 
+  # Apply our patches.  This actually edits the files containing the definitions
+  # for the types in types_independent.
   for filename, patch in file_patches.items():
     patch.Apply()
 
+  # Write out a file of checks for 32-bit architectures and a separate file for
+  # 64-bit architectures.  These only have checks for types that are
+  # architecture-dependent.
   c_source_root = os.path.join(options.ppapi_root, "tests")
   WriteArchSpecificCode(types32.values(),
                         c_source_root,
-- 
cgit v1.1