This is an archive of the discontinued LLVM Phabricator instance.

Fix -fsplit-lto-unit with ifuncs
ClosedPublic

Authored by danielkiss on Feb 28 2023, 10:01 AM.

Details

Summary

ifuncs can't take part of the whole-program devirtualization so no need them to be copied to the merged module.
The corresponding resolver function also kept out which caused the crash.

Fixes #60962 #57870

Diff Detail

Event Timeline

danielkiss created this revision.Feb 28 2023, 10:01 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 28 2023, 10:01 AM
danielkiss requested review of this revision.Feb 28 2023, 10:01 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 28 2023, 10:01 AM
danielkiss edited the summary of this revision. (Show Details)Mar 1 2023, 7:35 AM

I'm a little confused - did we create an illegal split module by cloning the ifunc to the regular LTO split module? Should the handling in CloneModule (particularly with its use of the ShouldCloneDefinition callback) be changed so that ifunc are not cloned if their resolver is not cloned?

llvm/test/ThinLTO/X86/ifunc_splitlto.ll
31

Is the second !2 supposed to be !3? Otherwise there is a circular metadata reference and no use of !3.

danielkiss updated this revision to Diff 502566.Mar 6 2023, 3:00 AM
danielkiss marked an inline comment as done.Mar 6 2023, 3:23 AM

I'm a little confused - did we create an illegal split module by cloning the ifunc to the regular LTO split module? Should the handling in CloneModule (particularly with its use of the ShouldCloneDefinition callback) be changed so that ifunc are not cloned if their resolver is not cloned?

simplifyExternals turns the resolver to external. Externals won't be in the module summary which will be referenced by the ifunc.

I'm a little confused - did we create an illegal split module by cloning the ifunc to the regular LTO split module? Should the handling in CloneModule (particularly with its use of the ShouldCloneDefinition callback) be changed so that ifunc are not cloned if their resolver is not cloned?

simplifyExternals turns the resolver to external. Externals won't be in the module summary which will be referenced by the ifunc.

Right, simplifyExternals ensures that any symbols cloned into the merged Module as declarations are either dropped if there are no references or made external if there are. But this is not specific to resolver functions.

Ah, I see that the latest version of the patch has moved the new ifunc handling into simplifyExternals, and only drops the ifunc if the resolver func is not defined in the module. This is better as it isn't unconditional.

