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 2019-08-19 11:24:05 -07:00 committed by Tamara Dahlgren
parent 0ea6e0f817
commit 31ff791180
2 changed files with 64 additions and 38 deletions

View File

@ -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

View File

@ -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)