diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index 8fa1c2683..b537fdd5c 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -746,39 +746,39 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard <* @param $Type : "The type to allocate" + @param #init : "The optional initializer" - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @return "A pointer to data of type $Type." *> -macro new($Type, ...) @nodiscard +macro new($Type, #init = ...) @nodiscard @safemacro { - $if $vacount == 0: - return ($Type*)calloc($Type.sizeof); - $else + $if $defined(#init): $Type* val = malloc($Type.sizeof); - *val = $vaexpr[0]; + *val = #init; return val; + $else + return ($Type*)calloc($Type.sizeof); $endif } <* @param $Type : "The type to allocate" @param padding : "The padding to add after the allocation" + @param #init : "The optional initializer" - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to data of type $Type." *> -macro new_with_padding($Type, usz padding, ...) @nodiscard +macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro { - $if $vacount == 0: - return ($Type*)calloc($Type.sizeof + padding); - $else + $if $defined(#init): $Type* val = malloc($Type.sizeof + padding); - *val = $vaexpr[0]; + *val = #init; return val; + $else + return ($Type*)calloc($Type.sizeof + padding); $endif } @@ -787,19 +787,19 @@ macro new_with_padding($Type, usz padding, ...) @nodiscard exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. @param $Type : "The type to allocate" + @param #init : "The optional initializer" - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to data of type $Type with the proper alignment" *> -macro new_aligned($Type, ...) @nodiscard +macro new_aligned($Type, #init = ...) @nodiscard @safemacro { - $if $vacount == 0: - return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof); - $else + $if $defined(#init): $Type* val = malloc_aligned($Type.sizeof, $Type.alignof); - *val = $vaexpr[0]; + *val = #init; return val; + $else + return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof); $endif } @@ -841,38 +841,38 @@ macro alloc_aligned($Type) @nodiscard <* @param $Type : "The type to allocate" + @param #init : "The optional initializer" - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to temporary data of type $Type." *> -macro tnew($Type, ...) @nodiscard +macro tnew($Type, #init = ...) @nodiscard @safemacro { - $if $vacount == 0: - return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline; - $else + $if $defined(#init): $Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline; - *val = $vaexpr[0]; + *val = #init; return val; + $else + return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline; $endif } <* @param $Type : "The type to allocate" @param padding : "The padding to add after the allocation" + @param #init : "The optional initializer" -@require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to temporary data of type $Type with added padding at the end." *> -macro temp_with_padding($Type, usz padding, ...) @nodiscard +macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro { - $if $vacount == 0: - return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline; - $else + $if $defined(#init): $Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline; - *val = $vaexpr[0]; + *val = #init; return val; + $else + return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline; $endif } diff --git a/lib/std/math/math_nolibc/round.c3 b/lib/std/math/math_nolibc/round.c3 index 31ea82e58..75c9872c6 100644 --- a/lib/std/math/math_nolibc/round.c3 +++ b/lib/std/math/math_nolibc/round.c3 @@ -31,7 +31,8 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip uint u = bitcast(x, uint); int e = (u >> 23) & 0xff; if (e >= 0x7f + 23) return x; - if (u >> 31) x = -x; + bool signed = u >> 31 != 0; + if (signed) x = -x; if (e < 0x7f - 1) { force_eval_add(x, TOINTF); @@ -47,7 +48,7 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip default: y = y + x; } - if (u >> 31) y = -y; + if (signed) y = -y; return y; } diff --git a/lib/std/math/runtime/math_supplemental.c3 b/lib/std/math/runtime/math_supplemental.c3 index 4093256fe..2842308f4 100644 --- a/lib/std/math/runtime/math_supplemental.c3 +++ b/lib/std/math/runtime/math_supplemental.c3 @@ -1,15 +1,78 @@ module std::math::math_rt; -fn float __roundevenf(float f) @extern("roundevenf") @weak @nostrip +const double TOINT = 1 / math::DOUBLE_EPSILON; +const float TOINTF = (float)(1 / math::FLOAT_EPSILON); +macro force_eval_add(x, v) { - // Slow implementation - return math::round(f / 2) * 2; + $typeof(x) temp @noinit; + @volatile_store(temp, x + v); } -fn double __roundeven(double d) @extern("roundeven") @weak @nostrip +fn double __roundeven(double x) @extern("roundeven") @weak @nostrip { - // Slow implementation - return math::round(d / 2) * 2; + ulong u = bitcast(x, ulong); + int e = (int)((u >> 52) & 0x7ff); + if (e >= 0x3ff + 52) return x; + bool signed = u >> 63 != 0; + if (signed) x = -x; + if (e < 0x3ff - 1) + { + /* raise inexact if x!=0 */ + force_eval_add(x, TOINT); + return 0 * x; + } + double y = (x + TOINT) - TOINT - x; + switch + { + case y > 0.5: + y = y + x - 1; + case y < -0.5: + y = y + x + 1; + case y == 0.5 || y == -0.5: + if (u & 1) + { + y = x + (y > 0 ? y + 1 : y - 1); + break; + } + nextcase; + default: + y = y + x; + } + if (signed) y = -y; + return y; +} + +fn float __roundevenf(float x) @extern("roundevenf") @weak @nostrip +{ + uint u = bitcast(x, uint); + int e = (u >> 23) & 0xff; + if (e >= 0x7f + 23) return x; + bool signed = u >> 31 != 0; + if (signed) x = -x; + if (e < 0x7f - 1) + { + force_eval_add(x, TOINTF); + return 0 * x; + } + float y = (x + TOINTF) - TOINTF - x; + switch + { + case y > 0.5f: + y = y + x - 1; + case y < -0.5f: + y = y + x + 1; + case y == 0.5f || y == -0.5f: + if (u & 1) + { + y = x + (y > 0.0f ? y + 1.0f : y - 1.0f); + break; + } + nextcase; + default: + y = y + x; + } + if (signed) y = -y; + return y; } fn double __powidf2(double a, int b) @extern("__powidf2") @weak @nostrip diff --git a/releasenotes.md b/releasenotes.md index 3c8de2134..0ea3e7e3a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -33,6 +33,8 @@ - Added `$kindof` compile time function. - Deprecated `@typekind` macro in favour of `$kindof`. - Deprecated `@typeis` macro in favour of `$typeof(#foo) == int`. +- `$defined(#hash)` will not check the internal expression, just that `#hash` exists. +- Added optional macro arguments using `macro foo(int x = ...)` which can be checked using `$defined(x)`. ### Fixes - List.remove_at would incorrectly trigger ASAN. @@ -104,6 +106,7 @@ - Added `String.trim_charset`. - Added array `@reduce`, `@filter`, `@any`, `@all`, `@sum`, `@product`, and `@indices_of` macros. - `String.bformat` has reduced overhead. +- Supplemental `roundeven` has a normal implementation. ## 0.7.4 Change list diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 2c2c82197..ee40c7423 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -6218,6 +6218,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr { // Skip vararg if (!val) continue; + if (val->var.no_init && val->var.defaulted) continue; // In case we have a constant, we never do an emit. The value is already folded. switch (val->var.kind) { diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 8275474ea..6d7099fdb 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -1679,7 +1679,19 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i { if (try_consume(c, TOKEN_EQ)) { - if (!parse_decl_initializer(c, param)) return poisoned_decl; + if (try_consume(c, TOKEN_ELLIPSIS)) + { + if (parse_kind != PARAM_PARSE_MACRO) + { + PRINT_ERROR_HERE("Optional arguments with '...' is only allowed as macro arguments."); + return poisoned_decl; + } + param->var.no_init = true; + } + else + { + if (!parse_decl_initializer(c, param)) return poisoned_decl; + } } } if (ellipsis) diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 028c956a0..4fa0f30eb 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -1643,7 +1643,15 @@ INLINE bool sema_set_default_argument(SemaContext *context, CalledDecl *callee, bool *no_match_ref, Expr **expr_ref, bool *optional) { Expr *init_expr = param->var.init_expr; - if (!init_expr) return true; + if (!init_expr) + { + // Handle foo = ... macro init + if (param->var.no_init) + { + param->var.defaulted = true; + } + return true; + } Expr *arg = copy_expr_single(init_expr); bool parameter_checked = false; if (arg->resolve_status != RESOLVE_DONE) @@ -2031,8 +2039,9 @@ SPLAT_NORMAL:; { if (i == vaarg_index) continue; if (actual_args[i]) continue; - // Argument missing, that's bad. Decl *param = params[i]; + if (param->var.no_init) continue; // Macro empty args + // Argument missing, that's bad. if (no_match_ref) { *no_match_ref = true; @@ -2118,6 +2127,7 @@ NEXT_FLAG: } if (idx == vacount) goto TOO_FEW_ARGUMENTS; expr = vaargs[idx]; + if (!expr) goto TOO_FEW_ARGUMENTS; Type *type = sema_get_va_type(context, expr, variadic); if (!type_ok(type)) return false; @@ -2132,6 +2142,7 @@ NEXT_FLAG: c = data[i]; if (++idx == vacount) goto TOO_FEW_ARGUMENTS; expr = vaargs[idx]; + if (!expr) goto TOO_FEW_ARGUMENTS; type = sema_get_va_type(context, expr, variadic); if (!type_ok(type)) return false; } @@ -2157,6 +2168,7 @@ NEXT_FLAG: c = data[i]; if (++idx == vacount) goto TOO_FEW_ARGUMENTS; expr = vaargs[idx]; + if (!expr) goto TOO_FEW_ARGUMENTS; type = sema_get_va_type(context, expr, variadic); if (!type_ok(type)) return false; } @@ -2675,6 +2687,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s } } param->var.init_expr = args[i]; + if (!args[i]) continue; // Lazy arguments doesn't affect optional arg. if (param->var.kind == VARDECL_PARAM_EXPR) continue; has_optional_arg = has_optional_arg || IS_OPTIONAL(args[i]); @@ -2794,6 +2807,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s { // Skip raw vararg if (!param) continue; + if (param->var.no_init && param->var.defaulted) continue; if (!sema_add_local(¯o_context, param)) goto EXIT_FAIL; if (param->var.init_expr) { @@ -10345,6 +10359,13 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr success = decl != NULL; break; } + case EXPR_HASH_IDENT: + { + Decl *decl = sema_find_symbol(active_context, main_expr->hash_ident_expr.identifier); + if (!decl_ok(decl)) goto FAIL; + success = decl != NULL; + break; + } case EXPR_COMPILER_CONST: success = sema_expr_analyse_compiler_const(active_context, main_expr, false); break; @@ -10373,13 +10394,6 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr success = eval != NULL; break; } - case EXPR_HASH_IDENT: - { - Decl *decl = sema_resolve_symbol(active_context, main_expr->hash_ident_expr.identifier, NULL, main_expr->span); - if (!decl) goto FAIL; - main_expr = copy_expr_single(decl->var.init_expr); - goto RETRY; - } case EXPR_SUBSCRIPT: { if (!sema_expr_analyse_subscript(active_context, main_expr, CHECK_VALUE, true))