From dd24f4ab676cab6a2fe81922e3042ff1a866034c Mon Sep 17 00:00:00 2001 From: Adrian Seyboldt Date: Mon, 5 Dec 2022 19:14:19 -0600 Subject: [PATCH 1/3] Add support for self referential metadata --- llvmlite/ir/module.py | 18 ++++++++++++++---- llvmlite/ir/values.py | 17 ++++++++++++++--- llvmlite/tests/test_ir.py | 11 +++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/llvmlite/ir/module.py b/llvmlite/ir/module.py index 464f91ec3..b23c37047 100644 --- a/llvmlite/ir/module.py +++ b/llvmlite/ir/module.py @@ -42,22 +42,32 @@ def _fix_di_operands(self, operands): fixed_ops.append((name, op)) return fixed_ops - def add_metadata(self, operands): + def add_metadata(self, operands, *, self_ref=False): """ Add an unnamed metadata to the module with the given *operands* (a sequence of values) or return a previous equivalent metadata. A MDValue instance is returned, it can then be associated to e.g. an instruction. + + A self referential metadata entry has itself as the first + operand to ensure uniquencess. These references are never + cached. """ if not isinstance(operands, (list, tuple)): raise TypeError("expected a list or tuple of metadata values, " "got %r" % (operands,)) operands = self._fix_metadata_operands(operands) key = tuple(operands) - if key not in self._metadatacache: + if self_ref or key not in self._metadatacache: n = len(self.metadata) - md = values.MDValue(self, operands, name=str(n)) - self._metadatacache[key] = md + md = values.MDValue( + self, + operands, + name=str(n), + self_ref=self_ref, + ) + if not self_ref: + self._metadatacache[key] = md else: md = self._metadatacache[key] return md diff --git a/llvmlite/ir/values.py b/llvmlite/ir/values.py index 853927724..a2314321c 100644 --- a/llvmlite/ir/values.py +++ b/llvmlite/ir/values.py @@ -662,11 +662,14 @@ class MDValue(NamedValue): """ name_prefix = '!' - def __init__(self, parent, values, name): + def __init__(self, parent, values, name, *, self_ref=False): super(MDValue, self).__init__(parent, types.MetaDataType(), name=name) - self.operands = tuple(values) + # Store only the non-self params to avoid + # ref counting cycles + self._operands = tuple(values) + self._is_self_ref = self_ref parent.metadata.append(self) def descr(self, buf): @@ -687,6 +690,8 @@ def _get_reference(self): def __eq__(self, other): if isinstance(other, MDValue): + if self._is_self_ref: + return self is other return self.operands == other.operands else: return False @@ -695,7 +700,13 @@ def __ne__(self, other): return not self.__eq__(other) def __hash__(self): - return hash(self.operands) + return hash(self._operands) + + @property + def operands(self): + if self._is_self_ref: + return (self,) + self._operands + return self._operands class DIToken: diff --git a/llvmlite/tests/test_ir.py b/llvmlite/tests/test_ir.py index 82bcef352..04ffc0a44 100644 --- a/llvmlite/tests/test_ir.py +++ b/llvmlite/tests/test_ir.py @@ -323,6 +323,17 @@ def test_unnamed_metadata_3(self): self.assert_ir_line('!1 = !{ i32 789 }', mod) self.assert_ir_line('!2 = !{ i32 123, !0, !1, !0 }', mod) + def test_self_relf_metadata(self): + mod = self.module() + value = mod.add_metadata((), self_ref=True) + self.assert_ir_line('!0 = !{ !0 }', mod) + value2 = mod.add_metadata((value,)) + assert value is not value2 + self.assert_ir_line('!1 = !{ !0 }', mod) + value3 = mod.add_metadata((), self_ref=True) + self.assert_ir_line('!2 = !{ !2 }', mod) + assert value is not value3 + def test_metadata_string(self): # Escaping contents of a metadata string mod = self.module() From fb9d501d44f3a3b9d775826c5eb822e888754ee0 Mon Sep 17 00:00:00 2001 From: Adrian Seyboldt Date: Thu, 19 Jan 2023 16:57:24 -0600 Subject: [PATCH 2/3] Improve docstring for self_ref metadata --- llvmlite/ir/module.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/llvmlite/ir/module.py b/llvmlite/ir/module.py index b23c37047..ffb76039d 100644 --- a/llvmlite/ir/module.py +++ b/llvmlite/ir/module.py @@ -50,8 +50,11 @@ def add_metadata(self, operands, *, self_ref=False): e.g. an instruction. A self referential metadata entry has itself as the first - operand to ensure uniquencess. These references are never - cached. + operand to ensure uniqueness. These references are never + cached. If `self_ref` is set, create a self referential + metadata entry by first creating a new metadata value + and then add itself as first argument. `operands` are then + added as additional operands. """ if not isinstance(operands, (list, tuple)): raise TypeError("expected a list or tuple of metadata values, " From fcaa467036b916cf971385921143d19c88b2acae Mon Sep 17 00:00:00 2001 From: Adrian Seyboldt Date: Thu, 19 Jan 2023 16:57:42 -0600 Subject: [PATCH 3/3] Add multi operand test for self_ref metadata --- llvmlite/tests/test_ir.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/llvmlite/tests/test_ir.py b/llvmlite/tests/test_ir.py index 04ffc0a44..e777af034 100644 --- a/llvmlite/tests/test_ir.py +++ b/llvmlite/tests/test_ir.py @@ -323,7 +323,7 @@ def test_unnamed_metadata_3(self): self.assert_ir_line('!1 = !{ i32 789 }', mod) self.assert_ir_line('!2 = !{ i32 123, !0, !1, !0 }', mod) - def test_self_relf_metadata(self): + def test_self_ref_metadata(self): mod = self.module() value = mod.add_metadata((), self_ref=True) self.assert_ir_line('!0 = !{ !0 }', mod) @@ -333,6 +333,8 @@ def test_self_relf_metadata(self): value3 = mod.add_metadata((), self_ref=True) self.assert_ir_line('!2 = !{ !2 }', mod) assert value is not value3 + value = mod.add_metadata([int32(123)], self_ref=True) + self.assert_ir_line('!3 = !{ !3, i32 123 }', mod) def test_metadata_string(self): # Escaping contents of a metadata string