LLVM frontend supporting arbitrary bit-width integral datatypes

Hello gyus,

I am working on a project, where we are trying to create a development environment
for new ASIP processor design. Part of this project is a compiler generator,
where we would like to generate C compiler from some instruction description.

To keep it short, let's say, that in each instruction's semantics
is described by some C code. What I would like to do is to compile this code with LLVM,
let it optimize it and then extract instruction selection rules for each instruction (either from LLVM IR
or from created code selection DAG).
Then, I will use these selection rules and some additional information about registers etc. to generate
LLVM compiler backend for the designed ASIP.

First, I would like to ask, whether you heard about some proects, where they are also trying to generate LLVM backends.
(any related information would be useful)

Second, in the code, that describes instruction's semantics, I need to use some special datatypes
like 5-bit wide unsigned integer and similar. I heard about some Embedded C extension for the CGG frontend,
but I have not found anything about it yet. Or, does the CLang support such datatypes?

Have a nice day and thanks in advance
  Adam

Take a look at http://tce.cs.tut.fi

This is a current LLVM project: http://llvm.org/ProjectsWithLLVM/#tta-tce

What they are doing might apply to your problem:

TCE generates new LLVM-based code generators “on the fly” for the designed TTA processors and loads them in to the compiler backend as runtime libraries to avoid per-target recompilation of larger parts of the compiler chain.

Cheers,
John

Hi Adam,

John is right, the TCE stuff would be useful for you. Our
compiler targets a processor template that the designer can
populate pretty freely. The compiler then reads the architecture
description and creates an LLVM backend on the fly.

Please don't hesitate to get in touch with us if you have
questions.

Hello,

thank you for your answers, in fact, the TCE project is quite similar to our project Lissom (Instruction Set Tools Lissom), I will take a closer look at it.

One problem, I was trying to solve was, that I need to declare variables of let's say 5-bit width like 'i5 var',
the maximal bit-width may be limited to 64 bits. I need such variables to represent instruction's operands,
example is at the end this message.

For now, I did not find any usable compiler frontend.

First attempt was quite old Valen-C based on SUIF compiler which requires unavailable version of
SUIF and would be hard to make work.
Second attempt was SpecC reference compiler - as most of high-level language synthetizers, turns
arbitrary bit-width integers into C++ templates which makes it unusable.
Then few other attempts, but not much luck.

The conclusion is, that I will need to modify some compiler frontend by my own, propably the clang.
Please, if you have some info afout this or if anyone has already tried such thing, could you write it here?
Also, is there anything I should be aware of when modifing the clang? Are there some passes, that are
made only for the standard C datatypes?
Stuff like sizeof() and C pointer arithmetics can be forbidden integers which bit width is not divisible by 8.

Thank you
  Adam

(I am posting this both to the llvmdev and clangdev, sry for possible spamming.)

Example of why do I need arbitrary bit-width integers:

I can extract from our architecture description language ISAC code, that for each instruction tells what it
does:

Syntax:

MIPS instrucion ADDDI

"ADDI" reg(0) "," reg(1) "," imm(2)

Semantics:

unsigned int gpregs[32];

void instr_direct_rri$op_addi$imm16$()
{
int op_arithm_imm = 0x08; {
int rt = 1; {
int rs = 28; {
short imm16 = imm_i16(2); //--- intrinsics, represents instruction's immediate operand
{
{

int simm = ((int)(imm16) << (32 - (16))) >> (32 - (16));

switch (op_arithm_imm)
{
//....

case 0x08:
case 0x09:
{if (rt != 0) gpregs[(rt)] = (( (((rs) != 0)?gpregs[(rs)]:0) ) + simm);};
break;

case 0x0A:
{if (rt != 0) gpregs[(rt)] = (( (((rs) != 0)?gpregs[(rs)]:0) ) < simm);};
break;

//....

}
}}}}}
}

Compile it with LLVM or any other compiler and get optimized code:

