mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Initial work on RISCV - completely untested.
This commit is contained in:
@@ -1459,7 +1459,7 @@ static inline bool type_is_pointer(Type *type);
|
||||
static inline bool type_is_promotable_integer(Type *type);
|
||||
static inline bool type_is_signed(Type *type);
|
||||
static inline bool type_is_structlike(Type *type);
|
||||
static inline bool type_is_promotable_integer(Type *type);
|
||||
static inline size_t type_min_alignment(size_t a, size_t b);
|
||||
bool type_is_subtype(Type *type, Type *possible_subtype);
|
||||
bool type_is_union_struct(Type *type);
|
||||
bool type_is_user_defined(Type *type);
|
||||
@@ -1635,7 +1635,7 @@ static inline bool type_is_signed(Type *type) { return type->type_kind >= TYPE_I
|
||||
static inline bool type_is_unsigned(Type *type) { return type->type_kind >= TYPE_U8 && type->type_kind <= TYPE_U64; }
|
||||
static inline bool type_ok(Type *type) { return !type || type->type_kind != TYPE_POISONED; }
|
||||
static inline bool type_info_ok(TypeInfo *type_info) { return !type_info || type_info->kind != TYPE_INFO_POISON; }
|
||||
|
||||
bool type_is_scalar(Type *type);
|
||||
static inline bool type_kind_is_derived(TypeKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
@@ -1700,6 +1700,15 @@ static inline bool type_is_promotable_integer(Type *type)
|
||||
return type_is_integer_kind(type) && type->builtin.bytesize < type_c_int->builtin.bytesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum alignment, values are either offsets or alignments.
|
||||
* @return
|
||||
*/
|
||||
static inline size_t type_min_alignment(size_t a, size_t b)
|
||||
{
|
||||
return (a | b) & (1 + ~(a | b));
|
||||
}
|
||||
|
||||
#define TRY_AST_OR(_ast_stmt, _res) ({ Ast* _ast = (_ast_stmt); if (!ast_ok(_ast)) return _res; _ast; })
|
||||
#define TRY_EXPR_OR(_expr_stmt, _res) ({ Expr* _expr = (_expr_stmt); if (!expr_ok(_expr)) return _res; _expr; })
|
||||
#define TRY_TYPE_OR(_type_stmt, _res) ({ TypeInfo* _type = (_type_stmt); if (!type_info_ok(_type)) return _res; _type; })
|
||||
|
||||
@@ -70,6 +70,7 @@ bool abi_arg_is_indirect(ABIArgInfo *info)
|
||||
case ABI_ARG_DIRECT_COERCE:
|
||||
case ABI_ARG_EXPAND:
|
||||
case ABI_ARG_DIRECT_PAIR:
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
return false;
|
||||
case ABI_ARG_INDIRECT:
|
||||
return true;
|
||||
@@ -96,7 +97,7 @@ ABIArgInfo *abi_arg_new_indirect_by_val(void)
|
||||
ABIArgInfo *abi_arg_new_indirect_not_by_val(void)
|
||||
{
|
||||
ABIArgInfo *info = abi_arg_new(ABI_ARG_INDIRECT);
|
||||
info->indirect.by_val = true;
|
||||
info->indirect.by_val = false;
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -178,6 +179,30 @@ ABIArgInfo *abi_arg_new_direct(void)
|
||||
return abi_arg_new(ABI_ARG_DIRECT_COERCE);
|
||||
}
|
||||
|
||||
ABIArgInfo *abi_arg_new_expand_coerce(AbiType *target_type, unsigned offset)
|
||||
{
|
||||
ABIArgInfo *arg = abi_arg_new(ABI_ARG_EXPAND_COERCE);
|
||||
arg->coerce_expand.packed = offset > 0;
|
||||
arg->coerce_expand.offset_lo = offset;
|
||||
arg->coerce_expand.lo_index = offset > 0 ? 1 : 0;
|
||||
arg->coerce_expand.lo = target_type;
|
||||
return arg;
|
||||
}
|
||||
|
||||
ABIArgInfo *abi_arg_new_expand_coerce_pair(AbiType *first_element, unsigned initial_offset, AbiType *second_element, unsigned padding, bool is_packed)
|
||||
{
|
||||
ABIArgInfo *arg = abi_arg_new(ABI_ARG_EXPAND_COERCE);
|
||||
arg->coerce_expand.packed = is_packed;
|
||||
arg->coerce_expand.offset_lo = initial_offset;
|
||||
arg->coerce_expand.lo_index = initial_offset > 0 ? 1 : 0;
|
||||
arg->coerce_expand.lo = first_element;
|
||||
arg->coerce_expand.hi = second_element;
|
||||
arg->coerce_expand.padding_hi = padding;
|
||||
arg->coerce_expand.offset_hi = padding + initial_offset + abi_type_size(first_element);
|
||||
arg->coerce_expand.hi_index = arg->coerce_expand.lo_index + (padding > 0 ? 1U : 0U);
|
||||
return arg;
|
||||
}
|
||||
|
||||
ABIArgInfo *abi_arg_new_direct_coerce(AbiType *target_type)
|
||||
{
|
||||
assert(target_type);
|
||||
|
||||
@@ -9,6 +9,7 @@ ABIArgInfo *aarch64_illegal_vector(Type *type)
|
||||
// Need to look up SVE vectors.
|
||||
return false;
|
||||
}
|
||||
|
||||
ABIArgInfo *aarch64_coerce_illegal_vector(Type *type)
|
||||
{
|
||||
TODO
|
||||
|
||||
@@ -30,6 +30,8 @@ ABIArgInfo *abi_arg_new_direct_pair(AbiType *low_type, AbiType *high_type);
|
||||
ABIArgInfo *abi_arg_new_direct(void);
|
||||
ABIArgInfo *abi_arg_new_direct_int_ext(Type *type_to_extend);
|
||||
ABIArgInfo *abi_arg_new_direct_coerce(AbiType *target_type);
|
||||
ABIArgInfo *abi_arg_new_expand_coerce(AbiType *target_type, unsigned offset);
|
||||
ABIArgInfo *abi_arg_new_expand_coerce_pair(AbiType *first_element, unsigned initial_offset, AbiType *second_element, unsigned padding, bool is_packed);
|
||||
ABIArgInfo *abi_arg_new_expand_padded(Type *padding);
|
||||
ABIArgInfo *abi_arg_new_indirect_realigned(unsigned alignment);
|
||||
ABIArgInfo *abi_arg_new_indirect_by_val(void);
|
||||
@@ -47,7 +49,7 @@ void c_abi_func_create_win64(GenContext *context, FunctionSignature *signature);
|
||||
void c_abi_func_create_x86(GenContext *context, FunctionSignature *signature);
|
||||
void c_abi_func_create_x64(GenContext *context, FunctionSignature *signature);
|
||||
void c_abi_func_create_aarch64(GenContext *context, FunctionSignature *signature);
|
||||
|
||||
void c_abi_func_create_riscv(GenContext *context, FunctionSignature *signature);
|
||||
|
||||
// Implementation
|
||||
static inline ABIArgInfo *abi_arg_by_reg_attr(ABIArgInfo *info)
|
||||
|
||||
333
src/compiler/llvm_codegen_c_abi_riscv.c
Normal file
333
src/compiler/llvm_codegen_c_abi_riscv.c
Normal file
@@ -0,0 +1,333 @@
|
||||
// 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 "llvm_codegen_c_abi_internal.h"
|
||||
|
||||
|
||||
static ABIArgInfo *riscv_coerce_and_expand_fpcc_struct(AbiType *field1, unsigned field1_offset, AbiType *field2, unsigned field2_offset)
|
||||
{
|
||||
if (!field2)
|
||||
{
|
||||
return abi_arg_new_expand_coerce(field1, field1_offset);
|
||||
}
|
||||
|
||||
unsigned field2_alignment = abi_type_abi_alignment(field2);
|
||||
unsigned field1_size = abi_type_size(field1);
|
||||
unsigned field2_offset_no_pad = aligned_offset(field1_size, field2_alignment);
|
||||
|
||||
unsigned padding = 0;
|
||||
|
||||
if (field2_offset > field2_offset_no_pad)
|
||||
{
|
||||
padding = field2_offset - field2_offset_no_pad;
|
||||
}
|
||||
else if (field2_offset != field2_alignment && field2_offset > field1_size)
|
||||
{
|
||||
padding = field2_offset - field1_size;
|
||||
}
|
||||
|
||||
bool is_packed = field2_offset % field2_alignment != 0;
|
||||
|
||||
return abi_arg_new_expand_coerce_pair(field1, field1_offset, field2, padding, is_packed);
|
||||
}
|
||||
|
||||
static bool riscv_detect_fpcc_struct_internal(GenContext *c, Type *type, unsigned current_offset, AbiType **field1, unsigned *field1_offset, AbiType **field2, unsigned *field2_offset)
|
||||
{
|
||||
bool is_int = type_is_integer(type);
|
||||
bool is_float = type_is_float(type);
|
||||
unsigned flen = build_target.riscv.flen;
|
||||
unsigned size = type_size(type);
|
||||
if (is_int || is_float)
|
||||
{
|
||||
if (is_int && size > build_target.riscv.xlen) return false;
|
||||
// Can't be eligible if larger than the FP registers. Half precision isn't
|
||||
// currently supported on RISC-V and the ABI hasn't been confirmed, so
|
||||
// default to the integer ABI in that case.
|
||||
if (is_float && (size > flen || size < 4)) return false;
|
||||
// Can't be eligible if an integer type was already found (int+int pairs
|
||||
// are not eligible).
|
||||
if (is_int && *field1 && abi_type_is_integer(*field1)) return false;
|
||||
if (!*field1)
|
||||
{
|
||||
*field1 = abi_type_new_plain(type);
|
||||
*field1_offset = current_offset;
|
||||
return true;
|
||||
}
|
||||
if (!*field2)
|
||||
{
|
||||
*field2 = abi_type_new_plain(type);
|
||||
*field2_offset = current_offset;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type->type_kind == TYPE_COMPLEX)
|
||||
{
|
||||
// Is the first field already occupied?
|
||||
// Then fail because both needs to be available.
|
||||
if (*field1) return false;
|
||||
// If the element doesn't fit a register - bail.
|
||||
Type *element_type = type->complex;
|
||||
unsigned element_size = type_size(element_type);
|
||||
if (element_size > flen) return false;
|
||||
assert(!current_offset && "Expected zero offset");
|
||||
*field1 = abi_type_new_plain(element_type);
|
||||
*field2 = abi_type_new_plain(element_type);
|
||||
*field1_offset = current_offset;
|
||||
*field2_offset = current_offset + element_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type->type_kind == TYPE_ARRAY)
|
||||
{
|
||||
size_t array_len = type->array.len;
|
||||
Type *element_type = type->array.base;
|
||||
unsigned element_size = type_size(element_type);
|
||||
for (size_t i = 0; i < array_len; i++)
|
||||
{
|
||||
if (!riscv_detect_fpcc_struct_internal(c,
|
||||
element_type,
|
||||
current_offset,
|
||||
field1,
|
||||
field1_offset,
|
||||
field2,
|
||||
field2_offset)) return false;
|
||||
current_offset += element_size;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type_is_structlike(type))
|
||||
{
|
||||
if (type_is_empty_union_struct(type, true)) return true;
|
||||
// Unions aren't eligible unless they're empty (which is caught above).
|
||||
if (type->type_kind == TYPE_UNION) return false;
|
||||
Decl **members = type->decl->strukt.members;
|
||||
VECEACH(members, i)
|
||||
{
|
||||
Decl *member = members[i];
|
||||
if (!riscv_detect_fpcc_struct_internal(c,
|
||||
member->type,
|
||||
current_offset + member->offset,
|
||||
field1,
|
||||
field1_offset,
|
||||
field2,
|
||||
field2_offset)) return false;
|
||||
|
||||
}
|
||||
return *field1 != NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool riscv_detect_fpcc_struct(GenContext *c, Type *type, AbiType **field1, unsigned *field1_offset, AbiType **field2, unsigned *field2_offset, unsigned *gprs, unsigned *fprs)
|
||||
{
|
||||
*field1 = NULL;
|
||||
*field2 = NULL;
|
||||
*gprs = 0;
|
||||
*fprs = 0;
|
||||
|
||||
bool is_candidate = riscv_detect_fpcc_struct_internal(c, type, 0, field1, field1_offset, field2, field2_offset);
|
||||
|
||||
// Not really a candidate if we have a single int but no float.
|
||||
if (*field1 && !*field2 && !abi_type_is_float(*field1)) return false;
|
||||
if (!is_candidate) return false;
|
||||
if (*field1)
|
||||
{
|
||||
if (abi_type_is_float(*field1))
|
||||
{
|
||||
(*fprs)++;
|
||||
}
|
||||
else
|
||||
{
|
||||
(*gprs)++;
|
||||
}
|
||||
}
|
||||
if (*field2)
|
||||
{
|
||||
if (abi_type_is_float(*field2))
|
||||
{
|
||||
(*fprs)++;
|
||||
}
|
||||
else
|
||||
{
|
||||
(*gprs)++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ABIArgInfo *riscv_classify_argument_type(GenContext *c, Type *type, bool is_fixed, unsigned *gprs, unsigned *fprs)
|
||||
{
|
||||
|
||||
assert(type == type->canonical);
|
||||
|
||||
unsigned xlen = build_target.riscv.xlen;
|
||||
|
||||
// Ignore empty structs/unions.
|
||||
if (type_is_empty_union_struct(type, true)) return abi_arg_new(ABI_ARG_IGNORE);
|
||||
|
||||
size_t size = type_size(type);
|
||||
|
||||
// Pass floating point values via FPRs if possible.
|
||||
if (is_fixed && type_is_float(type) && build_target.riscv.flen >= size && *fprs)
|
||||
{
|
||||
(*fprs)--;
|
||||
return abi_arg_new_direct();
|
||||
}
|
||||
|
||||
// Complex types for the hard float ABI must be passed direct rather than
|
||||
// using CoerceAndExpand.
|
||||
if (is_fixed && type->type_kind == TYPE_COMPLEX && *fprs >= 2)
|
||||
{
|
||||
Type *element_type = type->complex;
|
||||
if (type_size(element_type) <= build_target.riscv.flen)
|
||||
{
|
||||
(*fprs) -= 2;
|
||||
// TODO check that this will expand correctly.
|
||||
return abi_arg_new_direct();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_fixed && build_target.riscv.flen && (type->type_kind == TYPE_STRUCT || type->type_kind == TYPE_ERRTYPE))
|
||||
{
|
||||
AbiType *field1 = NULL;
|
||||
AbiType *field2 = NULL;
|
||||
unsigned offset1 = 0;
|
||||
unsigned offset2 = 0;
|
||||
unsigned needed_gprs;
|
||||
unsigned needed_fprs;
|
||||
bool is_candidate = riscv_detect_fpcc_struct(c,
|
||||
type,
|
||||
&field1,
|
||||
&offset1,
|
||||
&field2,
|
||||
&offset2,
|
||||
&needed_gprs,
|
||||
&needed_fprs);
|
||||
if (is_candidate && needed_gprs <= *gprs && needed_fprs <= *fprs)
|
||||
{
|
||||
*gprs -= needed_gprs;
|
||||
*fprs -= needed_fprs;
|
||||
return riscv_coerce_and_expand_fpcc_struct(field1, offset1, field2, offset2);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned alignment = type_abi_alignment(type);
|
||||
bool must_use_stack = false;
|
||||
// Clang: Determine the number of GPRs needed to pass the current argument
|
||||
// according to the ABI. 2*XLen-aligned varargs are passed in "aligned"
|
||||
// register pairs, so may consume 3 registers.
|
||||
unsigned needed_gprs = 1;
|
||||
if (!is_fixed && alignment == 2 * xlen)
|
||||
{
|
||||
needed_gprs = 2 + (*gprs % 2U);
|
||||
}
|
||||
else if (size > xlen && size <= 2 * xlen)
|
||||
{
|
||||
needed_gprs = 2;
|
||||
}
|
||||
if (needed_gprs > *gprs)
|
||||
{
|
||||
must_use_stack = true;
|
||||
needed_gprs = *gprs;
|
||||
}
|
||||
|
||||
*gprs -= needed_gprs;
|
||||
|
||||
if (!type_is_abi_aggregate(type) && type->type_kind != TYPE_VECTOR)
|
||||
{
|
||||
// All integral types are promoted to XLen width, unless passed on the
|
||||
// stack.
|
||||
if (size < xlen && type_is_integer(type) && !must_use_stack)
|
||||
{
|
||||
return abi_arg_new_expand_padded(type_int_unsigned_by_bitsize(xlen * 8));
|
||||
}
|
||||
if (size > 16 || (size > 8 && !build_target.int_128))
|
||||
{
|
||||
return abi_arg_new_indirect_not_by_val();
|
||||
}
|
||||
return abi_arg_new_direct();
|
||||
}
|
||||
|
||||
// Aggregates which are <= 2*XLen will be passed in registers if possible,
|
||||
// so coerce to integers.
|
||||
if (size <= 2 * xlen)
|
||||
{
|
||||
// Use a single XLen int if possible, 2*XLen if 2*XLen alignment is
|
||||
// required, and a 2-element XLen array if only XLen alignment is required.
|
||||
if (size <= xlen)
|
||||
{
|
||||
return abi_arg_new_direct_coerce(abi_type_new_int_bits(xlen * 8));
|
||||
}
|
||||
if (alignment == 2 * build_target.riscv.xlen)
|
||||
{
|
||||
return abi_arg_new_direct_coerce(abi_type_new_int_bits(xlen * 16));
|
||||
}
|
||||
ABIArgInfo *info = abi_arg_new_direct_coerce(abi_type_new_int_bits(xlen));
|
||||
info->direct_coerce.elements = 2;
|
||||
return info;
|
||||
}
|
||||
return abi_arg_new_indirect_not_by_val();
|
||||
}
|
||||
|
||||
static ABIArgInfo *riscv_classify_return(GenContext *c, Type *return_type)
|
||||
{
|
||||
if (return_type->type_kind == TYPE_VOID) return abi_arg_new(ABI_ARG_IGNORE);
|
||||
|
||||
unsigned arg_gpr_left = 2;
|
||||
unsigned arg_fpr_left = build_target.riscv.flen ? 2 : 0;
|
||||
|
||||
// The rules for return and argument types are the same, so defer to
|
||||
// classifyArgumentType.
|
||||
return riscv_classify_argument_type(c, return_type, true, &arg_gpr_left, &arg_fpr_left);
|
||||
}
|
||||
|
||||
void c_abi_func_create_riscv(GenContext *context, FunctionSignature *signature)
|
||||
{
|
||||
// Registers
|
||||
unsigned gpr = 8;
|
||||
unsigned fpr = 8;
|
||||
|
||||
Type *return_type = signature->failable ? type_error : signature->rtype->type;
|
||||
return_type = type_lowering(return_type);
|
||||
ABIArgInfo *return_abi = riscv_classify_return(context, return_type);
|
||||
// TODO fixup of failable.
|
||||
|
||||
// IsRetIndirect is true if classifyArgumentType indicated the value should
|
||||
// be passed indirect, or if the type size is a scalar greater than 2*XLen
|
||||
// and not a complex type with elements <= FLen. e.g. fp128 is passed direct
|
||||
// in LLVM IR, relying on the backend lowering code to rewrite the argument
|
||||
// list and pass indirectly on RV32.
|
||||
bool is_ret_indirect = abi_arg_is_indirect(signature->failable ? signature->failable_abi_info : signature->ret_abi_info);
|
||||
if (!is_ret_indirect && type_is_scalar(return_type) && type_size(return_type) > 2 * build_target.riscv.xlen)
|
||||
{
|
||||
if (return_type->type_kind == TYPE_COMPLEX && build_target.riscv.flen)
|
||||
{
|
||||
is_ret_indirect = type_size(return_type->complex) > build_target.riscv.flen;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal scalar > 2 * XLen, e.g. f128 on RV32
|
||||
is_ret_indirect = true;
|
||||
}
|
||||
}
|
||||
// Clang: We must track the number of GPRs used in order to conform to the RISC-V
|
||||
// ABI, as integer scalars passed in registers should have signext/zeroext
|
||||
// when promoted, but are anyext if passed on the stack. As GPR usage is
|
||||
// different for variadic arguments, we must also track whether we are
|
||||
// examining a vararg or not.
|
||||
unsigned arg_gprs_left = is_ret_indirect ? gpr - 1 : gpr;
|
||||
unsigned arg_fprs_left = build_target.riscv.flen ? fpr : 0;
|
||||
|
||||
// unsigned fixed_arguments = vec_size(signature->params); todo
|
||||
// TODO fix failable.
|
||||
Decl **params = signature->params;
|
||||
VECEACH(params, i)
|
||||
{
|
||||
bool is_fixed = true;
|
||||
params[i]->var.abi_info = riscv_classify_argument_type(context, type_lowering(params[i]->type), is_fixed, &arg_gprs_left, &arg_fprs_left);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ LLVMValueRef llvm_emit_is_no_error(GenContext *c, LLVMValueRef error)
|
||||
return LLVMBuildICmp(c->builder, LLVMIntEQ, domain, llvm_get_zero(c, type_usize), "noerr");
|
||||
}
|
||||
|
||||
|
||||
static inline LLVMValueRef gencontext_emit_add_int(GenContext *context, Type *type, bool use_mod, LLVMValueRef left, LLVMValueRef right)
|
||||
{
|
||||
if (use_mod)
|
||||
@@ -2110,8 +2111,31 @@ static void llvm_expand_type_to_args(GenContext *context, Type *param_type, LLVM
|
||||
}
|
||||
}
|
||||
|
||||
LLVMValueRef llvm_emit_struct_gep(GenContext *context, LLVMValueRef ptr, LLVMTypeRef struct_type, unsigned index, unsigned struct_alignment, unsigned offset, unsigned *alignment)
|
||||
{
|
||||
*alignment = type_min_alignment(offset, struct_alignment);
|
||||
LLVMValueRef addr = LLVMBuildStructGEP2(context->builder, struct_type, ptr, index, "");
|
||||
return addr;
|
||||
}
|
||||
void llvm_value_struct_gep(GenContext *c, BEValue *element, BEValue *struct_pointer, unsigned index)
|
||||
{
|
||||
llvm_value_fold_failable(c, struct_pointer);
|
||||
Decl *member = struct_pointer->type->decl->strukt.members[index];
|
||||
unsigned alignment;
|
||||
LLVMValueRef ref = llvm_emit_struct_gep(c,
|
||||
struct_pointer->value,
|
||||
llvm_get_type(c, struct_pointer->type),
|
||||
index,
|
||||
struct_pointer->alignment,
|
||||
member->offset,
|
||||
&alignment);
|
||||
llvm_value_set_address(element, ref, member->type);
|
||||
element->alignment = alignment;
|
||||
}
|
||||
|
||||
void gencontext_emit_call_expr(GenContext *context, BEValue *be_value, Expr *expr)
|
||||
{
|
||||
printf("Optimize call return\n");
|
||||
FunctionSignature *signature;
|
||||
LLVMTypeRef func_type;
|
||||
LLVMValueRef func;
|
||||
@@ -2162,6 +2186,7 @@ void gencontext_emit_call_expr(GenContext *context, BEValue *be_value, Expr *exp
|
||||
case ABI_ARG_DIRECT_PAIR:
|
||||
case ABI_ARG_IGNORE:
|
||||
case ABI_ARG_DIRECT_COERCE:
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
break;
|
||||
}
|
||||
unsigned param_index = 0;
|
||||
@@ -2198,7 +2223,6 @@ void gencontext_emit_call_expr(GenContext *context, BEValue *be_value, Expr *exp
|
||||
vec_add(values, llvm_value_rvalue_store(context, be_value));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!abi_info_should_flatten(info))
|
||||
{
|
||||
vec_add(values, llvm_emit_coerce(context, coerce_type, be_value, param->type));
|
||||
@@ -2237,35 +2261,42 @@ void gencontext_emit_call_expr(GenContext *context, BEValue *be_value, Expr *exp
|
||||
}
|
||||
case ABI_ARG_DIRECT_PAIR:
|
||||
{
|
||||
printf("TODO: Optimize load\n");
|
||||
LLVMValueRef value = llvm_value_rvalue_store(context, be_value);
|
||||
llvm_value_addr(context, be_value);
|
||||
printf("Handle invalid alignment");
|
||||
// Here we do the following transform:
|
||||
// struct -> { lo, hi } -> lo, hi
|
||||
LLVMTypeRef lo = llvm_abi_type(context, info->direct_pair.lo);
|
||||
LLVMTypeRef hi = llvm_abi_type(context, info->direct_pair.hi);
|
||||
LLVMTypeRef struct_type = llvm_get_coerce_type(context, info);
|
||||
unsigned max_align = MAX(llvm_abi_alignment(struct_type), type_abi_alignment(param->type));
|
||||
// Create the alloca holding the struct.
|
||||
LLVMValueRef temp = llvm_emit_alloca(context, struct_type, max_align, "temphold");
|
||||
// Bit cast to the original type type.
|
||||
LLVMValueRef cast = LLVMBuildBitCast(context->builder, temp, llvm_get_ptr_type(context, param->type), "casttemp");
|
||||
// Store the value.
|
||||
llvm_store_aligned(context, cast, value, max_align);
|
||||
LLVMValueRef cast = LLVMBuildBitCast(context->builder, be_value->value, llvm_get_ptr_type(context, param->type), "casttemp");
|
||||
// Get the lo value.
|
||||
LLVMValueRef lo_ptr = LLVMBuildStructGEP2(context->builder, struct_type, temp, 0, "lo");
|
||||
LLVMValueRef lo_ptr = LLVMBuildStructGEP2(context->builder, struct_type, cast, 0, "lo");
|
||||
vec_add(values, llvm_emit_load_aligned(context, lo, lo_ptr, llvm_abi_alignment(lo), "lo"));
|
||||
// Get the hi value.
|
||||
LLVMValueRef hi_ptr = LLVMBuildStructGEP2(context->builder, struct_type, temp, 1, "hi");
|
||||
LLVMValueRef hi_ptr = LLVMBuildStructGEP2(context->builder, struct_type, cast, 1, "hi");
|
||||
vec_add(values, llvm_emit_load_aligned(context, hi, hi_ptr, llvm_abi_alignment(hi), "hi"));
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
{
|
||||
// Move this to an address (if needed)
|
||||
llvm_value_addr(context, be_value);
|
||||
LLVMTypeRef coerce_type = llvm_get_coerce_type(context, info);
|
||||
LLVMValueRef temp = LLVMBuildBitCast(context->builder, be_value->value, LLVMPointerType(coerce_type, 0), "coerce");
|
||||
LLVMValueRef gep_first = LLVMBuildStructGEP2(context->builder, coerce_type, temp, info->coerce_expand.lo_index, "first");
|
||||
vec_add(values, LLVMBuildLoad2(context->builder, llvm_abi_type(context, info->coerce_expand.lo), gep_first, ""));
|
||||
if (info->coerce_expand.hi)
|
||||
{
|
||||
LLVMValueRef gep_second = LLVMBuildStructGEP2(context->builder, coerce_type, temp, info->coerce_expand.hi_index, "second");
|
||||
vec_add(values, LLVMBuildLoad2(context->builder, llvm_abi_type(context, info->coerce_expand.hi), gep_second, ""));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_EXPAND:
|
||||
{
|
||||
printf("TODO: Optimize load\n");
|
||||
LLVMValueRef value = llvm_value_rvalue_store(context, be_value);
|
||||
LLVMValueRef expand = llvm_emit_alloca_aligned(context, param->type, "expand");
|
||||
llvm_store_aligned(context, expand, value, type_abi_alignment(param->type));
|
||||
llvm_expand_type_to_args(context, param->type, expand, &values);
|
||||
// Move this to an address (if needed)
|
||||
llvm_value_addr(context, be_value);
|
||||
llvm_expand_type_to_args(context, param->type, be_value->value, &values);
|
||||
// Expand the padding here.
|
||||
if (info->expand.padding_type)
|
||||
{
|
||||
@@ -2305,11 +2336,51 @@ void gencontext_emit_call_expr(GenContext *context, BEValue *be_value, Expr *exp
|
||||
// TODO look at failable
|
||||
LLVMTypeRef lo = llvm_abi_type(context, ret_info->direct_pair.lo);
|
||||
LLVMTypeRef hi = llvm_abi_type(context, ret_info->direct_pair.hi);
|
||||
LLVMTypeRef struct_type = gencontext_get_twostruct(context, lo, hi);
|
||||
LLVMTypeRef struct_type = llvm_get_twostruct(context, lo, hi);
|
||||
call = llvm_emit_convert_value_from_coerced(context, struct_type, call, return_type);
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
{
|
||||
LLVMValueRef ret = llvm_emit_alloca_aligned(context, return_type, "");
|
||||
LLVMTypeRef coerce_type = llvm_get_coerce_type(context, ret_info);
|
||||
LLVMValueRef coerce = LLVMBuildBitCast(context->builder, ret, coerce_type, "");
|
||||
|
||||
LLVMTypeRef lo_type = llvm_abi_type(context, ret_info->coerce_expand.lo);
|
||||
|
||||
// Find the address to the low value
|
||||
unsigned alignment;
|
||||
LLVMValueRef lo = llvm_emit_struct_gep(context, coerce, coerce_type, ret_info->coerce_expand.lo_index,
|
||||
type_abi_alignment(return_type),
|
||||
ret_info->coerce_expand.offset_lo, &alignment);
|
||||
|
||||
// If there is only a single element, we simply store the value.
|
||||
if (!ret_info->coerce_expand.hi)
|
||||
{
|
||||
llvm_store_aligned(context, lo, call, alignment);
|
||||
call = llvm_emit_load_aligned(context, llvm_get_type(context, return_type), ret, 0, "");
|
||||
break;
|
||||
}
|
||||
|
||||
LLVMTypeRef hi_type = llvm_abi_type(context, ret_info->coerce_expand.hi);
|
||||
|
||||
LLVMValueRef lo_value = LLVMBuildExtractValue(context->builder, call, 0, "");
|
||||
LLVMValueRef hi_value = LLVMBuildExtractValue(context->builder, call, 1, "");
|
||||
|
||||
// Store the low value.
|
||||
llvm_store_aligned(context, lo, lo_value, alignment);
|
||||
|
||||
LLVMValueRef hi = llvm_emit_struct_gep(context, coerce, coerce_type, ret_info->coerce_expand.hi_index,
|
||||
type_abi_alignment(return_type),
|
||||
ret_info->coerce_expand.offset_hi, &alignment);
|
||||
|
||||
// Store the high value.
|
||||
llvm_store_aligned(context, lo, lo_value, alignment);
|
||||
|
||||
// Now we can get the actual return value.
|
||||
call = llvm_emit_load_aligned(context, llvm_get_type(context, return_type), ret, 0, "");
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_DIRECT_COERCE:
|
||||
{
|
||||
// TODO look at failable
|
||||
|
||||
@@ -135,13 +135,27 @@ static inline void llvm_process_parameter_value(GenContext *c, Decl *decl, unsig
|
||||
llvm_emit_memcpy_to_decl(c, decl, pointer, info->indirect.realignment);
|
||||
return;
|
||||
}
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
{
|
||||
// Create the expand type:
|
||||
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, info);
|
||||
LLVMValueRef temp = LLVMBuildBitCast(c->builder, decl->backend_ref, LLVMPointerType(coerce_type, 0), "coerce");
|
||||
LLVMValueRef gep_first = LLVMBuildStructGEP2(c->builder, coerce_type, temp, info->coerce_expand.lo_index, "first");
|
||||
llvm_store_aligned(c, gep_first, llvm_get_next_param(c, index), 0);
|
||||
if (info->coerce_expand.hi)
|
||||
{
|
||||
LLVMValueRef gep_second = LLVMBuildStructGEP2(c->builder, coerce_type, temp, info->coerce_expand.hi_index, "second");
|
||||
llvm_store_aligned(c, gep_second, llvm_get_next_param(c, index), 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_DIRECT_PAIR:
|
||||
{
|
||||
// Here we do the following transform:
|
||||
// lo, hi -> { lo, hi } -> struct
|
||||
LLVMTypeRef lo = llvm_abi_type(c, info->direct_pair.lo);
|
||||
LLVMTypeRef hi = llvm_abi_type(c, info->direct_pair.hi);
|
||||
LLVMTypeRef struct_type = gencontext_get_twostruct(c, lo, hi);
|
||||
LLVMTypeRef struct_type = llvm_get_twostruct(c, lo, hi);
|
||||
unsigned decl_alignment = decl_abi_alignment(decl);
|
||||
// Cast to { lo, hi }
|
||||
LLVMValueRef cast = LLVMBuildBitCast(c->builder, decl->backend_ref, LLVMPointerType(struct_type, 0), "pair");
|
||||
@@ -264,6 +278,47 @@ void llvm_emit_return_abi(GenContext *c, BEValue *return_value, BEValue *failabl
|
||||
// Expands to multiple slots -
|
||||
// Not applicable to return values.
|
||||
UNREACHABLE
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
{
|
||||
// Pick the return as an address.
|
||||
llvm_value_addr(c, return_value);
|
||||
// Get the coerce type.
|
||||
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, info);
|
||||
// Create the new pointer
|
||||
LLVMValueRef coerce = LLVMBuildBitCast(c->builder, return_value->value, coerce_type, "");
|
||||
// We might have only one value, in that case, build a GEP to that one.
|
||||
LLVMValueRef lo_val;
|
||||
unsigned alignment;
|
||||
LLVMValueRef lo = llvm_emit_struct_gep(c, coerce, coerce_type, info->coerce_expand.lo_index,
|
||||
return_value->alignment,
|
||||
info->coerce_expand.offset_lo, &alignment);
|
||||
LLVMTypeRef lo_type = llvm_abi_type(c, info->coerce_expand.hi);
|
||||
lo_val = llvm_emit_load_aligned(c, lo_type, lo, alignment, "");
|
||||
|
||||
// We're done if there's a single element.
|
||||
if (!info->coerce_expand.hi)
|
||||
{
|
||||
llvm_emit_return_value(c, lo_val);
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's make a first class aggregate
|
||||
LLVMValueRef hi = llvm_emit_struct_gep(c, coerce, coerce_type, info->coerce_expand.hi_index,
|
||||
return_value->alignment,
|
||||
info->coerce_expand.offset_hi, &alignment);
|
||||
LLVMTypeRef hi_type = llvm_abi_type(c, info->coerce_expand.hi);
|
||||
LLVMValueRef hi_val = llvm_emit_load_aligned(c, hi_type, hi, alignment, "");
|
||||
|
||||
LLVMTypeRef unpadded_type = llvm_get_twostruct(c, lo_type, hi_type);
|
||||
LLVMValueRef composite = LLVMGetUndef(unpadded_type);
|
||||
|
||||
composite = LLVMBuildInsertValue(c->builder, composite, lo_val, 0, "");
|
||||
composite = LLVMBuildInsertValue(c->builder, composite, hi_val, 1, "");
|
||||
|
||||
// And return that unpadded result
|
||||
llvm_emit_return_value(c, composite);
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_DIRECT_PAIR:
|
||||
case ABI_ARG_DIRECT_COERCE:
|
||||
{
|
||||
@@ -427,6 +482,7 @@ static void llvm_emit_param_attributes(GenContext *context, LLVMValueRef functio
|
||||
case ABI_ARG_IGNORE:
|
||||
case ABI_ARG_DIRECT_COERCE:
|
||||
case ABI_ARG_DIRECT_PAIR:
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
break;
|
||||
case ABI_ARG_INDIRECT:
|
||||
if (info->indirect.realignment)
|
||||
|
||||
@@ -50,6 +50,7 @@ typedef enum
|
||||
ABI_ARG_IGNORE,
|
||||
ABI_ARG_DIRECT_PAIR,
|
||||
ABI_ARG_DIRECT_COERCE,
|
||||
ABI_ARG_EXPAND_COERCE,
|
||||
ABI_ARG_INDIRECT,
|
||||
ABI_ARG_EXPAND,
|
||||
} ABIKind;
|
||||
@@ -94,6 +95,17 @@ typedef struct ABIArgInfo_
|
||||
AbiType *hi;
|
||||
} direct_pair;
|
||||
struct
|
||||
{
|
||||
unsigned char offset_lo;
|
||||
unsigned char padding_hi;
|
||||
unsigned char lo_index;
|
||||
unsigned char hi_index;
|
||||
unsigned char offset_hi;
|
||||
bool packed : 1;
|
||||
AbiType *lo;
|
||||
AbiType *hi;
|
||||
} coerce_expand;
|
||||
struct
|
||||
{
|
||||
AbiType *partial_type;
|
||||
};
|
||||
@@ -210,6 +222,7 @@ void llvm_value_set(BEValue *value, LLVMValueRef llvm_value, Type *type);
|
||||
void llvm_value_set_address_align(BEValue *value, LLVMValueRef llvm_value, Type *type, unsigned alignment);
|
||||
void llvm_value_set_address(BEValue *value, LLVMValueRef llvm_value, Type *type);
|
||||
void llvm_value_fold_failable(GenContext *c, BEValue *value);
|
||||
void llvm_value_struct_gep(GenContext *c, BEValue *element, BEValue *struct_pointer, unsigned index);
|
||||
|
||||
LLVMValueRef llvm_value_rvalue_store(GenContext *c, BEValue *value);
|
||||
|
||||
@@ -255,6 +268,7 @@ static inline LLVMValueRef llvm_emit_store(GenContext *context, Decl *decl, LLVM
|
||||
void llvm_emit_panic_on_true(GenContext *c, LLVMValueRef value, const char *panic_name);
|
||||
void llvm_emit_return_abi(GenContext *c, BEValue *return_value, BEValue *failable);
|
||||
void llvm_emit_return_implicit(GenContext *c);
|
||||
LLVMValueRef llvm_emit_struct_gep(GenContext *context, LLVMValueRef ptr, LLVMTypeRef struct_type, unsigned index, unsigned struct_alignment, unsigned offset, unsigned *alignment);
|
||||
|
||||
LLVMValueRef llvm_get_next_param(GenContext *context, unsigned *index);
|
||||
LLVMTypeRef llvm_get_coerce_type(GenContext *c, ABIArgInfo *arg_info);
|
||||
@@ -283,7 +297,7 @@ void llvm_store_self_aligned(GenContext *context, LLVMValueRef pointer, LLVMValu
|
||||
void llvm_store_aligned(GenContext *context, LLVMValueRef pointer, LLVMValueRef value, unsigned alignment);
|
||||
void llvm_store_aligned_decl(GenContext *context, Decl *decl, LLVMValueRef value);
|
||||
|
||||
LLVMTypeRef gencontext_get_twostruct(GenContext *context, LLVMTypeRef lo, LLVMTypeRef hi);
|
||||
LLVMTypeRef llvm_get_twostruct(GenContext *context, LLVMTypeRef lo, LLVMTypeRef hi);
|
||||
LLVMValueRef llvm_emit_coerce(GenContext *context, LLVMTypeRef coerced, BEValue *value, Type *original_type);
|
||||
|
||||
static inline LLVMValueRef gencontext_emit_load(GenContext *c, Type *type, LLVMValueRef value)
|
||||
|
||||
@@ -207,6 +207,13 @@ static inline void add_func_type_param(GenContext *context, Type *param_type, AB
|
||||
case ABI_ARG_INDIRECT:
|
||||
vec_add(*params, llvm_get_ptr_type(context, param_type));
|
||||
break;
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
vec_add(*params, llvm_abi_type(context, arg_info->coerce_expand.lo));
|
||||
if (arg_info->coerce_expand.hi)
|
||||
{
|
||||
vec_add(*params, llvm_abi_type(context, arg_info->coerce_expand.hi));
|
||||
}
|
||||
break;
|
||||
case ABI_ARG_EXPAND:
|
||||
// Expanding a structs
|
||||
param_expand(context, params, param_type->canonical);
|
||||
@@ -267,6 +274,18 @@ LLVMTypeRef llvm_func_type(GenContext *context, Type *type)
|
||||
case ABI_ARG_INDIRECT:
|
||||
vec_add(params, llvm_get_ptr_type(context, real_return_type));
|
||||
FALLTHROUGH;
|
||||
case ABI_ARG_EXPAND_COERCE:
|
||||
{
|
||||
LLVMTypeRef lo = llvm_abi_type(context, ret_arg_info->direct_pair.lo);
|
||||
if (!ret_arg_info->direct_pair.hi)
|
||||
{
|
||||
return_type = lo;
|
||||
break;
|
||||
}
|
||||
LLVMTypeRef hi = llvm_abi_type(context, ret_arg_info->direct_pair.hi);
|
||||
return_type = llvm_get_twostruct(context, lo, hi);
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_IGNORE:
|
||||
return_type = llvm_get_type(context, type_void);
|
||||
break;
|
||||
@@ -274,7 +293,7 @@ LLVMTypeRef llvm_func_type(GenContext *context, Type *type)
|
||||
{
|
||||
LLVMTypeRef lo = llvm_abi_type(context, ret_arg_info->direct_pair.lo);
|
||||
LLVMTypeRef hi = llvm_abi_type(context, ret_arg_info->direct_pair.hi);
|
||||
return_type = gencontext_get_twostruct(context, lo, hi);
|
||||
return_type = llvm_get_twostruct(context, lo, hi);
|
||||
break;
|
||||
}
|
||||
case ABI_ARG_DIRECT_COERCE:
|
||||
@@ -369,7 +388,9 @@ LLVMTypeRef llvm_get_type(GenContext *c, Type *any_type)
|
||||
case TYPE_VECTOR:
|
||||
return any_type->backend_type = LLVMVectorType(llvm_get_type(c, any_type->vector.base), any_type->vector.len);
|
||||
case TYPE_COMPLEX:
|
||||
return any_type->backend_type = gencontext_get_twostruct(c, llvm_get_type(c, any_type->complex), llvm_get_type(c, any_type->complex));
|
||||
return any_type->backend_type = llvm_get_twostruct(c,
|
||||
llvm_get_type(c, any_type->complex),
|
||||
llvm_get_type(c, any_type->complex));
|
||||
}
|
||||
UNREACHABLE;
|
||||
}
|
||||
@@ -377,6 +398,27 @@ LLVMTypeRef llvm_get_type(GenContext *c, Type *any_type)
|
||||
|
||||
LLVMTypeRef llvm_get_coerce_type(GenContext *c, ABIArgInfo *arg_info)
|
||||
{
|
||||
if (arg_info->kind == ABI_ARG_EXPAND_COERCE)
|
||||
{
|
||||
unsigned element_index = 0;
|
||||
LLVMTypeRef elements[4];
|
||||
// Add padding if needed.
|
||||
if (arg_info->coerce_expand.offset_lo)
|
||||
{
|
||||
elements[element_index++] = LLVMArrayType(llvm_get_type(c, type_byte), arg_info->coerce_expand.offset_lo);
|
||||
}
|
||||
elements[element_index++] = llvm_abi_type(c, arg_info->coerce_expand.lo);
|
||||
if (arg_info->coerce_expand.padding_hi)
|
||||
{
|
||||
elements[element_index++] = LLVMArrayType(llvm_get_type(c, type_byte), arg_info->coerce_expand.padding_hi);
|
||||
}
|
||||
if (arg_info->coerce_expand.hi)
|
||||
{
|
||||
elements[element_index++] = llvm_abi_type(c, arg_info->coerce_expand.hi);
|
||||
}
|
||||
return LLVMStructType(elements, element_index, arg_info->coerce_expand.packed);
|
||||
}
|
||||
|
||||
if (arg_info->kind == ABI_ARG_DIRECT_COERCE)
|
||||
{
|
||||
if (!arg_info->direct_coerce.type) return NULL;
|
||||
@@ -393,12 +435,12 @@ LLVMTypeRef llvm_get_coerce_type(GenContext *c, ABIArgInfo *arg_info)
|
||||
{
|
||||
LLVMTypeRef lo = llvm_abi_type(c, arg_info->direct_pair.lo);
|
||||
LLVMTypeRef hi = llvm_abi_type(c, arg_info->direct_pair.hi);
|
||||
return gencontext_get_twostruct(c, lo, hi);
|
||||
return llvm_get_twostruct(c, lo, hi);
|
||||
}
|
||||
UNREACHABLE
|
||||
}
|
||||
|
||||
LLVMTypeRef gencontext_get_twostruct(GenContext *context, LLVMTypeRef lo, LLVMTypeRef hi)
|
||||
LLVMTypeRef llvm_get_twostruct(GenContext *context, LLVMTypeRef lo, LLVMTypeRef hi)
|
||||
{
|
||||
LLVMTypeRef types[2] = { lo, hi };
|
||||
return LLVMStructTypeInContext(context->context, types, 2, false);
|
||||
|
||||
@@ -591,7 +591,7 @@ void target_setup(void)
|
||||
case ARCH_TYPE_RISCV64:
|
||||
case ARCH_TYPE_RISCV32:
|
||||
build_target.riscv.xlen = 0; // pointer width
|
||||
build_target.riscv.abiflen = 32; // ends with f / d (64)
|
||||
build_target.riscv.flen = 32; // ends with f / d (64)
|
||||
build_target.abi = ABI_RISCV;
|
||||
TODO
|
||||
case ARCH_TYPE_X86:
|
||||
|
||||
@@ -281,7 +281,7 @@ typedef struct
|
||||
struct
|
||||
{
|
||||
unsigned xlen;
|
||||
unsigned abiflen;
|
||||
unsigned flen;
|
||||
} riscv;
|
||||
struct
|
||||
{
|
||||
|
||||
@@ -913,6 +913,42 @@ type_create(#_name, &_shortname, _type, _bits, target->align_ ## _align, target-
|
||||
type_create("error", &t_error, TYPE_ERR_UNION, target->width_pointer * 2, target->align_pointer, target->align_pref_pointer);
|
||||
}
|
||||
|
||||
bool type_is_scalar(Type *type)
|
||||
{
|
||||
RETRY:
|
||||
switch (type->type_kind)
|
||||
{
|
||||
case TYPE_POISONED:
|
||||
case TYPE_TYPEINFO:
|
||||
case TYPE_MEMBER:
|
||||
UNREACHABLE
|
||||
case TYPE_VOID:
|
||||
case TYPE_FUNC:
|
||||
case TYPE_STRUCT:
|
||||
case TYPE_UNION:
|
||||
case TYPE_ARRAY:
|
||||
case TYPE_SUBARRAY:
|
||||
case TYPE_VECTOR:
|
||||
return false;
|
||||
case TYPE_BOOL:
|
||||
case ALL_INTS:
|
||||
case ALL_FLOATS:
|
||||
case TYPE_TYPEID:
|
||||
case TYPE_POINTER:
|
||||
case TYPE_ENUM:
|
||||
case TYPE_ERRTYPE:
|
||||
case TYPE_ERR_UNION:
|
||||
case TYPE_VARARRAY:
|
||||
case TYPE_COMPLEX:
|
||||
return true;
|
||||
case TYPE_TYPEDEF:
|
||||
type = type->canonical;
|
||||
goto RETRY;
|
||||
case TYPE_STRING:
|
||||
TODO
|
||||
}
|
||||
UNREACHABLE
|
||||
}
|
||||
/**
|
||||
* Check if a type is contained in another type.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user