- $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)`.
- Supplemental `roundeven` has a normal implementation.
This commit is contained in:
Christoffer Lerno
2025-08-29 11:23:39 +02:00
parent 0178a44b3c
commit ca2fabc9f9
7 changed files with 147 additions and 53 deletions

View File

@@ -746,39 +746,39 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
<* <*
@param $Type : "The type to allocate" @param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require $vacount < 2 : "Too many arguments." @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @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." @return "A pointer to data of type $Type."
*> *>
macro new($Type, ...) @nodiscard macro new($Type, #init = ...) @nodiscard @safemacro
{ {
$if $vacount == 0: $if $defined(#init):
return ($Type*)calloc($Type.sizeof);
$else
$Type* val = malloc($Type.sizeof); $Type* val = malloc($Type.sizeof);
*val = $vaexpr[0]; *val = #init;
return val; return val;
$else
return ($Type*)calloc($Type.sizeof);
$endif $endif
} }
<* <*
@param $Type : "The type to allocate" @param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation" @param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require $vacount < 2 : "Too many arguments." @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type"
@return "A pointer to data of type $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: $if $defined(#init):
return ($Type*)calloc($Type.sizeof + padding);
$else
$Type* val = malloc($Type.sizeof + padding); $Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0]; *val = #init;
return val; return val;
$else
return ($Type*)calloc($Type.sizeof + padding);
$endif $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. exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@param $Type : "The type to allocate" @param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require $vacount < 2 : "Too many arguments." @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type"
@return "A pointer to data of type $Type with the proper alignment" @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: $if $defined(#init):
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof); $Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = $vaexpr[0]; *val = #init;
return val; return val;
$else
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$endif $endif
} }
@@ -841,38 +841,38 @@ macro alloc_aligned($Type) @nodiscard
<* <*
@param $Type : "The type to allocate" @param $Type : "The type to allocate"
@param #init : "The optional initializer"
@require $vacount < 2 : "Too many arguments." @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type"
@return "A pointer to temporary data of type $Type." @return "A pointer to temporary data of type $Type."
*> *>
macro tnew($Type, ...) @nodiscard macro tnew($Type, #init = ...) @nodiscard @safemacro
{ {
$if $vacount == 0: $if $defined(#init):
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$else
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline; $Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = $vaexpr[0]; *val = #init;
return val; return val;
$else
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$endif $endif
} }
<* <*
@param $Type : "The type to allocate" @param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation" @param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require $vacount < 2 : "Too many arguments." @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $vacount == 0 ||| $defined($Type a = $vaexpr[0]) : "The second argument must be an initializer for the type"
@return "A pointer to temporary data of type $Type with added padding at the end." @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: $if $defined(#init):
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$else
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline; $Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = $vaexpr[0]; *val = #init;
return val; return val;
$else
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$endif $endif
} }

View File

@@ -31,7 +31,8 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip
uint u = bitcast(x, uint); uint u = bitcast(x, uint);
int e = (u >> 23) & 0xff; int e = (u >> 23) & 0xff;
if (e >= 0x7f + 23) return x; if (e >= 0x7f + 23) return x;
if (u >> 31) x = -x; bool signed = u >> 31 != 0;
if (signed) x = -x;
if (e < 0x7f - 1) if (e < 0x7f - 1)
{ {
force_eval_add(x, TOINTF); force_eval_add(x, TOINTF);
@@ -47,7 +48,7 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip
default: default:
y = y + x; y = y + x;
} }
if (u >> 31) y = -y; if (signed) y = -y;
return y; return y;
} }

View File

@@ -1,15 +1,78 @@
module std::math::math_rt; 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 $typeof(x) temp @noinit;
return math::round(f / 2) * 2; @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 ulong u = bitcast(x, ulong);
return math::round(d / 2) * 2; 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 fn double __powidf2(double a, int b) @extern("__powidf2") @weak @nostrip

View File

@@ -33,6 +33,8 @@
- Added `$kindof` compile time function. - Added `$kindof` compile time function.
- Deprecated `@typekind` macro in favour of `$kindof`. - Deprecated `@typekind` macro in favour of `$kindof`.
- Deprecated `@typeis` macro in favour of `$typeof(#foo) == int`. - 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 ### Fixes
- List.remove_at would incorrectly trigger ASAN. - List.remove_at would incorrectly trigger ASAN.
@@ -104,6 +106,7 @@
- Added `String.trim_charset`. - Added `String.trim_charset`.
- Added array `@reduce`, `@filter`, `@any`, `@all`, `@sum`, `@product`, and `@indices_of` macros. - Added array `@reduce`, `@filter`, `@any`, `@all`, `@sum`, `@product`, and `@indices_of` macros.
- `String.bformat` has reduced overhead. - `String.bformat` has reduced overhead.
- Supplemental `roundeven` has a normal implementation.
## 0.7.4 Change list ## 0.7.4 Change list

View File

@@ -6218,6 +6218,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr
{ {
// Skip vararg // Skip vararg
if (!val) continue; 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. // In case we have a constant, we never do an emit. The value is already folded.
switch (val->var.kind) switch (val->var.kind)
{ {

View File

@@ -1679,7 +1679,19 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i
{ {
if (try_consume(c, TOKEN_EQ)) 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) if (ellipsis)

View File

@@ -1643,7 +1643,15 @@ INLINE bool sema_set_default_argument(SemaContext *context, CalledDecl *callee,
bool *no_match_ref, Expr **expr_ref, bool *optional) bool *no_match_ref, Expr **expr_ref, bool *optional)
{ {
Expr *init_expr = param->var.init_expr; 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); Expr *arg = copy_expr_single(init_expr);
bool parameter_checked = false; bool parameter_checked = false;
if (arg->resolve_status != RESOLVE_DONE) if (arg->resolve_status != RESOLVE_DONE)
@@ -2031,8 +2039,9 @@ SPLAT_NORMAL:;
{ {
if (i == vaarg_index) continue; if (i == vaarg_index) continue;
if (actual_args[i]) continue; if (actual_args[i]) continue;
// Argument missing, that's bad.
Decl *param = params[i]; Decl *param = params[i];
if (param->var.no_init) continue; // Macro empty args
// Argument missing, that's bad.
if (no_match_ref) if (no_match_ref)
{ {
*no_match_ref = true; *no_match_ref = true;
@@ -2118,6 +2127,7 @@ NEXT_FLAG:
} }
if (idx == vacount) goto TOO_FEW_ARGUMENTS; if (idx == vacount) goto TOO_FEW_ARGUMENTS;
expr = vaargs[idx]; expr = vaargs[idx];
if (!expr) goto TOO_FEW_ARGUMENTS;
Type *type = sema_get_va_type(context, expr, variadic); Type *type = sema_get_va_type(context, expr, variadic);
if (!type_ok(type)) return false; if (!type_ok(type)) return false;
@@ -2132,6 +2142,7 @@ NEXT_FLAG:
c = data[i]; c = data[i];
if (++idx == vacount) goto TOO_FEW_ARGUMENTS; if (++idx == vacount) goto TOO_FEW_ARGUMENTS;
expr = vaargs[idx]; expr = vaargs[idx];
if (!expr) goto TOO_FEW_ARGUMENTS;
type = sema_get_va_type(context, expr, variadic); type = sema_get_va_type(context, expr, variadic);
if (!type_ok(type)) return false; if (!type_ok(type)) return false;
} }
@@ -2157,6 +2168,7 @@ NEXT_FLAG:
c = data[i]; c = data[i];
if (++idx == vacount) goto TOO_FEW_ARGUMENTS; if (++idx == vacount) goto TOO_FEW_ARGUMENTS;
expr = vaargs[idx]; expr = vaargs[idx];
if (!expr) goto TOO_FEW_ARGUMENTS;
type = sema_get_va_type(context, expr, variadic); type = sema_get_va_type(context, expr, variadic);
if (!type_ok(type)) return false; 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]; param->var.init_expr = args[i];
if (!args[i]) continue;
// Lazy arguments doesn't affect optional arg. // Lazy arguments doesn't affect optional arg.
if (param->var.kind == VARDECL_PARAM_EXPR) continue; if (param->var.kind == VARDECL_PARAM_EXPR) continue;
has_optional_arg = has_optional_arg || IS_OPTIONAL(args[i]); 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 // Skip raw vararg
if (!param) continue; if (!param) continue;
if (param->var.no_init && param->var.defaulted) continue;
if (!sema_add_local(&macro_context, param)) goto EXIT_FAIL; if (!sema_add_local(&macro_context, param)) goto EXIT_FAIL;
if (param->var.init_expr) if (param->var.init_expr)
{ {
@@ -10345,6 +10359,13 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr
success = decl != NULL; success = decl != NULL;
break; 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: case EXPR_COMPILER_CONST:
success = sema_expr_analyse_compiler_const(active_context, main_expr, false); success = sema_expr_analyse_compiler_const(active_context, main_expr, false);
break; break;
@@ -10373,13 +10394,6 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr
success = eval != NULL; success = eval != NULL;
break; 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: case EXPR_SUBSCRIPT:
{ {
if (!sema_expr_analyse_subscript(active_context, main_expr, CHECK_VALUE, true)) if (!sema_expr_analyse_subscript(active_context, main_expr, CHECK_VALUE, true))