This is an archive of the discontinued LLVM Phabricator instance.

[SDAG] avoid generating libcall to function with same name
ClosedPublic

Authored by spatel on Aug 9 2022, 1:46 PM.

Details

Summary

This is a potentially better alternative to D131452 that also appears to avoid the infinite loop bug from:
https://github.com/llvm/llvm-project/issues/56403

This is again a minimal fix to minimize merging pain for the release. But if this makes sense, then we'd want to guard all of the RTLIB generation (and other libcalls?) with 'nobuiltin'?

Diff Detail

Event Timeline

spatel created this revision.Aug 9 2022, 1:46 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 9 2022, 1:46 PM
spatel requested review of this revision.Aug 9 2022, 1:46 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 9 2022, 1:46 PM

The langref says of nobuiltin:

nobuiltin
This indicates that the callee function at a call site is not recognized as a built-in function. LLVM will retain the original call and not replace it with equivalent code based on the semantics of the built-in function, unless the call site uses the builtin attribute. This is valid at call sites and on function declarations and definitions.

Isn't this patch now doing the opposite? If the callee is marked nobuiltin, we should retain the call explicitly as written unless the call site is attributed as builtin. Or am I misreading the langref? Otherwise, what is the point of nobuiltin in this test?

(I also think --rtlib=none needs to be recorded in IR to avoid libcalls, too).

llvm/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp
4431–4434

I feel like if TLI.getLibcallName() returns the same string as the fn we're currently ISEL'ing, then we've found a potential inf loop.

spatel marked an inline comment as done.Aug 10 2022, 5:56 AM

The langref says of nobuiltin:

nobuiltin
This indicates that the callee function at a call site is not recognized as a built-in function. LLVM will retain the original call and not replace it with equivalent code based on the semantics of the built-in function, unless the call site uses the builtin attribute. This is valid at call sites and on function declarations and definitions.

Isn't this patch now doing the opposite? If the callee is marked nobuiltin, we should retain the call explicitly as written unless the call site is attributed as builtin. Or am I misreading the langref? Otherwise, what is the point of nobuiltin in this test?

I hadn't read that text in a while. I was assuming that "nobuiltin" on this function meant that it could not call builtins (and I think that was the desired behavior in the bug report). But I agree - the way it is defined is the opposite. I'm not familiar with how this is supposed to work. I see that if you build C source with "-ffreestanding", then the functions will have a different attribute -- "no-builtins" -- is that the attribute we should be detecting here?

Or we bail out with a combination of (1) this function is marked "nobuiltin" / "no-builtins" and (2) it is potentially calling a function with the same name (as suggested in the inline comment)?

There are basically two different forms of LLVM "knowing" the meaning of a library call:

  1. We assume we have a full-featured C library, with properties annotated in TargetLibraryInfo. This basically corresponds to -fno-builtin/"no-builtins".
  2. If input IR contains certain constructs, we assume the corresponding compiler-rt/libm calls from include/llvm/IR/RuntimeLibcalls.def are available. Really, the issue here is that even if we wanted to avoid generating libcalls, we don't actually have an alternative in a lot of cases: no other lowering is implemented in LLVM.

Most the functionality from RuntimeLibcalls.def for integer/float arithmetic doesn't require any runtime, so we could in theory provide it some other way, like embedding an implementation into the object file. We've sort of started moving in this direction (see D126644).

Not sure we should be keying this off of "no-builtins"; having a C library is different from having compiler-rt builtins. We generally expect freestanding binaries to include compiler-rt builtins, but not a C library.

I see that if you build C source with "-ffreestanding", then the functions will have a different attribute -- "no-builtins" -- is that the attribute we should be detecting here?

No (and I agree with @efriedma ).

My understanding (which itself might not be precise) is that C code can be "hosted" or "freestanding."

Hosted is the common case for userspace application developers, while freestanding is more common in embedded use cases, though these aren't always strictly the case. The Linux kernel for example hasn't yet decided whether it wants to be freestanding or not, and changes it mind per arch. *yikes*

A C program tends to implicitly link against a libc and a compiler runtime. -ffreestanding implies a libc will not be available, but it implies nothing about the compiler runtime. Instead --rtlib=none implies no compiler-rt should be linked against, but implies nothing about the libc.

The function/symbol for this patch is __muloti4 which we should expect to be provided by the compiler runtime, not the libc. So -ffreestanding is relevant for libcall optimizations that assume function symbols with specific identifiers can be assumed to have semantics specific to libc.

I don't think we have an equivalent fn attr or mdnode for --rtlib=none in IR. If we did, and it was explicitly set, then we can't assume functions like __muloti4 will be available to link against.

So I agree when @efriedma says:

We generally expect freestanding binaries to include compiler-rt builtins, but not a C library.

I don't see no-builtins specifically in the LangRef; not sure if that was a typo or worse: undocumented IR attributes. *yikes*
Oh, you weren't kidding: https://godbolt.org/z/o49oK6GYG *yikes*
Well I sure wouldn't do anything with that attr until its semantics are well documented.

nickdesaulniers added a comment.EditedAug 10 2022, 3:07 PM

I don't think we have an equivalent fn attr or mdnode for --rtlib=none in IR. If we did, and it was explicitly set, then we can't assume functions like __muloti4 will be available to link against.

Further, a libc like musl might compile with -ffreestanding to avoid the compiler creating inf loops in the implementations of these runtime-provided function definitions, just like the test case in this bug. They would miss out on other potential libcall optimizations the compiler could find that don't result in inf loops, but I think musl also wants individual definitions to not have cross TU dependencies, which makes picking and choosing a subset of the runtime to ship more feasible.

So if someone defined __muloti4 (as this test case does, as we do in compiler-rt) I'd imagine they'd run into a similar problem (as demonstrated by this test case) and would require a similar solution. I wonder how we build compiler-rt to avoid this dilemma?

I wonder how we build compiler-rt to avoid this dilemma?

We use ad-hoc solutions that happen to work. For muloti, this particular multiply+divide pattern doesn't show up, so it's not a problem. This basically works as long as compiler-rt and the compiler are tightly coupled. It sort of breaks down when people try to build compiler-rt equivalents in other languages. Given there's now at least three implementations that are built with LLVM (compiler-rt itself, zig, and rust), maybe a more principled solution is appropriate.

I don't see no-builtins specifically in the LangRef; not sure if that was a typo or worse: undocumented IR attributes. *yikes*
Oh, you weren't kidding: https://godbolt.org/z/o49oK6GYG *yikes*
Well I sure wouldn't do anything with that attr until its semantics are well documented.

That should be documented, yes.

spatel planned changes to this revision.Aug 17 2022, 9:10 AM

So there's a lot of potential follow-up to make this work in general.

I'm still looking at a quick fix for the release branch to avoid the particular inf-loop that was reported in #56403. Change this patch to string match the function name as suggested earlier or proceed with D131452?

spatel updated this revision to Diff 453307.Aug 17 2022, 9:32 AM
spatel retitled this revision from [SDAG] avoid generating libcall in function with nobuiltin to [SDAG] avoid generating libcall to function with same name.

Updated to bail out if the function name matches the libcall name.

nickdesaulniers accepted this revision.Aug 17 2022, 10:31 AM

I think this is a good enough approach for now; thanks for pursuing a fix.

This revision is now accepted and ready to land.Aug 17 2022, 10:31 AM
This revision was landed with ongoing or failed builds.Aug 17 2022, 1:20 PM
This revision was automatically updated to reflect the committed changes.