mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-26 06:51:34 +00:00
Merge pull request #7327 from kalsprite/abi_conformance
ABI Conformance Harness + Fixes
This commit is contained in:
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -40,6 +40,8 @@ jobs:
|
||||
./odin test tests/core/speed.odin -file -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -microarch:native
|
||||
./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -microarch:native
|
||||
(cd tests/issues; ./run.sh)
|
||||
(cd tests/abi; ./run.sh)
|
||||
(cd tests/abi; ABI_CFLAGS=-O2 ./run.sh "" "" -o:speed)
|
||||
./odin check tests/benchmark -vet -strict-style -no-entry-point
|
||||
|
||||
build_freebsd:
|
||||
@@ -73,6 +75,8 @@ jobs:
|
||||
./odin test tests/core/speed.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
|
||||
./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
|
||||
(cd tests/issues; ./run.sh)
|
||||
(cd tests/abi; ./run.sh)
|
||||
(cd tests/abi; ABI_CFLAGS=-O2 ./run.sh "" "" -o:speed)
|
||||
./odin check tests/benchmark -vet -strict-style -no-entry-point
|
||||
ci:
|
||||
strategy:
|
||||
@@ -171,6 +175,12 @@ jobs:
|
||||
cd tests/issues
|
||||
./run.sh
|
||||
|
||||
- name: ABI comparator
|
||||
run: |
|
||||
cd tests/abi
|
||||
./run.sh
|
||||
ABI_CFLAGS=-O2 ./run.sh "" "" -o:speed
|
||||
|
||||
- name: Run demo on WASI WASM32
|
||||
run: |
|
||||
./odin build examples/demo -target:wasi_wasm32 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -out:demo
|
||||
@@ -284,6 +294,10 @@ jobs:
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
cd tests/issues
|
||||
call run.bat
|
||||
cd ../abi
|
||||
call run.bat
|
||||
set ABI_CFLAGS=-O2
|
||||
call run.bat -o:speed
|
||||
- name: Check benchmarks
|
||||
shell: cmd
|
||||
run: |
|
||||
@@ -359,3 +373,9 @@ jobs:
|
||||
|
||||
- name: Internals tests
|
||||
run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
|
||||
|
||||
- name: ABI comparator
|
||||
run: |
|
||||
cd tests/abi
|
||||
./run.sh linux_riscv64 riscv64-linux-gnu "-extra-linker-flags:-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
|
||||
ABI_CFLAGS=-O2 ./run.sh linux_riscv64 riscv64-linux-gnu "-extra-linker-flags:-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath -o:speed
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -307,6 +307,8 @@ build.sh
|
||||
*.raddbg
|
||||
*.rdi
|
||||
tests/issues/build/*
|
||||
tests/abi/build/*
|
||||
tests/abi/build-cross/*
|
||||
misc/featuregen/featuregen
|
||||
|
||||
# Clangd stuff
|
||||
|
||||
@@ -1067,8 +1067,8 @@ quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
|
||||
return quaternion(w=t0, x=t1, y=t2, z=t3)
|
||||
}
|
||||
|
||||
@(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
truncsfhf2 :: proc "c" (value: f32) -> __float16 {
|
||||
@(private="file")
|
||||
f32_to_f16 :: proc "contextless" (value: f32) -> __float16 {
|
||||
v: struct #raw_union { i: u32, f: f32 }
|
||||
i, s, e, m: i32
|
||||
|
||||
@@ -1124,18 +1124,8 @@ truncsfhf2 :: proc "c" (value: f32) -> __float16 {
|
||||
}
|
||||
}
|
||||
|
||||
@(link_name="__aeabi_d2h", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
aeabi_d2h :: proc "c" (value: f64) -> __float16 {
|
||||
return truncsfhf2(f32(value))
|
||||
}
|
||||
|
||||
@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
truncdfhf2 :: proc "c" (value: f64) -> __float16 {
|
||||
return truncsfhf2(f32(value))
|
||||
}
|
||||
|
||||
@(link_name="__gnu_h2f_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
gnu_h2f_ieee :: proc "c" (value_: __float16) -> f32 {
|
||||
@(private="file")
|
||||
f16_to_f32 :: proc "contextless" (value_: __float16) -> f32 {
|
||||
fp32 :: struct #raw_union { u: u32, f: f32 }
|
||||
|
||||
value := transmute(u16)value_
|
||||
@@ -1154,14 +1144,56 @@ gnu_h2f_ieee :: proc "c" (value_: __float16) -> f32 {
|
||||
}
|
||||
|
||||
|
||||
// The conversion helpers below are libgcc / compiler-rt entry points, so their calling convention
|
||||
// is compiler-rt's and not the target's ordinary one. On ARM they take and return their values in
|
||||
// the core registers even though the target is AAPCS-VFP, where an ordinary `proc "c"` float
|
||||
// travels in `s0`. clang's call sites move the value out of the VFP register and back around the
|
||||
// call:
|
||||
//
|
||||
// vmov r0, s0 ; bl __gnu_h2f_ieee ; vmov s0, r0
|
||||
//
|
||||
// Typing the boundary as integers is what puts them in the same registers. Declared as floats they
|
||||
// land in `s0` at both ends and every `_Float16` conversion in C code linked against this runtime
|
||||
// reads whatever the other register happened to hold. Everywhere else the helpers really do take
|
||||
// and return floats, so only arm32 changes shape.
|
||||
when ODIN_ARCH == .arm32 {
|
||||
__f16_abi :: u16
|
||||
__f32_abi :: u32
|
||||
__f64_abi :: u64
|
||||
} else {
|
||||
__f16_abi :: __float16
|
||||
__f32_abi :: f32
|
||||
__f64_abi :: f64
|
||||
}
|
||||
|
||||
@(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
truncsfhf2 :: proc "c" (value: __f32_abi) -> __f16_abi {
|
||||
return transmute(__f16_abi)f32_to_f16(transmute(f32)value)
|
||||
}
|
||||
|
||||
@(link_name="__gnu_f2h_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
gnu_f2h_ieee :: proc "c" (value: f32) -> __float16 {
|
||||
return truncsfhf2(value)
|
||||
gnu_f2h_ieee :: proc "c" (value: __f32_abi) -> __f16_abi {
|
||||
return transmute(__f16_abi)f32_to_f16(transmute(f32)value)
|
||||
}
|
||||
|
||||
@(link_name="__aeabi_d2h", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
aeabi_d2h :: proc "c" (value: __f64_abi) -> __f16_abi {
|
||||
return transmute(__f16_abi)f32_to_f16(f32(transmute(f64)value))
|
||||
}
|
||||
|
||||
@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
truncdfhf2 :: proc "c" (value: __f64_abi) -> __f16_abi {
|
||||
return transmute(__f16_abi)f32_to_f16(f32(transmute(f64)value))
|
||||
}
|
||||
|
||||
@(link_name="__gnu_h2f_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
gnu_h2f_ieee :: proc "c" (value: __f16_abi) -> __f32_abi {
|
||||
return transmute(__f32_abi)f16_to_f32(transmute(__float16)value)
|
||||
}
|
||||
|
||||
@(link_name="__extendhfsf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
extendhfsf2 :: proc "c" (value: __float16) -> f32 {
|
||||
return gnu_h2f_ieee(value)
|
||||
extendhfsf2 :: proc "c" (value: __f16_abi) -> __f32_abi {
|
||||
return transmute(__f32_abi)f16_to_f32(transmute(__float16)value)
|
||||
}
|
||||
|
||||
when .Address in ODIN_SANITIZER_FLAGS {
|
||||
|
||||
@@ -694,100 +694,100 @@ gb_internal isize MAX_ERROR_COLLECTOR_COUNT(void) {
|
||||
gb_global TargetMetrics target_windows_i386 = {
|
||||
TargetOs_windows,
|
||||
TargetArch_i386,
|
||||
4, 4, I386_MAX_ALIGNMENT, 16,
|
||||
4, 4, I386_MAX_ALIGNMENT, 512,
|
||||
str_lit("i386-pc-windows-msvc"),
|
||||
};
|
||||
gb_global TargetMetrics target_windows_amd64 = {
|
||||
TargetOs_windows,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-pc-windows-msvc"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_linux_i386 = {
|
||||
TargetOs_linux,
|
||||
TargetArch_i386,
|
||||
4, 4, I386_MAX_ALIGNMENT, 16,
|
||||
4, 4, I386_MAX_ALIGNMENT, 512,
|
||||
str_lit("i386-pc-linux-gnu"),
|
||||
};
|
||||
gb_global TargetMetrics target_linux_amd64 = {
|
||||
TargetOs_linux,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-pc-linux-gnu"),
|
||||
};
|
||||
gb_global TargetMetrics target_linux_arm64 = {
|
||||
TargetOs_linux,
|
||||
TargetArch_arm64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 16,
|
||||
str_lit("aarch64-linux-elf"),
|
||||
};
|
||||
gb_global TargetMetrics target_linux_arm32 = {
|
||||
TargetOs_linux,
|
||||
TargetArch_arm32,
|
||||
4, 4, 8, 16,
|
||||
4, 4, 8, 8,
|
||||
str_lit("arm-unknown-linux-gnueabihf"),
|
||||
};
|
||||
gb_global TargetMetrics target_linux_riscv64 = {
|
||||
TargetOs_linux,
|
||||
TargetArch_riscv64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 512,
|
||||
str_lit("riscv64-linux-gnu"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_darwin_amd64 = {
|
||||
TargetOs_darwin,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 16,
|
||||
str_lit("x86_64-apple-macosx"), // NOTE: Changes during initialization based on build flags.
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_darwin_arm64 = {
|
||||
TargetOs_darwin,
|
||||
TargetArch_arm64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 16,
|
||||
str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags.
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_freebsd_i386 = {
|
||||
TargetOs_freebsd,
|
||||
TargetArch_i386,
|
||||
4, 4, I386_MAX_ALIGNMENT, 16,
|
||||
4, 4, I386_MAX_ALIGNMENT, 512,
|
||||
str_lit("i386-unknown-freebsd-elf"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_freebsd_amd64 = {
|
||||
TargetOs_freebsd,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-unknown-freebsd-elf"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_freebsd_arm64 = {
|
||||
TargetOs_freebsd,
|
||||
TargetArch_arm64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 16,
|
||||
str_lit("aarch64-unknown-freebsd-elf"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_openbsd_amd64 = {
|
||||
TargetOs_openbsd,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-unknown-openbsd-elf"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_netbsd_amd64 = {
|
||||
TargetOs_netbsd,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-unknown-netbsd-elf"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_netbsd_arm64 = {
|
||||
TargetOs_netbsd,
|
||||
TargetArch_arm64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 16,
|
||||
str_lit("aarch64-unknown-netbsd-elf"),
|
||||
};
|
||||
|
||||
@@ -795,21 +795,21 @@ gb_global TargetMetrics target_netbsd_arm64 = {
|
||||
gb_global TargetMetrics target_freestanding_wasm32 = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_wasm32,
|
||||
4, 4, 8, 16,
|
||||
4, 4, 8, 512,
|
||||
str_lit("wasm32-freestanding-js"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_js_wasm32 = {
|
||||
TargetOs_js,
|
||||
TargetArch_wasm32,
|
||||
4, 4, 8, 16,
|
||||
4, 4, 8, 512,
|
||||
str_lit("wasm32-js-js"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_wasi_wasm32 = {
|
||||
TargetOs_wasi,
|
||||
TargetArch_wasm32,
|
||||
4, 4, 8, 16,
|
||||
4, 4, 8, 512,
|
||||
str_lit("wasm32-wasi-js"),
|
||||
};
|
||||
|
||||
@@ -817,7 +817,7 @@ gb_global TargetMetrics target_wasi_wasm32 = {
|
||||
gb_global TargetMetrics target_orca_wasm32 = {
|
||||
TargetOs_orca,
|
||||
TargetArch_wasm32,
|
||||
4, 4, 8, 16,
|
||||
4, 4, 8, 512,
|
||||
str_lit("wasm32-wasi-js"),
|
||||
};
|
||||
|
||||
@@ -825,21 +825,21 @@ gb_global TargetMetrics target_orca_wasm32 = {
|
||||
gb_global TargetMetrics target_freestanding_wasm64p32 = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_wasm64p32,
|
||||
4, 8, 8, 16,
|
||||
4, 8, 8, 512,
|
||||
str_lit("wasm32-freestanding-js"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_js_wasm64p32 = {
|
||||
TargetOs_js,
|
||||
TargetArch_wasm64p32,
|
||||
4, 8, 8, 16,
|
||||
4, 8, 8, 512,
|
||||
str_lit("wasm32-js-js"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_wasi_wasm64p32 = {
|
||||
TargetOs_wasi,
|
||||
TargetArch_wasm32,
|
||||
4, 8, 8, 16,
|
||||
4, 8, 8, 512,
|
||||
str_lit("wasm32-wasi-js"),
|
||||
};
|
||||
|
||||
@@ -848,7 +848,7 @@ gb_global TargetMetrics target_wasi_wasm64p32 = {
|
||||
gb_global TargetMetrics target_freestanding_amd64_sysv = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-pc-none-gnu"),
|
||||
TargetABI_SysV,
|
||||
};
|
||||
@@ -856,7 +856,7 @@ gb_global TargetMetrics target_freestanding_amd64_sysv = {
|
||||
gb_global TargetMetrics target_freestanding_amd64_win64 = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-pc-windows-msvc"),
|
||||
TargetABI_Win64,
|
||||
};
|
||||
@@ -864,7 +864,7 @@ gb_global TargetMetrics target_freestanding_amd64_win64 = {
|
||||
gb_global TargetMetrics target_freestanding_amd64_mingw = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_amd64,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 32,
|
||||
8, 8, AMD64_MAX_ALIGNMENT, 512,
|
||||
str_lit("x86_64-pc-windows-gnu"),
|
||||
TargetABI_Win64,
|
||||
};
|
||||
@@ -873,20 +873,20 @@ gb_global TargetMetrics target_freestanding_amd64_mingw = {
|
||||
gb_global TargetMetrics target_freestanding_arm64 = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_arm64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 16,
|
||||
str_lit("aarch64-none-elf"),
|
||||
};
|
||||
|
||||
gb_global TargetMetrics target_freestanding_arm32 = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_arm32,
|
||||
4, 4, 8, 16,
|
||||
4, 4, 8, 8,
|
||||
str_lit("arm-none-eabihf"),
|
||||
};
|
||||
gb_global TargetMetrics target_freestanding_riscv64 = {
|
||||
TargetOs_freestanding,
|
||||
TargetArch_riscv64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 16, 512,
|
||||
str_lit("riscv64-unknown-gnu"),
|
||||
};
|
||||
|
||||
|
||||
@@ -677,7 +677,9 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
|
||||
|
||||
scope_reserve(ctx->scope, min_field_count);
|
||||
|
||||
if (st->is_raw_union && min_field_count > 1) {
|
||||
// Even a one-field `#raw_union` must be marked. RISC-V psABI excludes unions from the hardware
|
||||
// floating-point convention. `struct{union{f32}}` goes in `a0` where `struct{f32}` goes in `fa0`.
|
||||
if (st->is_raw_union) {
|
||||
struct_type->Struct.is_raw_union = true;
|
||||
context = str_lit("struct #raw_union");
|
||||
}
|
||||
|
||||
530
src/llvm_abi.cpp
530
src/llvm_abi.cpp
@@ -49,9 +49,15 @@ gb_internal lbArgType lb_arg_type_indirect(LLVMTypeRef type, LLVMAttributeRef at
|
||||
return lbArgType{lbArg_Indirect, type, nullptr, nullptr, attr, nullptr, 0, false};
|
||||
}
|
||||
|
||||
gb_internal lbArgType lb_arg_type_indirect_byval(LLVMContextRef c, LLVMTypeRef type) {
|
||||
i64 alignment = lb_alignof(type);
|
||||
alignment = gb_max(alignment, 8);
|
||||
gb_internal lbArgType lb_arg_type_indirect_byval(LLVMContextRef c, LLVMTypeRef type, Type *source_type = nullptr) {
|
||||
// the outgoing stack slot, which i386 never over-aligns, not even for an over-aligned struct
|
||||
i64 alignment = build_context.ptr_size;
|
||||
if (build_context.metrics.arch != TargetArch_i386) {
|
||||
// `#align` and `#min_field_align` do not survive lowering, so ask the source
|
||||
// type where there is one
|
||||
i64 a = source_type != nullptr ? type_align_of(source_type) : lb_alignof(type);
|
||||
alignment = gb_max(alignment, a);
|
||||
}
|
||||
|
||||
LLVMAttributeRef byval_attr = lb_create_enum_attribute_with_type(c, "byval", type);
|
||||
LLVMAttributeRef align_attr = lb_create_enum_attribute(c, "align", alignment);
|
||||
@@ -357,6 +363,32 @@ gb_internal i64 lb_alignof(LLVMTypeRef type) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The alignment LLVM itself will give the lowered type, which is not `lb_alignof`:
|
||||
// that applies `max_simd_align`, and LLVM knows nothing about it. A 32-byte vector
|
||||
// is 16-aligned on arm64 and Darwin and 32-aligned to LLVM, and a struct holding
|
||||
// one has to be packed or LLVM re-inserts padding and moves the member.
|
||||
gb_internal i64 lb_llvm_natural_alignof(LLVMTypeRef type) {
|
||||
switch (LLVMGetTypeKind(type)) {
|
||||
case LLVMStructTypeKind:
|
||||
{
|
||||
if (LLVMIsPackedStruct(type)) {
|
||||
return 1;
|
||||
}
|
||||
unsigned field_count = LLVMCountStructElementTypes(type);
|
||||
i64 max_align = 1;
|
||||
for (unsigned i = 0; i < field_count; i++) {
|
||||
max_align = gb_max(max_align, lb_llvm_natural_alignof(LLVMStructGetTypeAtIndex(type, i)));
|
||||
}
|
||||
return max_align;
|
||||
}
|
||||
case LLVMArrayTypeKind:
|
||||
return lb_llvm_natural_alignof(OdinLLVMGetArrayElementType(type));
|
||||
case LLVMVectorTypeKind:
|
||||
return gb_max(next_pow2(lb_sizeof(type)), 1);
|
||||
}
|
||||
return lb_alignof(type);
|
||||
}
|
||||
|
||||
|
||||
#define LB_ABI_INFO(name) lbFunctionType *name(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_type)
|
||||
typedef LB_ABI_INFO(lbAbiInfoType);
|
||||
@@ -401,21 +433,124 @@ gb_internal lbArgType lb_abi_modify_return_is_tuple(lbFunctionType *ft, LLVMCont
|
||||
} while (0)
|
||||
|
||||
// NOTE(bill): I hate `namespace` in C++ but this is just because I don't want to prefix everything
|
||||
|
||||
// Every psABI except AAPCS64 and Win64 makes the caller widen a sub-word integer to 32 bits, in
|
||||
// both argument and return position, and clang records that as `signext`/`zeroext`. A callee
|
||||
// compiled against the attribute reads the whole 32-bit register rather than the byte, so omitting
|
||||
// it hands the callee whatever the high bits happened to hold.
|
||||
gb_internal LLVMAttributeRef lb_integer_extension_attribute(LLVMContextRef c, LLVMTypeRef type, Type *source_type) {
|
||||
if (source_type == nullptr) {
|
||||
// Knowable without the source: an `i1` is always zero-extended.
|
||||
return type == LLVMInt1TypeInContext(c) ? lb_create_enum_attribute(c, "zeroext") : nullptr;
|
||||
}
|
||||
if (lb_sizeof(type) >= 4) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!is_type_integer_like(source_type) && !is_type_enum(source_type)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (is_type_unsigned(source_type) || is_type_boolean(source_type)) {
|
||||
return lb_create_enum_attribute(c, "zeroext");
|
||||
}
|
||||
return lb_create_enum_attribute(c, "signext");
|
||||
}
|
||||
|
||||
// The source type of each parameter, where one exists. `arg_types` can carry entries with no
|
||||
// counterpart, so the tuple is walked rather than indexed.
|
||||
gb_internal Array<Type *> lb_abi_param_source_types(Type *proc_type, unsigned arg_count) {
|
||||
auto out = array_make<Type *>(temporary_allocator(), cast(isize)arg_count);
|
||||
Entity **params = nullptr;
|
||||
isize param_count = 0;
|
||||
if (proc_type != nullptr && proc_type->kind == Type_Proc && proc_type->Proc.params != nullptr) {
|
||||
params = proc_type->Proc.params->Tuple.variables.data;
|
||||
param_count = proc_type->Proc.params->Tuple.variables.count;
|
||||
}
|
||||
for (unsigned i = 0, j = 0; i < arg_count; i++, j++) {
|
||||
while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) {
|
||||
j++;
|
||||
}
|
||||
out[i] = cast(isize)j < param_count ? params[j]->type : nullptr;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// A single result can be classified from its source type. A tuple cannot: it is split into
|
||||
// out-pointers, and C has no such return shape anyway.
|
||||
gb_internal Type *lb_abi_single_result_type(Type *proc_type) {
|
||||
if (proc_type != nullptr && proc_type->kind == Type_Proc &&
|
||||
proc_type->Proc.results != nullptr &&
|
||||
proc_type->Proc.results->Tuple.variables.count == 1) {
|
||||
return proc_type->Proc.results->Tuple.variables[0]->type;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace lbAbi386 {
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count);
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type);
|
||||
gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type);
|
||||
|
||||
gb_internal LB_ABI_INFO(abi_info) {
|
||||
LLVMContextRef c = m->ctx;
|
||||
lbFunctionType *ft = permanent_alloc_item<lbFunctionType>();
|
||||
ft->ctx = c;
|
||||
ft->args = compute_arg_types(c, arg_types, arg_count);
|
||||
ft->args = compute_arg_types(c, arg_types, arg_count, original_type);
|
||||
ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple);
|
||||
|
||||
// `bit_field` is treated as a struct.
|
||||
Type *return_source = lb_abi_single_result_type(original_type);
|
||||
if (return_is_defined && !return_is_tuple &&
|
||||
return_source != nullptr && is_type_bit_field(return_source) &&
|
||||
!lb_is_type_kind(return_type, LLVMStructTypeKind) &&
|
||||
!lb_is_type_kind(return_type, LLVMArrayTypeKind)) {
|
||||
// Windows and the BSDs return a small struct in registers, same as the scalar
|
||||
// path so only the psABI targets need moving to the hidden pointer.
|
||||
bool small_in_registers = build_context.metrics.os == TargetOs_windows ||
|
||||
build_context.metrics.os == TargetOs_freebsd ||
|
||||
build_context.metrics.os == TargetOs_openbsd;
|
||||
i64 sz = lb_sizeof(return_type);
|
||||
bool returned_in_registers = small_in_registers && (sz == 1 || sz == 2 || sz == 4 || sz == 8);
|
||||
if (!returned_in_registers) {
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
ft->ret = lb_arg_type_indirect(return_type, attr);
|
||||
}
|
||||
}
|
||||
|
||||
// A complex lowers to a struct of two floats. The struct rule sends every struct through
|
||||
// a hidden pointer. The psABI gives complex its own rule: one of eight bytes or fewer
|
||||
// comes back in EAX:EDX, and only the wider ones go through memory. `complex64` is
|
||||
// returned coerced to `i64` and `complex128` keeps the hidden pointer.
|
||||
if (return_is_defined && !return_is_tuple &&
|
||||
return_source != nullptr && is_type_complex(return_source)) {
|
||||
i64 sz = lb_sizeof(return_type);
|
||||
if (sz > 0 && sz <= 8) {
|
||||
ft->ret = lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, cast(unsigned)(sz*8)), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
ft->calling_convention = calling_convention;
|
||||
return ft;
|
||||
}
|
||||
|
||||
gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) {
|
||||
gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return, Type *source_type) {
|
||||
// A bare vector is passed and returned as itself; only aggregates
|
||||
// take the indirect path below, ergo the vector check has to be first.
|
||||
//
|
||||
// Exception is an 8-byte vector Arg whose element is an integer: its an MMX type;
|
||||
// clang coerces it to `i64` to keep it out of the MMX registers. An 8-byte
|
||||
// vector of floats is an SSE type and stays itself, so the rule turns on the element and
|
||||
// not on the width alone:
|
||||
//
|
||||
// <8 x i8> <4 x i16> <2 x i32> -> i64
|
||||
// <2 x float> <4 x half> -> unchanged
|
||||
//
|
||||
// The RETURN is never coerced -- `<8 x i8>` comes back as itself.
|
||||
if (LLVMGetTypeKind(type) == LLVMVectorTypeKind) {
|
||||
if (!is_return && lb_sizeof(type) == 8 &&
|
||||
LLVMGetTypeKind(LLVMGetElementType(type)) == LLVMIntegerTypeKind) {
|
||||
return lb_arg_type_direct(type, LLVMIntTypeInContext(c, 64), nullptr, nullptr);
|
||||
}
|
||||
return lb_arg_type_direct(type, nullptr, nullptr, nullptr);
|
||||
}
|
||||
if (!is_return && lb_sizeof(type) > 8) {
|
||||
return lb_arg_type_indirect(type, nullptr);
|
||||
}
|
||||
@@ -434,22 +569,23 @@ namespace lbAbi386 {
|
||||
return lb_arg_type_direct(type, cast_type, nullptr, nullptr);
|
||||
}
|
||||
|
||||
LLVMAttributeRef attr = nullptr;
|
||||
LLVMTypeRef i1 = LLVMInt1TypeInContext(c);
|
||||
if (type == i1) {
|
||||
attr = lb_create_enum_attribute(c, "zeroext");
|
||||
}
|
||||
LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type);
|
||||
return lb_arg_type_direct(type, nullptr, nullptr, attr);
|
||||
}
|
||||
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) {
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type) {
|
||||
auto args = array_make<lbArgType>(lb_function_type_args_allocator(), arg_count);
|
||||
auto srcs = lb_abi_param_source_types(original_type, arg_count);
|
||||
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
LLVMTypeRef t = arg_types[i];
|
||||
LLVMTypeKind kind = LLVMGetTypeKind(t);
|
||||
i64 sz = lb_sizeof(t);
|
||||
if (kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind) {
|
||||
// `bit_field` lowers to a bare integer; C represents it as a struct with bit-field
|
||||
// members, and i386 passes every struct by value on the stack. Use Src Type to match
|
||||
bool is_aggregate = kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind ||
|
||||
(srcs[i] != nullptr && is_type_bit_field(srcs[i]));
|
||||
if (is_aggregate) {
|
||||
if (sz == 0) {
|
||||
args[i] = lb_arg_type_ignore(t);
|
||||
} else {
|
||||
@@ -458,7 +594,7 @@ namespace lbAbi386 {
|
||||
args[i] = lb_arg_type_indirect_byval(c, t);
|
||||
}
|
||||
} else {
|
||||
args[i] = non_struct(c, t, false);
|
||||
args[i] = non_struct(c, t, false, srcs[i]);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
@@ -490,25 +626,25 @@ namespace lbAbi386 {
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
}
|
||||
return non_struct(c, return_type, true);
|
||||
return non_struct(c, return_type, true, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
namespace lbAbiAmd64Win64 {
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count);
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type);
|
||||
gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type);
|
||||
|
||||
gb_internal LB_ABI_INFO(abi_info) {
|
||||
LLVMContextRef c = m->ctx;
|
||||
lbFunctionType *ft = permanent_alloc_item<lbFunctionType>();
|
||||
ft->ctx = c;
|
||||
ft->args = compute_arg_types(c, arg_types, arg_count);
|
||||
ft->args = compute_arg_types(c, arg_types, arg_count, original_type);
|
||||
ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple);
|
||||
ft->calling_convention = calling_convention;
|
||||
return ft;
|
||||
}
|
||||
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) {
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type *original_type) {
|
||||
auto args = array_make<lbArgType>(lb_function_type_args_allocator(), arg_count);
|
||||
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
@@ -528,7 +664,7 @@ namespace lbAbiAmd64Win64 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
args[i] = lbAbi386::non_struct(c, t, false);
|
||||
args[i] = lbAbi386::non_struct(c, t, false, nullptr);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
@@ -551,7 +687,7 @@ namespace lbAbiAmd64Win64 {
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
}
|
||||
return lbAbi386::non_struct(c, return_type, true);
|
||||
return lbAbi386::non_struct(c, return_type, true, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -734,16 +870,28 @@ namespace lbAbiAmd64SysV {
|
||||
}
|
||||
|
||||
gb_internal bool is_aggregate(LLVMTypeRef type) {
|
||||
// A single-member wrapper is passed like its member, but only while that
|
||||
// member still fits one eightbyte. `struct{i128}` needs two registers and
|
||||
// goes to memory when they are gone. A bare `i128` does not; clang emits
|
||||
// `byval align 16` for the struct and a plain `i128` for the scalar.
|
||||
LLVMTypeKind kind = LLVMGetTypeKind(type);
|
||||
switch (kind) {
|
||||
case LLVMStructTypeKind:
|
||||
if (LLVMCountStructElementTypes(type) == 1) {
|
||||
return is_aggregate(LLVMStructGetTypeAtIndex(type, 0));
|
||||
LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, 0);
|
||||
// A VECTOR member keeps the wrapper an aggregate whatever its size:
|
||||
// LLVM gives a vector its own oversized stack slot, so an unwrapped
|
||||
// `struct{#simd[2]f32}` takes 16 bytes where the ABI wants 8.
|
||||
if (LLVMGetTypeKind(elem) == LLVMVectorTypeKind) {
|
||||
return true;
|
||||
}
|
||||
return lb_sizeof(elem) > 8 || is_aggregate(elem);
|
||||
}
|
||||
return true;
|
||||
case LLVMArrayTypeKind:
|
||||
if (LLVMGetArrayLength(type) == 1) {
|
||||
return is_aggregate(LLVMGetElementType(type));
|
||||
LLVMTypeRef elem = OdinLLVMGetArrayElementType(type);
|
||||
return lb_sizeof(elem) > 8 || is_aggregate(elem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -793,15 +941,25 @@ namespace lbAbiAmd64SysV {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_register(type)) {
|
||||
LLVMAttributeRef attribute = nullptr;
|
||||
if (type == LLVMInt1TypeInContext(c)) {
|
||||
attribute = lb_create_enum_attribute(c, "zeroext");
|
||||
if (cls.count > 2 && is_sse(cls[0])) {
|
||||
// An SSE run wider than two eightbytes has no register to land in at
|
||||
// the baseline ISA. It goes to memory, bare vector or struct-wrapped.
|
||||
// A bare vector RETURN is the one exception: it is not an aggregate.
|
||||
// Clang gives it no hidden pointer and lets LLVM split it across
|
||||
// xmm0:xmm1, but the struct that wraps it still gets one.
|
||||
if (is_arg) {
|
||||
return lb_arg_type_indirect_byval(c, type, source_type);
|
||||
}
|
||||
if (LLVMGetTypeKind(type) != LLVMVectorTypeKind) {
|
||||
all_mem(&cls);
|
||||
}
|
||||
}
|
||||
if (is_register(type)) {
|
||||
LLVMAttributeRef attribute = lb_integer_extension_attribute(c, type, source_type);
|
||||
return lb_arg_type_direct(type, nullptr, nullptr, attribute);
|
||||
} else if (ran_out_of_regs) {
|
||||
if (is_arg) {
|
||||
return lb_arg_type_indirect_byval(c, type);
|
||||
return lb_arg_type_indirect_byval(c, type, source_type);
|
||||
} else {
|
||||
LLVMAttributeRef attribute = lb_create_enum_attribute_with_type(c, "sret", type);
|
||||
return lb_arg_type_indirect(type, attribute);
|
||||
@@ -812,7 +970,7 @@ namespace lbAbiAmd64SysV {
|
||||
if (is_calling_convention_odin(calling_convention)) {
|
||||
return lb_arg_type_indirect(type, attribute);
|
||||
}
|
||||
return lb_arg_type_indirect_byval(c, type);
|
||||
return lb_arg_type_indirect_byval(c, type, source_type);
|
||||
} else if (attribute_kind == Amd64TypeAttribute_StructRect) {
|
||||
attribute = lb_create_enum_attribute_with_type(c, "sret", type);
|
||||
}
|
||||
@@ -827,7 +985,11 @@ namespace lbAbiAmd64SysV {
|
||||
} else {
|
||||
reg_type = llreg(c, cls, type);
|
||||
}
|
||||
return lb_arg_type_direct(type, reg_type, nullptr, nullptr);
|
||||
// `is_register` above answers false for every integer narrower than 16 bytes, so a
|
||||
// sub-word scalar lands HERE rather than in the direct arm, and this is where its
|
||||
// extension attribute has to go.
|
||||
LLVMAttributeRef attribute = lb_integer_extension_attribute(c, type, source_type);
|
||||
return lb_arg_type_direct(type, reg_type, nullptr, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,7 +1131,11 @@ namespace lbAbiAmd64SysV {
|
||||
i64 sz = lb_sizeof(t);
|
||||
i64 words = (sz + 7)/8;
|
||||
auto reg_classes = array_make<RegClass>(heap_allocator(), cast(isize)words);
|
||||
if (words > 4) {
|
||||
if (words > 4 && LLVMGetTypeKind(t) != LLVMVectorTypeKind) {
|
||||
// A BARE vector is exempt: it is not an aggregate. Clang never gives it
|
||||
// a hidden pointer however wide it is. `<16 x float>` is returned directly
|
||||
// and split across xmm0-xmm3, and the SSE run below still sends it to
|
||||
// memory as an ARGUMENT.
|
||||
all_mem(®_classes);
|
||||
} else {
|
||||
bool from_source = source_type != nullptr && source_is_classifiable(source_type) &&
|
||||
@@ -996,6 +1162,17 @@ namespace lbAbiAmd64SysV {
|
||||
return reg_classes;
|
||||
}
|
||||
|
||||
// How much of its eightbyte an SSE class occupies, which is what `llreg` turns
|
||||
// it back into: the scalar classes are as wide as their element, and every `v`
|
||||
// class becomes a vector spanning the whole eightbyte.
|
||||
gb_internal i64 sse_class_width(RegClass c) {
|
||||
switch (c) {
|
||||
case RegClass_SSEHs: return 2;
|
||||
case RegClass_SSEFs: return 4;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
gb_internal void unify(Array<RegClass> *cls, i64 i, RegClass const newv) {
|
||||
RegClass const oldv = (*cls)[cast(isize)i];
|
||||
if (oldv == newv) {
|
||||
@@ -1029,6 +1206,14 @@ namespace lbAbiAmd64SysV {
|
||||
case RegClass_SSEInt64:
|
||||
return;
|
||||
}
|
||||
} else if (is_sse(oldv) && is_sse(newv) && sse_class_width(oldv) > sse_class_width(newv)) {
|
||||
// The members OVERLAP, a union. Last-writer-wins would pass
|
||||
// `union{f64, f32}` as a 4-byte float, and `union{[2]f32, f32}` as one
|
||||
// lane of two, losing the top half of the eightbyte either way. Keeping
|
||||
// the WIDER class is what leaves `struct{f32, f16}` alone: that is Fs
|
||||
// then Hv at offset 4, and Hv spans the eightbyte, so it still widens
|
||||
// rather than gets picked over.
|
||||
return;
|
||||
}
|
||||
|
||||
(*cls)[cast(isize)i] = to_write;
|
||||
@@ -1043,7 +1228,8 @@ namespace lbAbiAmd64SysV {
|
||||
RegClass &oldv = (*cls)[cast(isize)i];
|
||||
if (is_sse(oldv)) {
|
||||
for (i++; i < e; i++) {
|
||||
if (oldv != RegClass_SSEUp) {
|
||||
// NOTE: the current eightbyte, not `oldv`, is bound to cls[0], which is never SSEUp
|
||||
if ((*cls)[cast(isize)i] != RegClass_SSEUp) {
|
||||
all_mem(cls);
|
||||
return;
|
||||
}
|
||||
@@ -1109,6 +1295,15 @@ namespace lbAbiAmd64SysV {
|
||||
}
|
||||
|
||||
i64 sz = lb_sizeof(type);
|
||||
if (LLVMGetTypeKind(type) == LLVMVectorTypeKind && sz == 8 &&
|
||||
reg_classes.count == 1 && is_sse(reg_classes[0])) {
|
||||
// LLVM rounds a bare vector's stack slot up to the legal vector width.
|
||||
// An 8-byte one takes 16 bytes where the ABI wants 8. clang coerces every
|
||||
// 64-bit vector to `double`, which lands in the same half of the same xmm
|
||||
// and takes one eightbyte on the stack.
|
||||
array_free(&types);
|
||||
return LLVMDoubleTypeInContext(c);
|
||||
}
|
||||
if (all_ints) {
|
||||
for_array(i, reg_classes) {
|
||||
GB_ASSERT(sz > 0);
|
||||
@@ -1175,7 +1370,16 @@ namespace lbAbiAmd64SysV {
|
||||
}
|
||||
|
||||
unsigned vec_len = llvec_len(reg_classes, i+1);
|
||||
LLVMTypeRef vec_type = LLVMVectorType(elem_type, vec_len * elems_per_word);
|
||||
unsigned lanes = vec_len * elems_per_word;
|
||||
// Never widen past what is actually left: a 4-byte vector
|
||||
// occupies half an eightbyte, and padding it to a whole one
|
||||
// makes the parameter 8 bytes where clang coerces to i32.
|
||||
i64 elem_bytes = lb_sizeof(elem_type);
|
||||
if (elem_bytes > 0 && sz > 0 && cast(i64)lanes * elem_bytes > sz) {
|
||||
lanes = cast(unsigned)(sz / elem_bytes);
|
||||
}
|
||||
if (lanes == 0) { lanes = 1; }
|
||||
LLVMTypeRef vec_type = LLVMVectorType(elem_type, lanes);
|
||||
array_add(&types, vec_type);
|
||||
sz -= lb_sizeof(vec_type);
|
||||
i += vec_len;
|
||||
@@ -1275,6 +1479,13 @@ namespace lbAbiAmd64SysV {
|
||||
LLVMTypeRef elem = OdinLLVMGetVectorElementType(t);
|
||||
i64 elem_sz = lb_sizeof(elem);
|
||||
LLVMTypeKind elem_kind = LLVMGetTypeKind(elem);
|
||||
if (t_size < 8) {
|
||||
// A vector narrower than an eightbyte is INTEGER, not SSE:
|
||||
// clang coerces `<4 x i8>` to `i32` and passes it in an integer
|
||||
// register.
|
||||
unify(cls, ix + off/8, RegClass_Int);
|
||||
break;
|
||||
}
|
||||
RegClass reg = RegClass_NoClass;
|
||||
switch (elem_kind) {
|
||||
case LLVMIntegerTypeKind: {
|
||||
@@ -1435,6 +1646,10 @@ namespace lbAbiArm64 {
|
||||
unsigned field_member_count = 0;
|
||||
|
||||
LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, i);
|
||||
if (lb_is_type_kind(elem, LLVMStructTypeKind) && lb_sizeof(elem) == 0) {
|
||||
// an empty struct occupies nothing and is ignored
|
||||
continue;
|
||||
}
|
||||
if (!is_homogenous_aggregate(c, elem, &field_type, &field_member_count)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1467,6 +1682,7 @@ namespace lbAbiArm64 {
|
||||
gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_) {
|
||||
LLVMTypeKind kind = LLVMGetTypeKind(type);
|
||||
switch (kind) {
|
||||
case LLVMHalfTypeKind:
|
||||
case LLVMFloatTypeKind:
|
||||
case LLVMDoubleTypeKind:
|
||||
if (base_type_) *base_type_ = type;
|
||||
@@ -1504,6 +1720,10 @@ namespace lbAbiArm64 {
|
||||
switch (bt->kind) {
|
||||
case Type_Basic:
|
||||
switch (bt->Basic.kind) {
|
||||
case Basic_f16:
|
||||
if (base_type_) *base_type_ = LLVMHalfTypeInContext(c);
|
||||
if (member_count_) *member_count_ = 1;
|
||||
return true;
|
||||
case Basic_f32:
|
||||
if (base_type_) *base_type_ = LLVMFloatTypeInContext(c);
|
||||
if (member_count_) *member_count_ = 1;
|
||||
@@ -1515,6 +1735,10 @@ namespace lbAbiArm64 {
|
||||
}
|
||||
return false;
|
||||
case Type_Array: {
|
||||
if (bt->Array.count == 0) {
|
||||
// a zero-length member disqualifies the aggregate, unlike an empty struct
|
||||
return false;
|
||||
}
|
||||
LLVMTypeRef elem_base = nullptr;
|
||||
unsigned elem_count = 0;
|
||||
if (!is_homogenous_aggregate_source(c, bt->Array.elem, &elem_base, &elem_count)) {
|
||||
@@ -1531,6 +1755,11 @@ namespace lbAbiArm64 {
|
||||
LLVMTypeRef found_base = nullptr;
|
||||
unsigned total = 0;
|
||||
for (Entity *f : bt->Struct.fields) {
|
||||
Type *fbt = base_type(f->type);
|
||||
if (fbt != nullptr && fbt->kind == Type_Struct && type_size_of(f->type) == 0) {
|
||||
// an empty struct occupies nothing and is ignored
|
||||
continue;
|
||||
}
|
||||
LLVMTypeRef field_base = nullptr;
|
||||
unsigned field_count = 0;
|
||||
if (!is_homogenous_aggregate_source(c, f->type, &field_base, &field_count)) {
|
||||
@@ -1572,20 +1801,12 @@ namespace lbAbiArm64 {
|
||||
return lb_arg_type_direct(LLVMVoidTypeInContext(c));
|
||||
} else if (is_register(return_type)) {
|
||||
return non_struct(c, return_type, nullptr);
|
||||
} else if (is_homogenous_aggregate(c, return_type, &homo_base_type, &homo_member_count)) {
|
||||
if (is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) {
|
||||
return lb_arg_type_direct(return_type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr);
|
||||
} else {
|
||||
//TODO(Platin): do i need to create stuff that can handle the diffrent return type?
|
||||
// else this needs a fix in llvm_backend_proc as we would need to cast it to the correct array type
|
||||
|
||||
LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO();
|
||||
|
||||
//LLVMTypeRef array_type = llvm_array_type(homo_base_type, homo_member_count);
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
}
|
||||
} else if (is_homogenous_aggregate(c, return_type, &homo_base_type, &homo_member_count) &&
|
||||
is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) {
|
||||
return lb_arg_type_direct(return_type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr);
|
||||
} else {
|
||||
// too many members to be an HFA falls through to the size rule, it does not
|
||||
// become indirect on its own: `struct{[5]f16}` is 10 bytes and goes in x0:x1
|
||||
i64 size = lb_sizeof(return_type);
|
||||
if (size > 16) {
|
||||
LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO();
|
||||
@@ -1595,6 +1816,13 @@ namespace lbAbiArm64 {
|
||||
}
|
||||
|
||||
GB_ASSERT(size <= 16);
|
||||
if (LLVMGetTypeKind(return_type) == LLVMVectorTypeKind) {
|
||||
// A vector too narrow to be a short vector is still RETURNED as
|
||||
// itself. clang coerces a 4-byte vector argument to `i32` and puts
|
||||
// it in w0, but returns `<4 x i8>` in v0; coercing the return too
|
||||
// picks the wrong register file.
|
||||
return lb_arg_type_direct(return_type, nullptr, nullptr, nullptr);
|
||||
}
|
||||
LLVMTypeRef cast_type = nullptr;
|
||||
if (size == 0) {
|
||||
cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
|
||||
@@ -1632,12 +1860,9 @@ namespace lbAbiArm64 {
|
||||
|
||||
if (is_register(type)) {
|
||||
args[i] = non_struct(c, type, ptype);
|
||||
} else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count)) {
|
||||
if (is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) {
|
||||
args[i] = lb_arg_type_direct(type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr);
|
||||
} else {
|
||||
args[i] = lb_arg_type_indirect(type, nullptr);;
|
||||
}
|
||||
} else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count) &&
|
||||
is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) {
|
||||
args[i] = lb_arg_type_direct(type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr);
|
||||
} else if (is_homogenous_aggregate_source(c, ptype, &src_base_type, &src_member_count) &&
|
||||
is_homogenous_aggregate_small_enough(src_base_type, src_member_count)) {
|
||||
args[i] = lb_arg_type_direct(type, llvm_array_type(src_base_type, src_member_count), nullptr, nullptr);
|
||||
@@ -1881,15 +2106,15 @@ namespace lbAbiWasm {
|
||||
}
|
||||
|
||||
namespace lbAbiArm32 {
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention);
|
||||
gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention);
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type);
|
||||
gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention, Type *return_source);
|
||||
|
||||
gb_internal LB_ABI_INFO(abi_info) {
|
||||
LLVMContextRef c = m->ctx;
|
||||
lbFunctionType *ft = permanent_alloc_item<lbFunctionType>();
|
||||
ft->ctx = c;
|
||||
ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention);
|
||||
ft->ret = compute_return_type(c, return_type, return_is_defined, calling_convention);
|
||||
ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention, original_type);
|
||||
ft->ret = compute_return_type(c, return_type, return_is_defined, calling_convention, lb_abi_single_result_type(original_type));
|
||||
ft->calling_convention = calling_convention;
|
||||
return ft;
|
||||
}
|
||||
@@ -1913,28 +2138,95 @@ namespace lbAbiArm32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) {
|
||||
LLVMAttributeRef attr = nullptr;
|
||||
LLVMTypeRef i1 = LLVMInt1TypeInContext(c);
|
||||
if (type == i1) {
|
||||
attr = lb_create_enum_attribute(c, "zeroext");
|
||||
gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return, Type *source_type) {
|
||||
// A bare vector narrower than a word has no register of its own to sit in, clang coerces
|
||||
// it to `i32` as an argument whatever its element is. The return keeps the vector
|
||||
// type, same as x86, except: a half vector is not a legal type at this microarchitecture
|
||||
// (`arm1176jzf-s` has VFP2 but no fp16), so clang coerces that one in both directions.
|
||||
//
|
||||
// <4 x i8> <2 x i16> <2 x half> as an argument -> i32
|
||||
// <4 x i8> <2 x i16> as a return -> unchanged
|
||||
// <2 x half> as a return -> i32
|
||||
if (LLVMGetTypeKind(type) == LLVMVectorTypeKind && lb_sizeof(type) == 4) {
|
||||
bool is_half = LLVMGetTypeKind(LLVMGetElementType(type)) == LLVMHalfTypeKind;
|
||||
if (!is_return || is_half) {
|
||||
return lb_arg_type_direct(type, LLVMIntTypeInContext(c, 32), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type);
|
||||
return lb_arg_type_direct(type, nullptr, nullptr, attr);
|
||||
}
|
||||
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention) {
|
||||
// AAPCS32 §5.5, the VFP variant that the `gnueabihf` triple selects: an aggregate of at most
|
||||
// four members that are all the same fp type is a Homogeneous fp Aggregate, and travels
|
||||
// in s0-s3 / d0-d3 rather than in the core registers. Everything below coerces aggregates to
|
||||
// `[N x i32]`, which puts an HFA in r0-r3 where the C side reads s0-s3.
|
||||
//
|
||||
// The detector is arm64's: AAPCS64 states the same rule over the same shapes.
|
||||
// `coerce_` is set when the lowered type cannot express the HFA and LLVM has to be handed an
|
||||
// `[N x base]` instead of the type itself. That happens for a `#raw_union`, which has become
|
||||
// an opaque integer by now and AAPCS32 DOES count a union of floats as homogeneous
|
||||
gb_internal bool is_hfa(LLVMContextRef c, LLVMTypeRef type, Type *source_type,
|
||||
ProcCallingConvention calling_convention, LLVMTypeRef *coerce_) {
|
||||
if (is_calling_convention_odin(calling_convention)) {
|
||||
// Both sides are Odin, so the existing lowering is self-consistent; leave it alone.
|
||||
return false;
|
||||
}
|
||||
LLVMTypeRef base_type = nullptr;
|
||||
unsigned member_count = 0;
|
||||
bool needs_coerce = false;
|
||||
if (!lbAbiArm64::is_homogenous_aggregate(c, type, &base_type, &member_count)) {
|
||||
if (source_type == nullptr ||
|
||||
!lbAbiArm64::is_homogenous_aggregate_source(c, source_type, &base_type, &member_count)) {
|
||||
return false;
|
||||
}
|
||||
needs_coerce = true;
|
||||
}
|
||||
if (member_count == 0 || member_count > 4) {
|
||||
return false;
|
||||
}
|
||||
switch (LLVMGetTypeKind(base_type)) {
|
||||
case LLVMFloatTypeKind:
|
||||
case LLVMDoubleTypeKind:
|
||||
break;
|
||||
case LLVMVectorTypeKind:
|
||||
// AAPCS32's short vectors are the 64-bit and 128-bit ones. An aggregate of up to
|
||||
// four of them is a Homogeneous Vector Aggregate, which rides in the VFP registers
|
||||
// exactly as an HFA does. Any other width is not a short vector and does not qualify.
|
||||
{
|
||||
i64 vec_size = lb_sizeof(base_type);
|
||||
if (vec_size != 8 && vec_size != 16) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (coerce_) {
|
||||
*coerce_ = needs_coerce ? llvm_array_type(base_type, member_count) : nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type) {
|
||||
auto args = array_make<lbArgType>(lb_function_type_args_allocator(), arg_count);
|
||||
auto srcs = lb_abi_param_source_types(original_type, arg_count);
|
||||
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
LLVMTypeRef t = arg_types[i];
|
||||
if (is_register(t, false)) {
|
||||
args[i] = non_struct(c, t, false);
|
||||
args[i] = non_struct(c, t, false, srcs[i]);
|
||||
} else {
|
||||
i64 sz = lb_sizeof(t);
|
||||
i64 a = lb_alignof(t);
|
||||
|
||||
LLVMTypeRef hfa_coerce = nullptr;
|
||||
// Added to support hard floats included in the playdates cortex-m7.
|
||||
if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) {
|
||||
args[i] = lb_arg_type_direct(t);
|
||||
} else if (is_hfa(c, t, srcs[i], calling_convention, &hfa_coerce)) {
|
||||
args[i] = lb_arg_type_direct(t, hfa_coerce, nullptr, nullptr);
|
||||
} else if (is_calling_convention_odin(calling_convention) && sz > 8) {
|
||||
// Minor change to improve performance using the Odin calling conventions
|
||||
args[i] = lb_arg_type_indirect(t, nullptr);
|
||||
@@ -1950,22 +2242,36 @@ namespace lbAbiArm32 {
|
||||
return args;
|
||||
}
|
||||
|
||||
gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention) {
|
||||
gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention, Type *return_source) {
|
||||
if (!return_is_defined) {
|
||||
return lb_arg_type_direct(LLVMVoidTypeInContext(c));
|
||||
} else if (LLVMGetTypeKind(return_type) == LLVMVectorTypeKind && lb_sizeof(return_type) > 16) {
|
||||
// A bare vector wider than a short vector has no register file to come back in. It
|
||||
// is returned through a hidden pointer. `is_register` answers true for every vector,
|
||||
// without this the caller returns it directly while the C callee stores
|
||||
// through an `sret` pointer that was never passed (segfault)
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
} else if (!is_register(return_type, true)) {
|
||||
if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) {
|
||||
return lb_arg_type_direct(return_type);
|
||||
}
|
||||
// An HFA is returned in s0-s3 / d0-d3 too. It must not fall through to the
|
||||
// integer coercions or to `sret`.
|
||||
LLVMTypeRef hfa_coerce = nullptr;
|
||||
if (is_hfa(c, return_type, return_source, calling_convention, &hfa_coerce)) {
|
||||
return lb_arg_type_direct(return_type, hfa_coerce, nullptr, nullptr);
|
||||
}
|
||||
// `lb_arg_type_direct` takes (type, cast_type), and the cast type is what the function actually returns.
|
||||
switch (lb_sizeof(return_type)) {
|
||||
case 1: return lb_arg_type_direct(LLVMIntTypeInContext(c, 8), return_type, nullptr, nullptr);
|
||||
case 2: return lb_arg_type_direct(LLVMIntTypeInContext(c, 16), return_type, nullptr, nullptr);
|
||||
case 3: case 4: return lb_arg_type_direct(LLVMIntTypeInContext(c, 32), return_type, nullptr, nullptr);
|
||||
case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr);
|
||||
case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr);
|
||||
case 3: case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr);
|
||||
}
|
||||
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
|
||||
return lb_arg_type_indirect(return_type, attr);
|
||||
}
|
||||
return non_struct(c, return_type, true);
|
||||
return non_struct(c, return_type, true, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1996,12 +2302,8 @@ namespace lbAbiRiscv64 {
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type) {
|
||||
LLVMAttributeRef attr = nullptr;
|
||||
LLVMTypeRef i1 = LLVMInt1TypeInContext(c);
|
||||
if (type == i1) {
|
||||
attr = lb_create_enum_attribute(c, "zeroext");
|
||||
}
|
||||
gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, Type *source_type) {
|
||||
LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type);
|
||||
return lb_arg_type_direct(type, nullptr, nullptr, attr);
|
||||
}
|
||||
|
||||
@@ -2125,7 +2427,45 @@ namespace lbAbiRiscv64 {
|
||||
return LLVMGetTypeKind(type) == LLVMIntegerTypeKind && lb_sizeof(type) > 0;
|
||||
}
|
||||
|
||||
gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *odin_type) {
|
||||
// The psABI applies the hardware floating-point convention to a struct's MEMBERS. A union is
|
||||
// never flattened, so an aggregate holding one ANYWHERE, at any depth, and through an array,
|
||||
// takes the integer convention instead, whatever the union itself contains.
|
||||
//
|
||||
// The lowered type cannot answer this. A `#raw_union{f32}` comes out as a bare `float`, and a
|
||||
// two-member one comes out as the integer its padding filler is, which is indistinguishable
|
||||
// from a real integer member. Both have to be read off the source type.
|
||||
gb_internal bool contains_union(Type *t) {
|
||||
if (t == nullptr) {
|
||||
return false;
|
||||
}
|
||||
Type *bt = base_type(t);
|
||||
if (bt == nullptr) {
|
||||
return false;
|
||||
}
|
||||
switch (bt->kind) {
|
||||
case Type_Union:
|
||||
return true;
|
||||
case Type_Struct:
|
||||
if (bt->Struct.is_raw_union) {
|
||||
return true;
|
||||
}
|
||||
for (Entity *f : bt->Struct.fields) {
|
||||
if (contains_union(f->type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case Type_Array:
|
||||
return contains_union(bt->Array.elem);
|
||||
case Type_EnumeratedArray:
|
||||
return contains_union(bt->EnumeratedArray.elem);
|
||||
case Type_Matrix:
|
||||
return contains_union(bt->Matrix.elem);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *source_type) {
|
||||
LLVMContextRef c = m->ctx;
|
||||
|
||||
int xlen = 8; // 8 byte int register size for riscv64.
|
||||
@@ -2184,7 +2524,9 @@ namespace lbAbiRiscv64 {
|
||||
fp_size = lb_sizeof(fp_type);
|
||||
}
|
||||
|
||||
if (is_float(fp_type) && fp_size <= flen && *fprs_left >= 1) {
|
||||
bool integer_only = contains_union(source_type);
|
||||
|
||||
if (!integer_only && is_float(fp_type) && fp_size <= flen && *fprs_left >= 1) {
|
||||
*fprs_left -= 1;
|
||||
if (fp_type != orig_type) {
|
||||
// A struct that flattened to a single float has to be coerced to that float;
|
||||
@@ -2194,10 +2536,10 @@ namespace lbAbiRiscv64 {
|
||||
}
|
||||
return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr);
|
||||
}
|
||||
return non_struct(c, orig_type);
|
||||
return non_struct(c, orig_type, source_type);
|
||||
}
|
||||
|
||||
if (fp_kind == LLVMStructTypeKind && fp_size <= 2*flen) {
|
||||
if (!integer_only && fp_kind == LLVMStructTypeKind && fp_size <= 2*flen) {
|
||||
unsigned elem_count = LLVMCountStructElementTypes(fp_type);
|
||||
if (elem_count == 2) {
|
||||
LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(fp_type, 0);
|
||||
@@ -2240,7 +2582,7 @@ namespace lbAbiRiscv64 {
|
||||
if (size <= xlen) {
|
||||
*gprs_left -= 1;
|
||||
if (is_register(type)) {
|
||||
return non_struct(c, orig_type);
|
||||
return non_struct(c, orig_type, source_type);
|
||||
} else {
|
||||
return lb_arg_type_direct(orig_type, LLVMIntTypeInContext(c, cast(unsigned)(size*8)), nullptr, nullptr);
|
||||
}
|
||||
@@ -2259,9 +2601,24 @@ namespace lbAbiRiscv64 {
|
||||
gb_internal Array<lbArgType> compute_arg_types(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *odin_type, int *gprs, int *fprs) {
|
||||
auto args = array_make<lbArgType>(lb_function_type_args_allocator(), arg_count);
|
||||
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
// The source type of each parameter, where one exists. `arg_types` can carry entries with
|
||||
// no counterpart, so this walks the tuple the way lbAbiAmd64SysV does and hands back
|
||||
// nullptr once it runs out.
|
||||
Entity **params = nullptr;
|
||||
isize param_count = 0;
|
||||
if (odin_type != nullptr && odin_type->kind == Type_Proc && odin_type->Proc.params != nullptr) {
|
||||
params = odin_type->Proc.params->Tuple.variables.data;
|
||||
param_count = odin_type->Proc.params->Tuple.variables.count;
|
||||
}
|
||||
|
||||
for (unsigned i = 0, j = 0; i < arg_count; i++, j++) {
|
||||
while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) {
|
||||
j++;
|
||||
}
|
||||
Type *source_type = cast(isize)j < param_count ? params[j]->type : nullptr;
|
||||
|
||||
LLVMTypeRef type = arg_types[i];
|
||||
args[i] = compute_arg_type(m, type, gprs, fprs, odin_type);
|
||||
args[i] = compute_arg_type(m, type, gprs, fprs, source_type);
|
||||
}
|
||||
|
||||
return args;
|
||||
@@ -2274,10 +2631,21 @@ namespace lbAbiRiscv64 {
|
||||
return lb_arg_type_direct(LLVMVoidTypeInContext(c));
|
||||
}
|
||||
|
||||
// A single result is classified from its source type. The union rule reaches the return
|
||||
// as well. A tuple keeps nullptr: it is split into out-pointers below. The recursive call
|
||||
// for the last tuple field lands here with a result count above one, so it takes the same path.
|
||||
Type *return_source = nullptr;
|
||||
if (!return_is_tuple &&
|
||||
odin_type != nullptr && odin_type->kind == Type_Proc &&
|
||||
odin_type->Proc.results != nullptr &&
|
||||
odin_type->Proc.results->Tuple.variables.count == 1) {
|
||||
return_source = odin_type->Proc.results->Tuple.variables[0]->type;
|
||||
}
|
||||
|
||||
// There are two registers for return types.
|
||||
int gprs = 2;
|
||||
int fprs = 2;
|
||||
lbArgType ret = compute_arg_type(m, return_type, &gprs, &fprs, odin_type);
|
||||
lbArgType ret = compute_arg_type(m, return_type, &gprs, &fprs, return_source);
|
||||
|
||||
// Return didn't fit into the return registers, so caller allocates and it is returned via
|
||||
// an out-pointer.
|
||||
|
||||
@@ -46,6 +46,13 @@ gb_internal String get_default_microarchitecture() {
|
||||
}
|
||||
} else if (build_context.metrics.arch == TargetArch_riscv64) {
|
||||
default_march = str_lit("generic-rv64");
|
||||
} else if (build_context.metrics.arch == TargetArch_arm32) {
|
||||
// The arm32 triple is `gnueabihf`, and the hard-float ABI passes floating point in the
|
||||
// VFP registers. `generic` has no FPU at all. LLVM cannot honor the ABI its own
|
||||
// triple asks for and quietly falls back to the soft-float convention.
|
||||
//
|
||||
// `arm1176jzf-s` is what clang picks by default for this same triple.
|
||||
default_march = str_lit("arm1176jzf-s");
|
||||
}
|
||||
|
||||
return default_march;
|
||||
|
||||
@@ -1287,7 +1287,9 @@ gb_internal lbValue lb_emit_matrix_mul_vector(lbProcedure *p, lbValue lhs, lbVal
|
||||
LLVMValueRef rhs_ptr = LLVMGetOperand(rhs.value, 0);
|
||||
LLVMTypeRef vector_type = LLVMVectorType(lb_type(p->module, elem), cast(unsigned)vector_count);
|
||||
LLVMValueRef rhs_vector = LLVMBuildLoad2(p->builder, vector_type, rhs_ptr, "");
|
||||
LLVMSetAlignment(rhs_vector, cast(unsigned)type_align_of(type));
|
||||
// The alignment of what is being loaded, which is the right-hand vector. `type` is the
|
||||
// result, and asking it cannot be right except by coincidence.
|
||||
LLVMSetAlignment(rhs_vector, cast(unsigned)type_align_of(vt));
|
||||
|
||||
for (unsigned i = 0; i < column_count; i++) {
|
||||
LLVMValueRef mask = llvm_mask_same(p->module, i, row_count);
|
||||
|
||||
@@ -2671,7 +2671,17 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
|
||||
// so check the alignment of all fields to see if packing is required.
|
||||
requires_packing = requires_packing || ((offset % type_align_of(field_type)) != 0);
|
||||
|
||||
array_add(&fields, lb_type(m, field_type));
|
||||
LLVMTypeRef field_llvm_type = lb_type(m, field_type);
|
||||
|
||||
// `max_simd_align` can cap a member below what LLVM gives the lowered
|
||||
// type. Unpacked, LLVM lays the struct out by its own alignment and the
|
||||
// member moves: `struct{i8, #simd[8]f32}` is 48 bytes here and 64 to
|
||||
// LLVM on every target that caps the vector at 16.
|
||||
i64 natural_align = lb_llvm_natural_alignof(field_llvm_type);
|
||||
requires_packing = requires_packing || ((offset % natural_align) != 0) ||
|
||||
natural_align > full_type_align;
|
||||
|
||||
array_add(&fields, field_llvm_type);
|
||||
|
||||
prev_offset = offset + type_size_of(field->type);
|
||||
}
|
||||
|
||||
@@ -1092,6 +1092,11 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue
|
||||
if (attribute != nullptr) {
|
||||
LLVMAddCallSiteAttribute(ret, param_offset, attribute);
|
||||
}
|
||||
// `byval`'s alignment decides the outgoing stack slot, and LLVM reads it
|
||||
// from the CALL, not the declaration
|
||||
if (ft->args[i].align_attribute != nullptr) {
|
||||
LLVMAddCallSiteAttribute(ret, param_offset, ft->args[i].align_attribute);
|
||||
}
|
||||
param_offset += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1607,16 +1607,8 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) {
|
||||
// could be maximally aligned but as a compromise, having no padding will be
|
||||
// beneficial to third libraries that assume no padding
|
||||
|
||||
i64 total_expected_size = row_count*column_count*elem_size;
|
||||
// i64 min_alignment = prev_pow2(elem_align * row_count);
|
||||
i64 min_alignment = prev_pow2(total_expected_size);
|
||||
while (total_expected_size != 0 && (total_expected_size % min_alignment) != 0) {
|
||||
min_alignment >>= 1;
|
||||
}
|
||||
min_alignment = gb_max(min_alignment, elem_align);
|
||||
|
||||
i64 align = gb_min(min_alignment, build_context.max_simd_align);
|
||||
return align;
|
||||
gb_unused(row_count); gb_unused(column_count); gb_unused(elem_size);
|
||||
return gb_clamp(elem_align, 1, build_context.max_simd_align);
|
||||
}
|
||||
|
||||
|
||||
@@ -4363,6 +4355,18 @@ gb_internal i64 type_align_of(Type *t) {
|
||||
}
|
||||
|
||||
|
||||
// The largest alignment the target permits. The i386 System V psABI caps every scalar at 4, unlike
|
||||
// Windows. Anything that derives its alignment from a COMPONENT rather than from its own size has
|
||||
// to be capped here too.
|
||||
gb_internal i64 type_target_max_align(void) {
|
||||
i64 max_align = build_context.max_align;
|
||||
if (build_context.metrics.arch == TargetArch_i386 &&
|
||||
build_context.metrics.os != TargetOs_windows) {
|
||||
max_align = gb_min(max_align, 4);
|
||||
}
|
||||
return max_align;
|
||||
}
|
||||
|
||||
gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
|
||||
GB_ASSERT(path != nullptr);
|
||||
if (t->failure) {
|
||||
@@ -4387,10 +4391,11 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
|
||||
case Basic_uintptr: case Basic_rawptr:
|
||||
return build_context.ptr_size;
|
||||
|
||||
// A complex aligns to one component and a quaternion to one of its four.
|
||||
case Basic_complex32: case Basic_complex64: case Basic_complex128:
|
||||
return type_size_of_internal(t, path) / 2;
|
||||
return gb_min(type_size_of_internal(t, path) / 2, type_target_max_align());
|
||||
case Basic_quaternion64: case Basic_quaternion128: case Basic_quaternion256:
|
||||
return type_size_of_internal(t, path) / 4;
|
||||
return gb_min(type_size_of_internal(t, path) / 4, type_target_max_align());
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -4529,7 +4534,7 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
|
||||
|
||||
case Type_SimdVector: {
|
||||
// IMPORTANT TODO(bill): Figure out the alignment of vector types
|
||||
return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_simd_align*2);
|
||||
return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_simd_align);
|
||||
}
|
||||
|
||||
case Type_Matrix:
|
||||
@@ -4541,7 +4546,7 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
|
||||
|
||||
// NOTE(bill): Things that are bigger than build_context.ptr_size, are actually comprised of smaller types
|
||||
// TODO(bill): Is this correct for 128-bit types (integers)?
|
||||
return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align);
|
||||
return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, type_target_max_align());
|
||||
}
|
||||
|
||||
gb_internal i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_packed, bool is_raw_union, i64 min_field_align, i64 max_field_align) {
|
||||
|
||||
165
tests/abi/cross.sh
Executable file
165
tests/abi/cross.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
# LOCAL ONLY: not wired into CI.
|
||||
#
|
||||
# This covers what is not in CI: the targets with no CI job and no cross libc,
|
||||
# i386 and arm32, for checking manually.
|
||||
#
|
||||
# `abi_main.odin` is the corpus without core:testing, exiting with the number of
|
||||
# failing types, so this needs no threads, no libc and no cross sysroot, only
|
||||
# clang (which targets everything) and qemu-user.
|
||||
#
|
||||
# ./cross.sh linux_arm64 aarch64-linux-gnu qemu-aarch64
|
||||
# ./cross.sh linux_i386 i386-linux-gnu qemu-i386 -microarch:pentium4
|
||||
# ./cross.sh linux_arm32 arm-linux-gnueabihf qemu-arm
|
||||
# ./cross.sh linux_riscv64 riscv64-linux-gnu qemu-riscv64
|
||||
#
|
||||
# i386 needs a microarch: below SSE2 the x86 backend cannot legalise a
|
||||
# sub-16-byte `f16` vector, and merely declaring a `proc "c"` that takes a
|
||||
# `#simd[2]f16` aborts the compiler with "LLVM ERROR: Do not know how to split
|
||||
# the result of this operator!".
|
||||
#
|
||||
# `pentium4` is what clang's own default for `i386-linux-gnu` is, so it is the
|
||||
# baseline to compare against. BOTH SIDES must agree on it: the C side follows
|
||||
# clang's default unless ABI_CFLAGS says otherwise, so `-microarch:haswell`
|
||||
# alone makes the two disagree about where a 32-byte vector lives and reports
|
||||
# phantom vector failures. Match them (`ABI_CFLAGS=-march=haswell`) or use
|
||||
# pentium4 on both.
|
||||
|
||||
TARGET=${1:?odin target, e.g. linux_arm64}
|
||||
TRIPLE=${2:?clang triple, e.g. aarch64-linux-gnu}
|
||||
QEMU=${3:?qemu binary, e.g. qemu-aarch64}
|
||||
shift 3 # anything else goes to `odin build`
|
||||
: "${ODIN:=../../odin}"
|
||||
: "${CLANG:=clang}"
|
||||
# The C side's optimisation level. An ABI is a link-time contract, so the two
|
||||
# sides are built independently and either may be optimised: `ABI_CFLAGS=-O2`.
|
||||
: "${ABI_CFLAGS:=}"
|
||||
|
||||
case "$TARGET" in
|
||||
*i386*) START='.text
|
||||
.globl _start
|
||||
_start:
|
||||
call probe_main
|
||||
movl %eax, %ebx
|
||||
movl $1, %eax
|
||||
int $0x80' ;;
|
||||
*arm64*) START='.text
|
||||
.globl _start
|
||||
_start:
|
||||
bl probe_main
|
||||
mov x8, #93
|
||||
svc #0' ;;
|
||||
*arm32*) START='.text
|
||||
.globl _start
|
||||
_start:
|
||||
bl probe_main
|
||||
mov r7, #1
|
||||
svc #0
|
||||
|
||||
@ The runtime does 64-bit division. On Arm, the compiler emits `__aeabi_uldivmod`
|
||||
@ instead of `__udivdi3`. It returns the quotient in r0:r1 and the remainder in
|
||||
@ r2:r3, which C cannot express. It is written here and forwards to the shim.
|
||||
.globl __aeabi_uldivmod
|
||||
__aeabi_uldivmod:
|
||||
push {lr}
|
||||
sub sp, sp, #12
|
||||
add r12, sp, #4
|
||||
str r12, [sp]
|
||||
bl shim_udivmod
|
||||
ldr r2, [sp, #4]
|
||||
ldr r3, [sp, #8]
|
||||
add sp, sp, #12
|
||||
pop {pc}' ;;
|
||||
*riscv64*) START='.text
|
||||
.globl _start
|
||||
_start:
|
||||
call probe_main
|
||||
mv a0, a0
|
||||
li a7, 93
|
||||
ecall' ;;
|
||||
*) echo "no start stub for $TARGET" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
|
||||
# cleaned BEFORE, not after -- the driver is left in place to inspect
|
||||
rm -rf build-cross
|
||||
mkdir -p build-cross/p
|
||||
$ODIN run gen.odin -file -- build-cross
|
||||
|
||||
# Ask the C compiler which tiers it has, by preprocessing the generated `build-cross/tiers.c`.
|
||||
# The Odin side must use the same tiers or it references symbols C never emitted.
|
||||
have() { $CLANG --target="$TRIPLE" -E build-cross/tiers.c 2>/dev/null | grep -q "ABI_YES_$1" && echo true || echo false; }
|
||||
TIERS="-define:ABI_TIER_GNU=$(have GNU) -define:ABI_TIER_F16=$(have F16) -define:ABI_TIER_I128=$(have I128)"
|
||||
mv build-cross/abi_main.odin build-cross/p/
|
||||
printf '%s\n' "$START" > build-cross/start.s
|
||||
|
||||
# A freestanding shim: the runtime reaches for a few libc symbols even with
|
||||
# -no-crt, and 64-bit division on a 32-bit target is a compiler-rt call.
|
||||
cat > build-cross/shim.c <<'EOF'
|
||||
typedef unsigned long usz;
|
||||
static char heap[1<<20];
|
||||
static usz hoff;
|
||||
void *malloc(usz n){ usz a=(hoff+15)&~(usz)15; if(a+n>sizeof heap) return 0; hoff=a+n; return heap+a; }
|
||||
void free(void *p){ (void)p; }
|
||||
void *calloc(usz n, usz m){ char*p=malloc(n*m); if(p) for(usz i=0;i<n*m;i++)p[i]=0; return p; }
|
||||
void *realloc(void *p, usz n){ char*q=malloc(n); if(q&&p) for(usz i=0;i<n;i++)q[i]=((char*)p)[i]; return q; }
|
||||
void *memcpy(void *d, const void *s, usz n){ char*a=d; const char*b=s; for(usz i=0;i<n;i++)a[i]=b[i]; return d; }
|
||||
void *memmove(void *d, const void *s, usz n){ char*a=d; const char*b=s;
|
||||
if(a<b){for(usz i=0;i<n;i++)a[i]=b[i];} else {for(usz i=n;i>0;i--)a[i-1]=b[i-1];} return d; }
|
||||
void *memset(void *d, int c, usz n){ char*a=d; for(usz i=0;i<n;i++)a[i]=(char)c; return d; }
|
||||
int memcmp(const void *x, const void *y, usz n){ const unsigned char*a=x,*b=y;
|
||||
for(usz i=0;i<n;i++) if(a[i]!=b[i]) return a[i]<b[i]?-1:1; return 0; }
|
||||
void abort(void){ __builtin_trap(); }
|
||||
unsigned long __stack_chk_guard = 0x2b2b2b2b;
|
||||
void __stack_chk_fail(void){ __builtin_trap(); }
|
||||
typedef unsigned long long u64; typedef long long i64;
|
||||
static u64 udivmod(u64 a, u64 b, u64 *rem){ u64 q=0,r=0;
|
||||
if(b==0){ if(rem)*rem=0; return 0; }
|
||||
for(int i=63;i>=0;i--){ r=(r<<1)|((a>>i)&1); if(r>=b){ r-=b; q|=(u64)1<<i; } }
|
||||
if(rem)*rem=r; return q; }
|
||||
u64 shim_udivmod(u64 a, u64 b, u64 *rem){ return udivmod(a,b,rem); }
|
||||
u64 __udivdi3(u64 a, u64 b){ return udivmod(a,b,0); }
|
||||
u64 __umoddi3(u64 a, u64 b){ u64 r; udivmod(a,b,&r); return r; }
|
||||
i64 __divdi3(i64 a, i64 b){ int n=0; u64 ua=a<0?(n^=1,(u64)-a):(u64)a, ub=b<0?(n^=1,(u64)-b):(u64)b;
|
||||
u64 q=udivmod(ua,ub,0); return n?-(i64)q:(i64)q; }
|
||||
i64 __moddi3(i64 a, i64 b){ int n=a<0; u64 ua=a<0?(u64)-a:(u64)a, ub=b<0?(u64)-b:(u64)b;
|
||||
u64 r; udivmod(ua,ub,&r); return n?-(i64)r:(i64)r; }
|
||||
EOF
|
||||
|
||||
set -x
|
||||
$ODIN build build-cross/p -target:"$TARGET" -build-mode:obj -no-entry-point \
|
||||
-no-thread-local -reloc-mode:static $TIERS -out:build-cross/o "$@"
|
||||
$CLANG --target="$TRIPLE" $ABI_CFLAGS -c build-cross/abi_corpus.c -o build-cross/abi_corpus_c.o -w -fno-stack-protector
|
||||
$CLANG --target="$TRIPLE" -c build-cross/start.s -o build-cross/start.o
|
||||
$CLANG --target="$TRIPLE" -ffreestanding -fno-builtin -O1 -w -c build-cross/shim.c -o build-cross/shim.o
|
||||
$CLANG --target="$TRIPLE" -nostdlib -static -fuse-ld=lld \
|
||||
build-cross/*.o -o build-cross/bin
|
||||
set +x
|
||||
|
||||
set +e
|
||||
"$QEMU" build-cross/bin
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
# A driver that DIES reports 128+signal, and that collides with the type indices: 139 is both
|
||||
# SIGSEGV and a perfectly good index, so reading it as an index names an innocent type. There is no
|
||||
# cheap way to tell them apart here. ABI_SKIP is a compile-time `-define`
|
||||
if [ "$rc" -gt 128 ] && [ "$rc" -lt 165 ]; then
|
||||
echo "$TARGET: exit $rc is AMBIGUOUS." >&2
|
||||
echo " Either type index $rc, or the driver died of signal $((rc-128)) (11 = SIGSEGV)." >&2
|
||||
echo " Re-run with -define:ABI_SKIP=$((rc+1)): if the result moves it was the type," >&2
|
||||
echo " and if it does not, a wrong-ABI call is corrupting the process." >&2
|
||||
fi
|
||||
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
echo "$TARGET: every type agrees with clang"
|
||||
else
|
||||
# the driver returns the INDEX of the first disagreement, and the generator
|
||||
# emits the index -> name map, so the failure names a type rather than a count
|
||||
name=$(grep -m1 "^// $rc " build-cross/p/abi_main.odin | cut -f2)
|
||||
echo "$TARGET: DISAGREES with clang, first at type '${name:-#$rc}'" >&2
|
||||
echo " re-run with -define:ABI_SKIP=$rc to find the next one" >&2
|
||||
fi
|
||||
exit $rc
|
||||
1502
tests/abi/gen.odin
Normal file
1502
tests/abi/gen.odin
Normal file
File diff suppressed because it is too large
Load Diff
42
tests/abi/run.bat
Normal file
42
tests/abi/run.bat
Normal file
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
|
||||
REM The ABI comparator. Every check is "Odin agrees with the platform C compiler"
|
||||
|
||||
REM An ABI is a link-time contract, so the two sides are built independently and
|
||||
REM either may be optimised: `set ABI_CFLAGS=-O2` for the C side, and any
|
||||
REM argument here goes to `odin test`, e.g. `run.bat -o:speed`.
|
||||
|
||||
REM cleaned BEFORE, not after: the generated corpus is left to inspect
|
||||
if exist "build\" rmdir /S /Q build
|
||||
mkdir build
|
||||
pushd build
|
||||
|
||||
set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused-defineables
|
||||
|
||||
@echo on
|
||||
|
||||
..\..\..\odin run ..\gen.odin -file -- . || exit /b
|
||||
|
||||
@echo off
|
||||
REM Ask the C compiler which tiers it has, by preprocessing the generated
|
||||
REM `tiers.c`. Clang targeting MSVC doesnt define `__GNUC__` or `_Float16`,
|
||||
REM Odin side needs to match
|
||||
set TIER_GNU=false
|
||||
set TIER_F16=false
|
||||
set TIER_I128=false
|
||||
clang -E tiers.c 2>nul | findstr /C:"ABI_YES_GNU" >nul && set TIER_GNU=true
|
||||
clang -E tiers.c 2>nul | findstr /C:"ABI_YES_F16" >nul && set TIER_F16=true
|
||||
clang -E tiers.c 2>nul | findstr /C:"ABI_YES_I128" >nul && set TIER_I128=true
|
||||
set TIERS=-define:ABI_TIER_GNU=%TIER_GNU% -define:ABI_TIER_F16=%TIER_F16% -define:ABI_TIER_I128=%TIER_I128%
|
||||
echo tiers: %TIERS%
|
||||
|
||||
@echo on
|
||||
|
||||
REM -w because the corpus deliberately uses zero-length arrays and empty
|
||||
REM structs; both are the extensions under test.
|
||||
clang %ABI_CFLAGS% -c abi_corpus.c -o abi_corpus_c.o -w || exit /b
|
||||
..\..\..\odin test abi_corpus.odin %COMMON% %TIERS% %* || exit /b
|
||||
|
||||
@echo off
|
||||
|
||||
popd
|
||||
53
tests/abi/run.sh
Executable file
53
tests/abi/run.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
# The ABI comparator.
|
||||
#
|
||||
# Every check is "Odin agrees with the platform C compiler"
|
||||
#
|
||||
# ./run.sh
|
||||
# ./run.sh linux_riscv64 riscv64-linux-gnu \
|
||||
# "-extra-linker-flags:-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
|
||||
#
|
||||
# For a target with no cross libc -- i386, arm32 -- see `cross.sh`, which builds
|
||||
# the same corpus freestanding.
|
||||
|
||||
TARGET=${1:-}
|
||||
TRIPLE=${2:-}
|
||||
if [ $# -gt 2 ]; then shift 2; else shift $#; fi # anything else goes to `odin test`
|
||||
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
: "${ODIN:=$here/../../odin}"
|
||||
: "${CLANG:=clang}"
|
||||
# The C side's optimisation level. An ABI is a link-time contract, so the two
|
||||
# sides are built independently and either may be optimised: `ABI_CFLAGS=-O2`.
|
||||
: "${ABI_CFLAGS:=}"
|
||||
COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused-defineables"
|
||||
|
||||
CC_TARGET=""; [ -n "$TRIPLE" ] && CC_TARGET="--target=$TRIPLE"
|
||||
ODIN_TARGET=""; [ -n "$TARGET" ] && ODIN_TARGET="-target:$TARGET"
|
||||
|
||||
|
||||
# Cleaned BEFORE, not after: the generated corpus is left in place so it can be
|
||||
# read after a failure. CI throws the tree away anyway.
|
||||
rm -rf "$here/build"
|
||||
mkdir -p "$here/build"
|
||||
pushd "$here/build" > /dev/null
|
||||
|
||||
set -x
|
||||
|
||||
$ODIN run ../gen.odin -file -- .
|
||||
|
||||
# Ask the C compiler which tiers it has, by preprocessing the generated `build-cross/tiers.c`.
|
||||
# The Odin side must use the same tiers or it references symbols C never emitted.
|
||||
have() { $CLANG $CC_TARGET -E tiers.c 2>/dev/null | grep -q "ABI_YES_$1" && echo true || echo false; }
|
||||
TIERS="-define:ABI_TIER_GNU=$(have GNU) -define:ABI_TIER_F16=$(have F16) -define:ABI_TIER_I128=$(have I128)"
|
||||
|
||||
# `-w` because the corpus deliberately uses zero-length arrays and empty
|
||||
# structs; both are the extensions under test.
|
||||
$CLANG $CC_TARGET $ABI_CFLAGS -c abi_corpus.c -o abi_corpus_c.o -w
|
||||
$ODIN test abi_corpus.odin $COMMON $ODIN_TARGET $TIERS "$@"
|
||||
|
||||
set +x
|
||||
|
||||
popd > /dev/null
|
||||
Reference in New Issue
Block a user