소스 검색

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.
gingerBill 1 년 전
부모
커밋
68df35b378
6개의 변경된 파일64개의 추가작업 그리고 24개의 파일을 삭제
  1. 1 0
      core/odin/ast/ast.odin
  2. 7 0
      core/odin/parser/parser.odin
  3. 25 15
      src/check_type.cpp
  4. 20 5
      src/parser.cpp
  5. 1 0
      src/parser.hpp
  6. 10 4
      src/types.cpp

+ 1 - 0
core/odin/ast/ast.odin

@@ -768,6 +768,7 @@ Struct_Type :: struct {
 	tok_pos:       tokenizer.Pos,
 	poly_params:   ^Field_List,
 	align:         ^Expr,
+	field_align:   ^Expr,
 	where_token:   tokenizer.Token,
 	where_clauses: []^Expr,
 	is_packed:     bool,

+ 7 - 0
core/odin/parser/parser.odin

@@ -2547,6 +2547,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 
 		poly_params: ^ast.Field_List
 		align:        ^ast.Expr
+		field_align:  ^ast.Expr
 		is_packed:    bool
 		is_raw_union: 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)
 				}
 				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":
 				if is_raw_union {
 					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.poly_params   = poly_params
 		st.align         = align
+		st.field_align   = field_align
 		st.is_packed     = is_packed
 		st.is_raw_union  = is_raw_union
 		st.is_no_copy    = is_no_copy

+ 25 - 15
src/check_type.cpp

@@ -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);
 	Operand o = {};
 	check_expr(ctx, &o, node);
 	if (o.mode != Addressing_Constant) {
 		if (o.mode != Addressing_Invalid) {
-			error(node, "#align must be a constant");
+			error(node, "#%s must be a constant", msg);
 		}
 		return false;
 	}
@@ -237,13 +237,13 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_)
 			if (v.used > 1) {
 				gbAllocator a = heap_allocator();
 				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);
 				return false;
 			}
 			i64 align = big_int_to_i64(&v);
 			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;
 			}
 			*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;
 }
 
@@ -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);
 	}
 
-	if (st->align != nullptr) {
-		if (st->is_packed) {
-			syntax_error(st->align, "'#align' cannot be applied with '#packed'");
-			return;
-		}
-		i64 custom_align = 1;
-		if (check_custom_align(ctx, st->align, &custom_align)) {
-			struct_type->Struct.custom_align = custom_align;
-		}
+#define ST_ALIGN(_name) if (st->_name != nullptr) {                                                \
+		if (st->is_packed) {                                                               \
+			syntax_error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \
+			return;                                                                    \
+		}                                                                                  \
+		i64 align = 1;                                                                     \
+		if (check_custom_align(ctx, st->_name, &align, #_name)) {                          \
+			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_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) {
 		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) {
 				error(ut->align, "An empty union cannot have a custom alignment");
 			} else {

+ 20 - 5
src/parser.cpp

@@ -383,10 +383,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) {
 		n->DynamicArrayType.elem = clone_ast(n->DynamicArrayType.elem, f);
 		break;
 	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.align  = clone_ast(n->StructType.align, f);
-		n->StructType.where_clauses  = clone_ast_array(n->StructType.where_clauses, f);
+		n->StructType.align              = clone_ast(n->StructType.align, 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;
 	case Ast_UnionType:
 		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,
                      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) {
 	Ast *result = alloc_ast_node(f, Ast_StructType);
 	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_no_copy         = is_no_copy;
 	result->StructType.align              = align;
+	result->StructType.field_align        = field_align;
 	result->StructType.where_token        = where_token;
 	result->StructType.where_clauses      = slice_from_array(where_clauses);
 	return result;
@@ -2507,6 +2509,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
 		bool is_raw_union       = false;
 		bool no_copy            = false;
 		Ast *align              = nullptr;
+		Ast *field_align        = nullptr;
 
 		if (allow_token(f, Token_OpenParen)) {
 			isize param_count = 0;
@@ -2543,6 +2546,18 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
 					error_line("\tSuggestion: #align(%s)", 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") {
 				if (is_raw_union) {
 					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;
 		}
 
-		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;
 
 	case Token_union: {

+ 1 - 0
src/parser.hpp

@@ -713,6 +713,7 @@ AST_KIND(_TypeBegin, "", bool) \
 		isize field_count;          \
 		Ast *polymorphic_params;    \
 		Ast *align;                 \
+		Ast *field_align;           \
 		Token where_token;          \
 		Slice<Ast *> where_clauses; \
 		bool is_packed;             \

+ 10 - 4
src/types.cpp

@@ -137,6 +137,7 @@ struct TypeStruct {
 	Scope *         scope;
 
 	i64             custom_align;
+	i64             custom_field_align;
 	Type *          polymorphic_params; // Type_Tuple
 	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);
 }
 
-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();
 	auto offsets = gb_alloc_array(a, i64, fields.count);
 	i64 curr_offset = 0;
+
+	if (min_field_align == 0) {
+		min_field_align = 1;
+	}
+
 	if (is_raw_union) {
 		for_array(i, fields) {
 			offsets[i] = 0;
@@ -3692,7 +3698,7 @@ gb_internal i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_pack
 				offsets[i] = -1;
 			} else {
 				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);
 				curr_offset = align_formula(curr_offset, align);
 				offsets[i] = curr_offset;
@@ -3709,7 +3715,7 @@ gb_internal bool type_set_offsets(Type *t) {
 		MUTEX_GUARD(&t->Struct.offset_mutex);
 		if (!t->Struct.are_offsets_set) {
 			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_set = true;
 			return true;
@@ -3718,7 +3724,7 @@ gb_internal bool type_set_offsets(Type *t) {
 		MUTEX_GUARD(&t->Tuple.mutex);
 		if (!t->Tuple.are_offsets_set) {
 			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_set = true;
 			return true;