Using vectors and insertelement

I am trying to understand the llvm insert element instruction:
https://llvm.org/docs/LangRef.html#insertelement-instruction

From the description, it seems that one can assign the elements of a vector one at time.
I have tried using this to create a function that sorts a double vector of length two.
Below is a bash script that contains my example which does not pass it’s test and I do not know why ?

#! /bin/bash -e
# ---------------------------------------------------------------------------
# The file vec.ll is intended to implement the following python function:
# where the element types are double:
#	def ir_function(arg_0, arg_1) :
#		if arg_0 < arg_1 :
#			return (arg_0, arg_1)
#		else :
#			return (arg_1, arg_0)
# 
# Create vec.ll
cat << EOF > vec.ll
define <2 x double> @ir_function(double %arg_0, double %arg_1, ...) {
EntryBlock:
  %ifcond = fcmp ult double %arg_0, %arg_1
  %then_vec_0 = insertelement <2 x double> undef, double %arg_0, i64 0
  %then_vec_1 = insertelement <2 x double> %then_vec_0, double %arg_1, i64 1
  %else_vec_0 = insertelement <2 x double> undef, double %arg_1, i64 0
  %else_vec_1 = insertelement <2 x double> %else_vec_0, double %arg_0, i64 1
  %iftmp = select i1 %ifcond, <2 x double> %then_vec_1, <2 x double> %else_vec_1
  ret <2 x double> %iftmp
}
EOF
# ---------------------------------------------------------------------------
# The file vec.c below tests the vec.ll implementation
#
# create vec.c
cat << EOF > vec.c
# include <stdio.h>
# include <stdlib.h>
struct vector_2 { double a; double b; };
extern struct vector_2 ir_function(double arg_0, double arg_1);
int main(int argc, const char* argv[])
{	if( argc != 3 )
	{	fprintf(stderr, "usage: ./vec.sh arg_0 arg_1\n");
		return 1;
	}
	double arg_0 = atof( argv[1] );
	double arg_1 = atof( argv[2] );	
	struct vector_2 result = ir_function(arg_0, arg_1);
	printf("ir_function(%s, %s) = (%f, %f)\\n", argv[1], argv[2], result.a, result.b );
	int ok = 1;
	if( arg_0 < arg_1 )
	{	ok &= result.a == arg_0;
		ok &= result.b == arg_1;
	}
	else
	{	ok &= result.a == arg_1;
		ok &= result.b == arg_0;
	}
	if( ok )
		return 0;
	return 1;
} 
EOF
# ----------------------------------------------------------------------------
# The bash commands below test the ir_function.  It's output is as follows:
#	ir_function(3.0, 4.0) = (3.000000, 4.000000)
#	./vec.sh 3.0 4.0: OK
#	ir_function(4.0, 3.0) = (3.000000, 3.000000)
#	./vec.sh 4.0 3.0: Error
#
clang -Wno-override-module vec.c vec.ll -o vec
if ./vec 3.0 4.0
then
	echo './vec.sh 3.0 4.0: OK'
else
	echo './vec.sh 3.0 4.0: Error'
	exit 1
fi
if ./vec 4.0 3.0
then
	echo './vec.sh 4.0 3.0: OK'
else
	echo './vec.sh 4.0 3.0: Error'
	exit 1
fi
exit 0

Hi,

From what I can understand (and see in the code), you seem to have understood insertelement correctly. And in fact, Clang is not wrong either: .ll compiled to x86-64 ASM on Linux

I turned on optimization just because it’s much easier to read. But the semantics are the same in the debug version. Alright, so as you can see here (and as it’s apparent from the comments of Clang), the function does in assembly exactly what you want.

Ok, now going into calling conventions (can’t post 3rd link on Discourse, you should find it easily on Google by typing e.g., x86 calling conventions). I’m not sure if you’re familiar. Basically, it’s just a contract on how functions should call / communicate with other functions and it depends on your OS and other things. So, that when you write a function and I write a function and I call your function, I don’t need to tell you e.g., in what registers I pass the arguments. There is a generally accepted contract and so each one can write our function independently.

So, the x86 calling convention (in Linux) tells us for example that the first double argument goes
to (the lowest 64 bits of) xmm0 and the second to (the lowest 64 bits of) xmm1 (you can see that in the asm which uses ucomisd to compare the lowest 64 bits of each).

What it also tells us is that when you return e.g., a vector of two doubles (which is what your ir_function in the LLVM IR is supposed to return), these are saved in xmm0. The first in its low 64 bits and the other in its high 64 bits.

