mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
1340 lines
47 KiB
C
1340 lines
47 KiB
C
// Copyright (c) 2022-2025 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 <math.h>
|
|
|
|
#include "sema_internal.h"
|
|
|
|
|
|
typedef enum
|
|
{
|
|
BA_POINTER,
|
|
BA_SIZE,
|
|
BA_BOOL,
|
|
BA_CHAR,
|
|
BA_FLOATLIKE,
|
|
BA_INTEGER,
|
|
BA_FLOAT,
|
|
BA_INTLIKE,
|
|
BA_NUMLIKE,
|
|
BA_BOOLVEC,
|
|
BA_BOOLINTVEC,
|
|
BA_BOOLINT,
|
|
BA_INTVEC,
|
|
BA_FLOATVEC,
|
|
BA_VEC,
|
|
BA_NUMVEC,
|
|
BA_PTRVEC,
|
|
BA_NUM,
|
|
BA_TYPEID,
|
|
} BuiltinArg;
|
|
|
|
static bool sema_check_builtin_args_match(SemaContext *context, Expr **args, size_t arg_len);
|
|
static bool sema_check_builtin_args_const(SemaContext *context, Expr **args, size_t arg_len);
|
|
static bool sema_check_builtin_args(SemaContext *context, Expr **args, BuiltinArg *arg_type, size_t arg_len);
|
|
static bool sema_expr_analyse_compare_exchange(SemaContext *context, Expr *expr);
|
|
static bool sema_expr_analyse_syscall(SemaContext *context, Expr *expr);
|
|
static inline bool sema_expr_analyse_swizzle(SemaContext *context, Expr *expr, bool swizzle_two);
|
|
static inline int builtin_expected_args(BuiltinFunction func);
|
|
static inline bool is_valid_atomicity(SemaContext *context, Expr *expr);
|
|
static bool sema_check_alignment_expression(SemaContext *context, Expr *align);
|
|
|
|
static bool sema_expr_is_valid_mask_for_value(SemaContext *context, Expr *expr, Expr *value)
|
|
{
|
|
if (type_flatten(value->type)->array.len != type_flatten(expr->type)->array.len)
|
|
{
|
|
RETURN_SEMA_ERROR(expr, "The mask must have the same length as the value.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Used for when we have a builtin that has a constraint between argument types that
|
|
* they should be the same.
|
|
*/
|
|
static bool sema_check_builtin_args_match(SemaContext *context, Expr **args, size_t arg_len)
|
|
{
|
|
ASSERT(arg_len > 1);
|
|
Type *first = type_no_optional(args[0]->type->canonical);
|
|
for (size_t i = 1; i < arg_len; i++)
|
|
{
|
|
if (first != type_no_optional(args[i]->type->canonical))
|
|
{
|
|
RETURN_SEMA_ERROR(args[i], "Expected an expression of type %s.", type_quoted_error_string(args[0]->type));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check the constraint that the arguments are constant.
|
|
*/
|
|
static bool sema_check_builtin_args_const(SemaContext *context, Expr **args, size_t arg_len)
|
|
{
|
|
for (size_t i = 0; i < arg_len; i++)
|
|
{
|
|
if (!sema_cast_const(args[i])) RETURN_SEMA_ERROR(args[i], "Expected a compile time constant value for this argument.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool sema_check_alignment_expression(SemaContext *context, Expr *align)
|
|
{
|
|
if (!sema_analyse_expr_rhs(context, type_usz, align, false, NULL, false)) return false;
|
|
if (!expr_is_const_int(align)
|
|
|| !int_fits(align->const_expr.ixx, TYPE_U64)
|
|
|| (!is_power_of_two(align->const_expr.ixx.i.low) && align->const_expr.ixx.i.low))
|
|
{
|
|
RETURN_SEMA_ERROR(align, "Expected a constant power-of-two alignment or zero.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool sema_check_builtin_args(SemaContext *context, Expr **args, BuiltinArg *arg_type, size_t arg_len)
|
|
{
|
|
for (size_t i = 0; i < arg_len; i++)
|
|
{
|
|
Expr *arg = args[i];
|
|
Type *type = type_flatten(arg->type->canonical);
|
|
switch (arg_type[i])
|
|
{
|
|
case BA_POINTER:
|
|
if (type_is_pointer(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a pointer.");
|
|
case BA_CHAR:
|
|
if (type == type_char || type == type_ichar) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a char or ichar.");
|
|
case BA_SIZE:
|
|
if (type_is_integer(type) && type_size(type) == type_size(type_usz)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected an usz or isz value.");
|
|
case BA_BOOL:
|
|
if (type == type_bool) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a bool.");
|
|
case BA_NUM:
|
|
if (type_is_number(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected an integer or a float.");
|
|
case BA_TYPEID:
|
|
if (type == type_typeid) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a typeid.");
|
|
case BA_NUMLIKE:
|
|
if (type_flat_is_numlike(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a number or vector.");
|
|
case BA_FLOATLIKE:
|
|
if (type_flat_is_floatlike(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a floating point or floating point vector, but was %s.",
|
|
type_quoted_error_string(type));
|
|
case BA_VEC:
|
|
if (type->type_kind == TYPE_VECTOR) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a vector.");
|
|
case BA_PTRVEC:
|
|
if (type_is_pointer_vector(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a pointer vector.");
|
|
case BA_NUMVEC:
|
|
if (type->type_kind == TYPE_VECTOR && type_is_number_or_bool(type->array.base)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a numeric vector.");
|
|
case BA_INTVEC:
|
|
if (type->type_kind == TYPE_VECTOR && type_flat_is_intlike(type->array.base)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected an integer vector.");
|
|
case BA_BOOLINT:
|
|
if (type_is_integer_or_bool_kind(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a boolean or integer value.");
|
|
case BA_BOOLVEC:
|
|
if (type_flat_is_bool_vector(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a boolean vector.");
|
|
case BA_BOOLINTVEC:
|
|
if (type->type_kind == TYPE_VECTOR && type_flat_is_boolintlike(type->array.base)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a boolean or integer vector.");
|
|
case BA_FLOATVEC:
|
|
if (type->type_kind == TYPE_VECTOR && type_flat_is_floatlike(type->array.base)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected an float vector.");
|
|
case BA_INTLIKE:
|
|
if (type_flat_is_intlike(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected an integer or integer vector.");
|
|
case BA_INTEGER:
|
|
if (type_is_integer(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected an integer.");
|
|
case BA_FLOAT:
|
|
if (type_is_float(type)) continue;
|
|
RETURN_SEMA_ERROR(arg, "Expected a float or double.");
|
|
}
|
|
UNREACHABLE
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Analyse both $$swizzle and $$swizzle2
|
|
*/
|
|
static inline bool sema_expr_analyse_swizzle(SemaContext *context, Expr *expr, bool swizzle_two)
|
|
{
|
|
Expr **args = expr->call_expr.arguments;
|
|
unsigned arg_count = vec_size(args);
|
|
bool optional = false;
|
|
int first_mask_value = swizzle_two ? 2 : 1;
|
|
for (unsigned i = 0; i < first_mask_value; i++)
|
|
{
|
|
Expr *arg = args[i];
|
|
// Analyse the expressions
|
|
if (!sema_analyse_expr(context, arg)) return false;
|
|
// Expect vector
|
|
if (!type_flat_is_vector(arg->type)) RETURN_SEMA_ERROR(arg, "A vector was expected here.");
|
|
// Optional-ness updated
|
|
optional = optional || type_is_optional(args[i]->type);
|
|
}
|
|
// Ensure matching types
|
|
if (swizzle_two && !sema_check_builtin_args_match(context, args, 2)) return false;
|
|
|
|
unsigned components = type_flatten(args[0]->type)->array.len;
|
|
if (swizzle_two) components *= 2;
|
|
for (unsigned i = first_mask_value; i < arg_count; i++)
|
|
{
|
|
Expr *mask_val = args[i];
|
|
if (!sema_analyse_expr_rhs(context, type_int, mask_val, false, NULL, false)) return false;
|
|
if (!expr_is_const_int(mask_val))
|
|
{
|
|
RETURN_SEMA_ERROR(mask_val, "The swizzle positions must be compile time constants.");
|
|
}
|
|
if (mask_val->const_expr.ixx.i.low >= components)
|
|
{
|
|
if (components == 1) RETURN_SEMA_ERROR(mask_val, "The only possible swizzle position is 0.");
|
|
RETURN_SEMA_ERROR(mask_val, "The swizzle position must be in the range 0-%d.", components - 1);
|
|
}
|
|
}
|
|
expr->type = type_add_optional(type_get_vector(type_get_indexed_type(args[0]->type), arg_count - first_mask_value), optional);
|
|
return true;
|
|
}
|
|
|
|
static inline bool is_valid_atomicity(SemaContext *context, Expr *expr)
|
|
{
|
|
if (!expr_is_const_int(expr) || !int_fits(expr->const_expr.ixx, TYPE_U8) || expr->const_expr.ixx.i.low > 6)
|
|
{
|
|
RETURN_SEMA_ERROR(expr, "Expected a constant integer value < 7.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool sema_expr_analyse_compare_exchange(SemaContext *context, Expr *expr)
|
|
{
|
|
Expr **args = expr->call_expr.arguments;
|
|
Expr *pointer = args[0];
|
|
|
|
if (!sema_analyse_expr(context, pointer)) return false;
|
|
|
|
bool optional = IS_OPTIONAL(pointer);
|
|
Type *comp_type = type_flatten(pointer->type);
|
|
if (!type_is_pointer(comp_type)) RETURN_SEMA_ERROR(pointer, "Expected a pointer here.");
|
|
Type *pointee = comp_type->pointer;
|
|
for (int i = 1; i < 3; i++)
|
|
{
|
|
Expr *arg = args[i];
|
|
if (!sema_analyse_expr_rhs(context, type_is_void(pointee) ? NULL : pointee, arg, true, NULL, false)) return false;
|
|
if (type_is_void(pointee)) pointee = arg->type->canonical;
|
|
if (!type_is_atomic(type_flatten(arg->type)))
|
|
{
|
|
RETURN_SEMA_ERROR(arg, "%s may not be used with atomics.", type_quoted_error_string(arg->type));
|
|
}
|
|
optional = optional || IS_OPTIONAL(args[i]);
|
|
}
|
|
for (int i = 3; i < 5; i++)
|
|
{
|
|
if (!sema_analyse_expr_rhs(context, type_bool, args[i], false, NULL, false)) return false;
|
|
if (!sema_cast_const(args[i]))
|
|
{
|
|
RETURN_SEMA_ERROR(args[i], "Expected a constant boolean value.");}
|
|
}
|
|
for (int i = 5; i < 7; i++)
|
|
{
|
|
if (!sema_analyse_expr_rhs(context, type_char, args[i], false, NULL, false)) return false;
|
|
if (!is_valid_atomicity(context, args[i])) return false;
|
|
}
|
|
unsigned success = args[5]->const_expr.ixx.i.low;
|
|
unsigned failure = args[6]->const_expr.ixx.i.low;
|
|
if (success < ATOMIC_RELAXED) RETURN_SEMA_ERROR(args[5], "Success ordering must be at least RELAXED.");
|
|
if (failure < ATOMIC_RELAXED) RETURN_SEMA_ERROR(args[6], "Failure ordering must be at least RELAXED.");
|
|
if (failure == ATOMIC_ACQUIRE_RELEASE || failure == ATOMIC_RELEASE)
|
|
{
|
|
RETURN_SEMA_ERROR(args[6], "Failure ordering may not be RELEASE / ACQUIRE_RELEASE.");
|
|
}
|
|
Expr *align = args[7];
|
|
if (!sema_check_alignment_expression(context, align)) return false;
|
|
expr->type = type_add_optional(args[1]->type, optional);
|
|
return true;
|
|
}
|
|
|
|
static bool sema_expr_analyse_syscall(SemaContext *context, Expr *expr)
|
|
{
|
|
Expr **args = expr->call_expr.arguments;
|
|
unsigned arg_count = vec_size(args);
|
|
if (arg_count > 7)
|
|
{
|
|
RETURN_SEMA_ERROR(args[7], "Only 7 arguments supported for $$syscall.");
|
|
}
|
|
bool optional = false;
|
|
for (unsigned i = 0; i < arg_count; i++)
|
|
{
|
|
Expr *arg = args[i];
|
|
if (!sema_analyse_expr_rhs(context, type_uptr, arg, true, NULL, false)) return false;
|
|
optional = optional || type_is_optional(arg->type);
|
|
}
|
|
switch (compiler.platform.arch)
|
|
{
|
|
case ARCH_TYPE_AARCH64:
|
|
case ARCH_TYPE_AARCH64_BE:
|
|
case ARCH_TYPE_X86:
|
|
case ARCH_TYPE_X86_64:
|
|
break;
|
|
default:
|
|
RETURN_SEMA_ERROR(expr, "Target does not support $$syscall.");
|
|
}
|
|
expr->type = type_add_optional(type_uptr, optional);
|
|
return true;
|
|
}
|
|
|
|
uint64_t rand_u64()
|
|
{
|
|
return ((uint64_t)rand() << 48) ^ ((uint64_t)rand() << 32) ^ ((uint64_t)rand() << 16) ^ rand();
|
|
}
|
|
|
|
bool sema_expr_analyse_rnd(SemaContext *context UNUSED, Expr *expr)
|
|
{
|
|
uint64_t r = rand_u64();
|
|
uint64_t mantissa = r >> 11;
|
|
double val = (double)mantissa / (double)(1ULL << 53); // Not secure random but...
|
|
expr_rewrite_const_float(expr, type_double, val);
|
|
return true;
|
|
}
|
|
|
|
bool sema_expr_analyse_str_hash(SemaContext *context, Expr *expr)
|
|
{
|
|
Expr *inner = expr->call_expr.arguments[0];
|
|
if (!sema_analyse_expr(context, inner)) return true;
|
|
if (!expr_is_const_string(inner))
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "You need a compile time constant string to take the hash of it.");
|
|
}
|
|
uint32_t hash = fnv1a(inner->const_expr.bytes.ptr, inner->const_expr.bytes.len);
|
|
expr_rewrite_const_int(expr, type_uint, hash);
|
|
return true;
|
|
}
|
|
|
|
bool sema_expr_analyse_str_find(SemaContext *context, Expr *expr)
|
|
{
|
|
Expr *inner = expr->call_expr.arguments[0];
|
|
Expr *inner_find = expr->call_expr.arguments[1];
|
|
if (!sema_analyse_expr(context, inner) || !sema_analyse_expr(context, inner_find)) return true;
|
|
if (!expr_is_const_string(inner))
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "You need a compile time constant string to search.");
|
|
}
|
|
if (!expr_is_const_string(inner_find))
|
|
{
|
|
RETURN_SEMA_ERROR(inner_find, "You need a compile time constant string to search for.");
|
|
}
|
|
const char *inner_str = inner->const_expr.bytes.ptr;
|
|
const char *find_str = inner_find->const_expr.bytes.ptr;
|
|
char *ret = strstr(inner_str, find_str);
|
|
expr_rewrite_const_int(expr, type_isz, (uint64_t)(ret == NULL ? -1 : ret - inner_str));
|
|
return true;
|
|
}
|
|
|
|
bool sema_expr_analyse_str_conv(SemaContext *context, Expr *expr, BuiltinFunction func)
|
|
{
|
|
Expr *inner = expr->call_expr.arguments[0];
|
|
if (!sema_analyse_expr(context, inner)) return true;
|
|
if (!expr_is_const_string(inner))
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "You need a compile time constant string to take convert.");
|
|
}
|
|
const char *string = inner->const_expr.bytes.ptr;
|
|
ArraySize len = inner->const_expr.bytes.len;
|
|
// Empty string: no conversion
|
|
if (!len)
|
|
{
|
|
expr_replace(expr, inner);
|
|
return true;
|
|
}
|
|
char *new_string = malloc_string(len + 1);
|
|
switch (func)
|
|
{
|
|
case BUILTIN_STR_LOWER:
|
|
for (ArraySize i = 0; i < len; i++)
|
|
{
|
|
char c = string[i];
|
|
new_string[i] = (char)(char_is_upper(c) ? (c | 0x20) : c);
|
|
}
|
|
break;
|
|
case BUILTIN_STR_UPPER:
|
|
for (ArraySize i = 0; i < len; i++)
|
|
{
|
|
char c = string[i];
|
|
new_string[i] = (char)(char_is_lower(c) ? (c & ~0x20) : c);
|
|
}
|
|
break;
|
|
default:
|
|
UNREACHABLE
|
|
}
|
|
expr->expr_kind = EXPR_CONST;
|
|
expr->const_expr.const_kind = CONST_STRING;
|
|
new_string[len] = 0;
|
|
expr->const_expr.bytes.ptr = new_string;
|
|
expr->const_expr.bytes.len = len;
|
|
expr->resolve_status = RESOLVE_DONE;
|
|
expr->type = type_string;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Interpret a UTF-8 codepoint and return an integer corresponding to the raw codepoint's value.
|
|
* @param data_ptr pointer to the pointer which scrolls along the input data string
|
|
* @param inc pointer to the integer which will be incremented by the number of bytes consumed
|
|
*/
|
|
static uint32_t utf8_to_codepoint(const char *data_ptr, int *inc)
|
|
{
|
|
int bytes = 1;
|
|
const char *data = data_ptr;
|
|
uint32_t result = 0;
|
|
|
|
ASSERT(data);
|
|
ASSERT(inc);
|
|
|
|
while ((0xC0 & *(data++)) == 0x80) ++bytes;
|
|
data = data_ptr - 1;
|
|
|
|
switch (bytes) {
|
|
case 1: return (uint32_t)(data[0] & 0x7FU);
|
|
case 2:
|
|
result = ((data[0] & 0x1FU) << 6U) | (data[1] & 0x3FU);
|
|
break;
|
|
case 3:
|
|
result = ((data[0] & 0x0FU) << 12U) | ((data[1] & 0x3FU) << 6U) | (data[2] & 0x3FU);
|
|
break;
|
|
case 4:
|
|
result = ((data[0] & 0x07U) << 18U) | ((data[1] & 0x3FU) << 12U) | ((data[2] & 0x3FU) << 6U) | (data[3] & 0x3FU);
|
|
break;
|
|
default: return 0; // U+10FFFF is the highest codepoint in UTF-8, consisting of a maximum of 4 bytes.
|
|
}
|
|
|
|
*inc = bytes;
|
|
return result;
|
|
}
|
|
|
|
bool sema_expr_analyse_str_wide(SemaContext *context, Expr *expr, BuiltinFunction func)
|
|
{
|
|
Expr **args = expr->call_expr.arguments;
|
|
Expr *inner = args[0];
|
|
bool zero_terminate = true;
|
|
unsigned arg_count = vec_size(args);
|
|
if (arg_count > 2)
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "Too many arguments, expected 1 or 2 arguments.");
|
|
}
|
|
if (arg_count == 2)
|
|
{
|
|
Expr *zero_term = args[1];
|
|
if (!sema_analyse_expr(context, zero_term)) return false;
|
|
if (!sema_cast_const(zero_term) || !expr_is_const_bool(zero_term))
|
|
{
|
|
RETURN_SEMA_ERROR(zero_term, "Expected a boolean value here, to determine zero termination or not.");
|
|
}
|
|
zero_terminate = zero_term->const_expr.b;
|
|
}
|
|
if (!sema_analyse_expr(context, inner)) return false;
|
|
if (!sema_cast_const(inner) && !expr_is_const_string(inner))
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "You need a compile time constant string to convert to a wide string.");
|
|
}
|
|
|
|
const char *string = inner->const_expr.bytes.ptr;
|
|
ArraySize original_len = inner->const_expr.bytes.len;
|
|
|
|
int bytes_per_unit = func == BUILTIN_WIDESTRING_16 ? 2 : 4;
|
|
|
|
Type *type = func == BUILTIN_WIDESTRING_16 ? type_ushort : type_uint;
|
|
ArraySize len = (original_len + 1) * bytes_per_unit; // inflate to unit size +null-term
|
|
ConstInitializer **elements = VECNEW(ConstInitializer*, len);
|
|
|
|
const char *data = string;
|
|
const char *end = data + original_len;
|
|
while (data < end)
|
|
{
|
|
char c = *(data++);
|
|
if ((0xC0 & c) != 0xC0)
|
|
{
|
|
// ASCII
|
|
Expr *value = expr_new_const_int(expr->span, type, (c & 0x7F));
|
|
vec_add(elements, const_init_new_value(value));
|
|
continue;
|
|
}
|
|
|
|
int increment = 0;
|
|
uint32_t from_codepoint = utf8_to_codepoint(data, &increment);
|
|
data += increment - 1;
|
|
if (!from_codepoint)
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "Unparseable codepoint in string.");
|
|
}
|
|
if (type == type_ushort && from_codepoint & 0xFFFF0000)
|
|
{
|
|
RETURN_SEMA_ERROR(inner, "Invalid codepoint in string: a 16-bit wide-string cannot accommodate codepoints over U+FFFF.");
|
|
}
|
|
Expr *value = expr_new_const_int(expr->span, type, from_codepoint);
|
|
vec_add(elements, const_init_new_value(value));
|
|
}
|
|
if (zero_terminate) vec_add(elements, const_init_new_zero(type));
|
|
|
|
Type *array_type = type_get_array(type, vec_size(elements));
|
|
ConstInitializer *init = const_init_new_array_full(array_type, elements);
|
|
expr_rewrite_const_initializer(expr, array_type, init);
|
|
return true;
|
|
}
|
|
|
|
INLINE BinaryOp operator_from_builtin(BuiltinFunction fn)
|
|
{
|
|
switch (fn)
|
|
{
|
|
case BUILTIN_VECCOMPLT: return BINARYOP_VEC_LT;
|
|
case BUILTIN_VECCOMPLE: return BINARYOP_VEC_LE;
|
|
case BUILTIN_VECCOMPGT: return BINARYOP_VEC_GT;
|
|
case BUILTIN_VECCOMPGE: return BINARYOP_VEC_GE;
|
|
case BUILTIN_VECCOMPEQ: return BINARYOP_VEC_EQ;
|
|
case BUILTIN_VECCOMPNE: return BINARYOP_VEC_NE;
|
|
default: UNREACHABLE
|
|
}
|
|
}
|
|
|
|
bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr)
|
|
{
|
|
expr->call_expr.is_builtin = true;
|
|
expr->call_expr.arguments = sema_expand_vasplat_exprs(context, expr->call_expr.arguments);
|
|
|
|
BuiltinFunction func = exprptr(expr->call_expr.function)->builtin_expr.builtin;
|
|
Expr **args = expr->call_expr.arguments;
|
|
unsigned arg_count = vec_size(args);
|
|
int expected_args = builtin_expected_args(func);
|
|
bool expect_vararg = false;
|
|
if (expected_args < 0)
|
|
{
|
|
expected_args = -expected_args;
|
|
expect_vararg = true;
|
|
}
|
|
|
|
// 1. Handle arg count, so at least we know that is ok.
|
|
if (arg_count < expected_args || (arg_count > expected_args && !expect_vararg))
|
|
{
|
|
if (arg_count == 0)
|
|
{
|
|
RETURN_SEMA_ERROR(expr, "Expected %s%d arguments to builtin.", expect_vararg ? "at least " : "", expected_args);
|
|
}
|
|
if (arg_count < expected_args) RETURN_SEMA_ERROR(args[arg_count - 1], "Expected more arguments after this one.");
|
|
RETURN_SEMA_ERROR(args[expected_args], "Too many arguments.");
|
|
}
|
|
|
|
switch (func)
|
|
{
|
|
case BUILTIN_SPRINTF:
|
|
return sema_expr_analyse_sprintf(context, expr, args[0], &args[1], arg_count - 1);
|
|
case BUILTIN_RND:
|
|
return sema_expr_analyse_rnd(context, expr);
|
|
case BUILTIN_STR_HASH:
|
|
return sema_expr_analyse_str_hash(context, expr);
|
|
case BUILTIN_STR_UPPER:
|
|
case BUILTIN_STR_LOWER:
|
|
return sema_expr_analyse_str_conv(context, expr, func);
|
|
case BUILTIN_STR_FIND:
|
|
return sema_expr_analyse_str_find(context, expr);
|
|
case BUILTIN_SWIZZLE2:
|
|
case BUILTIN_SWIZZLE:
|
|
return sema_expr_analyse_swizzle(context, expr, func == BUILTIN_SWIZZLE2);
|
|
case BUILTIN_SYSCALL:
|
|
return sema_expr_analyse_syscall(context, expr);
|
|
case BUILTIN_TRAP:
|
|
case BUILTIN_UNREACHABLE:
|
|
expr->call_expr.no_return = true;
|
|
FALLTHROUGH;
|
|
case BUILTIN_BREAKPOINT:
|
|
expr->type = type_void;
|
|
return true;
|
|
case BUILTIN_SYSCLOCK:
|
|
expr->type = type_ulong;
|
|
return true;
|
|
case BUILTIN_GET_ROUNDING_MODE:
|
|
expr->type = type_int;
|
|
return true;
|
|
case BUILTIN_COMPARE_EXCHANGE:
|
|
return sema_expr_analyse_compare_exchange(context, expr);
|
|
case BUILTIN_WIDESTRING_16:
|
|
case BUILTIN_WIDESTRING_32:
|
|
return sema_expr_analyse_str_wide(context, expr, func);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
bool optional = false;
|
|
|
|
// 2. We can now check all the arguments, since they in general work on the
|
|
// exact type size, we don't do any forced promotion.
|
|
for (unsigned i = 0; i < arg_count; i++)
|
|
{
|
|
if (!sema_analyse_expr(context, args[i])) return false;
|
|
optional = optional || type_is_optional(args[i]->type);
|
|
}
|
|
|
|
Type *rtype = NULL;
|
|
switch (func)
|
|
{
|
|
case BUILTIN_SET_ROUNDING_MODE:
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTEGER}, 1)) return false;
|
|
rtype = type_void;
|
|
break;
|
|
case BUILTIN_SYSCALL:
|
|
case BUILTIN_STR_HASH:
|
|
case BUILTIN_STR_UPPER:
|
|
case BUILTIN_STR_LOWER:
|
|
case BUILTIN_STR_FIND:
|
|
case BUILTIN_WIDESTRING_16:
|
|
case BUILTIN_WIDESTRING_32:
|
|
case BUILTIN_SPRINTF:
|
|
UNREACHABLE
|
|
case BUILTIN_VECCOMPGE:
|
|
case BUILTIN_VECCOMPEQ:
|
|
case BUILTIN_VECCOMPLE:
|
|
case BUILTIN_VECCOMPGT:
|
|
case BUILTIN_VECCOMPLT:
|
|
case BUILTIN_VECCOMPNE:
|
|
{
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_NUMVEC, BA_NUMVEC}, 2)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
Type *vec_type = type_flatten(args[0]->type);
|
|
rtype = type_get_vector(type_bool, vec_type->array.len);
|
|
expr->expr_kind = EXPR_BINARY;
|
|
expr->binary_expr = (ExprBinary) {
|
|
.left = exprid(args[0]),
|
|
.right = exprid(args[1]),
|
|
.operator = operator_from_builtin(func)
|
|
};
|
|
break;
|
|
}
|
|
case BUILTIN_SELECT:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_BOOLVEC, BA_VEC, BA_VEC}, 3)) return false;
|
|
if (!sema_check_builtin_args_match(context, &args[1], 2)) return false;
|
|
rtype = args[1]->type;
|
|
if (!sema_expr_is_valid_mask_for_value(context, args[0], args[1])) return false;
|
|
break;
|
|
case BUILTIN_OVERFLOW_ADD:
|
|
case BUILTIN_OVERFLOW_MUL:
|
|
case BUILTIN_OVERFLOW_SUB:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args,
|
|
(BuiltinArg[]) {BA_INTLIKE, BA_INTLIKE, BA_POINTER},
|
|
3)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
if (type_no_optional(args[0]->type->canonical) != type_no_optional(args[2]->type->canonical->pointer))
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "Expected %s, not %s.",
|
|
type_to_error_string(type_get_ptr(args[0]->type)),
|
|
type_to_error_string(args[2]->type));
|
|
}
|
|
rtype = type_bool;
|
|
break;
|
|
case BUILTIN_EXACT_ADD:
|
|
case BUILTIN_EXACT_DIV:
|
|
case BUILTIN_EXACT_MUL:
|
|
case BUILTIN_EXACT_SUB:
|
|
case BUILTIN_EXACT_MOD:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTEGER, BA_INTEGER}, 2)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
rtype = args[0]->type->canonical;
|
|
break;
|
|
case BUILTIN_ANY_MAKE:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_TYPEID}, 2)) return false;
|
|
{
|
|
expr->expr_kind = EXPR_MAKE_ANY;
|
|
expr->make_any_expr = (ExprMakeAny) {
|
|
.inner = args[0],
|
|
.typeid = args[1]
|
|
};
|
|
expr->type = type_add_optional(type_any, optional);
|
|
return true;
|
|
}
|
|
case BUILTIN_EXACT_NEG:
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTLIKE}, 1)) return false;
|
|
rtype = args[0]->type->canonical;
|
|
break;
|
|
case BUILTIN_MEMCOPY_INLINE:
|
|
ASSERT(arg_count == 6);
|
|
if (!sema_check_builtin_args(context, args,
|
|
(BuiltinArg[]) {BA_POINTER, BA_POINTER, BA_SIZE, BA_BOOL, BA_SIZE, BA_SIZE},
|
|
6)) return false;
|
|
if (!sema_check_builtin_args_const(context, &args[2], 4)) return false;
|
|
rtype = type_void;
|
|
break;
|
|
case BUILTIN_MEMCOPY:
|
|
case BUILTIN_MEMMOVE:
|
|
ASSERT(arg_count == 6);
|
|
if (!sema_check_builtin_args(context, args,
|
|
(BuiltinArg[]) {BA_POINTER, BA_POINTER, BA_SIZE, BA_BOOL, BA_SIZE, BA_SIZE},
|
|
6)) return false;
|
|
if (!sema_check_builtin_args_const(context, &args[3], 3)) return false;
|
|
rtype = type_void;
|
|
break;
|
|
case BUILTIN_MEMSET:
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_CHAR, BA_SIZE, BA_BOOL, BA_SIZE},
|
|
5)) return false;
|
|
if (!sema_check_builtin_args_const(context, &args[3], 2)) return false;
|
|
rtype = type_void;
|
|
break;
|
|
case BUILTIN_MEMSET_INLINE:
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_CHAR, BA_SIZE, BA_BOOL, BA_SIZE},
|
|
5)) return false;
|
|
if (!sema_check_builtin_args_const(context, &args[2], 3)) return false;
|
|
rtype = type_void;
|
|
break;
|
|
case BUILTIN_BITREVERSE:
|
|
case BUILTIN_BSWAP:
|
|
case BUILTIN_CTLZ:
|
|
case BUILTIN_POPCOUNT:
|
|
case BUILTIN_CTTZ:
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTLIKE}, 1)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_MATRIX_TRANSPOSE:
|
|
{
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_VEC, BA_INTEGER, BA_INTEGER}, 3)) return false;
|
|
if (!sema_check_builtin_args_const(context, &args[1], 2)) return false;
|
|
ArraySize vec_len = type_flatten(args[0]->type)->array.len;
|
|
Int sum = int_mul(args[1]->const_expr.ixx, args[2]->const_expr.ixx);
|
|
if (!int_icomp(sum, vec_len, BINARYOP_EQ))
|
|
{
|
|
RETURN_SEMA_ERROR(args[1], "Expected row * col to equal %d.", vec_len);
|
|
}
|
|
rtype = args[0]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_MATRIX_MUL:
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_VEC, BA_VEC, BA_INTEGER, BA_INTEGER, BA_INTEGER }, 5)) return false;
|
|
if (!sema_check_builtin_args_const(context, &args[2], 3)) return false;
|
|
Type *flat1 = type_flatten(args[0]->type);
|
|
Type *flat2 = type_flatten(args[1]->type);
|
|
if (flat1->array.base != flat2->array.base)
|
|
{
|
|
RETURN_SEMA_ERROR(args[1], "Expected both matrices to be of the same type.");
|
|
}
|
|
ArraySize vec_len1 = flat1->array.len;
|
|
ArraySize vec_len2 = flat2->array.len;
|
|
Int128 sum = i128_mult(args[2]->const_expr.ixx.i, args[3]->const_expr.ixx.i);
|
|
args[2]->type = type_int;
|
|
args[3]->type = type_int;
|
|
if (sum.high != 0 || sum.low != vec_len1)
|
|
{
|
|
RETURN_SEMA_ERROR(args[3], "Expected outer row * inner to equal %d.", vec_len1);
|
|
}
|
|
sum = i128_mult(args[3]->const_expr.ixx.i, args[4]->const_expr.ixx.i);
|
|
if (sum.high != 0 || sum.low != vec_len2)
|
|
{
|
|
RETURN_SEMA_ERROR(args[4], "Expected inner * outer col to equal %d.", vec_len2);
|
|
}
|
|
rtype = type_get_vector(flat1->array.base, i128_mult(args[2]->const_expr.ixx.i, args[4]->const_expr.ixx.i).low);
|
|
break;
|
|
case BUILTIN_SAT_SHL:
|
|
case BUILTIN_SAT_SUB:
|
|
case BUILTIN_SAT_ADD:
|
|
case BUILTIN_SAT_MUL:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTLIKE, BA_INTLIKE}, 2)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_REVERSE:
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_VEC}, 1)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_EXPECT:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_BOOLINT, BA_BOOLINT}, 2)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_EXPECT_WITH_PROBABILITY:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_BOOLINT, BA_BOOLINT}, 2)) return false;
|
|
if (!cast_implicit(context, args[2], type_double, false))
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "Expected a 'double', but was %s.", type_quoted_error_string(args[2]->type));
|
|
}
|
|
if (!sema_cast_const(args[2]))
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "This value must be a constant.");
|
|
}
|
|
Real r = args[2]->const_expr.fxx.f;
|
|
if (r < 0 || r > 1) RETURN_SEMA_ERROR(args[2], "The probability must be between 0 and 1.");
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_CEIL:
|
|
case BUILTIN_COPYSIGN:
|
|
case BUILTIN_COS:
|
|
case BUILTIN_EXP:
|
|
case BUILTIN_EXP2:
|
|
case BUILTIN_FLOOR:
|
|
case BUILTIN_LLRINT:
|
|
case BUILTIN_LLROUND:
|
|
case BUILTIN_LOG:
|
|
case BUILTIN_LOG2:
|
|
case BUILTIN_LOG10:
|
|
case BUILTIN_LRINT:
|
|
case BUILTIN_LROUND:
|
|
case BUILTIN_NEARBYINT:
|
|
case BUILTIN_RINT:
|
|
case BUILTIN_ROUND:
|
|
case BUILTIN_ROUNDEVEN:
|
|
case BUILTIN_SIN:
|
|
case BUILTIN_SQRT:
|
|
case BUILTIN_TRUNC:
|
|
ASSERT(arg_count);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_FLOATLIKE, BA_FLOATLIKE, BA_FLOATLIKE},
|
|
arg_count)) return false;
|
|
rtype = args[0]->type;
|
|
if (func == BUILTIN_CEIL && expr_is_const_float(args[0]))
|
|
{
|
|
expr_rewrite_const_float(expr, rtype, ceil(args[0]->const_expr.fxx.f));
|
|
return true;
|
|
}
|
|
break;
|
|
case BUILTIN_FRAMEADDRESS:
|
|
case BUILTIN_RETURNADDRESS:
|
|
ASSERT(arg_count);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTEGER}, arg_count)) return false;
|
|
if (!cast_implicit(context, args[0], type_int, false)) return false;
|
|
if (!expr_is_const_int(args[0])) RETURN_SEMA_ERROR(args[0], "Expected a compile time constant integer.");
|
|
rtype = type_voidptr;
|
|
break;
|
|
case BUILTIN_WASM_MEMORY_SIZE:
|
|
ASSERT(arg_count == 1);
|
|
if (!cast_implicit(context, args[0], type_uint, false)) return false;
|
|
rtype = type_uptr;
|
|
break;
|
|
case BUILTIN_WASM_MEMORY_GROW:
|
|
ASSERT(arg_count == 2);
|
|
if (!cast_implicit(context, args[0], type_uint, false)) return false;
|
|
if (!cast_implicit(context, args[1], type_uptr, false)) return false;
|
|
rtype = type_iptr;
|
|
break;
|
|
case BUILTIN_PREFETCH:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_INTEGER, BA_INTEGER}, 3)) return false;
|
|
for (unsigned i = 1; i < 3; i++)
|
|
{
|
|
if (!sema_cast_const(args[i])) RETURN_SEMA_ERROR(args[i], "A constant value is required.");
|
|
if (!cast_implicit(context, args[i], type_int, false)) return false;
|
|
}
|
|
if (!expr_in_int_range(args[1], 0, 1))
|
|
{
|
|
SEMA_ERROR(args[1], "Expected a value between 0 and 1.");
|
|
return false;
|
|
}
|
|
if (!expr_in_int_range(args[2], 0, 3))
|
|
{
|
|
SEMA_ERROR(args[2], "Expected a value between 0 and 3.");
|
|
return false;
|
|
}
|
|
if (!cast_implicit(context, args[0], type_voidptr, false)) return false;
|
|
rtype = type_void;
|
|
break;
|
|
case BUILTIN_POW:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_FLOATLIKE, BA_FLOATLIKE}, 2)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_POW_INT:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_FLOATLIKE, BA_INTLIKE}, 2)) return false;
|
|
if (!cast_implicit(context, args[1], type_cint, false)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_REDUCE_FMUL:
|
|
case BUILTIN_REDUCE_FADD:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_FLOATVEC, BA_FLOAT}, 2)) return false;
|
|
if (!cast_implicit(context, args[1], args[0]->type->canonical->array.base, false)) return false;
|
|
{
|
|
Expr *arg = args[0];
|
|
args[0] = args[1];
|
|
args[1] = arg;
|
|
}
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_REDUCE_MAX:
|
|
case BUILTIN_REDUCE_MIN:
|
|
{
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_NUMVEC}, 1)) return false;
|
|
rtype = type_get_indexed_type(args[0]->type);
|
|
break;
|
|
}
|
|
case BUILTIN_REDUCE_ADD:
|
|
case BUILTIN_REDUCE_AND:
|
|
case BUILTIN_REDUCE_OR:
|
|
case BUILTIN_REDUCE_XOR:
|
|
case BUILTIN_REDUCE_MUL:
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_BOOLINTVEC}, 1)) return false;
|
|
rtype = type_get_indexed_type(args[0]->type);
|
|
break;
|
|
case BUILTIN_ABS:
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_NUMLIKE}, 1)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_GATHER:
|
|
{
|
|
ASSERT(arg_count == 4);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_PTRVEC, BA_BOOLVEC, BA_VEC, BA_INTEGER}, 4)) return false;
|
|
Type *flat_pointer_vec = type_flatten(args[0]->type);
|
|
Type *flat_passthru_vec = type_flatten(args[2]->type);
|
|
Type *pointer_type = flat_pointer_vec->array.base;
|
|
ArraySize len = flat_pointer_vec->array.len;
|
|
if (pointer_type->pointer->canonical != flat_passthru_vec->array.base->canonical)
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "Expected the vector to have elements of type %s.", type_quoted_error_string(pointer_type->pointer));
|
|
}
|
|
if (len != flat_passthru_vec->array.len)
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "Expected the vector to be %s, not %s.",
|
|
type_quoted_error_string(
|
|
type_get_vector(pointer_type->pointer, len)),
|
|
type_quoted_error_string(args[2]->type));
|
|
}
|
|
if (!sema_check_alignment_expression(context, args[3])) return false;
|
|
if (!sema_expr_is_valid_mask_for_value(context, args[1], args[2])) return false;
|
|
rtype = type_get_vector(pointer_type->pointer, len);
|
|
break;
|
|
}
|
|
case BUILTIN_SCATTER:
|
|
{
|
|
ASSERT(arg_count == 4);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_PTRVEC, BA_VEC, BA_BOOLVEC, BA_INTEGER}, 4)) return false;
|
|
Type *flat_pointer_vec = type_flatten(args[0]->type);
|
|
Type *flat_value_vec = type_flatten(args[1]->type);
|
|
Type *pointer_type = flat_pointer_vec->array.base;
|
|
if (pointer_type->pointer->canonical != flat_value_vec->array.base->canonical)
|
|
{
|
|
RETURN_SEMA_ERROR(args[1], "Expected the vector to have elements of type %s.", type_quoted_error_string(pointer_type->pointer));
|
|
}
|
|
if (flat_pointer_vec->array.len != flat_value_vec->array.len)
|
|
{
|
|
RETURN_SEMA_ERROR(args[1], "Expected the vector to be %s, not %s.",
|
|
type_quoted_error_string(
|
|
type_get_vector(pointer_type->pointer, flat_pointer_vec->array.len)),
|
|
type_quoted_error_string(args[2]->type));
|
|
}
|
|
if (!sema_check_alignment_expression(context, args[3])) return false;
|
|
if (!sema_expr_is_valid_mask_for_value(context, args[2], args[1])) return false;
|
|
rtype = type_void;
|
|
break;
|
|
}
|
|
case BUILTIN_MASKED_LOAD:
|
|
{
|
|
ASSERT(arg_count == 4);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_BOOLVEC, BA_VEC, BA_INTEGER}, 4)) return false;
|
|
Type *pointer_type = args[0]->type;
|
|
if (!type_is_pointer(pointer_type)) RETURN_SEMA_ERROR(args[0], "Expected a direct pointer.");
|
|
if (pointer_type->pointer->canonical != args[2]->type->canonical)
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "Expected the value to be of type '%s'.", type_quoted_error_string(pointer_type->pointer));
|
|
}
|
|
if (!sema_check_alignment_expression(context, args[3])) return false;
|
|
if (!sema_expr_is_valid_mask_for_value(context, args[1], args[2])) return false;
|
|
rtype = pointer_type->pointer;
|
|
break;
|
|
}
|
|
case BUILTIN_MASKED_STORE:
|
|
{
|
|
ASSERT(arg_count == 4);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_VEC, BA_BOOLVEC, BA_INTEGER}, 4)) return false;
|
|
Type *pointer_type = args[0]->type;
|
|
if (!type_is_pointer(pointer_type)) RETURN_SEMA_ERROR(args[0], "Expected a direct pointer.");
|
|
if (pointer_type->pointer->canonical != args[1]->type->canonical)
|
|
{
|
|
RETURN_SEMA_ERROR(args[2], "Expected the value to be of type %s.", type_quoted_error_string(pointer_type->pointer));
|
|
}
|
|
if (!sema_check_alignment_expression(context, args[3])) return false;
|
|
if (!sema_expr_is_valid_mask_for_value(context, args[2], args[1])) return false;
|
|
rtype = type_void;
|
|
break;
|
|
}
|
|
case BUILTIN_MAX:
|
|
case BUILTIN_MIN:
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_NUMLIKE, BA_NUMLIKE}, 2)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 2)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_FMA:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args,
|
|
(BuiltinArg[]) {BA_FLOATLIKE, BA_FLOATLIKE, BA_FLOATLIKE},
|
|
3)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 3)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_FSHL:
|
|
case BUILTIN_FSHR:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTLIKE, BA_INTLIKE, BA_INTLIKE},
|
|
3)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 3)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_FMULADD:
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_FLOAT, BA_FLOAT, BA_FLOAT},
|
|
3)) return false;
|
|
if (!sema_check_builtin_args_match(context, args, 3)) return false;
|
|
rtype = args[0]->type;
|
|
break;
|
|
case BUILTIN_ATOMIC_LOAD:
|
|
{
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_BOOL, BA_INTEGER}, 3)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original == type_voidptr) RETURN_SEMA_ERROR(args[0], "Expected a typed pointer.");
|
|
if (!sema_cast_const(args[1])) RETURN_SEMA_ERROR(args[1], "'is_volatile' must be a compile time constant.");
|
|
if (!sema_cast_const(args[2])) RETURN_SEMA_ERROR(args[2], "Ordering must be a compile time constant.");
|
|
if (!is_valid_atomicity(context, args[2])) return false;
|
|
switch (args[2]->const_expr.ixx.i.low)
|
|
{
|
|
case ATOMIC_ACQUIRE_RELEASE:
|
|
case ATOMIC_RELEASE:
|
|
RETURN_SEMA_ERROR(args[2], "'release' and 'acquire release' are not valid for atomic loads.");
|
|
default:
|
|
break;
|
|
}
|
|
rtype = original->pointer;
|
|
break;
|
|
}
|
|
case BUILTIN_UNALIGNED_LOAD:
|
|
{
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_INTEGER}, 2)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original == type_voidptr) RETURN_SEMA_ERROR(args[0], "Expected a typed pointer.");
|
|
if (!sema_check_alignment_expression(context, args[1])) return false;
|
|
rtype = original->pointer;
|
|
break;
|
|
}
|
|
case BUILTIN_UNALIGNED_STORE:
|
|
{
|
|
ASSERT(arg_count == 3);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER}, 1)) return false;
|
|
if (!sema_check_builtin_args(context, &args[2], (BuiltinArg[]) {BA_INTEGER}, 1)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (!sema_check_alignment_expression(context, args[2])) return false;
|
|
if (original != type_voidptr)
|
|
{
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_VOLATILE_LOAD:
|
|
{
|
|
ASSERT(arg_count == 1);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER}, 1)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original == type_voidptr) RETURN_SEMA_ERROR(args[0], "Expected a typed pointer.");
|
|
rtype = original->pointer;
|
|
break;
|
|
}
|
|
case BUILTIN_VOLATILE_STORE:
|
|
{
|
|
ASSERT(arg_count == 2);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER}, 1)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original != type_voidptr)
|
|
{
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_ATOMIC_FETCH_INC_WRAP:
|
|
case BUILTIN_ATOMIC_FETCH_DEC_WRAP:
|
|
{
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_INTEGER}, 2)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
Type *val = type_flatten(args[1]->type);
|
|
if (!type_is_unsigned(val) )
|
|
{
|
|
RETURN_SEMA_ERROR(args[1], "Expected an unsigned integer.");
|
|
}
|
|
if (original != type_voidptr)
|
|
{
|
|
Type *pointer = type_flatten(original->pointer);
|
|
if (!type_is_unsigned(pointer))
|
|
{
|
|
RETURN_SEMA_ERROR(args[0], "Expected a pointer to an unsigned integer.");
|
|
}
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
if (!sema_cast_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant.");
|
|
if (!sema_cast_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant.");
|
|
if (!is_valid_atomicity(context, args[3])) return false;
|
|
if (args[3]->const_expr.ixx.i.low == ATOMIC_UNORDERED) RETURN_SEMA_ERROR(args[3], "'unordered' is not valid ordering.");
|
|
if (!sema_check_alignment_expression(context, args[4])) return false;
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_ATOMIC_FETCH_AND:
|
|
case BUILTIN_ATOMIC_FETCH_NAND:
|
|
case BUILTIN_ATOMIC_FETCH_OR:
|
|
case BUILTIN_ATOMIC_FETCH_XOR:
|
|
{
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_BOOLINT}, 2)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original != type_voidptr)
|
|
{
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
if (!sema_cast_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant.");
|
|
if (!sema_cast_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant.");
|
|
if (!is_valid_atomicity(context, args[3])) return false;
|
|
if (args[3]->const_expr.ixx.i.low == ATOMIC_UNORDERED) RETURN_SEMA_ERROR(args[3], "'unordered' is not valid ordering.");
|
|
if (!sema_check_alignment_expression(context, args[4])) return false;
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_ATOMIC_FETCH_EXCHANGE:
|
|
{
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER}, 1)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original != type_voidptr)
|
|
{
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
Type *val = type_flatten(args[1]->type);
|
|
if (!type_is_atomic(val)) RETURN_SEMA_ERROR(args[1], "%s exceeds pointer size.", val);
|
|
if (!sema_cast_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant.");
|
|
if (!sema_cast_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant.");
|
|
if (!is_valid_atomicity(context, args[3])) return false;
|
|
if (args[3]->const_expr.ixx.i.low == ATOMIC_UNORDERED) RETURN_SEMA_ERROR(args[3], "'unordered' is not valid ordering.");
|
|
if (!sema_check_alignment_expression(context, args[4])) return false;
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_ATOMIC_FETCH_ADD:
|
|
case BUILTIN_ATOMIC_FETCH_SUB:
|
|
case BUILTIN_ATOMIC_FETCH_MAX:
|
|
case BUILTIN_ATOMIC_FETCH_MIN:
|
|
{
|
|
ASSERT(arg_count == 5);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER, BA_NUM}, 2)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original != type_voidptr)
|
|
{
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
if (!sema_cast_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant.");
|
|
if (!sema_cast_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant.");
|
|
if (!is_valid_atomicity(context, args[3])) return false;
|
|
if (args[3]->const_expr.ixx.i.low == ATOMIC_UNORDERED) RETURN_SEMA_ERROR(args[3], "'unordered' is not valid ordering.");
|
|
if (!sema_check_alignment_expression(context, args[4])) return false;
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_ATOMIC_STORE:
|
|
{
|
|
ASSERT(arg_count == 4);
|
|
if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_POINTER}, 1)) return false;
|
|
if (!sema_check_builtin_args(context, &args[2], (BuiltinArg[]) {BA_BOOL, BA_INTEGER}, 2)) return false;
|
|
Type *original = type_flatten(args[0]->type);
|
|
if (original != type_voidptr)
|
|
{
|
|
if (!cast_implicit(context, args[1], original->pointer, false)) return false;
|
|
}
|
|
if (!sema_cast_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant.");
|
|
if (!sema_cast_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant.");
|
|
if (!is_valid_atomicity(context, args[3])) return false;
|
|
switch (args[3]->const_expr.ixx.i.low)
|
|
{
|
|
case ATOMIC_ACQUIRE_RELEASE:
|
|
case ATOMIC_ACQUIRE:
|
|
RETURN_SEMA_ERROR(args[3], "'acquire' and 'acquire release' are not valid for atomic stores.");
|
|
default:
|
|
break;
|
|
}
|
|
rtype = args[1]->type;
|
|
break;
|
|
}
|
|
case BUILTIN_NONE:
|
|
case BUILTIN_COMPARE_EXCHANGE:
|
|
case BUILTIN_GET_ROUNDING_MODE:
|
|
case BUILTIN_SWIZZLE:
|
|
case BUILTIN_SWIZZLE2:
|
|
case BUILTIN_SYSCLOCK:
|
|
case BUILTIN_TRAP:
|
|
case BUILTIN_BREAKPOINT:
|
|
case BUILTIN_UNREACHABLE:
|
|
case BUILTIN_RND:
|
|
UNREACHABLE
|
|
}
|
|
expr->type = type_add_optional(rtype, optional);
|
|
return true;
|
|
}
|
|
|
|
static inline int builtin_expected_args(BuiltinFunction func)
|
|
{
|
|
switch (func)
|
|
{
|
|
case BUILTIN_SPRINTF:
|
|
case BUILTIN_SYSCALL:
|
|
case BUILTIN_WIDESTRING_16:
|
|
case BUILTIN_WIDESTRING_32:
|
|
return -1;
|
|
case BUILTIN_SWIZZLE:
|
|
return -2;
|
|
case BUILTIN_SWIZZLE2:
|
|
return -3;
|
|
case BUILTIN_GET_ROUNDING_MODE:
|
|
case BUILTIN_SYSCLOCK:
|
|
case BUILTIN_TRAP:
|
|
case BUILTIN_BREAKPOINT:
|
|
case BUILTIN_UNREACHABLE:
|
|
case BUILTIN_RND:
|
|
return 0;
|
|
case BUILTIN_ABS:
|
|
case BUILTIN_BITREVERSE:
|
|
case BUILTIN_BSWAP:
|
|
case BUILTIN_CEIL:
|
|
case BUILTIN_COS:
|
|
case BUILTIN_CTLZ:
|
|
case BUILTIN_CTTZ:
|
|
case BUILTIN_EXACT_NEG:
|
|
case BUILTIN_EXP2:
|
|
case BUILTIN_EXP:
|
|
case BUILTIN_FLOOR:
|
|
case BUILTIN_FRAMEADDRESS:
|
|
case BUILTIN_LLRINT:
|
|
case BUILTIN_LLROUND:
|
|
case BUILTIN_LOG10:
|
|
case BUILTIN_LOG2:
|
|
case BUILTIN_LOG:
|
|
case BUILTIN_LRINT:
|
|
case BUILTIN_LROUND:
|
|
case BUILTIN_NEARBYINT:
|
|
case BUILTIN_POPCOUNT:
|
|
case BUILTIN_REDUCE_ADD:
|
|
case BUILTIN_REDUCE_AND:
|
|
case BUILTIN_REDUCE_MAX:
|
|
case BUILTIN_REDUCE_MIN:
|
|
case BUILTIN_REDUCE_MUL:
|
|
case BUILTIN_REDUCE_OR:
|
|
case BUILTIN_REDUCE_XOR:
|
|
case BUILTIN_RETURNADDRESS:
|
|
case BUILTIN_REVERSE:
|
|
case BUILTIN_RINT:
|
|
case BUILTIN_ROUND:
|
|
case BUILTIN_ROUNDEVEN:
|
|
case BUILTIN_SET_ROUNDING_MODE:
|
|
case BUILTIN_SIN:
|
|
case BUILTIN_SQRT:
|
|
case BUILTIN_STR_HASH:
|
|
case BUILTIN_STR_UPPER:
|
|
case BUILTIN_STR_LOWER:
|
|
case BUILTIN_TRUNC:
|
|
case BUILTIN_VOLATILE_LOAD:
|
|
case BUILTIN_WASM_MEMORY_SIZE:
|
|
return 1;
|
|
case BUILTIN_STR_FIND:
|
|
case BUILTIN_COPYSIGN:
|
|
case BUILTIN_EXACT_ADD:
|
|
case BUILTIN_EXACT_DIV:
|
|
case BUILTIN_EXACT_MOD:
|
|
case BUILTIN_EXACT_MUL:
|
|
case BUILTIN_EXACT_SUB:
|
|
case BUILTIN_EXPECT:
|
|
case BUILTIN_MAX:
|
|
case BUILTIN_MIN:
|
|
case BUILTIN_POW:
|
|
case BUILTIN_POW_INT:
|
|
case BUILTIN_REDUCE_FADD:
|
|
case BUILTIN_REDUCE_FMUL:
|
|
case BUILTIN_SAT_ADD:
|
|
case BUILTIN_SAT_SHL:
|
|
case BUILTIN_SAT_SUB:
|
|
case BUILTIN_SAT_MUL:
|
|
case BUILTIN_VOLATILE_STORE:
|
|
case BUILTIN_VECCOMPNE:
|
|
case BUILTIN_VECCOMPLT:
|
|
case BUILTIN_VECCOMPLE:
|
|
case BUILTIN_VECCOMPGE:
|
|
case BUILTIN_VECCOMPGT:
|
|
case BUILTIN_VECCOMPEQ:
|
|
case BUILTIN_WASM_MEMORY_GROW:
|
|
case BUILTIN_ANY_MAKE:
|
|
case BUILTIN_UNALIGNED_LOAD:
|
|
return 2;
|
|
case BUILTIN_EXPECT_WITH_PROBABILITY:
|
|
case BUILTIN_FMA:
|
|
case BUILTIN_FSHL:
|
|
case BUILTIN_FSHR:
|
|
case BUILTIN_FMULADD:
|
|
case BUILTIN_OVERFLOW_ADD:
|
|
case BUILTIN_OVERFLOW_MUL:
|
|
case BUILTIN_OVERFLOW_SUB:
|
|
case BUILTIN_PREFETCH:
|
|
case BUILTIN_ATOMIC_LOAD:
|
|
case BUILTIN_UNALIGNED_STORE:
|
|
case BUILTIN_SELECT:
|
|
case BUILTIN_MATRIX_TRANSPOSE:
|
|
return 3;
|
|
case BUILTIN_ATOMIC_STORE:
|
|
case BUILTIN_MASKED_STORE:
|
|
case BUILTIN_MASKED_LOAD:
|
|
case BUILTIN_GATHER:
|
|
case BUILTIN_SCATTER:
|
|
return 4;
|
|
case BUILTIN_ATOMIC_FETCH_EXCHANGE:
|
|
case BUILTIN_ATOMIC_FETCH_ADD:
|
|
case BUILTIN_ATOMIC_FETCH_INC_WRAP:
|
|
case BUILTIN_ATOMIC_FETCH_NAND:
|
|
case BUILTIN_ATOMIC_FETCH_AND:
|
|
case BUILTIN_ATOMIC_FETCH_OR:
|
|
case BUILTIN_ATOMIC_FETCH_XOR:
|
|
case BUILTIN_ATOMIC_FETCH_MAX:
|
|
case BUILTIN_ATOMIC_FETCH_MIN:
|
|
case BUILTIN_ATOMIC_FETCH_SUB:
|
|
case BUILTIN_ATOMIC_FETCH_DEC_WRAP:
|
|
case BUILTIN_MATRIX_MUL:
|
|
return 5;
|
|
case BUILTIN_MEMCOPY:
|
|
case BUILTIN_MEMCOPY_INLINE:
|
|
case BUILTIN_MEMMOVE:
|
|
return 6;
|
|
case BUILTIN_MEMSET:
|
|
case BUILTIN_MEMSET_INLINE:
|
|
return 5;
|
|
case BUILTIN_COMPARE_EXCHANGE:
|
|
return 8;
|
|
case BUILTIN_NONE:
|
|
UNREACHABLE
|
|
}
|
|
UNREACHABLE
|
|
}
|