GitHub - Konstantin8105/c4go: Transpiling C code to Go code (original) (raw)

Go Report Card codecov GitHub license Go Go Reference

A tool for transpiling C code to Go code.

Milestones of the project:

  1. Transpiling project GNU GSL.
  2. Transpiling project GTK+.

Notes:

Installation

Using a terminal window, position into a working directory (E.g. $HOME/development). Then, execute the following shell commands:

Create a local c4go repository and position into it.

git clone https://github.com/Konstantin8105/c4go cd c4go

Update the Go source files.

go generate ./...

Install the c4go executable in the standard Go bin directory: $HOME/go/bin.

go install

Make sure that $HOME/go/bin is in the path (good practice).

Then, verify that the c4go is available for use.

c4go version

Sample c4go version console output:

Build time: May  4 20:41:31 2024 UTC
Git hash:   "c5400a76df0f5157f234547bef3ae8fb7b692685"

Usage example

Change your location to the folder with examples:

cd $GOPATH/src/github.com/Konstantin8105/c4go/examples/

Transpile one file from the C example folder:

c4go transpile prime.c

Look at the result

nano prime.go

Check the result:

go run prime.go

Enter a number

13

The number is: 13

Prime number.

C code of file prime.c:

#include <stdio.h>

int main() { int n, c;

printf("Enter a number\n");
// get value
scanf("%d", &n);
printf("The number is: %d\n", n);

// -------
if (n == 2)
    printf("Prime number.\n");
else {
    for (c = 2; c <= n - 1; c++) {
        if (n % c == 0)
            break;
    }
    if (c != n)
        printf("Not prime.\n");
    else
        printf("Prime number.\n");
}
return 0;

}

Go code of file prime.go:

// // Package - transpiled by c4go // // If you have found any issues, please raise an issue at: // https://github.com/Konstantin8105/c4go/ //

package main

import "unsafe" import "github.com/Konstantin8105/c4go/noarch" import "fmt"

// main - transpiled function from C4GO/examples/prime.c:3 func main() { var n int32 var c int32 fmt.Printf("Enter a number\n") // get value noarch.Scanf([]byte("%d\x00"), c4goUnsafeConvert_int32(&n)) noarch.Printf([]byte("The number is: %d\n\x00"), n) if n == 2 { // ------- fmt.Printf("Prime number.\n") } else { for c = 2; c <= n-1; c++ { if n%c == 0 { break } } if c != n { fmt.Printf("Not prime.\n") } else { fmt.Printf("Prime number.\n") } } return }

// c4goUnsafeConvert_int32 : created by c4go func c4goUnsafeConvert_int32(c4go_name int32) []int32 { return ([1000000]int32)(unsafe.Pointer(c4go_name))[:] }

Example with binding function

C:

#include <math.h> #include <stdio.h>

int main() { int n; double param = 8.0, result; result = frexp(param, &n); printf("result = %5.2f\n", result); printf("n = %d\n", n); return 0; }

c4go add automatically C binding for function without implementation:

// // Package - transpiled by c4go // // If you have found any issues, please raise an issue at: // https://github.com/Konstantin8105/c4go/ //

package main

// #include </usr/include/math.h> import "C"

import "github.com/Konstantin8105/c4go/noarch" import "unsafe"

// main - transpiled function from C4GO/examples/math.c:4 func main() { var n int32 var param float64 = 8 var result float64 result = frexp(param, c4goUnsafeConvert_int32(&n)) noarch.Printf([]byte("result = %5.2f\n\x00"), result) noarch.Printf([]byte("n = %d\n\x00"), n) return }

// c4goUnsafeConvert_int32 : created by c4go func c4goUnsafeConvert_int32(c4go_name int32) []int32 { return ([1000000]int32)(unsafe.Pointer(c4go_name))[:] }

// frexp - add c-binding for implemention function func frexp(arg0 float64, arg1 []int32) float64 { return float64(C.frexp(C.double(arg0), (*C.int)(unsafe.Pointer(&arg1[0])))) }

Example with C-pointers and C-arrays

#include <stdio.h>

// input argument - C-pointer void a(int* v1) { printf("a: %d\n", *v1); }

// input argument - C-array void b(int v1[], int size) { for (size--; size >= 0; size--) { printf("b: %d %d\n", size, v1[size]); } }

int main() { // value int i1 = 42; a(&i1); b(&i1, 1);

// C-array
int i2[] = { 11, 22 };
a(i2);
b(i2, 2);

// C-pointer from value
int* i3 = &i1;
a(i3);
b(i3, 1);

// C-pointer from array
int* i4 = i2;
a(i4);
b(i4, 2);

// C-pointer from array
int* i5 = i2[1];
a(i5);
b(i5, 1);

return 0;

}

// // Package - transpiled by c4go // // If you have found any issues, please raise an issue at: // https://github.com/Konstantin8105/c4go/ //

package main

import "unsafe" import "github.com/Konstantin8105/c4go/noarch"

// a - transpiled function from C4GO/examples/ap.c:4 func a(v1 []int32) { // input argument - C-pointer noarch.Printf([]byte("a: %d\n\x00"), v1[0]) }

