Correctly call x64 varargs.

This commit is contained in:
Christoffer Lerno
2022-01-13 21:22:50 +01:00
parent 5683fe3f8c
commit eed5b7db54
19 changed files with 366 additions and 199 deletions

View File

@@ -9,6 +9,7 @@ set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
option(MI_BUILD_TESTS OFF)
option(MI_BUILD_SHARED OFF)
@@ -180,7 +181,6 @@ add_executable(c3c
src/compiler/tokens.c
src/compiler/types.c
src/main.c
src/target_info/target_info.c
src/utils/errors.c
src/utils/file_utils.c
src/utils/find_msvc.c

View File

@@ -389,6 +389,22 @@ static void parse_option(BuildOptions *options)
FAIL_WITH_ERR("Invalid optimization level.");
}
return;
case 'm':
if (match_shortopt("mno-avx"))
{
options->no_avx = true;
} else if (match_shortopt("mavx"))
{
options->avx = true;
} else if (match_shortopt("mavx512"))
{
options->avx512 = true;
}
else
{
FAIL_WITH_ERR("Invalid -m option.");
}
return;
case 'E':
if (options->compile_option != COMPILE_NORMAL)
{

View File

@@ -204,6 +204,9 @@ typedef struct BuildOptions_
bool emit_bitcode;
bool test_mode;
bool no_stdlib;
bool no_avx;
bool avx;
bool avx512;
} BuildOptions;
@@ -262,6 +265,8 @@ typedef struct
bool no_sse : 1;
bool no_mmx : 1;
bool no_avx : 1;
bool avx : 1;
bool avx512 : 1;
} feature;
} BuildTarget;

View File

@@ -117,6 +117,9 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions *
}
target->no_stdlib = options->no_stdlib;
target->emit_llvm = options->emit_llvm;
if (options->no_avx) target->feature.no_avx = true;
if (options->avx) target->feature.avx = true;
if (options->avx512) target->feature.avx512 = true;
switch (options->compile_option)
{
case COMPILE_NORMAL:

View File

@@ -201,6 +201,8 @@ void project_add_target(Project *project, TomlValue *wrapped_table, const char *
// Use the fact that they correspond to 0, 1, -1
target->feature.struct_return = get_valid_bool(table, "stack-struct-return", type, STRUCT_RETURN_DEFAULT);
target->feature.soft_float = get_valid_bool(table, "soft-float", type, SOFT_FLOAT_DEFAULT);
target->feature.avx = get_valid_bool(table, "avx", type, false);
target->feature.avx512 = get_valid_bool(table, "avx512", type, false);
target->feature.no_avx = get_valid_bool(table, "no-avx", type, false);
target->feature.no_sse = get_valid_bool(table, "no-sse", type, false);
target->feature.no_mmx = get_valid_bool(table, "no-mmx", type, false);

View File

@@ -1561,11 +1561,13 @@ typedef struct FunctionPrototype_
bool ret_by_ref : 1;
Type *rtype;
Type **params;
Type **varargs;
Type *ret_by_ref_type;
Type *abi_ret_type;
ABIArgInfo *ret_abi_info;
ABIArgInfo *ret_by_ref_abi_info;
ABIArgInfo **abi_args;
ABIArgInfo **abi_varargs;
void *tb_prototype;
} FunctionPrototype;
@@ -2050,6 +2052,7 @@ void *llvm_target_machine_create(void);
void target_setup(BuildTarget *build_target);
int target_alloca_addr_space();
void c_abi_func_create(FunctionPrototype *proto);
bool token_is_type(TokenType type);
bool token_is_any_type(TokenType type);

View File

@@ -359,7 +359,7 @@ void x64_classify_vector(Type *type, ByteSize offset_base, X64Class *current, X6
}
return;
}
if (size == 16 || named_arg || size <= x64_native_vector_size_for_avx())
if (size == 16 || (named_arg == NAMED && size <= x64_native_vector_size_for_avx()))
{
if (platform_target.x64.pass_int128_vector_in_mem) return;
@@ -921,4 +921,15 @@ void c_abi_func_create_x64(FunctionPrototype *prototype)
}
prototype->abi_args = args;
}
unsigned vararg_count = vec_size(prototype->varargs);
if (vararg_count)
{
ABIArgInfo **args = MALLOC(sizeof(ABIArgInfo) * vararg_count);
for (unsigned i = 0; i < vararg_count; i++)
{
args[i] = x64_classify_parameter(prototype->varargs[i], &available_registers, is_regcall, UNNAMED);
}
prototype->abi_varargs = args;
}
}

