@@ -215,9 +215,62 @@ def _backend_for_code_type(code_type: str) -> str:
215215)
216216
217217
218+ # Map each linker-relevant ProgramOptions field to the gate the Linker uses
219+ # to turn it into a flag (see ``_prepare_nvjitlink_options`` and
220+ # ``_prepare_driver_options`` in _linker.pyx). Collapsing inputs through
221+ # these gates means semantically-equivalent configurations
222+ # (``debug=False`` vs ``None``, ``time=True`` vs ``time="path"``) hash to
223+ # the same cache key instead of forcing spurious misses.
224+ def _gate_presence (v ):
225+ return v is not None
226+
227+
228+ def _gate_truthy (v ):
229+ return bool (v )
230+
231+
232+ def _gate_is_true (v ):
233+ return v is True
234+
235+
236+ def _gate_tristate_bool (v ):
237+ return None if v is None else bool (v )
238+
239+
240+ def _gate_identity (v ):
241+ return v
242+
243+
244+ _LINKER_FIELD_GATES = {
245+ "name" : _gate_identity ,
246+ "arch" : _gate_identity ,
247+ "max_register_count" : _gate_identity ,
248+ "time" : _gate_presence , # linker emits ``-time`` iff value is not None
249+ "link_time_optimization" : _gate_truthy ,
250+ "debug" : _gate_truthy ,
251+ "lineinfo" : _gate_truthy ,
252+ "ftz" : _gate_tristate_bool ,
253+ "prec_div" : _gate_tristate_bool ,
254+ "prec_sqrt" : _gate_tristate_bool ,
255+ "fma" : _gate_tristate_bool ,
256+ "split_compile" : _gate_identity ,
257+ "ptxas_options" : _gate_identity ,
258+ "no_cache" : _gate_is_true ,
259+ }
260+
261+
218262def _linker_option_fingerprint (options : ProgramOptions ) -> list [bytes ]:
219- """Stable byte fingerprint of ProgramOptions fields consumed by the Linker."""
220- return [f"{ name } ={ getattr (options , name , None )!r} " .encode () for name in _LINKER_RELEVANT_FIELDS ]
263+ """Stable byte fingerprint of ProgramOptions fields consumed by the Linker.
264+
265+ Each field is first passed through the gate the Linker itself uses so
266+ that equivalent inputs (e.g. ``debug=False`` / ``None``) produce the
267+ same bytes. Without this, callers hitting the same linker behavior
268+ would get different cache keys and needlessly re-compile.
269+ """
270+ return [
271+ f"{ name } ={ _LINKER_FIELD_GATES [name ](getattr (options , name , None ))!r} " .encode ()
272+ for name in _LINKER_RELEVANT_FIELDS
273+ ]
221274
222275
223276# ProgramOptions fields that map to LinkerOptions fields the cuLink (driver)
@@ -585,9 +638,11 @@ def _probe(label: str, fn):
585638 else :
586639 # Fallback for unexpected format.
587640 _update ("extra_source" , str (item ).encode ("utf-8" ))
588- use_libdevice = getattr (options , "use_libdevice" , None )
589- if use_libdevice is not None :
590- _update ("use_libdevice" , str (use_libdevice ).encode ("ascii" ))
641+ # Program_init gates ``use_libdevice`` on truthiness, not ``is not None``
642+ # (see _program.pyx), so False and None produce identical ObjectCode and
643+ # must hash the same way here.
644+ if getattr (options , "use_libdevice" , None ):
645+ _update ("use_libdevice" , b"1" )
591646
592647 # Program.compile() propagates options.name onto the returned ObjectCode,
593648 # so two compiles identical in everything but name produce ObjectCodes
0 commit comments