Unconditional Parameter Specialisation

This example will show how to unconditionally specialise a particular function with respect to a parameter. In particular we’ll use this simple example program:

int f(int x) {
  return x * x;
}

int main(int argc, char** argv) {
  return f(1) + f(2);
}

We can replace f with a version that assumes x is always 2 by running:

clang test.c -c -emit-llvm -o test.bc
$LLPE_OPT -llpe test.bc -o spec.bc -llpe-root f -spec-param 0,2

As usual, the definition of LLPE_OPT is taken from the tutorial. That spec-param parameter sets argument 0 (here int x) to integer 2.

If we run llvm-dis on spec.bc we’ll find the new and old f functions:

define i32 @f.old(i32 %x) #0 {
  %1 = alloca i32, align 4
  store i32 %x, i32* %1, align 4
  %2 = load i32, i32* %1, align 4
  %3 = load i32, i32* %1, align 4
  %4 = mul nsw i32 %2, %3
  ret i32 %4
}

define i32 @f(i32 %x) #0 {
entry:
  ret i32 4
}

Then if we compile and run the whole program:

clang spec.bc -o spec
./spec

We’ll find it returns 8 instead of 5 as expected. This is because we have produced an unconditional specialisation – the new f always assumes that its argument is 2, rather than checking that specialised code is appropriate at runtime. This sort of specialisation is thus only appropriate if we can externally guarantee that our specialisation assumption holds; otherwise you want conditional specialisation, which performs runtime assumption checking.

In addition to setting integer parameters, the spec-param parameter can also supply string constants and function pointers (e.g. -spec-param 0,HelloWorld or -spec-param 0,f if the type of the argument was const char* x or int (*x)(int) respectively.