View File

@@ -789,6 +789,7 @@ static inline void llvm_extract_bitvalue_from_array(GenContext *c, BEValue *be_v
res = element;
continue;
}
res = LLVMBuildOr(c->builder, element, res, "");
}
if (big_endian)
@@ -4331,6 +4332,31 @@ void llvm_emit_builtin_call(GenContext *c, BEValue *result_value, Expr *expr)
llvm_emit_intrinsic_expr(c, llvm_get_intrinsic(func), result_value, expr);
}
void llvm_add_call_attributes(GenContext *c, LLVMValueRef call_value, int start_index, int count, Type **types, ABIArgInfo **infos)
{
for (unsigned i = 0; i < count; i++)
{
Type *param = types[i];
ABIArgInfo *info = infos[i];
switch (info->kind)
{
case ABI_ARG_INDIRECT:
if (info->attributes.by_val)
{
llvm_attribute_add_call_type(c,
call_value,
attribute_id.byval,
(int)i + start_index,
llvm_get_type(c, info->indirect.type));
}
llvm_attribute_add_call(c, call_value, attribute_id.align, (int)i + start_index, info->indirect.alignment);
break;
default:
break;
}
}
}
void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr)
{
if (expr->call_expr.is_builtin)
@@ -4385,7 +4411,28 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr)
ABIArgInfo **abi_args = prototype->abi_args;
unsigned param_count = vec_size(params);
unsigned non_variadic_params = param_count;
Expr **args = expr->call_expr.arguments;
unsigned arguments = vec_size(args);
if (prototype->variadic == VARIADIC_TYPED) non_variadic_params--;
FunctionPrototype copy;
if (prototype->variadic == VARIADIC_RAW)
{
if (arguments > non_variadic_params)
{
copy = *prototype;
copy.varargs = NULL;
for (unsigned i = non_variadic_params; i < arguments; i++)
{
vec_add(copy.varargs, args[i]->type);
}
copy.ret_abi_info = NULL;
copy.ret_by_ref_abi_info = NULL;
copy.abi_args = NULL;
c_abi_func_create(&copy);
prototype = &copy;
}
}
ABIArgInfo *ret_info = prototype->ret_abi_info;
Type *call_return_type = prototype->abi_ret_type;
@@ -4442,12 +4489,11 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr)
// 8. Add all other arguments.
unsigned arguments = vec_size(expr->call_expr.arguments);
assert(arguments >= non_variadic_params);
for (unsigned i = 0; i < non_variadic_params; i++)
{
// 8a. Evaluate the expression.
Expr *arg_expr = expr->call_expr.arguments[i];
Expr *arg_expr = args[i];
llvm_emit_expr(c, &temp_value, arg_expr);
// 8b. Emit the parameter according to ABI rules.
@@ -4508,13 +4554,30 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr)
}
else
{
// 9. Emit varargs.
for (unsigned i = param_count; i < arguments; i++)
if (prototype->abi_varargs)
{
Expr *arg_expr = expr->call_expr.arguments[i];
llvm_emit_expr(c, &temp_value, arg_expr);
REMINDER("Varargs should be expanded correctly");
vec_add(values, llvm_load_value_store(c, &temp_value));
// 9. Emit varargs.
unsigned index = 0;
ABIArgInfo **abi_varargs = prototype->abi_varargs;
for (unsigned i = param_count; i < arguments; i++)
{
Expr *arg_expr = args[i];
llvm_emit_expr(c, &temp_value, arg_expr);
ABIArgInfo *info = abi_varargs[index];
llvm_emit_parameter(c, &values, info, &temp_value, prototype->varargs[index]);
index++;
}
}
else
{
// 9. Emit varargs.
for (unsigned i = param_count; i < arguments; i++)
{
Expr *arg_expr = args[i];
llvm_emit_expr(c, &temp_value, arg_expr);
REMINDER("Varargs should be expanded correctly");
vec_add(values, llvm_load_value_store(c, &temp_value));
}
}
}
@@ -4538,22 +4601,10 @@ void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr)
}
}
for (unsigned i = 0; i < non_variadic_params; i++)
llvm_add_call_attributes(c, call_value, 1, non_variadic_params, params, abi_args);
if (prototype->abi_varargs)
{
Type *param = params[i];
ABIArgInfo *info = abi_args[i];
switch (info->kind)
{
case ABI_ARG_INDIRECT:
if (info->attributes.by_val)
{
llvm_attribute_add_call_type(c, call_value, attribute_id.byval, (int)i + 1, llvm_get_type(c, info->indirect.type));
}
llvm_attribute_add_call(c, call_value, attribute_id.align, (int)i + 1, info->indirect.alignment);
break;
default:
break;
}
llvm_add_call_attributes(c, call_value, 1 + non_variadic_params, vec_size(prototype->varargs), prototype->varargs, prototype->abi_varargs);
}
// 11. Process the return value.

