From f6e18ded5b677e1ddc483d286ffd6150e1cf3391 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Fri, 15 Sep 2023 18:07:15 +0200 Subject: [PATCH] Add atomic_fetch builtins. --- releasenotes.md | 1 + src/compiler/compiler_internal.h | 6 ++ src/compiler/enums.h | 10 +++ src/compiler/llvm_codegen_builtins.c | 70 ++++++++++++++++++++ src/compiler/sema_builtins.c | 95 ++++++++++++++++++++++++++++ src/compiler/symtab.c | 10 +++ src/version.h | 2 +- 7 files changed, 193 insertions(+), 1 deletion(-) diff --git a/releasenotes.md b/releasenotes.md index 02b602010..a056c441a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -3,6 +3,7 @@ ## 0.5.0 Change List ### Changes / improvements +- Added `$$atomic_fetch_*` builtins. - vectors may now contain pointers. - `void!` does not convert to `anyfault`. - `$$masked_load` / `$$masked_store` / `$$gather` / `$$scatter` for vector masked load/store. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 83d7a1c6c..063e5b0b8 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2935,6 +2935,12 @@ INLINE bool type_is_number_or_bool(Type *type) return (kind >= TYPE_BOOL) && (kind <= TYPE_FLOAT_LAST); } +INLINE bool type_is_number(Type *type) +{ + DECL_TYPE_KIND_REAL(kind, type); + return (kind >= TYPE_I8) && (kind <= TYPE_FLOAT_LAST); +} + INLINE bool type_is_numeric(Type *type) { RETRY:; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 3ba372081..bff79f011 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -841,6 +841,16 @@ typedef enum BUILTIN_ABS, BUILTIN_ATOMIC_LOAD, BUILTIN_ATOMIC_STORE, + BUILTIN_ATOMIC_FETCH_ADD, + BUILTIN_ATOMIC_FETCH_SUB, + BUILTIN_ATOMIC_FETCH_AND, + BUILTIN_ATOMIC_FETCH_NAND, + BUILTIN_ATOMIC_FETCH_OR, + BUILTIN_ATOMIC_FETCH_XOR, + BUILTIN_ATOMIC_FETCH_MAX, + BUILTIN_ATOMIC_FETCH_MIN, + BUILTIN_ATOMIC_FETCH_INC_WRAP, + BUILTIN_ATOMIC_FETCH_DEC_WRAP, BUILTIN_BITREVERSE, BUILTIN_BSWAP, BUILTIN_CEIL, diff --git a/src/compiler/llvm_codegen_builtins.c b/src/compiler/llvm_codegen_builtins.c index fe186709d..217a51242 100644 --- a/src/compiler/llvm_codegen_builtins.c +++ b/src/compiler/llvm_codegen_builtins.c @@ -179,6 +179,64 @@ INLINE void llvm_emit_atomic_store(GenContext *c, BEValue *result_value, Expr *e } } +INLINE void llvm_emit_atomic_fetch(GenContext *c, BuiltinFunction func, BEValue *result_value, Expr *expr) +{ + BEValue value; + llvm_emit_expr(c, &value, expr->call_expr.arguments[0]); + llvm_emit_expr(c, result_value, expr->call_expr.arguments[1]); + llvm_value_rvalue(c, &value); + bool is_float = type_is_float(result_value->type); + bool is_unsigned = !is_float && type_is_unsigned(result_value->type); + LLVMAtomicRMWBinOp op; + static LLVMAtomicRMWBinOp LLVMAtomicRMWBinOpUIncWrap = LLVMAtomicRMWBinOpFMin + 1; + static LLVMAtomicRMWBinOp LLVMAtomicRMWBinOpUDecWrap = LLVMAtomicRMWBinOpFMin + 2; + switch (func) + { + case BUILTIN_ATOMIC_FETCH_ADD: + op = is_float ? LLVMAtomicRMWBinOpFAdd : LLVMAtomicRMWBinOpAdd; + break; + case BUILTIN_ATOMIC_FETCH_SUB: + op = is_float ? LLVMAtomicRMWBinOpFAdd : LLVMAtomicRMWBinOpAdd; + break; + case BUILTIN_ATOMIC_FETCH_MAX: + op = is_float ? LLVMAtomicRMWBinOpFMax : (is_unsigned ? LLVMAtomicRMWBinOpUMax : LLVMAtomicRMWBinOpMax); + break; + case BUILTIN_ATOMIC_FETCH_MIN: + op = is_float ? LLVMAtomicRMWBinOpFMin : (is_unsigned ? LLVMAtomicRMWBinOpUMin : LLVMAtomicRMWBinOpMin); + break; + case BUILTIN_ATOMIC_FETCH_OR: + op = LLVMAtomicRMWBinOpOr; + break; + case BUILTIN_ATOMIC_FETCH_XOR: + op = LLVMAtomicRMWBinOpXor; + break; + case BUILTIN_ATOMIC_FETCH_NAND: + op = LLVMAtomicRMWBinOpNand; + break; + case BUILTIN_ATOMIC_FETCH_AND: + op = LLVMAtomicRMWBinOpAnd; + break; + case BUILTIN_ATOMIC_FETCH_INC_WRAP: + op = LLVMAtomicRMWBinOpUIncWrap; + break; + case BUILTIN_ATOMIC_FETCH_DEC_WRAP: + op = LLVMAtomicRMWBinOpUDecWrap; + break; + default: + UNREACHABLE + } + LLVMValueRef res = LLVMBuildAtomicRMW(c->builder, op, value.value, llvm_load_value(c, result_value), + llvm_atomic_ordering(expr->call_expr.arguments[3]->const_expr.ixx.i.low), + false); + if (expr->call_expr.arguments[2]->const_expr.b) LLVMSetVolatile(res, true); + uint64_t alignment = expr->call_expr.arguments[3]->const_expr.ixx.i.low; + if (alignment) + { + LLVMSetAlignment(res, alignment); + } + llvm_value_set(result_value, res, result_value->type); +} + INLINE void llvm_emit_atomic_load(GenContext *c, BEValue *result_value, Expr *expr) { llvm_emit_expr(c, result_value, expr->call_expr.arguments[0]); @@ -747,6 +805,18 @@ void llvm_emit_builtin_call(GenContext *c, BEValue *result_value, Expr *expr) case BUILTIN_ATOMIC_STORE: llvm_emit_atomic_store(c, result_value, expr); return; + 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: + llvm_emit_atomic_fetch(c, func, result_value, expr); + return; case BUILTIN_ATOMIC_LOAD: llvm_emit_atomic_load(c, result_value, expr); return; diff --git a/src/compiler/sema_builtins.c b/src/compiler/sema_builtins.c index 45f3a3c9d..ac958049a 100644 --- a/src/compiler/sema_builtins.c +++ b/src/compiler/sema_builtins.c @@ -23,6 +23,7 @@ typedef enum BA_VEC, BA_NUMVEC, BA_PTRVEC, + BA_NUM, } BuiltinArg; static bool sema_check_builtin_args_match(Expr **args, size_t arg_len); @@ -107,6 +108,9 @@ static bool sema_check_builtin_args(Expr **args, BuiltinArg *arg_type, size_t ar 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_NUMLIKE: if (type_flat_is_numlike(type)) continue; RETURN_SEMA_ERROR(arg, "Expected a number or vector."); @@ -719,6 +723,86 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr) 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(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)) return false; + } + if (!expr_is_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant."); + if (!expr_is_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant."); + if (!is_valid_atomicity(args[3])) return false; + switch (args[3]->const_expr.ixx.i.low) + { + case 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(args, (BuiltinArg[]) { BA_POINTER, BA_INTEGER }, 2)) return false; + Type *original = type_flatten(args[0]->type); + if (original != type_voidptr) + { + if (!cast_implicit(context, args[1], original->pointer)) return false; + } + if (!expr_is_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant."); + if (!expr_is_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant."); + if (!is_valid_atomicity(args[3])) return false; + switch (args[3]->const_expr.ixx.i.low) + { + case 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(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)) return false; + } + if (!expr_is_const(args[2])) RETURN_SEMA_ERROR(args[2], "'is_volatile' must be a compile time constant."); + if (!expr_is_const(args[3])) RETURN_SEMA_ERROR(args[3], "Ordering must be a compile time constant."); + if (!is_valid_atomicity(args[3])) return false; + switch (args[3]->const_expr.ixx.i.low) + { + case 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); @@ -855,6 +939,17 @@ static inline int builtin_expected_args(BuiltinFunction func) case BUILTIN_GATHER: case BUILTIN_SCATTER: return 4; + 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: + return 5; case BUILTIN_MEMCOPY: case BUILTIN_MEMCOPY_INLINE: case BUILTIN_MEMMOVE: diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index e007ee00a..417770f20 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -199,6 +199,16 @@ void symtab_init(uint32_t capacity) builtin_list[BUILTIN_ABS] = KW_DEF("abs"); builtin_list[BUILTIN_ATOMIC_LOAD] = KW_DEF("atomic_load"); builtin_list[BUILTIN_ATOMIC_STORE] = KW_DEF("atomic_store"); + builtin_list[BUILTIN_ATOMIC_FETCH_ADD] = KW_DEF("atomic_fetch_add"); + builtin_list[BUILTIN_ATOMIC_FETCH_SUB] = KW_DEF("atomic_fetch_sub"); + builtin_list[BUILTIN_ATOMIC_FETCH_MAX] = KW_DEF("atomic_fetch_max"); + builtin_list[BUILTIN_ATOMIC_FETCH_MIN] = KW_DEF("atomic_fetch_min"); + builtin_list[BUILTIN_ATOMIC_FETCH_AND] = KW_DEF("atomic_fetch_and"); + builtin_list[BUILTIN_ATOMIC_FETCH_NAND] = KW_DEF("atomic_fetch_nand"); + builtin_list[BUILTIN_ATOMIC_FETCH_OR] = KW_DEF("atomic_fetch_or"); + builtin_list[BUILTIN_ATOMIC_FETCH_XOR] = KW_DEF("atomic_fetch_xor"); + builtin_list[BUILTIN_ATOMIC_FETCH_INC_WRAP] = KW_DEF("atomic_fetch_inc_wrap"); + builtin_list[BUILTIN_ATOMIC_FETCH_DEC_WRAP] = KW_DEF("atomic_fetch_dec_wrap"); builtin_list[BUILTIN_BITREVERSE] = KW_DEF("bitreverse"); builtin_list[BUILTIN_BSWAP] = KW_DEF("bswap"); builtin_list[BUILTIN_CEIL] = KW_DEF("ceil"); diff --git a/src/version.h b/src/version.h index f659bd71f..f82b0e51a 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.646" \ No newline at end of file +#define COMPILER_VERSION "0.4.647" \ No newline at end of file