ebitengine / purego

Apache License 2.0
1.95k stars 63 forks source link

Using with tinycc #174

Closed markusbkk closed 7 months ago

markusbkk commented 7 months ago

Curious. Has anybody had any success using purego with a shared object release of tinycc?

When running the following (admittedly, not very pretty) code, I receive two error messages by tcc itself. Namely:

tcc: error: library 'c' not found tcc: error: file 'crtn.o' not found

The code:

package main

import (
        "fmt"
        "runtime"

        "github.com/ebitengine/purego"
)

func getSystemLibrary() string {
        switch runtime.GOOS {
        case "darwin":
                return "/usr/lib/libSystem.B.dylib"
        case "linux":
                return "libc.so.6"
        default:
                panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
        }
}

func getTCC() string {
        return "./libtcc.so"
}

type TCCSTATE uintptr

func main() {
        libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
        if err != nil {
                panic(err)
        }
        var puts func(string)
        purego.RegisterLibFunc(&puts, libc, "puts")
        puts("Calling C from Go without Cgo!")

        libtcc, err2 := purego.Dlopen(getTCC(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
        if err2 != nil {
                panic(err2)
        }
        var tcc_new func() TCCSTATE
        purego.RegisterLibFunc(&tcc_new, libtcc, "tcc_new")
        tcc_context := tcc_new()

        var tcc_set_lib_path func(TCCSTATE, string)
        purego.RegisterLibFunc(&tcc_set_lib_path, libtcc, "tcc_set_lib_path")
        tcc_set_lib_path(tcc_context, "/home/blinx123/tinycc")

        var tcc_compile_string func(TCCSTATE, string)
        purego.RegisterLibFunc(&tcc_compile_string, libtcc, "tcc_compile_string")
        tcc_compile_string(tcc_context, `extern int printf(char* c,...);
        int main () {
                printf("Hello world!\n");
                return 0;
        }`)

        var tcc_run func(TCCSTATE, int)
        purego.RegisterLibFunc(&tcc_run, libtcc, "tcc_run")
        tcc_run(tcc_context, 0)
}

I grabbed tinycc from here, ran ./configure and compiled it via make libtcc.so.

TotallyGamerJet commented 7 months ago

Seems like an issue with tcc, no? The error complains about tcc not being able to find files. Perhaps those need to be in the working directory? Have you gotten tcc to work as a dynamic library in C? Also which version of purego and GOOS are you using?

markusbkk commented 7 months ago

Seems like an issue with tcc, no? The error complains about tcc not being able to find files. Perhaps those need to be in the working directory? Have you gotten tcc to work as a dynamic library in C? Also which version of purego and GOOS are you using?

Yea. g++ main.c -L. -l:libtcc.so -ldl -lpthread properly compiled and linked the library under C. I've got the latest Purego and GOOS is linux/amd64.

TotallyGamerJet commented 7 months ago

And the compiled main.c works as expected? One thing I see wrong is that tcc_new should return a pointer to TCC_STATE. All the other functions would take that pointer too. I doubt that's the cause though

markusbkk commented 7 months ago

And the compiled main.c works as expected? One thing I see wrong is that tcc_new should return a pointer to TCC_STATE. All the other functions would take that pointer too. I doubt that's the cause though

Yea.

This code works as expected in C

#include "./libtcc.h"

int add(int a, int b) { return a + b; }

char my_program[] =
"#include <stdio.h>"
"int add(int a, int b) { return a + b; }"
"int fib(int n) {\n"
"    if (n <= 2) return 1;\n"
"    else return fib(n-1) + fib(n-2);\n"
"}\n"
"int foobar(int n) {\n"
"    printf(\"fib(%d) = %d\\n\", n, fib(n));\n"
"    printf(\"add(%d, %d) = %d\\n\", n, 2 * n, add(n, 2 * n));\n"
"    return 1337;\n"
"}\n"
"int main() { foobar(2);}";

int main() {
    TCCState *s = tcc_new();

    tcc_set_output_type(s, TCC_OUTPUT_MEMORY);

    if (tcc_compile_string(s, my_program) != -1)
    {
        tcc_run(s, 0, 0);    
    }
    return 0;
}
TotallyGamerJet commented 7 months ago

Why didnt you write exactly what that C code does? The Go code is setting the library path but the C isn't. Also tcc_run has three arguments instead of two in the Go version. And you don't set the output type in the Go version

markusbkk commented 7 months ago

Why didnt you write exactly what that C code does? The Go code is setting the library path but the C isn't. Also tcc_run has three arguments instead of two in the Go version. And you don't set the output type in the Go version

I just sorta copied another example. Having a bit of a burn-out these past few days, so it's hard to focus. Thanks. I'll try replicating the C version in Go and get back to you.

markusbkk commented 7 months ago

Alright. Turns out it really was the output type that had to be set manually. A comment in libtcc.h said that it MUST be set, but then also said that in-memory is the default. That was a bit confusing.

Here's the final, hackish code.

package main

import (
    "fmt"
    "runtime"

    "github.com/ebitengine/purego"
)

func getSystemLibrary() string {
    switch runtime.GOOS {
    case "darwin":
        return "/usr/lib/libSystem.B.dylib"
    case "linux":
        return "libc.so.6"
    default:
        panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
    }
}

func getTCC() string {
    return "libtcc.so"
}

type TCCSTATE *uintptr

func main() {
    var program = `#include <stdio.h>
    int add(int a, int b) { return a + b; }
    int fib(int n) {
        if (n <= 2) return 1;
        else return fib(n-1) + fib(n-2);
    }
    int foobar(int n) {
        printf("fib(%d) = %d\n", n, fib(n));
        printf("add(%d, %d) = %d\n", n, 2 * n, add(n, 2 * n));
        return 1337;
    }
    int main() { foobar(2);}`

    libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
    if err != nil {
        panic(err)
    }
    var puts func(string)
    purego.RegisterLibFunc(&puts, libc, "puts")
    puts("Calling C from Go without Cgo!")

    libtcc, err2 := purego.Dlopen(getTCC(), purego.RTLD_NOW|purego.RTLD_GLOBAL)
    if err2 != nil {
        panic(err2)
    }
    var tcc_new func() TCCSTATE
    purego.RegisterLibFunc(&tcc_new, libtcc, "tcc_new")
    tcc_context := tcc_new()

    var tcc_set_output_type func(TCCSTATE, int)
    purego.RegisterLibFunc(&tcc_set_output_type, libtcc, "tcc_set_output_type")
    tcc_set_output_type(tcc_context, 1)

    var tcc_compile_string func(TCCSTATE, string)
    purego.RegisterLibFunc(&tcc_compile_string, libtcc, "tcc_compile_string")
    tcc_compile_string(tcc_context, program)

    var tcc_run func(TCCSTATE, int, int)
        purego.RegisterLibFunc(&tcc_run, libtcc, "tcc_run")
    tcc_run(tcc_context, 0, 0)
}

Thanks again.