Now, here’s the key. This is different from returning just two doubles (which is what your ir_function in the C code returns). In this case, the first one is returned in the low 64 bits of xmm0 and the second in the low 64 bits of xmm1. So, you can see why you have a problem.

You can even see it in the code: C version. Check lines
18, 19 where Clang upackes the supposedly returned two doubles.

If you change your code to e.g., this:

#include <stdio.h>
#include <string.h>
#include <immintrin.h>

void print_m128(__m128 var)
{
    double val[2];
    memcpy(val, &var, sizeof(val));
    printf("Numerical: %lf %lf \n", val[0], val[1]);
}

extern __m128 ir_function(double arg_0, double arg_1);

int main(int argc, const char* argv[]) {
	double arg_0 = 4.0;
	double arg_1 = 3.0;
	__m128 result = ir_function(arg_0, arg_1);
	print_m128(result);
}

You shoud see correct results.

Hope it helps,
Stefanos

There was no mention of calling conventions in the example
llvm/HowToUseLLJIT.cpp at master · llvm-mirror/llvm · GitHub
Where an LLVM program is compiled, called by C, and the return seems to use C calling convention ?

Also in the calling convention documentation is say that C is the default; see
https://llvm.org/docs/LangRef.html#i-call
where it states:

The optional “cconv” marker indicates which calling convention the call should use. If none is specified, the call defaults to using C calling conventions. The calling convention of the call must match the calling convention of the target function, or else the behavior is undefined.

Is there a way in llvm to specify that the return statement should also use C calling convention ?

I am not sure why my original message was marked as spam or how to correct it ?

I am not sure why my original message was marked as spam or how to correct it ?

I’m not sure. It has happened to me too for no apparent reason.

Other than that, I’m not sure I understood your answer. The return statement does follow the default calling convention. I mean calling it C convention is kind of bad. A better description would be x86_64 convention in Linux. You can see it here: x86 calling conventions - Wikipedia

Your problem isn’t a mismatch of calling conventions. It’s a mismatch between function declarations. In the C code, you declare that you return two doubles while in the IR version, you declare that you return a vector of two doubles. Following the same calling convention, the way you return those two is different.

Best,
Stefanos

What I am really trying to do is pass in a double vector and return a double vector where the calling function is in C (or C++) and the function being called is in llvm IR. Similar to the HowToUseLLJIT.cpp example. What is the best way to do this ?

One way to do this, without having to worry about memory allocation, is to create both vectors in the calling C routine and to pass pointers. For example, the routine being called would do the same as:

void f(double* inp, double* out)
{   if( inp[0] < inp[1] )
    {   out[0] = inp[0];
        out[1] = inp[1];
    }
    else
    {   out[0] = inp[1];
        out[1] = inp[0];
    }
    return;
}

When I compile this program using clang -O2 -S junk.c I get the following junk.ll code:

... snip ...
define dso_local void @f(double* nocapture readonly %0, double* nocapture %1) local_unnamed_addr #0 {
  %3 = load double, double* %0, align 8, !tbaa !2
  %4 = getelementptr inbounds double, double* %0, i64 1
  %5 = load double, double* %4, align 8, !tbaa !2
  %6 = fcmp olt double %3, %5
  %7 = select i1 %6, double %3, double %5
  store double %7, double* %1, align 8, !tbaa !2
  %8 = load double, double* %4, align 8
  %9 = load double, double* %0, align 8
  %10 = select i1 %6, double %8, double %9
  %11 = getelementptr inbounds double, double* %1, i64 1
  store double %10, double* %11, align 8, !tbaa !2
  ret void
}
... snip ...

I notice that the arguments are specified as having type double*.

I cannot directly see how to create a function with this prototype using llvm::Function::Create ?

In addition, I am worried that operations on pointers are not really supported due to the text below copied from the LLVM Language Reference Manual ?

‘Note: non-integral pointer types are a work in progress, and they should be considered experimental at this time.’

What I am really trying to do is pass in a double vector and return a double vector where the calling function is in C (or C++) and the function being called is in llvm IR

I think you may confuse what a vector of two doubles and e.g., a struct of two doubles are. They are different. The C code that you posted in your latest post does not act on vectors. When we’re talking about vectors, think CPU vectors, like those vector registers used in SSE.

If you want a function that gets two doubles and returns a struct of two doubles, here’s how you do it in C (on the left) and LLVM IR (on the right): Compiler Explorer

To call this function in C, you declare it as:

typedef struct Res {
  double a, b;
} Res;

extern Res f(double, double);

and you call it.

Here’s the version of passing a vector and getting a vector: Compiler Explorer
(or you can use the version I wrote above and also use the version I wrote above on how to call it).