features: Update compiler caching (#7675)
Compiler caching was using the `id()` function to refer to configuration dictionary objects. If these objects are garbage-collected, this can produce incorrect results (false positive cache hits). This change replaces `id()` with an object that keeps a reference to the config dictionary so that it is not garbage-collected.
This commit is contained in:
		 Peter Scheibel
					Peter Scheibel
				
			
				
					committed by
					
						 Tamara Dahlgren
						Tamara Dahlgren
					
				
			
			
				
	
			
			
			 Tamara Dahlgren
						Tamara Dahlgren
					
				
			
						parent
						
							0ea6e0f817
						
					
				
				
					commit
					31ff791180
				
			| @@ -278,7 +278,7 @@ def all_compilers(scope=None): | ||||
|     compilers = list() | ||||
|     for items in config: | ||||
|         items = items['compiler'] | ||||
|         compilers.append(compiler_from_config_entry(items)) | ||||
|         compilers.append(_compiler_from_config_entry(items)) | ||||
|     return compilers | ||||
|  | ||||
|  | ||||
| @@ -305,41 +305,69 @@ def compilers_for_arch(arch_spec, scope=None): | ||||
|     return list(get_compilers(config, arch_spec=arch_spec)) | ||||
|  | ||||
|  | ||||
| def compiler_from_config_entry(items): | ||||
|     config_id = id(items) | ||||
| class CacheReference(object): | ||||
|     """This acts as a hashable reference to any object (regardless of whether | ||||
|        the object itself is hashable) and also prevents the object from being | ||||
|        garbage-collected (so if two CacheReference objects are equal, they | ||||
|        will refer to the same object, since it will not have been gc'ed since | ||||
|        the creation of the first CacheReference). | ||||
|     """ | ||||
|     def __init__(self, val): | ||||
|         self.val = val | ||||
|         self.id = id(val) | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return self.id | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, CacheReference) and self.id == other.id | ||||
|  | ||||
|  | ||||
| def compiler_from_dict(items): | ||||
|     cspec = spack.spec.CompilerSpec(items['spec']) | ||||
|     os = items.get('operating_system', None) | ||||
|     target = items.get('target', None) | ||||
|  | ||||
|     if not ('paths' in items and | ||||
|             all(n in items['paths'] for n in _path_instance_vars)): | ||||
|         raise InvalidCompilerConfigurationError(cspec) | ||||
|  | ||||
|     cls  = class_for_compiler_name(cspec.name) | ||||
|  | ||||
|     compiler_paths = [] | ||||
|     for c in _path_instance_vars: | ||||
|         compiler_path = items['paths'][c] | ||||
|         if compiler_path != 'None': | ||||
|             compiler_paths.append(compiler_path) | ||||
|         else: | ||||
|             compiler_paths.append(None) | ||||
|  | ||||
|     mods = items.get('modules') | ||||
|     if mods == 'None': | ||||
|         mods = [] | ||||
|  | ||||
|     alias = items.get('alias', None) | ||||
|     compiler_flags = items.get('flags', {}) | ||||
|     environment = items.get('environment', {}) | ||||
|     extra_rpaths = items.get('extra_rpaths', []) | ||||
|  | ||||
|     return cls(cspec, os, target, compiler_paths, mods, alias, | ||||
|                environment, extra_rpaths, **compiler_flags) | ||||
|  | ||||
|  | ||||
| def _compiler_from_config_entry(items): | ||||
|     """Note this is intended for internal use only. To avoid re-parsing | ||||
|        the same config dictionary this keeps track of its location in | ||||
|        memory. If you provide the same dictionary twice it will return | ||||
|        the same Compiler object (regardless of whether the dictionary | ||||
|        entries have changed). | ||||
|     """ | ||||
|     config_id = CacheReference(items) | ||||
|     compiler = _compiler_cache.get(config_id, None) | ||||
|  | ||||
|     if compiler is None: | ||||
|         cspec = spack.spec.CompilerSpec(items['spec']) | ||||
|         os = items.get('operating_system', None) | ||||
|         target = items.get('target', None) | ||||
|  | ||||
|         if not ('paths' in items and | ||||
|                 all(n in items['paths'] for n in _path_instance_vars)): | ||||
|             raise InvalidCompilerConfigurationError(cspec) | ||||
|  | ||||
|         cls  = class_for_compiler_name(cspec.name) | ||||
|  | ||||
|         compiler_paths = [] | ||||
|         for c in _path_instance_vars: | ||||
|             compiler_path = items['paths'][c] | ||||
|             if compiler_path != 'None': | ||||
|                 compiler_paths.append(compiler_path) | ||||
|             else: | ||||
|                 compiler_paths.append(None) | ||||
|  | ||||
|         mods = items.get('modules') | ||||
|         if mods == 'None': | ||||
|             mods = [] | ||||
|  | ||||
|         alias = items.get('alias', None) | ||||
|         compiler_flags = items.get('flags', {}) | ||||
|         environment = items.get('environment', {}) | ||||
|         extra_rpaths = items.get('extra_rpaths', []) | ||||
|  | ||||
|         compiler = cls(cspec, os, target, compiler_paths, mods, alias, | ||||
|                        environment, extra_rpaths, **compiler_flags) | ||||
|         _compiler_cache[id(items)] = compiler | ||||
|         compiler = compiler_from_dict(items) | ||||
|         _compiler_cache[config_id] = compiler | ||||
|  | ||||
|     return compiler | ||||
|  | ||||
| @@ -367,7 +395,7 @@ def get_compilers(config, cspec=None, arch_spec=None): | ||||
|                                      target != 'any'): | ||||
|             continue | ||||
|  | ||||
|         compilers.append(compiler_from_config_entry(items)) | ||||
|         compilers.append(_compiler_from_config_entry(items)) | ||||
|  | ||||
|     return compilers | ||||
|  | ||||
|   | ||||
| @@ -129,7 +129,7 @@ def test_compiler_flags_from_config_are_grouped(): | ||||
|         'modules': None | ||||
|     } | ||||
|  | ||||
|     compiler = compilers.compiler_from_config_entry(compiler_entry) | ||||
|     compiler = compilers.compiler_from_dict(compiler_entry) | ||||
|     assert any(x == '-foo-flag foo-val' for x in compiler.flags['cflags']) | ||||
|  | ||||
|  | ||||
| @@ -179,9 +179,7 @@ def flag_value(flag, spec): | ||||
|     else: | ||||
|         compiler_entry = copy(default_compiler_entry) | ||||
|         compiler_entry['spec'] = spec | ||||
|         # Disable faulty id()-based cache (issue #7647). | ||||
|         compilers._compiler_cache = {} | ||||
|         compiler = compilers.compiler_from_config_entry(compiler_entry) | ||||
|         compiler = compilers.compiler_from_dict(compiler_entry) | ||||
|  | ||||
|     return getattr(compiler, flag) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user