View File

@@ -217,7 +217,6 @@ void llvm_value_set_decl(BEValue *value, Decl *decl);
void llvm_value_fold_failable(GenContext *c, BEValue *value);
void llvm_value_struct_gep(GenContext *c, BEValue *element, BEValue *struct_pointer, unsigned index);
LLVMTypeRef llvm_abi_type(GenContext *c, AbiType type);
TypeSize llvm_abi_size(GenContext *c, LLVMTypeRef type);
BitSize llvm_bitsize(GenContext *c, LLVMTypeRef type);
@@ -269,6 +268,8 @@ void llvm_emit_int_comparison(GenContext *c, BEValue *result, BEValue *lhs, BEVa
void llvm_emit_int_comp(GenContext *c, BEValue *result, Type *lhs_type, Type *rhs_type, LLVMValueRef lhs_value, LLVMValueRef rhs_value, BinaryOp binary_op);
void llvm_emit_comparison(GenContext *c, BEValue *be_value, BEValue *lhs, BEValue *rhs, BinaryOp binary_op);
void llvm_emit_len_for_expr(GenContext *c, BEValue *be_value, BEValue *expr_to_len);
// -- type ---
LLVMTypeRef llvm_func_type(GenContext *context, FunctionPrototype *prototype);
// -- instr ---
void llvm_emit_cond_br(GenContext *context, BEValue *value, LLVMBasicBlockRef then_block, LLVMBasicBlockRef else_block);

View File

@@ -217,10 +217,9 @@ static inline void add_func_type_param(GenContext *context, Type *param_type, AB
arg_info->param_index_end = (MemberIndex)vec_size(*params);
}
LLVMTypeRef llvm_func_type(GenContext *context, Type *type)
LLVMTypeRef llvm_func_type(GenContext *context, FunctionPrototype *prototype)
{
LLVMTypeRef *params = NULL;
FunctionPrototype *prototype = type->func.prototype;
LLVMTypeRef return_type = NULL;
@@ -282,6 +281,11 @@ LLVMTypeRef llvm_func_type(GenContext *context, Type *type)
add_func_type_param(context, prototype->params[i], prototype->abi_args[i], &params);
}
VECEACH(prototype->varargs, i)
{
add_func_type_param(context, prototype->varargs[i], prototype->abi_varargs[i], &params);
}
return LLVMFunctionType(return_type, params, vec_size(params), prototype->variadic == VARIADIC_RAW);
}
@@ -324,7 +328,7 @@ LLVMTypeRef llvm_get_type(GenContext *c, Type *any_type)
case TYPE_BITSTRUCT:
return any_type->backend_type = llvm_type_from_decl(c, any_type->decl);
case TYPE_FUNC:
return any_type->backend_type = llvm_func_type(c, any_type);
return any_type->backend_type = llvm_func_type(c, any_type->func.prototype);
case TYPE_VOID:
return any_type->backend_type = LLVMVoidTypeInContext(c->context);
case TYPE_F64:

View File

@@ -1,7 +1,6 @@
#include <llvm-c/Target.h>
#include <llvm-c/TargetMachine.h>
#include <llvm-c/Core.h>
#include <target_info/target_info.h>
#include "compiler_internal.h"
static unsigned arch_pointer_bit_width(OsType os, ArchType arch);
@@ -426,6 +425,8 @@ static inline void target_setup_x64_abi(BuildTarget *target)
platform_target.abi = ABI_X64;
platform_target.x64.avx_level = AVX;
platform_target.x64.is_win64 = platform_target.os == OS_TYPE_WIN32;
if (target->feature.avx512) platform_target.x64.avx_level = AVX_512;
if (target->feature.avx) platform_target.x64.avx_level = AVX;
if (target->feature.no_avx) platform_target.x64.avx_level = AVX_NONE;
if (target->feature.no_mmx) platform_target.x64.no_mmx = true;
if (target->feature.no_sse) platform_target.x64.no_sse = true;

