Handling of thread_local globals by llc -march=cpp

Hi all,

I've been exploring thread local globals in llvm and as part of this investigation I ran llc -march=cpp over a trivial llvm bc file which uses thread local storage for a global. For some reason llc -march=cpp seems to ignore the thread_local and produce code to create a standard global. Is this expected behaviour, a known limitation, or otherwise?

Thanks in advance.

Details are as follows.

The llvm bc I'm using (which clang produced from a simple c source file, thread_local.c, that has a __thread global, a, and a single function, f, which returns the value of the thread local global) is:

; ModuleID = 'thread_local.c'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32"
target triple = "i386-pc-linux-gnu"

@a = thread_local global i32 1, align 4 ; <i32*> [#uses=1]

define i32 @f() nounwind {
entry:
  %retval = alloca i32, align 4 ; <i32*> [#uses=2]
  %tmp = load i32* @a ; <i32> [#uses=1]
  store i32 %tmp, i32* %retval
  %0 = load i32* %retval ; <i32> [#uses=1]
  ret i32 %0
}

Within the cpp file the relevant code to create and initialise the global is:

  GlobalVariable* gvar_int32_a = new GlobalVariable(/*Module=*/*mod,
  /*Type=*/IntegerType::get(mod->getContext(), 32),
  /*isConstant=*/false,
  /*Linkage=*/GlobalValue::ExternalLinkage,
  /*Initializer=*/0, // has initializer, specified below
  /*Name=*/"a");
  gvar_int32_a->setAlignment(4);
  
  // Constant Definitions
  ConstantInt* const_int32_2 = ConstantInt::get(mod->getContext(), APInt(32, StringRef("1"), 10));
  
  // Global Variable Definitions
  gvar_int32_a->setInitializer(const_int32_2);

As you can see no actual parameter is provided for the optional formal ThreadLocal parameter of the GlobalVariable constructor; it instead then gets the default value of false. Compiling the cpp file and executing it to check the output of running the createPrintModulePass confirms that the llvm IR produced does not create a thread local global variable:

; ModuleID = 'thread_local.s'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32"
target triple = "i386-pc-linux-gnu"

@a = global i32 1, align 4 ; <i32*> [#uses=1]

define i32 @f() nounwind {
entry:
  %retval = alloca i32, align 4 ; <i32*> [#uses=2]
  %tmp = load i32* @a ; <i32> [#uses=1]
  store i32 %tmp, i32* %retval
  %0 = load i32* %retval ; <i32> [#uses=1]
  ret i32 %0
}

Hello

I've been exploring thread local globals in llvm and as part of this investigation I ran llc -march=cpp over a trivial llvm bc file which uses thread local storage for a global. For some reason llc -march=cpp seems to ignore the thread_local and produce code to create a standard global. Is this expected behaviour, a known limitation, or otherwise?

Thanks for the report. Should be fixed in r103702

Thanks for the quick reply and fix. Now it just means I have to hunt further in exploring why, in my simple test program, replacing the following line of code:

GlobalVariable* global = new GlobalVariable(*MyModule,
    IntegerType::get(getGlobalContext(), 32), false, GlobalValue::ExternalLinkage, 0, "global");

with the following line of code:

GlobalVariable* global = new GlobalVariable(*MyModule,
    IntegerType::get(getGlobalContext(), 32), false, GlobalValue::ExternalLinkage, 0, "global", 0, true);

in order to create a thread local global variable instead of a standard global variable causes the function (which uses the global) produced by the following line of code:

void *FPtr = MyExecutionEngine->getPointerToFunction(MyFunction);

to segfault when I call it in the following sequence of code:

int (*FP)() = (int (*)())FPtr;
int res = FP();

when the function executes correctly in the case of instead having created a standard global variable.

Doh!

As far as I got with this was to note that the machine code produced for the function is attempting to still do a move from a memory location (and an inaccessible one at that: 0xfffffffc) instead of doing a move from an offset from the segment register.

int (*FP)() = (int (*)())FPtr;
int res = FP();

when the function executes correctly in the case of instead having created a standard global variable.

What is the platform you're running the code on?

target triple = "i386-pc-linux-gnu"

I note also that this is not a currently unsupported target case where an error should/could/would be produced on attempting to emit code with thread local references. I say this because clang is able to compile both c source with __thread on a global and bc containing a @variable = thread_local global ... and a function that references the thread local global variable to both assembly and machine code that has segment register offset addressing.

I note also that this is not a currently unsupported target case where an error should/could/would be produced on attempting to emit code with thread local references. I say this because clang is able to compile both c source with __thread on a global and bc containing a @variable = thread_local global ... and a function that references the thread local global variable to both assembly and machine code that has segment register offset addressing.

Maybe TLS stuff is just broken inside JIT?

Hi all,

Following on from "Handling of thread_local globals by llc -march=cpp" I thought I'd repost queries that lead on from this under a more appropriate new title. My query now is does ExecutionEngine::getPointerToFunction() and related support thread local global storage. Thread local storage is supported by clang, both in bc generation, assembly generation, and machine code, thus LLVM clearly has working support for thread local global storage. However in the case of

Hi all,

Following on from "Handling of thread_local globals by llc -march=cpp" I thought I'd repost queries that lead on from this under a more appropriate new title. My query now is does ExecutionEngine::getPointerToFunction(), and related, support thread local global storage? Thread local storage is supported by clang, both in bc generation (it produces thread_local syntax), assembly generation (it produces segment register offset addressing), and machine code, thus LLVM clearly has working support for thread local global storage. However in the case of creating IR directly via calls into the LLVM api it appears that ExecutionEngine::getPointerToFunction() and related may not handle functions that reference thread local global storage. The reason I say this is captured in the details of one of my previous emails which are as follows.

In my simple test program, replacing the following line of code:

GlobalVariable* global = new GlobalVariable(*MyModule,
   IntegerType::get(getGlobalContext(), 32), false, GlobalValue::ExternalLinkage, 0, "global");

with the following line of code:

GlobalVariable* global = new GlobalVariable(*MyModule,
   IntegerType::get(getGlobalContext(), 32), false, GlobalValue::ExternalLinkage, 0, "global", 0, true);

in order to create a thread local global variable instead of a standard global variable causes the function (which uses the global) produced by the following line of code:

void *FPtr = MyExecutionEngine->getPointerToFunction(MyFunction);

to segfault when I call it in the following sequence of code:

int (*FP)() = (int (*)())FPtr;
int res = FP();

when the function executes correctly in the case of instead having created a standard global variable.