// b - transpiled function from C4GO/examples/ap.c:7 func b(v1 []int32, size int32) { { // input argument - C-array for size -= 1; size >= 0; size-- { noarch.Printf([]byte("b: %d %d\n\x00"), size, v1[size]) } } }

// main - transpiled function from C4GO/examples/ap.c:14 func main() { var i1 int32 = 42 // value a(c4goUnsafeConvert_int32(&i1)) b(c4goUnsafeConvert_int32(&i1), 1) var i2 []int32 = []int32{11, 22} // C-array a(i2) b(i2, 2) var i3 []int32 = c4goUnsafeConvert_int32(&i1) // C-pointer from value a(i3) b(i3, 1) var i4 []int32 = i2 // C-pointer from array a(i4) b(i4, 2) var i5 []int32 = i2[1:] // C-pointer from array a(i5) b(i5, 1)

return

}

// c4goUnsafeConvert_int32 : created by c4go func c4goUnsafeConvert_int32(c4go_name int32) []int32 { return ([1000000]int32)(unsafe.Pointer(c4go_name))[:] }

Example of transpilation neatvi.

clone git repository

git clone https://github.com/aligrudi/neatvi.git

move in folder

cd neatvi

look in Makefile

cat Makefile

Makefile:

CC = cc
CFLAGS = -Wall -O2
LDFLAGS =

OBJS = vi.o ex.o lbuf.o mot.o sbuf.o ren.o dir.o syn.o reg.o led.o \
    uc.o term.o rset.o regex.o cmd.o conf.o

all: vi

conf.o: conf.h

%.o: %.c
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>C</mi><mi>C</mi><mo stretchy="false">)</mo><mo>−</mo><mi>c</mi></mrow><annotation encoding="application/x-tex">(CC) -c </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.07153em;">CC</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">c</span></span></span></span>(CFLAGS) $<
vi: $(OBJS)
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>C</mi><mi>C</mi><mo stretchy="false">)</mo><mo>−</mo><mi>o</mi></mrow><annotation encoding="application/x-tex">(CC) -o </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.07153em;">CC</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">o</span></span></span></span>@ <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>O</mi><mi>B</mi><mi>J</mi><mi>S</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(OBJS) </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05017em;">OB</span><span class="mord mathnormal" style="margin-right:0.09618em;">J</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mclose">)</span></span></span></span>(LDFLAGS)
clean:
rm -f *.o vi

As we see not need any specific, for example -clang-flag.

Let's try transpile:

Result:

panic: clang failed: exit status 1:

/temp/neatvi/reg.c:6:14: error: redefinition of 'bufs' with a different type: 'char *[256]' vs 'struct buf [8]'
static char *bufs[256];
             ^
/temp/neatvi/ex.c:35:3: note: previous definition is here
} bufs[8];
  ^
/temp/neatvi/reg.c:12:9: error: returning 'struct buf' from a function with incompatible result type 'char *'
 return bufs[c];
        ^~~~~~~
/temp/neatvi/reg.c:17:81: error: invalid operands to binary expression ('int' and 'struct buf')
 char *pre = ((*__ctype_b_loc ())[(int) ((c))] & (unsigned short int) _ISupper) && bufs[tolower(c)] ? bufs[tolower(c)] : "";
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~~~~~~~~~~~~~
/temp/neatvi/reg.c:21:7: error: passing 'struct buf' to parameter of incompatible type 'void *'
 free(bufs[tolower(c)]);
      ^~~~~~~~~~~~~~~~
/usr/include/stdlib.h:483:25: note: passing argument to parameter '__ptr' here
extern void free (void *__ptr) __attribute__ ((__nothrow__ ));
                        ^
/temp/neatvi/reg.c:22:19: error: assigning to 'struct buf' from incompatible type 'char *'
 bufs[tolower(c)] = buf;
                  ^ ~~~
/temp/neatvi/reg.c:43:8: error: passing 'struct buf' to parameter of incompatible type 'void *'
  free(bufs[i]);
       ^~~~~~~
/usr/include/stdlib.h:483:25: note: passing argument to parameter '__ptr' here
extern void free (void *__ptr) __attribute__ ((__nothrow__ ));
                        ^
/temp/neatvi/regex.c:98:12: error: static declaration of 'uc_len' follows non-static declaration
static int uc_len(char *s)
           ^
/temp/neatvi/vi.h:83:5: note: previous declaration is here
int uc_len(char *s);
    ^
/temp/neatvi/regex.c:200:14: error: static declaration of 'uc_beg' follows non-static declaration
static char *uc_beg(char *beg, char *s)
             ^
/temp/neatvi/vi.h:101:7: note: previous declaration is here
char *uc_beg(char *beg, char *s);
      ^
/temp/neatvi/vi.c:635:12: error: redefinition of 'linecount'
static int linecount(char *s)
           ^
/temp/neatvi/lbuf.c:124:12: note: previous definition is here
static int linecount(char *s)
           ^
9 errors generated.


