Add #field_align(N)

It sets the minimum alignment for the fields within a struct. This cannot be used with `#packed`, but can be used with `#align(N)`.
If `#align(N)` is less than `#field_align(N)`, then a warning will be printed.
This commit is contained in:
gingerBill
2024-01-28 17:33:29 +00:00
parent c1d853a24e
commit 68df35b378
6 changed files with 64 additions and 24 deletions
+1
View File
@@ -768,6 +768,7 @@ Struct_Type :: struct {
tok_pos: tokenizer.Pos, tok_pos: tokenizer.Pos,
poly_params: ^Field_List, poly_params: ^Field_List,
align: ^Expr, align: ^Expr,
field_align: ^Expr,
where_token: tokenizer.Token, where_token: tokenizer.Token,
where_clauses: []^Expr, where_clauses: []^Expr,
is_packed: bool, is_packed: bool,
+7
View File
@@ -2547,6 +2547,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
poly_params: ^ast.Field_List poly_params: ^ast.Field_List
align: ^ast.Expr align: ^ast.Expr
field_align: ^ast.Expr
is_packed: bool is_packed: bool
is_raw_union: bool is_raw_union: bool
is_no_copy: bool is_no_copy: bool
@@ -2578,6 +2579,11 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
} }
align = parse_expr(p, true) align = parse_expr(p, true)
case "field_align":
if field_align != nil {
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
}
field_align = parse_expr(p, true)
case "raw_union": case "raw_union":
if is_raw_union { if is_raw_union {
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
@@ -2620,6 +2626,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
st := ast.new(ast.Struct_Type, tok.pos, end_pos(close)) st := ast.new(ast.Struct_Type, tok.pos, end_pos(close))
st.poly_params = poly_params st.poly_params = poly_params
st.align = align st.align = align
st.field_align = field_align
st.is_packed = is_packed st.is_packed = is_packed
st.is_raw_union = is_raw_union st.is_raw_union = is_raw_union
st.is_no_copy = is_no_copy st.is_no_copy = is_no_copy
+25 -15
View File
@@ -219,13 +219,13 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entit
} }
gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_) { gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_, char const *msg) {
GB_ASSERT(align_ != nullptr); GB_ASSERT(align_ != nullptr);
Operand o = {}; Operand o = {};
check_expr(ctx, &o, node); check_expr(ctx, &o, node);
if (o.mode != Addressing_Constant) { if (o.mode != Addressing_Constant) {
if (o.mode != Addressing_Invalid) { if (o.mode != Addressing_Invalid) {
error(node, "#align must be a constant"); error(node, "#%s must be a constant", msg);
} }
return false; return false;
} }
@@ -237,13 +237,13 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_)
if (v.used > 1) { if (v.used > 1) {
gbAllocator a = heap_allocator(); gbAllocator a = heap_allocator();
String str = big_int_to_string(a, &v); String str = big_int_to_string(a, &v);
error(node, "#align too large, %.*s", LIT(str)); error(node, "#%s too large, %.*s", msg, LIT(str));
gb_free(a, str.text); gb_free(a, str.text);
return false; return false;
} }
i64 align = big_int_to_i64(&v); i64 align = big_int_to_i64(&v);
if (align < 1 || !gb_is_power_of_two(cast(isize)align)) { if (align < 1 || !gb_is_power_of_two(cast(isize)align)) {
error(node, "#align must be a power of 2, got %lld", align); error(node, "#%s must be a power of 2, got %lld", msg, align);
return false; return false;
} }
*align_ = align; *align_ = align;
@@ -251,7 +251,7 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_)
} }
} }
error(node, "#align must be an integer"); error(node, "#%s must be an integer", msg);
return false; return false;
} }
@@ -645,16 +645,26 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context); check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context);
} }
if (st->align != nullptr) { #define ST_ALIGN(_name) if (st->_name != nullptr) { \
if (st->is_packed) { if (st->is_packed) { \
syntax_error(st->align, "'#align' cannot be applied with '#packed'"); syntax_error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \
return; return; \
} } \
i64 custom_align = 1; i64 align = 1; \
if (check_custom_align(ctx, st->align, &custom_align)) { if (check_custom_align(ctx, st->_name, &align, #_name)) { \
struct_type->Struct.custom_align = custom_align; struct_type->Struct.custom_##_name = align; \
} } \
} }
ST_ALIGN(field_align);
ST_ALIGN(align);
if (struct_type->Struct.custom_align < struct_type->Struct.custom_field_align) {
warning(st->align, "#align(%lld) is defined to be less than #field_name(%lld)",
cast(long long)struct_type->Struct.custom_align,
cast(long long)struct_type->Struct.custom_field_align);
}
#undef ST_ALIGN
} }
gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array<Operand> *poly_operands, Type *named_type, Type *original_type_for_poly) { gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array<Operand> *poly_operands, Type *named_type, Type *original_type_for_poly) {
GB_ASSERT(is_type_union(union_type)); GB_ASSERT(is_type_union(union_type));
@@ -746,7 +756,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
if (ut->align != nullptr) { if (ut->align != nullptr) {
i64 custom_align = 1; i64 custom_align = 1;
if (check_custom_align(ctx, ut->align, &custom_align)) { if (check_custom_align(ctx, ut->align, &custom_align, "align")) {
if (variants.count == 0) { if (variants.count == 0) {
error(ut->align, "An empty union cannot have a custom alignment"); error(ut->align, "An empty union cannot have a custom alignment");
} else { } else {
+20 -5
View File
@@ -383,10 +383,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) {
n->DynamicArrayType.elem = clone_ast(n->DynamicArrayType.elem, f); n->DynamicArrayType.elem = clone_ast(n->DynamicArrayType.elem, f);
break; break;
case Ast_StructType: case Ast_StructType:
n->StructType.fields = clone_ast_array(n->StructType.fields, f); n->StructType.fields = clone_ast_array(n->StructType.fields, f);
n->StructType.polymorphic_params = clone_ast(n->StructType.polymorphic_params, f); n->StructType.polymorphic_params = clone_ast(n->StructType.polymorphic_params, f);
n->StructType.align = clone_ast(n->StructType.align, f); n->StructType.align = clone_ast(n->StructType.align, f);
n->StructType.where_clauses = clone_ast_array(n->StructType.where_clauses, f); n->StructType.field_align = clone_ast(n->StructType.field_align, f);
n->StructType.where_clauses = clone_ast_array(n->StructType.where_clauses, f);
break; break;
case Ast_UnionType: case Ast_UnionType:
n->UnionType.variants = clone_ast_array(n->UnionType.variants, f); n->UnionType.variants = clone_ast_array(n->UnionType.variants, f);
@@ -1125,7 +1126,7 @@ gb_internal Ast *ast_dynamic_array_type(AstFile *f, Token token, Ast *elem) {
gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice<Ast *> fields, isize field_count, gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice<Ast *> fields, isize field_count,
Ast *polymorphic_params, bool is_packed, bool is_raw_union, bool is_no_copy, Ast *polymorphic_params, bool is_packed, bool is_raw_union, bool is_no_copy,
Ast *align, Ast *align, Ast *field_align,
Token where_token, Array<Ast *> const &where_clauses) { Token where_token, Array<Ast *> const &where_clauses) {
Ast *result = alloc_ast_node(f, Ast_StructType); Ast *result = alloc_ast_node(f, Ast_StructType);
result->StructType.token = token; result->StructType.token = token;
@@ -1136,6 +1137,7 @@ gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice<Ast *> fields, i
result->StructType.is_raw_union = is_raw_union; result->StructType.is_raw_union = is_raw_union;
result->StructType.is_no_copy = is_no_copy; result->StructType.is_no_copy = is_no_copy;
result->StructType.align = align; result->StructType.align = align;
result->StructType.field_align = field_align;
result->StructType.where_token = where_token; result->StructType.where_token = where_token;
result->StructType.where_clauses = slice_from_array(where_clauses); result->StructType.where_clauses = slice_from_array(where_clauses);
return result; return result;
@@ -2507,6 +2509,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
bool is_raw_union = false; bool is_raw_union = false;
bool no_copy = false; bool no_copy = false;
Ast *align = nullptr; Ast *align = nullptr;
Ast *field_align = nullptr;
if (allow_token(f, Token_OpenParen)) { if (allow_token(f, Token_OpenParen)) {
isize param_count = 0; isize param_count = 0;
@@ -2543,6 +2546,18 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
error_line("\tSuggestion: #align(%s)", s); error_line("\tSuggestion: #align(%s)", s);
gb_string_free(s); gb_string_free(s);
} }
} else if (tag.string == "field_align") {
if (field_align) {
syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string));
}
field_align = parse_expr(f, true);
if (field_align && field_align->kind != Ast_ParenExpr) {
ERROR_BLOCK();
gbString s = expr_to_string(field_align);
syntax_warning(tag, "#field_align requires parentheses around the expression");
error_line("\tSuggestion: #field_align(%s)", s);
gb_string_free(s);
}
} else if (tag.string == "raw_union") { } else if (tag.string == "raw_union") {
if (is_raw_union) { if (is_raw_union) {
syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string));
@@ -2591,7 +2606,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
decls = fields->FieldList.list; decls = fields->FieldList.list;
} }
return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, where_token, where_clauses); return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, field_align, where_token, where_clauses);
} break; } break;
case Token_union: { case Token_union: {
+1
View File
@@ -713,6 +713,7 @@ AST_KIND(_TypeBegin, "", bool) \
isize field_count; \ isize field_count; \
Ast *polymorphic_params; \ Ast *polymorphic_params; \
Ast *align; \ Ast *align; \
Ast *field_align; \
Token where_token; \ Token where_token; \
Slice<Ast *> where_clauses; \ Slice<Ast *> where_clauses; \
bool is_packed; \ bool is_packed; \
+10 -4
View File
@@ -137,6 +137,7 @@ struct TypeStruct {
Scope * scope; Scope * scope;
i64 custom_align; i64 custom_align;
i64 custom_field_align;
Type * polymorphic_params; // Type_Tuple Type * polymorphic_params; // Type_Tuple
Type * polymorphic_parent; Type * polymorphic_parent;
@@ -3668,10 +3669,15 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align); return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align);
} }
gb_internal i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_packed, bool is_raw_union) { gb_internal i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_packed, bool is_raw_union, i64 min_field_align) {
gbAllocator a = permanent_allocator(); gbAllocator a = permanent_allocator();
auto offsets = gb_alloc_array(a, i64, fields.count); auto offsets = gb_alloc_array(a, i64, fields.count);
i64 curr_offset = 0; i64 curr_offset = 0;
if (min_field_align == 0) {
min_field_align = 1;
}
if (is_raw_union) { if (is_raw_union) {
for_array(i, fields) { for_array(i, fields) {
offsets[i] = 0; offsets[i] = 0;
@@ -3692,7 +3698,7 @@ gb_internal i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_pack
offsets[i] = -1; offsets[i] = -1;
} else { } else {
Type *t = fields[i]->type; Type *t = fields[i]->type;
i64 align = gb_max(type_align_of(t), 1); i64 align = gb_max(type_align_of(t), min_field_align);
i64 size = gb_max(type_size_of( t), 0); i64 size = gb_max(type_size_of( t), 0);
curr_offset = align_formula(curr_offset, align); curr_offset = align_formula(curr_offset, align);
offsets[i] = curr_offset; offsets[i] = curr_offset;
@@ -3709,7 +3715,7 @@ gb_internal bool type_set_offsets(Type *t) {
MUTEX_GUARD(&t->Struct.offset_mutex); MUTEX_GUARD(&t->Struct.offset_mutex);
if (!t->Struct.are_offsets_set) { if (!t->Struct.are_offsets_set) {
t->Struct.are_offsets_being_processed = true; t->Struct.are_offsets_being_processed = true;
t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union); t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union, t->Struct.custom_field_align);
t->Struct.are_offsets_being_processed = false; t->Struct.are_offsets_being_processed = false;
t->Struct.are_offsets_set = true; t->Struct.are_offsets_set = true;
return true; return true;
@@ -3718,7 +3724,7 @@ gb_internal bool type_set_offsets(Type *t) {
MUTEX_GUARD(&t->Tuple.mutex); MUTEX_GUARD(&t->Tuple.mutex);
if (!t->Tuple.are_offsets_set) { if (!t->Tuple.are_offsets_set) {
t->Tuple.are_offsets_being_processed = true; t->Tuple.are_offsets_being_processed = true;
t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false); t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false, 1);
t->Tuple.are_offsets_being_processed = false; t->Tuple.are_offsets_being_processed = false;
t->Tuple.are_offsets_set = true; t->Tuple.are_offsets_set = true;
return true; return true;