; ModuleID = 't2.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128"
target triple = "x86_64-unknown-linux-gnu"
@gpregs = common global [32 x i32] zeroinitializer, align 4 ; <[32 x i32]*> [#uses=2]

define void @"instr_direct_rri$op_addi$imm16$"() nounwind {
entry:
        %tmp = tail call i16 @llvm.immread.i16.i32(i32 2) nounwind ; <i16> [#uses=1]
        %shr = sext i16 %tmp to i32 ; <i32> [#uses=1]
        %tmp10 = load i32* getelementptr ([32 x i32]* @gpregs, i32 0, i64 28) ; <i32> [#uses=1]
        %add = add i32 %tmp10, %shr ; <i32> [#uses=1]
        store i32 %add, i32* getelementptr ([32 x i32]* @gpregs, i32 0, i64 1)
        ret void
}

This I can quite easily transform to instruction selection rule:

set(reg, add(reg, sext(32, imm)))

which is then used by compiler backend generator, so the code selector can then use instruction ADDI.

The problem is, that the only supported types now are i8, i16 and i32 (and floating in the future).
If the processor designer would like to have 12-bit operand, I need to turn it into 16-bit and
then the compiler would generate useless code for extension/truncation and
extracted code selection rule would not be correct.

clang already has some code for types like this, but there isn't any
way to declare such a type yet. Patches welcome, or I'll probably get
around to writing it at some point.

(If you have any additional questions about that, please direct them
to cfe-dev only.)

-Eli

Hi Adam,

One problem, I was trying to solve was, that I need to declare variables of let's say 5-bit width like 'i5 var',
the maximal bit-width may be limited to 64 bits. I need such variables to represent instruction's operands,
example is at the end this message.

any standard compliant C compiler supports i5, believe it or not.

Try this:

#include <stdio.h>

struct i3 { int i:3; };

int main(void) {
   struct i3 A, B, C;

   A.i = 2;
   B.i = 3;
   C.i = A.i + B.i;
   printf("%d + %d = %d\n", A.i, B.i, C.i);
   return 0;
}

I get:
2 + 3 = -3
which is correct for i3 arithmetic!
There might be some problems with signed vs unsigned bitfields,
depending on the compiler.

Ciao,

Duncan.

Hi Adam,
any standard compliant C compiler supports i5, believe it or not.

Try this:

#include <stdio.h>

struct i3 { int i:3; };

...

Ciao,

Duncan.

Hi,

  i knew about this feature and i tried this already, but thank you very much for reminding it to me.
I just needed to make few changes to the generated code and everything works as i expected!
I was doing something wrong before, because the compiler has generated some additional code that would make
problems when creating instruction selection rules.

If somebody would be having similar problems, I am putting fixed example here:

unsigned int gpregs[32];

struct i16 { int a:16;};

void instr_direct_rri()
{

  int op_arithm_imm = 0x08; {
  int rt = 1; {
  int rs = 28; {
  struct i16 imm16; imm16.a = immread_i16(0); {{ //immread_i16 is a special builtin with return type i16

  int simm = ((int)(imm16.a) << (32 - (16))) >> (32 - (16));

  switch (op_arithm_imm)
  {
     case 0x08:
     case 0x09:
       {if (rt != 0) gpregs[(rt)] = (( (((rs) != 0)?gpregs[(rs)]:0) ) + simm);};
       break;

     case 0x0A:
       {if (rt != 0) gpregs[(rt)] = (( (((rs) != 0)?gpregs[(rs)]:0) ) < simm);};
       break;

  }

}}}}}

}

After clang-cc -O3 -std=c99 tt.c -emit-llvm -o - I get:

%tmp = tail call i16 @llvm.immread.i16.i32(i32 0) nounwind ; <i16> [#uses=1]
%conv = sext i16 %tmp to i32 ; <i32> [#uses=1]
%tmp14 = load i32* getelementptr ([32 x i32]* @gpregs, i32 0, i64 28) ; <i32> [#uses=1]
%add = add i32 %tmp14, %conv ; <i32> [#uses=1]
store i32 %add, i32* getelementptr ([32 x i32]* @gpregs, i32 0, i64 1)
ret void

that represents exactly what i needed:

set(reg(1),
  add(
    reg(28),
    sext(32, imm(0))
  )
)

Thanks a lot
  Adam

Hi Eli,

  thank you for your answer, I have already solved the issue using C structures ( i.e. struct i5 { int a:5}),
that work exactly as i needed. Description can be found here: http://lists.cs.uiuc.edu/pipermail/llvmdev/2009-June/023352.html

Have a nice day
  Adam

Duncan Sands wrote:

Hi Adam,

One problem, I was trying to solve was, that I need to declare variables of let's say 5-bit width like 'i5 var',
the maximal bit-width may be limited to 64 bits. I need such variables to represent instruction's operands,
example is at the end this message.
    
any standard compliant C compiler supports i5, believe it or not.

Try this:

#include <stdio.h>

struct i3 { int i:3; };

int main(void) {
   struct i3 A, B, C;

   A.i = 2;
   B.i = 3;
   C.i = A.i + B.i;
   printf("%d + %d = %d\n", A.i, B.i, C.i);
   return 0;
}

I get:
2 + 3 = -3
which is correct for i3 arithmetic!
There might be some problems with signed vs unsigned bitfields,
depending on the compiler.
  

You're producing a signed overflow, which is simply undefined behavior.
Unsigned overflow is well-defined, even for bitfields, but signed is not.

Sebastian

Hi Sebastian,

You're producing a signed overflow, which is simply undefined behavior.
Unsigned overflow is well-defined, even for bitfields, but signed is not.

that's true, here's an unsigned version (outputs 5 + 5 = 2):

#include <stdio.h>

struct i3 { unsigned i:3; };

int main(void) {
   struct i3 A, B, C;

   A.i = 5;
   B.i = 5;
   C.i = A.i + B.i;
   printf("%d + %d = %d\n", A.i, B.i, C.i);
   return 0;
}
Ciao,

Duncan.

Note that you're actually doing signed arithmetic here; it just
doesn't happen to matter because "int" is much larger than the
bitfield.

-Eli