[libcxx-commits] [libcxx] ddb2baf - [libc++] Make sure we don't cache DSL functions too aggressively

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Fri Oct 9 07:22:55 PDT 2020


Author: Louis Dionne
Date: 2020-10-09T10:22:46-04:00
New Revision: ddb2baf9fbff3d574c6c2bd69c2c9569100509a4

URL: https://github.com/llvm/llvm-project/commit/ddb2baf9fbff3d574c6c2bd69c2c9569100509a4
DIFF: https://github.com/llvm/llvm-project/commit/ddb2baf9fbff3d574c6c2bd69c2c9569100509a4.diff

LOG: [libc++] Make sure we don't cache DSL functions too aggressively

To make sure we don't store a mutable object (which could be modified by
outside code without us noticing) as the cache key, we pickle the cache
key to get a byte stream. If two keys are unequal, we know for sure they
will not have the same pickling. And if they are equal, there's a large
chance they will have the same pickling. If they don't, we might end up
not reusing a cached entry when we could have, but at least the behavior
we'll have is semantically correct.

Added: 
    

Modified: 
    libcxx/test/libcxx/selftest/dsl/dsl.sh.py
    libcxx/utils/libcxx/test/dsl.py

Removed: 
    


################################################################################
diff  --git a/libcxx/test/libcxx/selftest/dsl/dsl.sh.py b/libcxx/test/libcxx/selftest/dsl/dsl.sh.py
index 3c90ebe63d91..d20781ab9ea6 100644
--- a/libcxx/test/libcxx/selftest/dsl/dsl.sh.py
+++ b/libcxx/test/libcxx/selftest/dsl/dsl.sh.py
@@ -85,6 +85,18 @@ def getSubstitution(self, substitution):
         return found[0]
 
 
+def findIndex(list, pred):
+    """Finds the index of the first element satisfying 'pred' in a list, or
+       'len(list)' if there is no such element."""
+    index = 0
+    for x in list:
+        if pred(x):
+            break
+        else:
+            index += 1
+    return index
+
+
 class TestHasCompileFlag(SetupConfigs):
     """
     Tests for libcxx.test.dsl.hasCompileFlag
@@ -172,6 +184,29 @@ def test_pass_arguments_to_program(self):
         args = ["first-argument", "second-argument"]
         self.assertEqual(dsl.programOutput(self.config, source, args=args), "")
 
+    def test_caching_is_not_too_aggressive(self):
+        # Run a program, then change the substitutions and run it again.
+        # Make sure the program is run the second time and the right result
+        # is given, to ensure we're not incorrectly caching the result of the
+        # first program run.
+        source = """
+        #include <cstdio>
+        int main(int, char**) {
+            std::printf("MACRO=%u\\n", MACRO);
+        }
+        """
+        compileFlagsIndex = findIndex(self.config.substitutions, lambda x: x[0] == '%{compile_flags}')
+        compileFlags = self.config.substitutions[compileFlagsIndex][1]
+
+        self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}',  compileFlags + ' -DMACRO=1')
+        output1 = dsl.programOutput(self.config, source)
+        self.assertEqual(output1, "MACRO=1\n")
+
+        self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}',  compileFlags + ' -DMACRO=2')
+        output2 = dsl.programOutput(self.config, source)
+        self.assertEqual(output2, "MACRO=2\n")
+
+
 class TestHasLocale(SetupConfigs):
     """
     Tests for libcxx.test.dsl.hasLocale

diff  --git a/libcxx/utils/libcxx/test/dsl.py b/libcxx/utils/libcxx/test/dsl.py
index ecbb59ec8a0b..78f9841c0673 100644
--- a/libcxx/utils/libcxx/test/dsl.py
+++ b/libcxx/utils/libcxx/test/dsl.py
@@ -7,6 +7,7 @@
 #===----------------------------------------------------------------------===##
 
 import os
+import pickle
 import pipes
 import platform
 import re
@@ -24,21 +25,17 @@ def _memoizeExpensiveOperation(extractCacheKey):
   """
   Allows memoizing a very expensive operation.
 
-  The caching is implemented as a list, and we search linearly for existing
-  entries. This is incredibly naive, however this allows the cache keys to
-  contain lists and other non-hashable objects. Assuming the operation we're
-  memoizing is very expensive, this is still a win anyway.
+  We pickle the cache key to make sure we store an immutable representation
+  of it. If we stored an object and the object was referenced elsewhere, it
+  could be changed from under our feet, which would break the cache.
   """
   def decorator(function):
-    cache = []
+    cache = {}
     def f(*args, **kwargs):
-      cacheKey = extractCacheKey(*args, **kwargs)
-      try:
-        result = next(res for (key, res) in cache if key == cacheKey)
-      except StopIteration: # This wasn't in the cache
-        result = function(*args, **kwargs)
-        cache.append((cacheKey, result))
-      return result
+      cacheKey = pickle.dumps(extractCacheKey(*args, **kwargs))
+      if cacheKey not in cache:
+        cache[cacheKey] = function(*args, **kwargs)
+      return cache[cacheKey]
     return f
   return decorator
 


        


More information about the libcxx-commits mailing list