Skip to content

[mlir] fix LLVM type converter for structs #73231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 23, 2023
Merged

Conversation

ftynse
Copy link
Member

@ftynse ftynse commented Nov 23, 2023

Existing implementation of the LLVM type converter for LLVM structs containing incompatible types was attempting to change identifiers of the struct in case of name clash post-conversion (all identified structs have different names post-conversion since one cannot change the body of the struct once initialized). Beyond a trivial error of not updating the counter in renaming, this approach was broken for recursive structs that can't be made aware of the renaming and would use the pre-existing struct with clashing name instead.

For example, given

!llvm.struct<"_Converted.foo", (struct<"_Converted.foo">, f32)>

the following type

!llvm.struct<"foo", (struct<"foo", index>)>

would incorrectly convert to

!llvm.struct<"_Converted_1.foo",
             (struct<"_Converted.foo",
	             (struct<"_Converted.foo">, f32)>)>

Remove this incorrect renaming and simply refuse to convert types if it would lead to identifier clashes for structs with different bodies. Document the expectation that such generated names are reserved and must not be present in the input IR of the converter. If we ever actually need to use handle such cases, this can be achieved by temporarily renaming structs with reserved identifiers to an unreserved name and back in a pre/post-processing pass that does not use the type conversion infra.

@llvmbot
Copy link
Member

llvmbot commented Nov 23, 2023

@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-llvm

Author: Oleksandr "Alex" Zinenko (ftynse)

Changes

Existing implementation of the LLVM type converter for LLVM structs containing incompatible types was attempting to change identifiers of the struct in case of name clash post-conversion (all identified structs have different names post-conversion since one cannot change the body of the struct once initialized). Beyond a trivial error of not updating the counter in renaming, this approach was broken for recursive structs that can't be made aware of the renaming and would use the pre-existing struct with clashing name instead.

For example, given

!llvm.struct&lt;"_Converted.foo", (struct&lt;"_Converted.foo"&gt;, f32)&gt;

the following type

!llvm.struct&lt;"foo", (struct&lt;"foo", index&gt;)&gt;

would incorrectly convert to

!llvm.struct&lt;"_Converted_1.foo",
             (struct&lt;"_Converted.foo",
	             (struct&lt;"_Converted.foo"&gt;, f32)&gt;)&gt;

Remove this incorrect renaming and simply refuse to convert types if it would lead to identifier clashes for structs with different bodies. Document the expectation that such generated names are reserved and must not be present in the input IR of the converter. If we ever actually need to use handle such cases, this can be achieved by temporarily renaming structs with reserved identifiers to an unreserved name and back in a pre/post-processing pass that does not use the type conversion infra.


Full diff: https://github.com/llvm/llvm-project/pull/73231.diff

2 Files Affected:

  • (modified) mlir/docs/TargetLLVMIR.md (+15)
  • (modified) mlir/lib/Conversion/LLVMCommon/TypeConverter.cpp (+21-13)
diff --git a/mlir/docs/TargetLLVMIR.md b/mlir/docs/TargetLLVMIR.md
index 73a74c394f2af79..b7acf638be57ff4 100644
--- a/mlir/docs/TargetLLVMIR.md
+++ b/mlir/docs/TargetLLVMIR.md
@@ -290,6 +290,21 @@ memref<2 x vector<4x8 x f32>
 Tensor types cannot be converted to the LLVM dialect. Operations on tensors must
 be [bufferized](Bufferization.md) before being converted.
 
+### Conversion of LLVM Container Types with Non-Compatible Element Types
+
+Progressive lowering may result in there existing LLVM container types, such
+as LLVM dialect structures, containing non-compatible types:
+`!llvm.struct<(index)>`. Such types are converted recursively using the rules
+described above.
+
+Identified structures are converted to _new_ structures that have their
+identifiers prefixed with `_Converted.` since the bodies of identified types
+cannot be updated once initialized. Such names are considered _reserved_ and
+must not appear in the input code (in practice, C reserves names starting with
+`_` and a capital, and `.` cannot appear in valid C types anyway). If they do
+and have a different body than the result of the conversion, the type conversion
+will stop.
+
 ### Calling Conventions
 
 Calling conventions provides a mechanism to customize the conversion of function
