4

I'm trying to run libtcc from C++ to use C as a runtime scripting language. The runtime compiled code has to be able to run functions from the outside code. This is working fine when passing ints, but when passing a struct from the tcc-code to the gcc-code, weird things happen.

Minimal running example:

#include <libtcc.h>
#include <stdio.h>
struct Vec {
    int x;
};
void tmp(struct Vec test) {
    printf("got %x\n",test.x);
}
int main() {
    TCCState* tcc; tcc = tcc_new();
    tcc_set_output_type(tcc, TCC_OUTPUT_MEMORY);
    tcc_add_symbol(tcc, "tmp", (void*)&tmp);
    tcc_compile_string(tcc, "\
        struct Vec {int x;};\
        void tmp(struct Vec test);\
        void fun() {\
            struct Vec x = {0};\
            tmp(x);\
        }");
    tcc_relocate(tcc, TCC_RELOCATE_AUTO);
    void (*fun)(void) = (void(*)())tcc_get_symbol(tcc, "fun");
    fun();
}

Running with:

gcc -ltcc -ldl test.c && ./a.out
> got 23b472b0
tcc -ltcc -ldl test.c && ./a.out
> got 0

Why does the gcc compiled version not print the expected 0? When I put only long longs instead of ints into the struct, it works. Any other data type and random stuff is output.

At first I thought it was because of alignment or something, but it also happens when using only a single variable in the struct.

I'm using Linux 3.16 x86_64 and tcc 0.9.26

phiresky
  • 406
  • 4
  • 15
  • On which architecture -and which operating system ? Tinycc works better on 32 bits x86 (a.k.a. ia32) than on 64 bits x86-64 (a.k.a. amd64). – Basile Starynkevitch Oct 23 '14 at 20:09
  • `Arch Linux x86_64`. But what does "work better" mean? Is this a bug in the 64 bit version? – phiresky Oct 23 '14 at 20:15
  • I was never able to use `tcc` on serious programs on x86-64. It looks to me that tinycc is very buggy on 64 bits. – Basile Starynkevitch Oct 23 '14 at 20:17
  • See also [this](http://programmers.stackexchange.com/a/257873/40065) – Basile Starynkevitch Oct 23 '14 at 20:19
  • What part of that do you mean to be relevant? It doesn't really matter to me if the machine code is "slow", as long as it works as expected. – phiresky Oct 23 '14 at 20:27
  • It gave various alternative solutions. I would simply generate a `gen123.c` file on disk; then compile it with `gcc -O -g -fPIC -Wall -shared gen123.c -o gen123.so`; then `dlopen` the `"./gen123.so"` path. And I also mentioned several JIT compilation libraries like `libjit` – Basile Starynkevitch Oct 23 '14 at 20:30
  • You may need to compile the main program with `gcc -rdynamic -O -g -Wall test.c -ltcc -ldl -o test_prog` – Basile Starynkevitch Oct 23 '14 at 20:31
  • Hm i guess that's possible. I just liked the way of doing it directly in memory especially considering speed of compilation and portability (windows) – phiresky Oct 23 '14 at 20:39

2 Answers2

5

It appears that the problem centers around the way that C and C++ understand the whole "struct Vec test" as a parameter. In TCC, it is treated/assumed to be a pointer. In C++ it looks like one has to state that it is a pointer more clearly.

#include libtcc.h
#include stdio.h
struct Vec {
    int x;
};
void tmp(struct Vec * test) {
    printf("got %x\n",test->x);
}
int main() {
    TCCState* tcc; tcc = tcc_new();
    tcc_set_output_type(tcc, TCC_OUTPUT_MEMORY);
    tcc_add_symbol(tcc, "tmp", (void*)&tmp);
    tcc_compile_string(tcc, "\
        struct Vec {int x;};\
        void tmp(struct Vec test);\
        void fun() {\
            struct Vec x = {5};\
            tmp(x);\
        }");
    tcc_relocate(tcc, TCC_RELOCATE_AUTO);
    void (*fun)(void) = (void(*)())tcc_get_symbol(tcc, "fun");
    fun();
}

Output appears as:

got 5
Charles Lohr
  • 695
  • 1
  • 8
  • 23
  • 1
    What. Doesn't the C standard dictate how structs work? I mean none of that code is even C++. Thanks! – phiresky Oct 23 '14 at 22:55
  • 2
    @phiresky: Structs are passed by value. (If the compiler implements pass-by-value using pointers, that's fine, as long as the semantics are the same.) If a function with a parameter `struct Vec test` modifies a member of `Test`, that change must not affect the caller -- and a quick experiment with tcc version 0.9.25 indicates that tcc gets this right. (I don't know what all this `TCCState` stuff is about.) – Keith Thompson Oct 23 '14 at 23:09
  • Your code will not compile; it's missing the `<` and `>` delimiters on the `#include` directives. – Keith Thompson Oct 23 '14 at 23:09
  • @KeithThompson oh well, I guess you learn something new every day. The TCCState is just the way of calling the tcc library from c. – phiresky Oct 23 '14 at 23:24
  • @phiresky: If tcc assumed that a struct parameter is a pointer, then [this program](http://codepad.org/6NQ9xYvy) would print `FAILED`. With both gcc 4.8.2 and 0.9.25 on my system, it prints `PASSED` -- which suggests that this answer is incorrect. Charles, am I missing something? – Keith Thompson Oct 23 '14 at 23:33
  • @KeithThompson why would it? If tcc knows about it, it probably compiles the receiving function to in reality use a pointer, even without writing it. Explicitly writing struct* would mean you want only a reference. – phiresky Oct 23 '14 at 23:44
  • @phiresky: The answer suggests that tcc treats a struct parameter as if it were a pointer-to-struct parameter. If that were the case, the linked program would print `FAILED`, because the assignment to `param.ok` would update `arg.ok`. It doesn't. (BTW, C doesn't have C++-style references, if that's what you're referring to.) – Keith Thompson Oct 23 '14 at 23:48
  • @KeithThompson Why would it? I'd guess it copies the struct to some different location and then points to that location implicitly instead of putting it by value into the arguments. If it wouldn't copy it it would not be standard compliant. Didn't you say "If the compiler implements pass-by-value using pointers, that's fine, as long as the semantics are the same."? – phiresky Oct 24 '14 at 09:32
  • The question is whether it copies the struct. As far as the language semantics are concerned, all that matters is that the function operates on a copy of the struct object, not on the object defined in the caller. Whether (on the machine code level) the function accesses that copy directly or via its address is irrelevant as far as the language is concerned (though perhaps it's relevant to your question). – Keith Thompson Oct 24 '14 at 15:32
  • This answer implies that tcc is actually passing the struct argument by reference, giving the function access to the object in the caller. That would be illegal, and in my experiment tcc doesn't do that. It also implies that C and C++ have different rules for passing struct arguments; in fact, they do not. – Keith Thompson Oct 24 '14 at 15:33
1

In new version of tcc (libtcc) from official git repository it works as expected (prints "got 0"). tested on gcc 4.9.3 and tcc 0.9.26 (commit 00ba4b).

njoy
  • 11
  • 1