View File

@@ -1035,7 +1035,6 @@ static int compare_function(FunctionSignature *sig, FunctionPrototype *proto)
return 0;
}
void c_abi_func_create(FunctionPrototype *proto);
static inline Type *func_create_new_func_proto(FunctionSignature *sig, CallABI abi, uint32_t hash, FuncTypeEntry *entry)
{

View File

@@ -1,73 +0,0 @@
// Copyright (c) 2020 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by a LGPLv3.0
// a copy of which can be found in the LICENSE file.
#include "target_info_internal.h"
typedef enum
{
AVXABI_LEVEL_NONE,
AVXABI_AVX,
AVXABI_512
} X86AVXABILevel;
static unsigned x86avxabi_vector_size(X86AVXABILevel level)
{
switch (level)
{
case AVXABI_512:
return 512;
case AVXABI_AVX:
return 256;
case AVXABI_LEVEL_NONE:
return 128;
}
UNREACHABLE
}
TargetInfo target_info_new()
{
// From the glibc documentation, on GNU systems, malloc guarantees 16-byte
// alignment on 64-bit systems and 8-byte alignment on 32-bit systems. See
// https://www.gnu.org/software/libc/manual/html_node/Malloc-Examples.html.
// This alignment guarantee also applies to Windows and Android.
/*
if (T.isGNUEnvironment() || T.isWindowsMSVCEnvironment() || T.isAndroid())
NewAlign = Triple.isArch64Bit() ? 128 : Triple.isArch32Bit() ? 64 : 0;
else
NewAlign = 0; // Infer from basic type alignment.
*/
TargetInfo target_info = {
.little_endian = true,
.asm_supported = false,
.float_128 = false,
.float_16 = false,
.align_pointer = 8,
.align_char = 8,
.align_c_int = 32,
.align_c_long = 32,
.align_c_long_long = 64,
.align_c_long_double = 64,
.align_f128 = 128,
.align_large_array = 0,
.align_global_min = 0,
.align_new = 0,
.align_max_vector = 0,
.align_simd_default = 0,
.width_pointer = 32,
.width_c_int = 32,
.width_c_long = 32,
.width_c_long_long = 64,
.width_c_long_double = 64,
.width_large_array_min = 0,
.width_c_wchar = 32,
.width_c_wint = 32,
.reg_param_max = 0,
.sse_reg_param_max = 0,
.builtin_ms_valist = false,
.aarch64sve_types = false,
.platform_name = "unknown"
};
return target_info;
}

View File

@@ -1,87 +0,0 @@
#pragma once
// Copyright (c) 2020 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by a LGPLv3.0
// a copy of which can be found in the LICENSE file.
#include "utils/common.h"
typedef enum
{
SUB_ARCH_NONE,
SUB_ARCH_ARM_V8_5A,
SUB_ARCH_ARM_V8_4A,
SUB_ARCH_ARM_V8_3A,
SUB_ARCH_ARM_V8_2A,
SUB_ARCH_ARM_V8_1A,
SUB_ARCH_ARM_V8,
SUB_ARCH_ARM_V8R,
SUB_ARCH_ARM_V8M_BASELINE,
SUB_ARCH_ARM_V8M_MAINLINE,
SUB_ARCH_ARM_V8_1M_MAINLINE,
SUB_ARCH_ARM_V7,
SUB_ARCH_ARM_V7EM,
SUB_ARCH_ARM_V7M,
SUB_ARCH_ARM_V7S,
SUB_ARCH_ARM_V7K,
SUB_ARCH_ARM_V7VE,
SUB_ARCH_ARM_V6,
SUB_ARCH_ARM_V6M,
SUB_ARCH_ARM_V6K,
SUB_ARCH_ARM_V6T2,
SUB_ARCH_ARM_V5,
SUB_ARCH_ARM_V5TE,
SUB_ARCH_ARM_V4,
SUB_ARCH_KALIMBA_V3,
SUB_ARCH_KALIMBA_V4,
SUB_ARCH_KALIMBA_V5,
SUB_ARCH_MIPS_V6,
} SubArchType;
typedef struct
{
bool little_endian;
bool tls_supported;
bool asm_supported;
bool float_128;
bool float_16;
unsigned align_pointer;
unsigned align_char;
unsigned align_short;
unsigned align_int;
unsigned align_long;
unsigned align_half;
unsigned align_float;
unsigned align_double;
unsigned align_f128;
unsigned align_c_long_double;
unsigned align_c_int;
unsigned align_c_long;
unsigned align_c_long_long;
unsigned align_simd_default;
unsigned align_max_vector;
unsigned align_global_min;
unsigned align_new;
unsigned align_large_array;
unsigned width_size;
unsigned width_pointer;
unsigned width_c_int;
unsigned width_c_long;
unsigned width_c_long_long;
unsigned width_c_long_double;
unsigned width_c_wchar;
unsigned width_c_wint;
unsigned width_large_array_min;
unsigned reg_param_max;
unsigned sse_reg_param_max;
unsigned builtin_ms_valist;
unsigned aarch64sve_types;
char *platform_name;
} TargetInfo;

View File

@@ -1,7 +0,0 @@
#pragma once
// Copyright (c) 2020 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by a LGPLv3.0
// a copy of which can be found in the LICENSE file.
#include "target_info.h"

View File

@@ -58,6 +58,7 @@ class Issues:
self.files = []
self.errors = {}
self.warnings = {}
self.opts = []
def exit_error(self, message):
print('Error in file ' + self.sourcefile.filepath + ': ' + message)
@@ -109,7 +110,10 @@ class Issues:
target = " --target " + self.arch
if (self.debuginfo):
debug = "-g "
code = subprocess.run(self.conf.compiler + target + ' -O0 ' + debug + args, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
opts = ""
for opt in self.opts:
opts += ' -' + opt
code = subprocess.run(self.conf.compiler + target + ' -O0 ' + opts + ' ' + debug + args, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
os.chdir(self.conf.cwd)
if code.returncode != 0 and code.returncode != 1:
self.set_failed()
@@ -141,6 +145,9 @@ class Issues:
if (line.startswith("debuginfo:")):
self.debuginfo = line[10:].strip() == "yes"
return
if (line.startswith("opt:")):
self.opts.append(line[4:].strip())
return
if (line.startswith("target:")):
self.arch = line[7:].strip()
return

View File

@@ -0,0 +1,84 @@
// #target: x64-darwin
// #opt: mavx
module test;
define Mm256 = float[<8>];
struct St256 {
Mm256 m;
}
St256 x38;
Mm256 x37;
extern fn void f38(St256 x);
extern fn void f37(Mm256 x);
fn void f39() { f38(x38); f37(x37); }
// The two next tests make sure that the struct below is passed
// in the same way regardless of avx being used
// CHECK: declare void @func40(%struct.t128* byval(%struct.t128) align 16)
define Mm128 = float[<4>];
struct Two128 {
Mm128 m;
Mm128 n;
}
extern fn void func40(Two128 s);
fn void func41(Two128 s) {
func40(s);
}
struct Atwo128 {
Mm128[2] array;
}
struct Sa {
Atwo128 x;
}
extern fn void func42(Sa s);
fn void func43(Sa s) {
func42(s);
}
define Vec46 = float[<2>];
extern fn void f46(Vec46,Vec46,Vec46,Vec46,Vec46,Vec46,Vec46,Vec46,Vec46,Vec46);
fn void test46() { Vec46 x = {1,2}; f46(x,x,x,x,x,x,x,x,x,x); }
struct Vec47 { uint a; }
extern fn void f47(int,int,int,int,int,int,Vec47);
fn void test47(int a, Vec47 b) { f47(a, a, a, a, a, a, b); }
fn void test49_helper(double, ...);
fn void test49(double d, double e) { test49_helper(d, e); }
extern fn void test52_helper(int, ...);
Mm256 x52;
fn void test52() {
test52_helper(0, x52, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
}
/* #expect: test.ll
declare void @f38(<8 x float>)
declare void @f37(<8 x float>)
declare void @func40(%Two128* byval(%Two128) align 16)
define void @test.func41(%Two128* byval(%Two128) align 16 %0)
declare void @func42(%Sa* byval(%Sa) align 16)
define void @test.func43(%Sa* byval(%Sa) align 16 %0)
declare void @f46(double, double, double, double, double, double, double, double, <2 x float>* byval(<2 x float>) align 8, <2 x float>* byval(<2 x float>) align 8)
declare void @f47(i32, i32, i32, i32, i32, i32, i32)
declare void @test.test49_helper(double, ...)
define void @test.test49(double %0, double %1)
entry:
call void (double, ...) @test.test49_helper(double %0, double %1)
ret void
call void (i32, ...) @test52_helper(i32 0, <8 x float>* byval(<8 x float>) align 32 %indirectarg, double 1.000000e+00, double 1.000000e+00, double 1.000000e+00, double 1.000000e+00, double 1.000000e+00, double 1.000000e+00, double 1.000000e+00)

View File

@@ -0,0 +1,55 @@
// #target: x64-darwin
// #opt: mno-avx
module test;
define Mm256 = float[<8>];
struct St256 {
Mm256 m;
}
St256 x38;
Mm256 x37;
extern fn void f38(St256 x);
extern fn void f37(Mm256 x);
fn void f39() { f38(x38); f37(x37); }
// The two next tests make sure that the struct below is passed
// in the same way regardless of avx being used
// CHECK: declare void @func40(%struct.t128* byval(%struct.t128) align 16)
define Mm128 = float[<4>];
struct Two128 {
Mm128 m;
Mm128 n;
}
extern fn void func40(Two128 s);
fn void func41(Two128 s) {
func40(s);
}
struct Atwo128 {
Mm128[2] array;
}
struct Sa {
Atwo128 x;
}
extern fn void func42(Sa s);
fn void func43(Sa s) {
func42(s);
}
/* #expect: test.ll
declare void @f38(%St256* byval(%St256) align 32)
declare void @f37(<8 x float>* byval(<8 x float>) align 32)
declare void @func40(%Two128* byval(%Two128) align 16)
define void @test.func41(%Two128* byval(%Two128) align 16 %0)
declare void @func42(%Sa* byval(%Sa) align 16)
define void @test.func43(%Sa* byval(%Sa) align 16 %0)

View File

@@ -64,6 +64,86 @@ fn float[<4>] f25(float[<4>] x) {
return x+x;
}
struct Foo26 {
int *x;
float *y;
}
fn Foo26 f26(Foo26 *p) {
return *p;
}
struct V4f32wrapper {
float[<4>] v;
}
fn V4f32wrapper f27(V4f32wrapper x) {
return x;
}
// PR22563 - We should unwrap simple structs and arrays to pass
// and return them in the appropriate vector registers if possible.
define V8f32 = float[<8>];
struct V8f32wrapper {
V8f32 v;
}
fn V8f32wrapper f27a(V8f32wrapper x) {
return x;
}
struct V8f32wrapper_wrapper {
V8f32[1] v;
}
fn V8f32wrapper_wrapper f27b(V8f32wrapper_wrapper x) {
return x;
}
struct F28c {
double x;
int y;
}
fn void f28(F28c c) {
}
struct Inner
{
double x;
int y;
}
struct F29a
{
Inner[1] c;
}
fn void f29a(F29a a) {
}
struct St0 {
char[8] f0; char f2; char f3; char f4; }
fn void f30(St0 p_4) {
}
struct F31foo { float a, b, c; }
fn float f31(F31foo x) {
return x.c;
}
define V1i64 = ulong[<1>];
fn V1i64 f34(V1i64 arg) { return arg; }
define V1i64_2 = uint[<2>];
fn V1i64_2 f35(V1i64_2 arg) { return arg+arg; }
define V2i32 = float[<2>];
fn V2i32 f36(V2i32 arg) { return arg; }
/* #expect: test.ll
define i32 @test.f12_0()
@@ -91,3 +171,15 @@ entry:
%fadd = fadd <4 x float> %0, %0
ret <4 x float> %fadd
}
define { i32*, float* } @test.f26(%Foo26* %0)
define <4 x float> @test.f27(<4 x float> %0)
define <8 x float> @test.f27a(<8 x float> %0)
define <8 x float> @test.f27b(<8 x float> %0)
define void @test.f28(double %0, i32 %1)
define void @test.f29a(double %0, i32 %1)
define void @test.f30(i64 %0, i24 %1)
define float @test.f31(<2 x float> %0, float %1)
define double @test.f34(double %0)
define double @test.f35(double %0)
define double @test.f36(double %0)