diff --git a/mlir/lib/Conversion/LLVMCommon/TypeConverter.cpp b/mlir/lib/Conversion/LLVMCommon/TypeConverter.cpp
index 04496d6b8f63449..8d8f8a0546dba58 100644
--- a/mlir/lib/Conversion/LLVMCommon/TypeConverter.cpp
+++ b/mlir/lib/Conversion/LLVMCommon/TypeConverter.cpp
@@ -86,15 +86,7 @@ LLVMTypeConverter::LLVMTypeConverter(MLIRContext *ctx,
 
     if (type.isIdentified()) {
       auto convertedType = LLVM::LLVMStructType::getIdentified(
-          type.getContext(), ("_Converted_" + type.getName()).str());
-      unsigned counter = 1;
-      while (convertedType.isInitialized()) {
-        assert(counter != UINT_MAX &&
-               "about to overflow struct renaming counter in conversion");
-        convertedType = LLVM::LLVMStructType::getIdentified(
-            type.getContext(),
-            ("_Converted_" + std::to_string(counter) + type.getName()).str());
-      }
+          type.getContext(), ("_Converted." + type.getName()).str());
 
       SmallVectorImpl<Type> &recursiveStack = getCurrentThreadRecursiveStack();
       if (llvm::count(recursiveStack, type)) {
@@ -110,10 +102,26 @@ LLVMTypeConverter::LLVMTypeConverter(MLIRContext *ctx,
       if (failed(convertTypes(type.getBody(), convertedElemTypes)))
         return std::nullopt;
 
-      if (failed(convertedType.setBody(convertedElemTypes, type.isPacked())))
-        return failure();
-      results.push_back(convertedType);
-      return success();
+      // If the converted type has not been initialized yet, just set its body
+      // to be the converted arguments and return.
+      if (!convertedType.isInitialized()) {
+        if (failed(
+                convertedType.setBody(convertedElemTypes, type.isPacked()))) {
+          return failure();
+        }
+        results.push_back(convertedType);
+        return success();
+      }
+
+      // If it has been initialized and has the same body, just use it. This
+      // ensures that recursive structs keep being recursive rather than
+      // including a non-updated name.
+      if (TypeRange(convertedType.getBody()) == TypeRange(convertedElemTypes)) {
+        results.push_back(convertedType);
+        return success();
+      }
+
+      return failure();
     }
 
     SmallVector<Type> convertedSubtypes;

Copy link
Contributor

@gysit gysit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

It could be that the isPacked flag needs to be checked as well to avoid incompatibilities there?

@@ -290,6 +290,21 @@ memref<2 x vector<4x8 x f32>
Tensor types cannot be converted to the LLVM dialect. Operations on tensors must
be [bufferized](Bufferization.md) before being converted.

### Conversion of LLVM Container Types with Non-Compatible Element Types

Progressive lowering may result in there existing LLVM container types, such
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Progressive lowering may result in there existing LLVM container types, such
Progressive lowering may result in existing LLVM container types, such

nit: I believe the there should not be there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You accidentally deleted the wrong word, I think.

Existing implementation of the LLVM type converter for LLVM structs
containing incompatible types was attempting to change identifiers of
the struct in case of name clash post-conversion (all identified structs
have different names post-conversion since one cannot change the body of
the struct once initialized). Beyond a trivial error of not updating the
counter in renaming, this approach was broken for recursive structs that
can't be made aware of the renaming and would use the pre-existing
struct with clashing name instead.

For example, given

`!llvm.struct<"_Converted.foo", (struct<"_Converted.foo">, f32)>`

the following type

`!llvm.struct<"foo", (struct<"foo", index>)>`

would incorrectly convert to

```
!llvm.struct<"_Converted_1.foo",
             (struct<"_Converted.foo",
	             (struct<"_Converted.foo">, f32)>)>
```

Remove this incorrect renaming and simply refuse to convert types if it
would lead to identifier clashes for structs with different bodies.
Document the expectation that such generated names are reserved and must
not be present in the input IR of the converter. If we ever actually
need to use handle such cases, this can be achieved by temporarily
renaming structs with reserved identifiers to an unreserved name and
back in a pre/post-processing pass that does _not_ use the type
conversion infra.
@ftynse ftynse force-pushed the llvm-type-converter branch from b795075 to 4cf3bac Compare November 23, 2023 21:06
@ftynse ftynse merged commit 43bc81d into llvm:main Nov 23, 2023
@ftynse ftynse deleted the llvm-type-converter branch November 23, 2023 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants