Search code examples
cclangllvmllvm-irabi

Is there a standard way to reconstruct lowered struct function arguments?


I have a structure type:

typedef struct boundptr {
  uint8_t *ptr;
  size_t size;
} boundptr;

and I want to catch all the arguments of a function of that type. E.g. in this function:

boundptr sample_function_stub(boundptr lp, boundptr lp2);

On my 64bit machine, Clang translates that signature to:

define { i8*, i64 } @sample_function_stub(i8* %lp.coerce0, i64 %lp.coerce1, i8* %lp2.coerce0, i64 %lp2.coerce1) #0 {

Question:

Is there a better way to reconstruct such arguments?

Is it possible to forbid such argument lowering, while keeping the same ABI for external calls?

Some more context:

So in the LLVM IR, I guess, according to the platform ABI, the compiler broke down the structure into separate fields (which is not the worst case, see 1). BTW, it reconstructs the original two parameters lp and lp2 later in the function body.

Now for my analysis, I want to get those two parameters lp and lp2 in full, out of these 4(lp.coerce0, lp.coerce1, lp2.coerce0 and lp2.coerce1). In this case, I probably can rely on the names (.coerce0 means first field, .coerce1 - second).

I do not like this approach:

  • I am not sure, that Clang with keep this convention in later versions
  • It certainly depends on the ABI, so there may be a different breakdown on another platform.

On the other side, I can not use the reconstruction code in the beginning of the function, because I may confuse it with some user code for a local variable.


I use Clang 3.4.2 based on LLVM 3.4.2 for target x86_64-pc-linux-gnu.

P.S. Here is another example, showing how wildly Clang can mess up with function arguments.


Solution

  • I assume you are compiling not with O0. AFAIK, clang will reassemble the original type when you are not optimizing your code. Clang breaks down your structure to pass them through registers (at least on x86) to the called function. As you said, this depends on the ABI used.

    Here is a dummy example from your use case:

    #include <cstddef>
    
    typedef struct boundptr {
      void *ptr;
      size_t size;
    } boundptr;
    
    boundptr foo(boundptr ptr1, boundptr ptr2) { return {ptr1.ptr, ptr2.size}; }
    
    int main() {
      boundptr p1, p2;
      boundptr p3 = foo(p1, p2);
      return 0;
    }
    

    Compiling it with clang -O0 -std=c++11 -emit-llvm -S -c test.cpp generates foo:

    define { i8*, i64 } @_Z3foo8boundptrS_(i8* %ptr1.coerce0, i64 %ptr1.coerce1, i8* %ptr2.coerce0, i64 %ptr2.coerce1) #0 {
      %1 = alloca %struct.boundptr, align 8
      %ptr1 = alloca %struct.boundptr, align 8
      %ptr2 = alloca %struct.boundptr, align 8
      %2 = bitcast %struct.boundptr* %ptr1 to { i8*, i64 }*
      %3 = getelementptr { i8*, i64 }, { i8*, i64 }* %2, i32 0, i32 0
      store i8** %ptr1.coerce0, i8** %3
      %4 = getelementptr { i8*, i64 }, { i8*, i64 }* %2, i32 0, i32 1
      store i64 %ptr1.coerce1, i64* %4
      %5 = bitcast %struct.boundptr* %ptr2 to { i8*, i64 }*
      %6 = getelementptr { i8*, i64 }, { i8*, i64 }* %5, i32 0, i32 0
      store i8** %ptr2.coerce0, i8** %6
      %7 = getelementptr { i8**, i64 }, { i8**, i64 }* %5, i32 0, i32 1
      store i64 %ptr2.coerce1, i64* %7
      %8 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %1, i32 0, i32 0
      %9 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %ptr1, i32 0, i32 0
      %10 = load i8*, i8** %9, align 8
      store i8* %10, i8** %8, align 8
      %11 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %1, i32 0, i32 1
      %12 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %ptr2, i32 0, i32 1
      %13 = load i64, i64* %12, align 8
      store i64 %13, i64* %11, align 8
      %14 = bitcast %struct.boundptr* %1 to { i8*, i64 }*
      %15 = load { i8*, i64 }, { i8*, i64 }* %14, align 8
      ret { i8*, i64 } %15
    }
    

    boundptr is reconstructed on the called function stack (this also depends on the used calling convention).

    Now to find out which of the boundptr are your parameters you can do the following:

    1. Visit each alloca inst in your pass and follow its users.
    2. Follow the casts of the alloca as well as GEP instructions to find the store instructions on the boundptr.
    3. Inspect the value to be stored. If they are your function arguments and match types and names you have found your reassembled boundptr.

    Of course you can do it the other way around starting from the functions arguments.

    Is this future proof? No definitively not. Clang/LLVM is not designed to keep backward compatibility. For the compatibility the ABI is important.

    Drawback: You have to get your pass into the optimizer really early after code generation. Even 01 will remove these stack allocations of boundptr. So you have to modify your clang to execute your pass during optimization and you cannot make it a standalone pass (e.g., used by opt).

    A better solution: Since clang has to be modified somehow, you can add metadata identifying parameters of your boundptr type. So you can "pack" the fragments of your boundptr together to identify them as boundptr. This would survive the optimizer.