David Edelsohn <dje....@gmail.com> writes: > On Sat, Mar 28, 2020 at 6:42 AM Richard Sandiford > <richard.sandif...@arm.com> wrote: >> >> David Edelsohn via Gcc-patches <gcc-patches@gcc.gnu.org> writes: >> > This patch is for an AIX problem, but the only robust solution is in >> > common code: calls.c:precompute_register_parameters(). >> > >> > AIX, like other platforms, needs to call a function to obtain the >> > pointer to thread-local storage. If the thread local variable is >> > referenced as a parameter to a function call, the TLS resolution call >> > may clobber parameters to the primary function call. >> > >> > GCC calls.c has special provisions for this situation in >> > precompute_register_parameters, which tests each parameters with the >> > legitimate_constant_p target hook. Most targets define that hook to >> > return False for a TLS symbol. >> > >> > From the very initial implementation of the rs6000 port, Richard >> > Kenner utilized the GCC constant pool for the TOC managed by the >> > compiler -- the table of position independent pointers to global >> > symbols (like GOT). TLS symbols must be referenced by the TOC like >> > all global symbols and must be considered constants valid for the >> > constant pool. >> >> The way the hooks are set up, >> >> - legitimate_constant_p tests whether a constant is a legitimate >> immediate operand for insns >> >> - cannot_force_const_mem tests (in the negative) whether a constant >> is legitimate for the constant pool >> >> It's common for legitimate_constant_p to return false for constants that >> are valid for the constant pool. >> >> > Currently targetm.legitimate_constant_p() returns True for TLS symbols >> > on AIX, which inhibits the pre-legitimization of the TLS symbol in >> > calls.c and registers can be overridden. If the AIX port defines TLS >> > symbols as non-constant, emit_move_insn() prematurely calls >> > force_const_mem() and prevents the correct TLS and TOC code >> > generation. >> > >> > I have experimented extensively with attempts to have it both ways and >> > teach cannot_force_const_mem hook to reject TLS symbols during the >> > appropriate phases. This has proven too fragile a solution that >> > causes additional testsuite failures. targetm.legitimate_constant_p >> > has too many meanings and too many uses. >> >> This set-up sounds similar to many ELF targets. The approach there is that: >> >> (1) the TLS constant itself -- i.e. the actual runtime address for the >> current thread -- is not legitimate_constant_p >> >> (2) the TLS constant itself satisfies cannot_force_const_mem (i.e. can't >> go into the constant pool) >> >> (1) stops the constant reappearing in places that can't safely compute it >> (like the call case here). (2) is necessary because TLS constants, >> being thread-specific, are fundamentally not load-time constants. >> >> (1) + (2) together mean that the constant goes through the move expanders >> even though it's not a legitimate constant. (In some ways that's a bit >> of a hack, although not a bad one IMO.) The move expanders then reduce >> the constant to individual pieces. One of those pieces might be something >> that goes in the constant pool, with a TLS relocation applied to it. >> >> I'm probably well off the mark here, but it looks like the problem for >> AIX might be that it tries to force the TLS constant itself into the >> constant pool, rather than a piece of it. The structure is: >> >> static rtx >> rs6000_legitimize_tls_address_aix (rtx addr, enum tls_model model) >> { >> rtx sym, mem, tocref, tlsreg, tmpreg, dest, tlsaddr; >> const char *name; >> char *tlsname; >> >> name = XSTR (addr, 0); >> /* Append TLS CSECT qualifier, unless the symbol already is qualified >> or the symbol will be in TLS private data section. */ >> if (name[strlen (name) - 1] != ']' >> && (TREE_PUBLIC (SYMBOL_REF_DECL (addr)) >> || bss_initializer_p (SYMBOL_REF_DECL (addr)))) >> ...create tlsaddr with a CSECT qualifier...; >> else >> tlsaddr = addr; >> >> /* Place addr into TOC constant pool. */ >> sym = force_const_mem (GET_MODE (tlsaddr), tlsaddr); >> >> At this point we're forcing the original TLS constant into memory, >> i.e. the thread-specific runtime value that we're trying to compute. >> >> /* Output the TOC entry and create the MEM referencing the value. */ >> if (constant_pool_expr_p (XEXP (sym, 0)) >> && ASM_OUTPUT_SPECIAL_POOL_ENTRY_P (get_pool_constant (XEXP (sym, >> 0)), Pmode)) >> { >> tocref = create_TOC_reference (XEXP (sym, 0), NULL_RTX); >> mem = gen_const_mem (Pmode, tocref); >> set_mem_alias_set (mem, get_TOC_alias_set ()); >> } >> else >> return sym; >> >> I don't understand what the "return sym" case is handling, but it looks >> like that is the only time that the constant forced to memory above is >> the actual TLS constant. Down here... > > The entire rhythm is exactly the same as rs6000_legitimize_address for > the creating a normal TOC entry. If it's a valid symbol but not yet > in the constant pool, it converts it to a TOC reference, otherwise it > returns the previously re-written symbol that already is in the > constant pool (TOC). > >> >> /* Use global-dynamic for local-dynamic. */ >> if (model == TLS_MODEL_GLOBAL_DYNAMIC >> || model == TLS_MODEL_LOCAL_DYNAMIC) >> { >> ...use the tocref with tls_get_addr[sd]i... >> return dest; >> } >> /* Obtain TLS pointer: 32 bit call or 64 bit GPR 13. */ >> else if (TARGET_32BIT) >> { >> tlsreg = gen_reg_rtx (SImode); >> emit_insn (gen_tls_get_tpointer (tlsreg)); >> } >> else >> tlsreg = gen_rtx_REG (DImode, 13); >> >> /* Load the TOC value into temporary register. */ >> tmpreg = gen_reg_rtx (Pmode); >> emit_insn (gen_rtx_SET (tmpreg, mem)); >> set_unique_reg_note (get_last_insn (), REG_EQUAL, >> gen_rtx_MINUS (Pmode, addr, tlsreg)); >> >> /* Add TOC symbol value to TLS pointer. */ >> dest = force_reg (Pmode, gen_rtx_PLUS (Pmode, tmpreg, tlsreg)); >> >> return dest; >> >> ...we use "mem"/"tocref" as one piece of a larger calculation. >> I.e. the value in "mem"/"tocref" is just an input to the calculation, >> rather than the full result. >> >> On ELF targets, the usual way of handling this to use: >> >> (const (unspec [SYM] UNSPEC_SOME_TLS_RELOC)) >> >> for the constant pieces of the TLS calculation. That const unspec is >> then a legitimate constant and/or a legitimate constant pool entry, >> even though the TLS constant itself isn't. >> >> So it looks like in the non-"return sym" case, the value being forced >> to memory should be some kind of unspec wrapper around the constant, >> instead of being the constant itself. > > I realize that is how TLS for ELF is implemented in GCC and I realize > that GCC mostly is targeted at Linux/ELF. The support for AIX XCOFF > exists in GCC and functions. There is only so much that I can contort > XCOFF to look like ELF without completely re-writing TOC support, > which I'm not going to do. I'm trying to find the minimally invasive > solution for the TLS problem for AIX. > > AIX XCOFF is not ELF. AIX does not emit instructions referencing > symbols with special decorations for the assembler and linker to > manage the GOT and relocations. > > TLS on AIX creates additional, special TOC entries and appends > decorations to the symbol placed in the TOC (the GCC constant pool). > Frequently a GCC UNSPEC would be used to match a different pattern, > but for AIX it's a normal load of a symbol from the TOC. > > .toc > LC..1: > .tc result.2299[TC],result.2299[UL] > LCM..1: > .tc .result.2299[TC],result.2299[UL]@m > ... > lwz 3,LCM..1(2) > lwz 4,LC..1(2) > bla __tls_get_addr > > How does wrapping the TLS symbol in an UNSPEC help? I'm not expecting > you to remember how the TOC works on AIX or the peculiarities of the > way that it is implemented in GCC, but your advice seems to ignore the > infrastructure for the support of non-TLS TOC symbols in GCC. > > You are suggesting using the UNSPEC to flag a legitimized TLS symbol > versus a non-legitimized TLS symbol, and a legitimized TLS symbol is a > constant?
Well, this is all about what (symbol_ref NAME) means for a TLS symbol (with NAME always assumed to be TLS below). For all GCC ports it means the actual address of NAME for the current thread. AIUI that is the value returned by __tls_get_addr, not the [UL] value originally placed in the TOC. But by forcing the TLS symbol_ref into the constant pool as-is: .toc LC..1: .tc result.2299[TC],result.2299[UL] // (symbol_ref NAME) in memory in normal rtl semantics, the load from the constant pool would give the actual value of the symbol. I.e. this: lwz 4,LC..1(2) would load the address of NAME for the current thread into register 4. There would then be no need for __tls_get_addr call. But AIUI this is not what's actually happening. So it seems like the [UL] value must be something closely related to (symbol_ref NAME) rather than (symbol_ref NAME) itself. If instead of saying that (symbol_ref NAME) is being put into the TOC you say that (const (unspec [(symbol_ref NAME)] UNSPEC_TOC_UL)) or whatever is being put into the TOC, you can set it up so that: - legitimate_constant_p is false for (symbol_ref NAME) - cannot_force_const_mem is true for (symbol_ref NAME) - legitimate_constant_p is false for the (const (unspec ...)) [*] - cannot_force_const_mem is false for the (const (unspec ...)) [*] although true could probably be made to work too, if that's better for some reason This means that the move expanders still get the chance to legitimise the (symbol_ref NAME) access, just like they do now. But the value you put into the TOC works like any other TOC symbol. > I'm trying to solve the narrow problem of a bug in TLS support without > re-writing the entire TOC implementation. Yeah, I'm not suggesting anything that drastic. The only changes would be to the hooks above (for TLS symbols only) and adding a (const (unspec ...)) wrapper around tlsaddr before the force_const_mem in rs6000_legitimize_tls_address_aix. Thanks, Richard