What I was asking was whether we can handle this directly in CloneModule via the callback, instead of removing the ifunc later. I.e. should CloneModule be changed to invoke ShouldCloneDefinition on the ifunc resolver function before deciding whether to clone over each ifunc definition? Because it generally seems incorrect to create a module with an ifunc with a resolver function not defined in that module (i.e. not just when splitting the ThinLTO module, but any time we invoke CloneModule with a callback that doesn't simply return true for everything).

However, what would happen if one of the functions that we do decide to clone itself references/calls an ifunc? In that case with your patch or my proposed change you will I think end up with an bad module since you are removing the ifunc completely and not replacing it with anything, so you would have a reference to a nonexistent symbol. For aliases CloneModule handles this by converting the alias into either a function or variable external declaration. I'm not sure what the correct behavior for an ifunc is?

I'm a little confused - did we create an illegal split module by cloning the ifunc to the regular LTO split module? Should the handling in CloneModule (particularly with its use of the ShouldCloneDefinition callback) be changed so that ifunc are not cloned if their resolver is not cloned?

simplifyExternals turns the resolver to external. Externals won't be in the module summary which will be referenced by the ifunc.

Right, simplifyExternals ensures that any symbols cloned into the merged Module as declarations are either dropped if there are no references or made external if there are. But this is not specific to resolver functions.

Ah, I see that the latest version of the patch has moved the new ifunc handling into simplifyExternals, and only drops the ifunc if the resolver func is not defined in the module. This is better as it isn't unconditional.

What I was asking was whether we can handle this directly in CloneModule via the callback, instead of removing the ifunc later. I.e. should CloneModule be changed to invoke ShouldCloneDefinition on the ifunc resolver function before deciding whether to clone over each ifunc definition? Because it generally seems incorrect to create a module with an ifunc with a resolver function not defined in that module (i.e. not just when splitting the ThinLTO module, but any time we invoke CloneModule with a callback that doesn't simply return true for everything).

I think it is even more complicated because it is valid to call the resolver directly so copying the resolver might not mean anything for the ifunc. (see baz here: https://godbolt.org/z/PWzYoEv6z)

If the CloneModule handles this than we break the llvm-reduce/remove-ifunc-program-addrspace.ll.
I need to investigate why the ifunc_remove_as1_resolver_casted_in_1 won't see the resolver (name of it becomes empty) while cloning module.

However, what would happen if one of the functions that we do decide to clone itself references/calls an ifunc? In that case with your patch or my proposed change you will I think end up with an bad module since you are removing the ifunc completely and not replacing it with anything, so you would have a reference to a nonexistent symbol. For aliases CloneModule handles this by converting the alias into either a function or variable external declaration. I'm not sure what the correct behavior for an ifunc is?

Intuitively ifuncs would be external declaration for users as they are lowered today but that won't work for platforms where the resolver would be called in place(instead of PLTs on Linux/elf).
Probably in these cases we need to carry the resolver if the ifuncs need to be copied.

let's remove ifuncs if not used.

Last version fixes the Chromium and others crashes. Generic solution for CloneModule is conceptually more complex and probably orthogonal to this change.

I'm a little confused - did we create an illegal split module by cloning the ifunc to the regular LTO split module? Should the handling in CloneModule (particularly with its use of the ShouldCloneDefinition callback) be changed so that ifunc are not cloned if their resolver is not cloned?

simplifyExternals turns the resolver to external. Externals won't be in the module summary which will be referenced by the ifunc.

Right, simplifyExternals ensures that any symbols cloned into the merged Module as declarations are either dropped if there are no references or made external if there are. But this is not specific to resolver functions.

Ah, I see that the latest version of the patch has moved the new ifunc handling into simplifyExternals, and only drops the ifunc if the resolver func is not defined in the module. This is better as it isn't unconditional.

What I was asking was whether we can handle this directly in CloneModule via the callback, instead of removing the ifunc later. I.e. should CloneModule be changed to invoke ShouldCloneDefinition on the ifunc resolver function before deciding whether to clone over each ifunc definition? Because it generally seems incorrect to create a module with an ifunc with a resolver function not defined in that module (i.e. not just when splitting the ThinLTO module, but any time we invoke CloneModule with a callback that doesn't simply return true for everything).

I think it is even more complicated because it is valid to call the resolver directly so copying the resolver might not mean anything for the ifunc. (see baz here: https://godbolt.org/z/PWzYoEv6z)

If the CloneModule handles this than we break the llvm-reduce/remove-ifunc-program-addrspace.ll.
I need to investigate why the ifunc_remove_as1_resolver_casted_in_1 won't see the resolver (name of it becomes empty) while cloning module.

I suppose it is because it is valid to copy just the resolver as you mentioned, and I guess in general CloneModule isn't trying to split the module in 2.

However, what would happen if one of the functions that we do decide to clone itself references/calls an ifunc? In that case with your patch or my proposed change you will I think end up with an bad module since you are removing the ifunc completely and not replacing it with anything, so you would have a reference to a nonexistent symbol. For aliases CloneModule handles this by converting the alias into either a function or variable external declaration. I'm not sure what the correct behavior for an ifunc is?

Intuitively ifuncs would be external declaration for users as they are lowered today but that won't work for platforms where the resolver would be called in place(instead of PLTs on Linux/elf).
Probably in these cases we need to carry the resolver if the ifuncs need to be copied.

Can we at least add an assert for now to the split module code that we don't end up with either split module needing the ifunc but not containing its resolver?

tejohnson accepted this revision.Mar 21 2023, 6:22 AM

lgtm

llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
203

nit: its not it's in this context.

This revision is now accepted and ready to land.Mar 21 2023, 6:22 AM
This revision was automatically updated to reflect the committed changes.
danielkiss marked an inline comment as done.