RealNeGate / Cuik

A Modern C11 compiler (STILL EARLY)
MIT License
656 stars 32 forks source link

Linux target produces incorrect assembly for function calls with 5> parameters. #37

Open Goubermouche opened 9 months ago

Goubermouche commented 9 months ago

Hi, I believe I've discovered a bug in the Linux target, specifically, there seems to be an issue with how function calls with more than 5 parameters behave. I've attached a minimal reproducible example below:

#include "tb/include/tb.h"
#define CUIK_USE_TB

int main() {
    TB_FeatureSet features = { 0 };
    TB_Module* module = tb_module_create(TB_ARCH_X86_64, TB_SYSTEM_LINUX,  &features, false);
    TB_ModuleSectionHandle text = tb_module_get_text(module);

    TB_PrototypeParam printf_ret = { TB_TYPE_I32 };
    TB_PrototypeParam printf_param = { TB_TYPE_PTR };

    // printf
    TB_External* printf_external = tb_extern_create(module, 7, "printf", TB_EXTERNAL_SO_LOCAL);
    TB_FunctionPrototype* printf_proto = tb_prototype_create(module, TB_STDCALL, 1, &printf_param, 1, &printf_ret, true);

    // main
    TB_PrototypeParam main_ret = { TB_TYPE_I32 };

    // main
    TB_Function* main_f = tb_function_create(module, 5, "main", TB_LINKAGE_PUBLIC);
    TB_FunctionPrototype* main_prototype = tb_prototype_create(module, TB_STDCALL, 0, NULL, 1, &main_ret, false);
    tb_function_set_prototype(main_f, text, main_prototype, NULL);

    TB_Node* params[8] = {
        tb_inst_string(main_f, 22, "%d %d %d %d %d %d %d\n"),
        tb_inst_sint(main_f, TB_TYPE_I32, 1),
        tb_inst_sint(main_f, TB_TYPE_I32, 2),
        tb_inst_sint(main_f, TB_TYPE_I32, 3),
        tb_inst_sint(main_f, TB_TYPE_I32, 4),
        tb_inst_sint(main_f, TB_TYPE_I32, 5),
        tb_inst_sint(main_f, TB_TYPE_I32, 6),
        tb_inst_sint(main_f, TB_TYPE_I32, 7)
    };

    tb_inst_call(main_f, printf_proto, tb_inst_get_symbol_address(main_f, (TB_Symbol*)printf_external), 8, params);

    TB_Node* ret_value = tb_inst_sint(main_f, TB_TYPE_I32, 0);
    tb_inst_ret(main_f, 1, &ret_value);

    TB_Passes* p_main = tb_pass_enter(main_f, tb_function_get_arena(main_f));
    tb_pass_exit(p_main);

    TB_ExportBuffer buffer = tb_module_object_export(module, TB_DEBUGFMT_NONE);

    // copy into file
    if (!tb_export_buffer_to_file(buffer, "./a.o")) {
        printf("err\n");
    }

    return 0;
}

When we link the generated object file for TB_SYSTEM_LINUX using clang like so:

$ clang a.o -o test

The program partially prints gibberish:

1 2 3 4 5 1776 1872767257

If we were to compile the same IR with TB_SYSTEM_WINDOWS, and with the same command, we'd get the following, correct result:

1 2 3 4 5 6 7