This is an archive of the discontinued LLVM Phabricator instance.

[ConstantMerge] Only merge constant w/unnamed_addr
Needs ReviewPublic

Authored by gulfem on May 9 2023, 11:53 AM.

Details

Summary

Currently, ConstantMergePass merges an unnamed_addr with a
non-unnamed_addr constant as it is explained in LangRef.

"Note that a constant with significant address can be merged with a
unnamed_addr constant, the result being a constant whose address is
significant."

https://llvm.org/docs/LangRef.html#global-variables

This can result in a situation where Clang vioalates C semantics, and
here is a small reproducer to explain the problem:

const char foo_string[] = "foo";
const char* foo_func(void) { return "foo"; }
int is_foo(const char* p) { return p == foo_string; }

int main() {
  printf("is_foo: %d\n", is_foo("foo"));
}

When we compile with -O0, where ConstantMerge is not applied, Clang
and GCC have the same result.

clang -O0 foo.c -o foo
./foo
is_foo: 0

gcc -O0 foo.c -o foo
./foo
is_foo: 0

When we compile -O1 and higher, where ConstantMerge is applied, Clang
and GCC have different results.

clang -O3 foo.c -o foo
./foo
is_foo: 1

gcc -O3 foo.c -o foo
./foo
is_foo: 0

Here's the IR before ConstantMergePass pass:

@.str = private unnamed_addr constant [4 x i8] c"foo\00", align 1
@_ZL10foo_string = internal constant [4 x i8] c"foo\00", align 1
@.str.1 = private unnamed_addr constant [12 x i8] c"is_foo: %d\0A\00",
align 1

; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone
uwtable willreturn
define noundef i8* @_Z8foo_funcv() local_unnamed_addr #0 {
 ret i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0)
}

; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone
uwtable willreturn
define noundef i32 @_Z6is_fooPKc(i8* noundef readnone %0)
local_unnamed_addr #0 {
%2 = icmp eq i8* %0, getelementptr inbounds ([4 x i8], [4 x i8]*
@_ZL10foo_string, i64 0, i64 0)
%3 = zext i1 %2 to i32
ret i32 %3
}

; Function Attrs: mustprogress nofree norecurse nounwind uwtable
define noundef i32 @main() local_unnamed_addr #1 {
%1 = tail call i32 (i8*, ...) @printf(i8* noundef nonnull
dereferenceable(1) getelementptr inbounds ([12 x i8], [12 x i8]*
@.str.1, i64 0, i64 0), i32 noundef zext (i1 icmp eq (i8* getelementptr
inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i8* getelementptr
inbounds ([4 x i8], [4 x i8]* @_ZL10foo_string, i64 0, i64 0)) to i32))
ret i32 0
}

MergeConstantPass merges _ZL10foo_string into .str, where it merges
a non-unnamed_addr constant into an unnamed_addr constant.

  • IR Dump After ConstantMergePass on [module] ***
@.str = private constant [4 x i8] c"foo\00", align 1
@.str.1 = private unnamed_addr constant [12 x i8] c"is_foo: %d\0A\00",
align 1

; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone
uwtable willreturn
define noundef i8* @_Z8foo_funcv() local_unnamed_addr #0 {
ret i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0)
}

; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone
uwtable willreturn
define noundef i32 @_Z6is_fooPKc(i8* noundef readnone %0)
local_unnamed_addr #0 {
%2 = icmp eq i8* %0, getelementptr inbounds ([4 x i8], [4 x i8]* @.str,
i64 0, i64 0)
%3 = zext i1 %2 to i32
ret i32 %3
}

; Function Attrs: mustprogress nofree norecurse nounwind uwtable
define noundef i32 @main() local_unnamed_addr #1 {
%1 = tail call i32 (i8*, ...) @printf(i8* noundef nonnull
dereferenceable(1) getelementptr inbounds ([12 x i8], [12 x i8]*
@.str.1, i64 0, i64 0), i32 noundef 1)
ret i32 0
}

This transformation violates the following C pointer semantics:

"Two pointers compare equal if and only if both are null pointers,
both are pointers to the same object (including a pointer to an object
and a subobject at its beginning) or function, both are pointers to
one past the last element of the same array object, or one is a pointer
to one past the end of one array object and the other is a pointer to
the start of a different array object that happens to immediately
follow the first array object in the address space."
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2912.pdf

So, this patch changes ConstantMerge pass to only allow merging when
when a constant is marked with unnamed_addr attribute.
I also found an old GitHub issue where a similar issue about invalid
constant merging is explained.
https://github.com/llvm/llvm-project/issues/9299

Diff Detail

Event Timeline

gulfem created this revision.May 9 2023, 11:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 9 2023, 11:53 AM
gulfem requested review of this revision.May 9 2023, 11:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 9 2023, 11:53 AM
gulfem updated this revision to Diff 520780.May 9 2023, 11:56 AM

Changed the commit message

I'm far from a C or C++ expert, but FWIW I'm not entirely convinced that this example breaks language semantics.

The C standard says "It is unspecified whether these arrays are distinct provided their elements have the appropriate values." C++ says "Whether all string-literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified."

I suppose you could narrowly interpret the standards as only allowing string literals to overlap other string literals, but they don't explicitly say that. And allowing string literals to overlap with other constants hasn't caused any practical issues, as far as I know.

If we do decide that string literals need to be special, we might want to consider a distinct "unnamed_addr_only" or something like that, so we don't block merging in cases which aren't constrained by the C/C++ standards.