goroutine 1 [running]:
main.generateAstLines(0x1, 0x0, 0xc000010140, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
    /go/src/github.com/Konstantin8105/c4go/main.go:438 +0xd40
main.Start(0x1, 0x0, 0xc000010140, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
    /go/src/github.com/Konstantin8105/c4go/main.go:356 +0xa9
main.runCommand(0x0)
    /go/src/github.com/Konstantin8105/c4go/main.go:723 +0xa45
main.main()
    /go/src/github.com/Konstantin8105/c4go/main.go:556 +0x22

That error is good and enought information for next step of transpilation. Let's clarify one of them - we see 2 function with same function name and if you open files and compare.

/temp/neatvi/vi.c:635:12: error: redefinition of 'linecount'
static int linecount(char *s)

/temp/neatvi/lbuf.c:124:12: note: previous definition is here
static int linecount(char *s)

This is 2 absolute identical function, so we can remove one of them. I choose remove function linecount from file vi.c, because in according to error - it is duplicate.

Also remove next function in according to errors:

Next error is about duplicate of variable names:

/temp/neatvi/reg.c:6:14: error: redefinition of 'bufs' with a different type: 'char *[256]' vs 'struct buf [8]'
static char *bufs[256];
             ^
/temp/neatvi/ex.c:35:3: note: previous definition is here
} bufs[8];

Let's replace variable name in file reg.c and run:

sed -i.bak 's/bufs/bufs_postfix/g' reg.c

Now, let's try again and output in file vi.go:

c4go transpile -o vi.go *.c

Now transpiled without clang error. Look the result:

We see warning of lose some types:

// Warning (*ast.BinaryOperator):  /temp/neatvi/cmd.c:20 :Cannot transpile BinaryOperator with type 'int' : result type = {}. Error: operator is `=`. Cannot casting {__pid_t -> int}. err = Cannot resolve type '__pid_t' : I couldn't find an appropriate Go type for the C type '__pid_t'.

For fix that add flag -s for adding all type from C standard library:

c4go transpile -o vi.go -s *.c

Now, all is Ok. Look the result:

C standard library implementation

            assert.h	       1/1	         100%
             ctype.h	     13/13	         100%
             errno.h	       0/1	           0%
             float.h	          	    undefined
            iso646.h	          	    undefined
            limits.h	          	    undefined
            locale.h	       0/3	           0%
              math.h	     22/22	         100%
            setjmp.h	       0/3	           0%
            signal.h	       3/3	         100%
            stdarg.h	       4/4	         100%
            stddef.h	       4/4	         100%
             stdio.h	     37/41	        90.2%
            stdlib.h	     31/37	        83.8%
            string.h	     21/24	        87.5%
              time.h	      1/15	        6.67%
             wchar.h	      3/60	           5%
            wctype.h	      0/21	           0%

Contributing

Feel free to submit PRs or open issues. Main information from: en.cppreference.com

Testing

By default, only unit tests are run with go test. You can also include all tests:

For tests work like this:

  1. clang compiles the C to a binary as normal.
  2. c4go converts the C file to Go.
  3. Both binaries are executed and the output is compared. All C files will contain some output so the results can be verified.

Note

Use lastest version of clang.

If you use Ubuntu, then use command like next for choose clang version:

sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-6.0 1000

Performance

Main time of transpilation takes clang, for example run:

go test -run=Benchmark -bench=. -benchmem

Result looks for example:

goos: linux
goarch: amd64
pkg: github.com/Konstantin8105/c4go
BenchmarkTranspile/Full-6         	       5	 274922964 ns/op	43046865 B/op	  379676 allocs/op
BenchmarkTranspile/GoCode-6       	      20	  86806808 ns/op	36577533 B/op	  308060 allocs/op
PASS

So, transpilation time is just 30% of full time. In my point of view no need of performance optimization, see Amdahl's law.

Example of performance analyse

Please run:

Run cpuprofiling for sqlite transpilation example

time ./scripts/sqlite.sh

Example of output:

% Total % Received % Xferd Average Speed Time Time Time Current

Dload Upload Total Spent Left Speed

#100 2217k 100 2217k 0 0 235k 0 0:00:09 0:00:09 --:--:-- 357k #Archive: /tmp/SQLITE/sqlite-amalgamation-3250200.zip

creating: /tmp/SQLITE/sqlite-amalgamation-3250200/

inflating: /tmp/SQLITE/sqlite-amalgamation-3250200/sqlite3ext.h

inflating: /tmp/SQLITE/sqlite-amalgamation-3250200/sqlite3.c

inflating: /tmp/SQLITE/sqlite-amalgamation-3250200/sqlite3.h

inflating: /tmp/SQLITE/sqlite-amalgamation-3250200/shell.c

#After transpiling shell.c and sqlite3.c together, have summary: 695 warnings. #In file sqlite.go summary : 3 warnings in go build. #Amount unsafe package using: 2902 # #real 0m18.434s #user 0m14.212s #sys 0m1.434s

Run profiler

go tool pprof ./testdata/cpu.out

For more information, see Profiling Go Programs.