aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgingerBill <gingerBill@users.noreply.github.com>2026-01-29 10:48:05 +0000
committergingerBill <gingerBill@users.noreply.github.com>2026-01-29 10:49:26 +0000
commit07d814d9cf3bf2065e4122dba91817935dca7c60 (patch)
treee19ee510f617a487d8e6391b59bfaf121efd8e36
parent4a7fb4666722b3ff45998c3bc11f03150a2fe53f (diff)
Add `struct #simple` to force a struct to use simple comparison if all of the fields "nearly simply comparable".
-rw-r--r--base/runtime/core.odin1
-rw-r--r--base/runtime/print.odin2
-rw-r--r--core/odin/ast/ast.odin1
-rw-r--r--core/odin/parser/parser.odin7
-rw-r--r--core/reflect/types.odin5
-rw-r--r--src/check_expr.cpp6
-rw-r--r--src/check_type.cpp15
-rw-r--r--src/parser.cpp13
-rw-r--r--src/parser.hpp1
-rw-r--r--src/types.cpp25
10 files changed, 67 insertions, 9 deletions
diff --git a/base/runtime/core.odin b/base/runtime/core.odin
index 58a0b8ad1..5a0b3766c 100644
--- a/base/runtime/core.odin
+++ b/base/runtime/core.odin
@@ -122,6 +122,7 @@ Type_Info_Struct_Flag :: enum u8 {
raw_union = 1,
all_or_none = 2,
align = 3,
+ simple = 4,
}
Type_Info_Struct :: struct {
diff --git a/base/runtime/print.odin b/base/runtime/print.odin
index 2deb0f4b1..2cdde8152 100644
--- a/base/runtime/print.odin
+++ b/base/runtime/print.odin
@@ -421,6 +421,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
if .packed in info.flags { print_string("#packed ") }
if .raw_union in info.flags { print_string("#raw_union ") }
if .all_or_none in info.flags { print_string("#all_or_none ") }
+ if .simple in info.flags { print_string("#simple ") }
if .align in info.flags {
print_string("#align(")
print_u64(u64(ti.align))
@@ -835,6 +836,7 @@ write_write_type :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, t
if .packed in info.flags { write_string(i, buf, "#packed ") or_return }
if .raw_union in info.flags { write_string(i, buf, "#raw_union ") or_return }
if .all_or_none in info.flags { write_string(i, buf, "#all_or_none ") or_return }
+ if .simple in info.flags { write_string(i, buf, "#simple ") or_return }
if .align in info.flags {
write_string(i, buf, "#align(") or_return
write_u64(i, buf, u64(ti.align)) or_return
diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin
index 6755ca4d0..ec22db434 100644
--- a/core/odin/ast/ast.odin
+++ b/core/odin/ast/ast.odin
@@ -798,6 +798,7 @@ Struct_Type :: struct {
is_raw_union: bool,
is_no_copy: bool,
is_all_or_none: bool,
+ is_simple: bool,
fields: ^Field_List,
name_count: int,
}
diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin
index 719cb0374..a18942e6b 100644
--- a/core/odin/parser/parser.odin
+++ b/core/odin/parser/parser.odin
@@ -2667,6 +2667,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
is_raw_union: bool
is_no_copy: bool
is_all_or_none: bool
+ is_simple: bool
fields: ^ast.Field_List
name_count: int
@@ -2695,6 +2696,11 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
}
is_all_or_none = true
+ case "simple":
+ if is_simple {
+ error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
+ }
+ is_simple = true
case "align":
if align != nil {
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
@@ -2769,6 +2775,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
st.is_raw_union = is_raw_union
st.is_no_copy = is_no_copy
st.is_all_or_none = is_all_or_none
+ st.is_simple = is_simple
st.fields = fields
st.name_count = name_count
st.where_token = where_token
diff --git a/core/reflect/types.odin b/core/reflect/types.odin
index d6d7011d1..385edb19b 100644
--- a/core/reflect/types.odin
+++ b/core/reflect/types.odin
@@ -696,9 +696,10 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt
}
io.write_string(w, "struct ", &n) or_return
- if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return }
- if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return }
+ if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return }
+ if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return }
if .all_or_none in info.flags { io.write_string(w, "#all_or_none ", &n) or_return }
+ if .simple in info.flags { io.write_string(w, "#simple ", &n) or_return }
if .align in info.flags {
io.write_string(w, "#align(", &n) or_return
io.write_i64(w, i64(ti.align), 10, &n) or_return
diff --git a/src/check_expr.cpp b/src/check_expr.cpp
index 46455aacf..36b4f6ec6 100644
--- a/src/check_expr.cpp
+++ b/src/check_expr.cpp
@@ -12692,8 +12692,10 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan
str = write_expr_to_string(str, st->polymorphic_params, shorthand);
str = gb_string_appendc(str, ") ");
}
- if (st->is_packed) str = gb_string_appendc(str, "#packed ");
- if (st->is_raw_union) str = gb_string_appendc(str, "#raw_union ");
+ if (st->is_packed) str = gb_string_appendc(str, "#packed ");
+ if (st->is_raw_union) str = gb_string_appendc(str, "#raw_union ");
+ if (st->is_all_or_none) str = gb_string_appendc(str, "#all_or_none ");
+ if (st->is_simple) str = gb_string_appendc(str, "#simple ");
if (st->align) {
str = gb_string_appendc(str, "#align ");
str = write_expr_to_string(str, st->align, shorthand);
diff --git a/src/check_type.cpp b/src/check_type.cpp
index 4b8f7b6ac..41c5f48d1 100644
--- a/src/check_type.cpp
+++ b/src/check_type.cpp
@@ -679,6 +679,21 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
gb_unused(where_clause_ok);
}
check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context);
+
+ if (st->is_simple) {
+ bool success = true;
+ for (Entity *f : struct_type->Struct.fields) {
+ if (!is_type_nearly_simple_compare(f->type)) {
+ gbString s = type_to_string(f->type);
+ error(f->token, "'struct #simple' requires all fields to be at least 'nearly simple compare', got %s", s);
+ gb_string_free(s);
+ }
+ }
+ if (success) {
+ struct_type->Struct.is_simple = true;
+ }
+ }
+
wait_signal_set(&struct_type->Struct.fields_wait_signal);
}
diff --git a/src/parser.cpp b/src/parser.cpp
index af533d9a3..2eb559e06 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -1230,7 +1230,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_all_or_none,
+ Ast *polymorphic_params, bool is_packed, bool is_raw_union, bool is_all_or_none, bool is_simple,
Ast *align, Ast *min_field_align, Ast *max_field_align,
Token where_token, Array<Ast *> const &where_clauses) {
Ast *result = alloc_ast_node(f, Ast_StructType);
@@ -1241,6 +1241,7 @@ gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice<Ast *> fields, i
result->StructType.is_packed = is_packed;
result->StructType.is_raw_union = is_raw_union;
result->StructType.is_all_or_none = is_all_or_none;
+ result->StructType.is_simple = is_simple;
result->StructType.align = align;
result->StructType.min_field_align = min_field_align;
result->StructType.max_field_align = max_field_align;
@@ -2788,6 +2789,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
bool is_packed = false;
bool is_all_or_none = false;
bool is_raw_union = false;
+ bool is_simple = false;
Ast *align = nullptr;
Ast *min_field_align = nullptr;
Ast *max_field_align = nullptr;
@@ -2869,11 +2871,16 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
error_line("\tSuggestion: #max_field_align(%s)", s);
gb_string_free(s);
}
- }else if (tag.string == "raw_union") {
+ } else if (tag.string == "raw_union") {
if (is_raw_union) {
syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string));
}
is_raw_union = true;
+ } else if (tag.string == "simple") {
+ if (is_simple) {
+ syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string));
+ }
+ is_simple = true;
} else {
syntax_error(tag, "Invalid struct tag '#%.*s'", LIT(tag.string));
}
@@ -2919,7 +2926,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
parser_check_polymorphic_record_parameters(f, polymorphic_params);
return ast_struct_type(f, token, decls, name_count,
- polymorphic_params, is_packed, is_raw_union, is_all_or_none,
+ polymorphic_params, is_packed, is_raw_union, is_all_or_none, is_simple,
align, min_field_align, max_field_align,
where_token, where_clauses);
} break;
diff --git a/src/parser.hpp b/src/parser.hpp
index 011330438..39f56ffae 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -780,6 +780,7 @@ AST_KIND(_TypeBegin, "", bool) \
bool is_raw_union; \
bool is_no_copy; \
bool is_all_or_none; \
+ bool is_simple; \
}) \
AST_KIND(UnionType, "union type", struct { \
Scope *scope; \
diff --git a/src/types.cpp b/src/types.cpp
index 03ff95033..a7f2bfda2 100644
--- a/src/types.cpp
+++ b/src/types.cpp
@@ -163,6 +163,7 @@ struct TypeStruct {
bool is_packed : 1;
bool is_raw_union : 1;
bool is_all_or_none : 1;
+ bool is_simple : 1;
bool is_poly_specialized : 1;
std::atomic<bool> are_offsets_being_processed;
@@ -2708,6 +2709,9 @@ gb_internal bool is_type_simple_compare(Type *t) {
return is_type_simple_compare(t->Matrix.elem);
case Type_Struct:
+ if (t->Struct.is_simple) {
+ return true;
+ }
for_array(i, t->Struct.fields) {
Entity *f = t->Struct.fields[i];
if (!is_type_simple_compare(f->type)) {
@@ -2768,12 +2772,16 @@ gb_internal bool is_type_nearly_simple_compare(Type *t) {
case Type_SoaPointer:
case Type_Proc:
case Type_BitSet:
+ case Type_BitField:
return true;
case Type_Matrix:
return is_type_nearly_simple_compare(t->Matrix.elem);
case Type_Struct:
+ if (t->Struct.is_simple) {
+ return true;
+ }
for_array(i, t->Struct.fields) {
Entity *f = t->Struct.fields[i];
if (!is_type_nearly_simple_compare(f->type)) {
@@ -2795,6 +2803,17 @@ gb_internal bool is_type_nearly_simple_compare(Type *t) {
case Type_SimdVector:
return is_type_nearly_simple_compare(t->SimdVector.elem);
+ case Type_Tuple:
+ if (t->Tuple.variables.count == 1) {
+ return is_type_nearly_simple_compare(t->Tuple.variables[0]->type);
+ }
+ break;
+
+ case Type_Slice:
+ case Type_DynamicArray:
+ case Type_Map:
+ return false;
+
}
return false;
@@ -5110,9 +5129,11 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
str = gb_string_appendc(str, ")");
}
- if (type->Struct.is_packed) str = gb_string_appendc(str, " #packed");
- if (type->Struct.is_raw_union) str = gb_string_appendc(str, " #raw_union");
+ if (type->Struct.is_packed) str = gb_string_appendc(str, " #packed");
+ if (type->Struct.is_raw_union) str = gb_string_appendc(str, " #raw_union");
if (type->Struct.custom_align != 0) str = gb_string_append_fmt(str, " #align %d", cast(int)type->Struct.custom_align);
+ if (type->Struct.is_all_or_none) str = gb_string_appendc(str, " #all_or_none");
+ if (type->Struct.is_simple) str = gb_string_appendc(str, " #simple");
str = gb_string_appendc(str, " {");