diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1cc4e2bc..9486d398 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -10,7 +10,8 @@ jobs: strategy: matrix: llvm-version: [11, 13] - opt-level: ['-O0', '-O3'] + # Testing all levels because there was a bug that only happened with -O1. (#224) + opt-level: ['-O0', '-O1', '-O2', '-O3'] steps: - uses: actions/checkout@v3 - run: sudo apt install -y llvm-${{ matrix.llvm-version }}-dev clang-${{ matrix.llvm-version }} make valgrind diff --git a/jou.cbp b/jou.cbp index 0010181f..be02a9ca 100644 --- a/jou.cbp +++ b/jou.cbp @@ -68,6 +68,9 @@ + + diff --git a/runtests.sh b/runtests.sh index e208a6a5..f2527d24 100755 --- a/runtests.sh +++ b/runtests.sh @@ -150,7 +150,7 @@ function run_test() fi show_run $joufile - if diff -u --color=always \ + if diff --text -u --color=always \ <(generate_expected_output $joufile $correct_exit_code | tr -d '\r') \ <(ulimit -v 500000 2>/dev/null; bash -c "$command; echo Exit code: \$?" 2>&1 | post_process_output $joufile | tr -d '\r') \ &>> $diffpath diff --git a/src/codegen.c b/src/codegen.c index 60dee82c..0f4d1f4a 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -435,6 +435,9 @@ LLVMModuleRef codegen(const CfGraphFile *cfgfile, const TypeContext *typectx) .builder = LLVMCreateBuilder(), }; + LLVMSetTarget(st.module, get_target()->triple); + LLVMSetDataLayout(st.module, get_target()->data_layout); + for (GlobalVariable **v = typectx->globals.ptr; v < End(typectx->globals); v++) { LLVMTypeRef t = codegen_type((*v)->type); LLVMValueRef globalptr = LLVMAddGlobal(st.module, t, (*v)->name); diff --git a/src/jou_compiler.h b/src/jou_compiler.h index a0a6d3e7..69f99104 100644 --- a/src/jou_compiler.h +++ b/src/jou_compiler.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "util.h" void update_jou_compiler(void); @@ -548,6 +549,25 @@ struct CfGraphFile { }; +/* +LLVM makes a mess of how to define what kind of computer will run the +compiled programs. Sometimes it wants a target triple, sometimes a +data layout. Sometimes it wants a string, sometimes an object +representing the thing. + +This struct aims to provide everything you may ever need. Hopefully it +will make the mess slightly less miserable to you. +*/ +struct Target { + char triple[100]; + char data_layout[500]; + LLVMTargetRef target_ref; + LLVMTargetMachineRef target_machine_ref; + LLVMTargetDataRef target_data_ref; +}; +void init_target(void); +const struct Target *get_target(void); + /* The compiling functions, i.e. how to go from source code to LLVM IR and eventually running the LLVM IR. Each function's result is fed into the next. diff --git a/src/main.c b/src/main.c index 5c2a6372..1950d5f6 100644 --- a/src/main.c +++ b/src/main.c @@ -383,11 +383,16 @@ static void check_for_404_imports(const struct CompileState *compst) int main(int argc, char **argv) { + init_target(); init_types(); struct CompileState compst = { .stdlib_path = find_stdlib() }; const char *filename; parse_arguments(argc, argv, &compst.flags, &filename); + if (compst.flags.verbose) { + printf("Target triple: %s\n", get_target()->triple); + printf("Data layout: %s\n", get_target()->data_layout); + } #ifdef _WIN32 char *startup_path = malloc(strlen(compst.stdlib_path) + 50); diff --git a/src/run.c b/src/run.c index 694a5327..1f201f93 100644 --- a/src/run.c +++ b/src/run.c @@ -15,57 +15,18 @@ static void compile_to_object_file(LLVMModuleRef module, const char *path, const CommandLineFlags *flags) { - LLVMInitializeX86TargetInfo(); - LLVMInitializeX86Target(); - LLVMInitializeX86TargetMC(); - LLVMInitializeX86AsmParser(); - LLVMInitializeX86AsmPrinter(); - - char triple[100]; -#ifdef _WIN32 - // Default is x86_64-pc-windows-msvc - strcpy(triple, "x86_64-pc-windows-gnu"); -#else - char *tmp = LLVMGetDefaultTargetTriple(); - assert(strlen(tmp) < sizeof triple); - strcpy(triple, tmp); - LLVMDisposeMessage(tmp); -#endif - if (flags->verbose) - printf("Emitting object file \"%s\" for %s\n", path, triple); - - char *error = NULL; - LLVMTargetRef target = NULL; - if (LLVMGetTargetFromTriple(triple, &target, &error)) { - assert(error); - fprintf(stderr, "LLVMGetTargetFromTriple failed: %s\n", error); - exit(1); - } - assert(!error); - assert(target); - - // The CPU name is the first component of the target triple. - // But it's not quite that simple, achthually the typical cpu name is x86-64 instead of x86_64... - char cpu[100]; - snprintf(cpu, sizeof cpu, "%.*s", (int)strcspn(triple, "-"), triple); - if (!strcmp(cpu, "x86_64")) - strcpy(cpu, "x86-64"); - - LLVMTargetMachineRef machine = LLVMCreateTargetMachine( - target, triple, cpu, "", LLVMCodeGenLevelDefault, LLVMRelocDefault, LLVMCodeModelDefault); - assert(machine); + printf("Emitting object file \"%s\"\n", path); char *tmppath = strdup(path); - if (LLVMTargetMachineEmitToFile(machine, module, tmppath, LLVMObjectFile, &error)) { + char *error = NULL; + if (LLVMTargetMachineEmitToFile(get_target()->target_machine_ref, module, tmppath, LLVMObjectFile, &error)) { assert(error); fprintf(stderr, "failed to emit object file \"%s\": %s\n", path, error); exit(1); } free(tmppath); assert(!error); - - LLVMDisposeTargetMachine(machine); } static char *malloc_sprintf(const char *fmt, ...) diff --git a/src/target.c b/src/target.c new file mode 100644 index 00000000..d82af995 --- /dev/null +++ b/src/target.c @@ -0,0 +1,57 @@ +#include +#include "jou_compiler.h" + +static struct Target target = {0}; + +static void cleanup(void) +{ + LLVMDisposeTargetMachine(target.target_machine_ref); + LLVMDisposeTargetData(target.target_data_ref); +} + +void init_target(void) +{ + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86AsmParser(); + LLVMInitializeX86AsmPrinter(); + +#ifdef _WIN32 + // Default is x86_64-pc-windows-msvc + strcpy(target.triple, "x86_64-pc-windows-gnu"); +#else + char *triple = LLVMGetDefaultTargetTriple(); + assert(strlen(triple) < sizeof target.triple); + strcpy(target.triple, triple); + LLVMDisposeMessage(triple); +#endif + + char *error = NULL; + if (LLVMGetTargetFromTriple(target.triple, &target.target_ref, &error)) { + assert(error); + fprintf(stderr, "LLVMGetTargetFromTriple(\"%s\") failed: %s\n", target.triple, error); + exit(1); + } + assert(!error); + assert(target.target_ref); + + target.target_machine_ref = LLVMCreateTargetMachine( + target.target_ref, target.triple, "x86-64", "", LLVMCodeGenLevelDefault, LLVMRelocDefault, LLVMCodeModelDefault); + assert(target.target_machine_ref); + + target.target_data_ref = LLVMCreateTargetDataLayout(target.target_machine_ref); + assert(target.target_data_ref); + + char *tmp = LLVMCopyStringRepOfTargetData(target.target_data_ref); + assert(strlen(tmp) < sizeof target.data_layout); + strcpy(target.data_layout, tmp); + LLVMDisposeMessage(tmp); + + atexit(cleanup); +} + +const struct Target *get_target(void) +{ + return ⌖ +} diff --git a/stdlib/mem.jou b/stdlib/mem.jou index 4c313f8c..94250f58 100644 --- a/stdlib/mem.jou +++ b/stdlib/mem.jou @@ -4,3 +4,5 @@ # TODO: write a tutorial about using these and add a link declare malloc(size: long) -> void* declare free(ptr: void*) -> void + +declare memcpy(dest: void*, source: void*, count: long) -> void* diff --git a/tests/should_succeed/sizeof.jou b/tests/should_succeed/sizeof.jou index 6fee9a19..ed08359c 100644 --- a/tests/should_succeed/sizeof.jou +++ b/tests/should_succeed/sizeof.jou @@ -1,10 +1,28 @@ from "stdlib/io.jou" import printf +from "stdlib/mem.jou" import memcpy, malloc, free def side_effect() -> int: printf("Side Effect !!!!!\n") return 123 +struct Foo: + a: int + b: long + c: byte + +# See issue #224. +def ensure_sizeof_isnt_too_small_in_a_weird_corner_case() -> void: + value = Foo{a=1, b=2, c='x'} + # We need the heap allocation, because otherwise the optimizer happens to make things work. + ptr = malloc(50) as Foo* + memcpy(ptr, &value, sizeof value) + # If sizeof is too small, this prints garbage. + printf("%c\n", ptr->c) # Output: x + free(ptr) + def main() -> int: + ensure_sizeof_isnt_too_small_in_a_weird_corner_case() + b: byte n: int m: long