odin-lang / Odin

Odin Programming Language
https://odin-lang.org
BSD 3-Clause "New" or "Revised" License
6.55k stars 570 forks source link

Correct ABI Support for Linux and OS X #387

Closed gingerBill closed 4 years ago

gingerBill commented 5 years ago

Correctly implement ABI for all supported platforms.

This may require a huge change to how the IR is generated as certain ABIs, such as System V, will separate a value across multiple registers and the current LLVM IR generation only supports to a single register/stack value per passed value.

Example:

Foo :: struct {x, y, z: f32}

Would be passed to LLVM IR as the following parameters:

(<2 x f32> %a, f32 %b)
dimenus commented 5 years ago

Just want to add a repro example to this issue. Right now it doesn't seem possible to interop with C when using any struct that is larger than 8 bytes.

foo.c

#include <stdint.h>
#include <stdio.h>

typedef struct CXString {
    void* data;
    uint32_t flags;
} CXString;

const char *hellostr() {
    return "hello world";
}

CXString get_cxstring() {
    return (CXString){
        .data = (void*)"foo bar baz",
        .flags = 0xDEADBEEF,
    };
}

const char* get_cstr_from_cxstring(const char *val, CXString c) {
    printf("val: %s\n", val);
    printf("C: %s\n", (const char*)c.data);
    return (const char*)c.data;
}

main.odin

package repro

foreign import "foo.so"

import "core:c";
import "core:fmt";

CXString :: struct {
    data: rawptr,
    flags: u32,
}

foreign foo {
    @(link_name = "hellostr") hellostr :: proc "c" () -> cstring ---;
    @(link_name = "get_cxstring") get_cxstring :: proc "c" () -> CXString ---;
    @(link_name = "get_cstr_from_cxstring") get_cstr :: proc "c" (test: cstring, cx: CXString) -> cstring ---;
}

main :: proc() {
    fmt.println(hellostr());
    cxstr := get_cxstring();
    fmt.println(cast(cstring)cxstr.data, get_cstr("i am a str", cxstr));
}
travisdoor commented 4 years ago

Hi, I have a same issue with my programming language using LLVM backend, my first idea to fix this was use LLVM's 'byval' attribute and pass all structures by pointers (inspiration to do so came from clang C generated IR with same expected functionality), but it still does not work. What about to use implicit wrapper function for this (change generation of external calls seems to be quite hard since it requires lot of changes to IR generation pass); idea is not to call externals with struct passed by value as an argument directly, but create IR function taking struct by value as normal, do the split into register-friendly representation inside this function and then call original external.

gingerBill commented 4 years ago

This is now correctly supported on System V AMD64 ABI.