From 717b9f157889cdcdd60253eb656648e9251d3be7 Mon Sep 17 00:00:00 2001 From: Barinzaya Date: Sat, 24 May 2025 12:41:28 -0400 Subject: Change union tag size to account for `#align`. The prior behavior was adjusting the tag size based on the alignment of the types in the union, even when the union has a custom alignment specified with `#align`. This changes the behavior so that a custom alignment, if specified, takes precedence over the alignment of the types. --- src/types.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/types.cpp') diff --git a/src/types.cpp b/src/types.cpp index ce921796d..57e277035 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3059,11 +3059,15 @@ gb_internal i64 union_tag_size(Type *u) { compiler_error("how many variants do you have?! %lld", cast(long long)u->Union.variants.count); } - for_array(i, u->Union.variants) { - Type *variant_type = u->Union.variants[i]; - i64 align = type_align_of(variant_type); - if (max_align < align) { - max_align = align; + if (u->Union.custom_align > 0) { + max_align = gb_max(max_align, u->Union.custom_align); + } else { + for_array(i, u->Union.variants) { + Type *variant_type = u->Union.variants[i]; + i64 align = type_align_of(variant_type); + if (max_align < align) { + max_align = align; + } } } -- cgit v1.2.3 From cc08dca53d52609dcf5c06b4edb1b4ec47505c8a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 14 Jun 2025 01:13:36 +0200 Subject: Add additional nullptr checks in types.cpp Ran into a bunch of nullptr problems while reviving an 8-year old Odin problem. --- src/types.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) (limited to 'src/types.cpp') diff --git a/src/types.cpp b/src/types.cpp index ce921796d..c7573173c 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1203,6 +1203,7 @@ gb_internal Type *type_deref(Type *t, bool allow_multi_pointer) { } gb_internal bool is_type_named(Type *t) { + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return true; } @@ -1212,6 +1213,7 @@ gb_internal bool is_type_named(Type *t) { gb_internal bool is_type_boolean(Type *t) { // t = core_type(t); t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Boolean) != 0; } @@ -1220,6 +1222,7 @@ gb_internal bool is_type_boolean(Type *t) { gb_internal bool is_type_integer(Type *t) { // t = core_type(t); t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Integer) != 0; } @@ -1241,15 +1244,15 @@ gb_internal bool is_type_integer_like(Type *t) { gb_internal bool is_type_unsigned(Type *t) { t = base_type(t); - // t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Unsigned) != 0; } return false; } gb_internal bool is_type_integer_128bit(Type *t) { - // t = core_type(t); t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Integer) != 0 && t->Basic.size == 16; } @@ -1258,6 +1261,7 @@ gb_internal bool is_type_integer_128bit(Type *t) { gb_internal bool is_type_rune(Type *t) { // t = core_type(t); t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Rune) != 0; } @@ -1266,6 +1270,7 @@ gb_internal bool is_type_rune(Type *t) { gb_internal bool is_type_numeric(Type *t) { // t = core_type(t); t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Numeric) != 0; } else if (t->kind == Type_Enum) { @@ -1279,6 +1284,7 @@ gb_internal bool is_type_numeric(Type *t) { } gb_internal bool is_type_string(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_String) != 0; } @@ -1286,6 +1292,7 @@ gb_internal bool is_type_string(Type *t) { } gb_internal bool is_type_cstring(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return t->Basic.kind == Basic_cstring; } @@ -1293,9 +1300,7 @@ gb_internal bool is_type_cstring(Type *t) { } gb_internal bool is_type_typed(Type *t) { t = base_type(t); - if (t == nullptr) { - return false; - } + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Untyped) == 0; } @@ -1303,9 +1308,7 @@ gb_internal bool is_type_typed(Type *t) { } gb_internal bool is_type_untyped(Type *t) { t = base_type(t); - if (t == nullptr) { - return false; - } + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Untyped) != 0; } @@ -1313,6 +1316,7 @@ gb_internal bool is_type_untyped(Type *t) { } gb_internal bool is_type_ordered(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } switch (t->kind) { case Type_Basic: return (t->Basic.flags & BasicFlag_Ordered) != 0; @@ -1325,6 +1329,7 @@ gb_internal bool is_type_ordered(Type *t) { } gb_internal bool is_type_ordered_numeric(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } switch (t->kind) { case Type_Basic: return (t->Basic.flags & BasicFlag_OrderedNumeric) != 0; @@ -1333,6 +1338,7 @@ gb_internal bool is_type_ordered_numeric(Type *t) { } gb_internal bool is_type_constant_type(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_ConstantType) != 0; } @@ -1346,6 +1352,7 @@ gb_internal bool is_type_constant_type(Type *t) { } gb_internal bool is_type_float(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Float) != 0; } @@ -1353,6 +1360,7 @@ gb_internal bool is_type_float(Type *t) { } gb_internal bool is_type_complex(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Complex) != 0; } @@ -1360,6 +1368,7 @@ gb_internal bool is_type_complex(Type *t) { } gb_internal bool is_type_quaternion(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Quaternion) != 0; } @@ -1367,6 +1376,7 @@ gb_internal bool is_type_quaternion(Type *t) { } gb_internal bool is_type_complex_or_quaternion(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & (BasicFlag_Complex|BasicFlag_Quaternion)) != 0; } @@ -1374,6 +1384,7 @@ gb_internal bool is_type_complex_or_quaternion(Type *t) { } gb_internal bool is_type_pointer(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Pointer) != 0; } @@ -1381,10 +1392,12 @@ gb_internal bool is_type_pointer(Type *t) { } gb_internal bool is_type_soa_pointer(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_SoaPointer; } gb_internal bool is_type_multi_pointer(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_MultiPointer; } gb_internal bool is_type_internally_pointer_like(Type *t) { @@ -1393,6 +1406,7 @@ gb_internal bool is_type_internally_pointer_like(Type *t) { gb_internal bool is_type_tuple(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Tuple; } gb_internal bool is_type_uintptr(Type *t) { @@ -1415,14 +1429,17 @@ gb_internal bool is_type_u8(Type *t) { } gb_internal bool is_type_array(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Array; } gb_internal bool is_type_enumerated_array(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_EnumeratedArray; } gb_internal bool is_type_matrix(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Matrix; } @@ -1566,22 +1583,27 @@ gb_internal bool is_type_valid_for_matrix_elems(Type *t) { gb_internal bool is_type_dynamic_array(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_DynamicArray; } gb_internal bool is_type_slice(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Slice; } gb_internal bool is_type_proc(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Proc; } gb_internal bool is_type_asm_proc(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Proc && t->Proc.calling_convention == ProcCC_InlineAsm; } gb_internal bool is_type_simd_vector(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_SimdVector; } @@ -1621,11 +1643,13 @@ gb_internal Type *base_any_array_type(Type *t) { gb_internal bool is_type_generic(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Generic; } gb_internal bool is_type_u8_slice(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Slice) { return is_type_u8(t->Slice.elem); } @@ -1633,6 +1657,7 @@ gb_internal bool is_type_u8_slice(Type *t) { } gb_internal bool is_type_u8_array(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Array) { return is_type_u8(t->Array.elem); } @@ -1640,6 +1665,7 @@ gb_internal bool is_type_u8_array(Type *t) { } gb_internal bool is_type_u8_ptr(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Pointer) { return is_type_u8(t->Slice.elem); } @@ -1647,6 +1673,7 @@ gb_internal bool is_type_u8_ptr(Type *t) { } gb_internal bool is_type_u8_multi_ptr(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_MultiPointer) { return is_type_u8(t->Slice.elem); } @@ -1654,6 +1681,7 @@ gb_internal bool is_type_u8_multi_ptr(Type *t) { } gb_internal bool is_type_rune_array(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Array) { return is_type_rune(t->Array.elem); } @@ -1979,7 +2007,13 @@ gb_internal bool is_type_untyped_uninit(Type *t) { } gb_internal bool is_type_empty_union(Type *t) { + if (t == nullptr) { + return false; + } t = base_type(t); + if (t == nullptr) { + return false; + } return t->kind == Type_Union && t->Union.variants.count == 0; } @@ -2668,7 +2702,7 @@ gb_internal bool are_types_identical(Type *x, Type *y) { y = y->Named.base; } } - if (x->kind != y->kind) { + if (x == nullptr || y == nullptr || x->kind != y->kind) { return false; } -- cgit v1.2.3 From 3a86bc9c6d3ca51ce02397f73605a7fbd7f3a899 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 20 Jun 2025 21:56:50 +0200 Subject: Fix WASM C ABI for raw unions --- src/llvm_abi.cpp | 70 ++++++++++++++++++++++++++++++++-------- src/llvm_backend_general.cpp | 2 +- src/types.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 15 deletions(-) (limited to 'src/types.cpp') diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index af08722c3..e1cbe7558 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1313,7 +1313,7 @@ namespace lbAbiWasm { registers/arguments if possible rather than by pointer. */ gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type); - gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); + gb_internal lbArgType compute_return_type(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type* original_type); enum {MAX_DIRECT_STRUCT_SIZE = 32}; @@ -1323,7 +1323,9 @@ namespace lbAbiWasm { ft->ctx = c; ft->calling_convention = calling_convention; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention, original_type); - ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + + GB_ASSERT(original_type->kind == Type_Proc); + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple, original_type->Proc.results); return ft; } @@ -1359,7 +1361,7 @@ namespace lbAbiWasm { return false; } - gb_internal bool type_can_be_direct(LLVMTypeRef type, ProcCallingConvention calling_convention) { + gb_internal bool type_can_be_direct(LLVMTypeRef type, Type *original_type, ProcCallingConvention calling_convention) { LLVMTypeKind kind = LLVMGetTypeKind(type); i64 sz = lb_sizeof(type); if (sz == 0) { @@ -1372,9 +1374,21 @@ namespace lbAbiWasm { return false; } else if (kind == LLVMStructTypeKind) { unsigned count = LLVMCountStructElementTypes(type); + + // NOTE(laytan): raw unions are always structs with 1 field in LLVM, need to check our own def. + Type *bt = base_type(original_type); + if (bt->kind == Type_Struct && bt->Struct.is_raw_union) { + count = cast(unsigned)bt->Struct.fields.count; + } + if (count == 1) { - return type_can_be_direct(LLVMStructGetTypeAtIndex(type, 0), calling_convention); + return type_can_be_direct( + LLVMStructGetTypeAtIndex(type, 0), + type_internal_index(original_type, 0), + calling_convention + ); } + } else if (is_basic_register_type(type)) { return true; } @@ -1398,7 +1412,7 @@ namespace lbAbiWasm { return false; } - gb_internal lbArgType is_struct(LLVMContextRef c, LLVMTypeRef type, ProcCallingConvention calling_convention) { + gb_internal lbArgType is_struct(LLVMContextRef c, LLVMTypeRef type, Type *original_type, ProcCallingConvention calling_convention) { LLVMTypeKind kind = LLVMGetTypeKind(type); GB_ASSERT(kind == LLVMArrayTypeKind || kind == LLVMStructTypeKind); @@ -1406,15 +1420,15 @@ namespace lbAbiWasm { if (sz == 0) { return lb_arg_type_ignore(type); } - if (type_can_be_direct(type, calling_convention)) { + if (type_can_be_direct(type, original_type, calling_convention)) { return lb_arg_type_direct(type); } return lb_arg_type_indirect(type, nullptr); } - gb_internal lbArgType pseudo_slice(LLVMContextRef c, LLVMTypeRef type, ProcCallingConvention calling_convention) { + gb_internal lbArgType pseudo_slice(LLVMContextRef c, LLVMTypeRef type, Type *original_type, ProcCallingConvention calling_convention) { if (build_context.metrics.ptr_size < build_context.metrics.int_size && - type_can_be_direct(type, calling_convention)) { + type_can_be_direct(type, original_type, calling_convention)) { LLVMTypeRef types[2] = { LLVMStructGetTypeAtIndex(type, 0), // ignore padding @@ -1423,7 +1437,7 @@ namespace lbAbiWasm { LLVMTypeRef new_type = LLVMStructTypeInContext(c, types, gb_count_of(types), false); return lb_arg_type_direct(type, new_type, nullptr, nullptr); } else { - return is_struct(c, type, calling_convention); + return is_struct(c, type, original_type, calling_convention); } } @@ -1444,9 +1458,9 @@ namespace lbAbiWasm { LLVMTypeKind kind = LLVMGetTypeKind(t); if (kind == LLVMStructTypeKind || kind == LLVMArrayTypeKind) { if (is_type_slice(ptype) || is_type_string(ptype)) { - args[i] = pseudo_slice(c, t, calling_convention); + args[i] = pseudo_slice(c, t, ptype, calling_convention); } else { - args[i] = is_struct(c, t, calling_convention); + args[i] = is_struct(c, t, ptype, calling_convention); } } else { args[i] = non_struct(c, t, false); @@ -1455,11 +1469,11 @@ namespace lbAbiWasm { return args; } - gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { + gb_internal lbArgType compute_return_type(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type* original_type) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { - if (type_can_be_direct(return_type, ft->calling_convention)) { + if (type_can_be_direct(return_type, original_type, ft->calling_convention)) { return lb_arg_type_direct(return_type); } else if (ft->calling_convention != ProcCC_CDecl) { i64 sz = lb_sizeof(return_type); @@ -1471,7 +1485,35 @@ namespace lbAbiWasm { } } - LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); + // Multiple returns. + if (return_is_tuple) { \ + lbArgType return_arg = {}; + if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { + unsigned field_count = LLVMCountStructElementTypes(return_type); + if (field_count > 1) { + ft->original_arg_count = ft->args.count; + ft->multiple_return_original_type = return_type; + + for (unsigned i = 0; i < field_count-1; i++) { + LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(return_type, i); + LLVMTypeRef field_pointer_type = LLVMPointerType(field_type, 0); + lbArgType ret_partial = lb_arg_type_direct(field_pointer_type); + array_add(&ft->args, ret_partial); + } + + return_arg = compute_return_type( + ft, + c, + LLVMStructGetTypeAtIndex(return_type, field_count-1), + true, false, + type_internal_index(original_type, field_count-1) + ); + } + } + if (return_arg.type != nullptr) { + return return_arg; + } + } LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 5aaa7f63a..5d6a55973 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2206,7 +2206,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { field_count = 3; } LLVMTypeRef *fields = gb_alloc_array(permanent_allocator(), LLVMTypeRef, field_count); - fields[0] = LLVMPointerType(lb_type(m, type->Pointer.elem), 0); + fields[0] = LLVMPointerType(lb_type(m, type->SoaPointer.elem), 0); if (bigger_int) { fields[1] = lb_type_padding_filler(m, build_context.ptr_size, build_context.ptr_size); fields[2] = LLVMIntTypeInContext(ctx, 8*cast(unsigned)build_context.int_size); diff --git a/src/types.cpp b/src/types.cpp index c7573173c..861aa5bf8 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -4614,6 +4614,82 @@ gb_internal Type *alloc_type_proc_from_types(Type **param_types, unsigned param_ // return type; // } +// Index a type that is internally a struct or array. +gb_internal Type *type_internal_index(Type *t, isize index) { + Type *bt = base_type(t); + if (bt == nullptr) { + return nullptr; + } + + switch (bt->kind) { + case Type_Basic: + { + switch (bt->Basic.kind) { + case Basic_complex32: return t_f16; + case Basic_complex64: return t_f32; + case Basic_complex128: return t_f64; + case Basic_quaternion64: return t_f16; + case Basic_quaternion128: return t_f32; + case Basic_quaternion256: return t_f64; + case Basic_string: + { + GB_ASSERT(index == 0 || index == 1); + return index == 0 ? t_u8_ptr : t_int; + } + case Basic_any: + { + GB_ASSERT(index == 0 || index == 1); + return index == 0 ? t_rawptr : t_typeid; + } + } + } + break; + + case Type_Array: return bt->Array.elem; + case Type_EnumeratedArray: return bt->EnumeratedArray.elem; + case Type_SimdVector: return bt->SimdVector.elem; + case Type_Slice: + { + GB_ASSERT(index == 0 || index == 1); + return index == 0 ? t_rawptr : t_typeid; + } + case Type_DynamicArray: + { + switch (index) { + case 0: return t_rawptr; + case 1: return t_int; + case 2: return t_int; + case 3: return t_allocator; + default: GB_PANIC("invalid raw dynamic array index"); + }; + } + case Type_Struct: + return get_struct_field_type(bt, index); + case Type_Union: + if (index < bt->Union.variants.count) { + return bt->Union.variants[index]; + } + return union_tag_type(bt); + case Type_Tuple: + return bt->Tuple.variables[index]->type; + case Type_Matrix: + return bt->Matrix.elem; + case Type_SoaPointer: + { + GB_ASSERT(index == 0 || index == 1); + return index == 0 ? t_rawptr : t_int; + } + case Type_Map: + return type_internal_index(bt->Map.debug_metadata_type, index); + case Type_BitField: + return type_internal_index(bt->BitField.backing_type, index); + case Type_Generic: + return type_internal_index(bt->Generic.specialized, index); + }; + + GB_PANIC("Unhandled type %s", type_to_string(bt)); +}; + gb_internal gbString write_type_to_string(gbString str, Type *type, bool shorthand=false, bool allow_polymorphic=false) { if (type == nullptr) { return gb_string_appendc(str, ""); -- cgit v1.2.3 From 3db8972c990746557916aa640842817094f9ecff Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 20 Jun 2025 22:07:46 +0200 Subject: add return --- src/types.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/types.cpp') diff --git a/src/types.cpp b/src/types.cpp index 861aa5bf8..cb353516d 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -4688,6 +4688,7 @@ gb_internal Type *type_internal_index(Type *t, isize index) { }; GB_PANIC("Unhandled type %s", type_to_string(bt)); + return nullptr; }; gb_internal gbString write_type_to_string(gbString str, Type *type, bool shorthand=false, bool allow_polymorphic=false) { -- cgit v1.2.3 From 8410871cb8cc122572ff55ef929f6acd35782677 Mon Sep 17 00:00:00 2001 From: Tohei Ichikawa Date: Tue, 24 Jun 2025 22:58:00 -0400 Subject: Fix bug where compiler treats uint enums as ints --- minimal_test.odin | 31 ++++++++ real_test.odin | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/types.cpp | 4 + 3 files changed, 262 insertions(+) create mode 100644 minimal_test.odin create mode 100644 real_test.odin (limited to 'src/types.cpp') diff --git a/minimal_test.odin b/minimal_test.odin new file mode 100644 index 000000000..29ed6f123 --- /dev/null +++ b/minimal_test.odin @@ -0,0 +1,31 @@ +package test + +import "core:fmt" + +main :: proc() { + // The bug involves the compiler handling enums backed by unsigned ints as signed ints + + // I have never seen it happen when working with enums directly + Test_Enum :: enum u32 { + SMALL_VALUE = 0xFF, + BIG_VALUE = 0xFF00_0000, // negative if interpreted as i32 + } + // These will all evaluate to false + fmt.printfln("Should be false. Got %t.", Test_Enum.SMALL_VALUE > Test_Enum.BIG_VALUE) + fmt.printfln("Should be false. Got %t.", Test_Enum(0xF) > Test_Enum.BIG_VALUE) + fmt.printfln("Should be false. Got %t.", Test_Enum(0xF) > Test_Enum(0xF000_0000)) + fmt.printfln("Should be false. Got %t.", Test_Enum.SMALL_VALUE > max(Test_Enum)) + fmt.printfln("Should be false. Got %t.", Test_Enum(0xF) > max(Test_Enum)) + + // But I have seen it happen when working with enums generically + test_proc :: proc(lhs: $T, rhs: T) -> bool { + return lhs > rhs + } + // The enum value comparisons below are the same as the comparisons in the block above + // These will all evaluate to true + fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum.SMALL_VALUE, Test_Enum.BIG_VALUE)) + fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum(0xF), Test_Enum.BIG_VALUE)) + fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum(0xF), Test_Enum(0xF000_0000))) + fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum.SMALL_VALUE, max(Test_Enum))) + fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum(0xF), max(Test_Enum))) +} diff --git a/real_test.odin b/real_test.odin new file mode 100644 index 000000000..d715708a4 --- /dev/null +++ b/real_test.odin @@ -0,0 +1,227 @@ +package test + +import "core:fmt" +import refl "core:reflect" + +main :: proc() { + // This test recreates how I discovered the bug + // I've copy-pasted some code from a project I'm working on + + // This will evaluate to false + should_be_true := refl.enum_value_has_name(Scancode.NUM_LOCK) + fmt.printfln("Should be true. Got %v.", should_be_true) +} + +/* +Scancodes for Win32 keyboard input: +https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes + +Win32 scancodes are derived from PS/2 Set 1 scancodes. +Win32 scancodes are 2 bytes, but PS/2 Set 1 contains scancodes longer than 2 bytes. +Win32 invents its own 2 byte scancodes to replace these long scancodes. +(E.g. Print Screen is 0xE02AE037 in PS/2 Set 1, but is 0xE037 in Win32.) +Table of Win32 scancodes: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes + +⚠️ WARNING: Some keys sort of share the same scancode due to new and legacy scancode definitions. +For some reason, Microsoft uses different scancodes in the context of keyboard messages (WM_KEYDOWN, and everything else in this +list: https://learn.microsoft.com/en-us/windows/win32/inputdev/keyboard-input-notifications). I call these "legacy scancodes." +E.g. Num Lock's scancode is 0xE045 in the context of WM_KEYDOWN, but MapVirtualKeyW(VK_NUMLOCK) returns 0x45. +This causes multiple physical keys to share the same scancode. +E.g. Num Lock's new scancode is 0x45, but Pause's legacy scancode is also 0x45. +But so long as you strictly use new scancodes are strictly use legacy scancodes, no physical keys will share a scancode. +Thanks Microsoft... >:^( + +⚠️ WARNING: There are still other edge cases: some physical keys have multiple scancodes, some scancodes are over 2 bytes, etc. +All such edge cases are commented next to the relevant enum value definitions. +Again, thanks Microsoft... >:^( +*/ +Scancode :: enum u16 { + POWER_DOWN = 0xE05E, + SLEEP = 0xE05F, + WAKE_UP = 0xE063, + + ERROR_ROLL_OVER = 0xFF, + + A = 0x1E, + B = 0x30, + C = 0x2E, + D = 0x20, + E = 0x12, + F = 0x21, + G = 0x22, + H = 0x23, + I = 0x17, + J = 0x24, + K = 0x25, + L = 0x26, + M = 0x32, + N = 0x31, + O = 0x18, + P = 0x19, + Q = 0x10, + R = 0x13, + S = 0x1F, + T = 0x14, + U = 0x16, + V = 0x2F, + W = 0x11, + X = 0x2D, + Y = 0x15, + Z = 0x2C, + _1 = 0x02, + _2 = 0x03, + _3 = 0x04, + _4 = 0x05, + _5 = 0x06, + _6 = 0x07, + _7 = 0x08, + _8 = 0x09, + _9 = 0x0A, + _0 = 0x0B, + + ENTER = 0x1C, + ESCAPE = 0x01, + DELETE = 0x0E, + TAB = 0x0F, + SPACEBAR = 0x39, + MINUS = 0x0C, + EQUALS = 0x0D, + L_BRACE = 0x1A, + R_BRACE = 0x1B, + BACKSLASH = 0x2B, + NON_US_HASH = 0x2B, + SEMICOLON = 0x27, + APOSTROPHE = 0x28, + GRAVE_ACCENT = 0x29, + COMMA = 0x33, + PERIOD = 0x34, + FORWARD_SLASH = 0x35, + CAPS_LOCK = 0x3A, + + F1 = 0x3B, + F2 = 0x3C, + F3 = 0x3D, + F4 = 0x3E, + F5 = 0x3F, + F6 = 0x40, + F7 = 0x41, + F8 = 0x42, + F9 = 0x43, + F10 = 0x44, + F11 = 0x57, + F12 = 0x58, + + // These are all the same physical key + // Windows maps Print Screen and Sys Rq to system-level shortcuts, so by default, WM_KEYDOWN does not report Print Screen or Sys Rq + PRINT_SCREEN = 0xE037, + PRINT_SCREEN_SYS_RQ = 0x54, // Alt + PrintScreen + + SCROLL_LOCK = 0x46, + + // These are all the same physical key + PAUSE = 0xE11D, // NOT used by legacy keyboard messages + // Win32 scancodes are 2 bytes, the documentation says Pause's scancode is 0xE11D45, which is 3 bytes??? + // MapVirtualKeyW(VK_PAUSE) returns 0xE11D, so we're just gonna use that + PAUSE_BREAK = 0xE046, // Ctrl + Pause + PAUSE_LEGACY = 0x45, // ONLY used by legacy keyboard messages + + INSERT = 0xE052, + HOME = 0xE047, + PAGE_UP = 0xE049, + DELETE_FORWARD = 0xE053, + END = 0xE04F, + PAGE_DOWN = 0xE051, + RIGHT_ARROW = 0xE04D, + LEFT_ARROW = 0xE04B, + DOWN_ARROW = 0xE050, + UP_ARROW = 0xE048, + + // These are all the same physical key + NUM_LOCK = 0x45, // NOT used by legacy keyboard messages + NUM_LOCK_LEGACY = 0xE045, // ONLY used by legacy keyboard messages + + KEYPAD_FORWARD_SLASH = 0xE035, + KEYPAD_STAR = 0x37, + KEYPAD_DASH = 0x4A, + KEYPAD_PLUS = 0x4E, + KEYPAD_ENTER = 0xE01C, + KEYPAD_1 = 0x4F, + KEYPAD_2 = 0x50, + KEYPAD_3 = 0x51, + KEYPAD_4 = 0x4B, + KEYPAD_5 = 0x4C, + KEYPAD_6 = 0x4D, + KEYPAD_7 = 0x47, + KEYPAD_8 = 0x48, + KEYPAD_9 = 0x49, + KEYPAD_0 = 0x52, + KEYPAD_PERIOD = 0x53, + NON_US_BACKSLASH = 0x56, + APPLICATION = 0xE05D, + POWER = 0xE05E, + KEYPAD_EQUALS = 0x59, + + F13 = 0x64, + F14 = 0x65, + F15 = 0x66, + F16 = 0x67, + F17 = 0x68, + F18 = 0x69, + F19 = 0x6A, + F20 = 0x6B, + F21 = 0x6C, + F22 = 0x6D, + F23 = 0x6E, + F24 = 0x76, + + KEYPAD_COMMA = 0x7E, // on Brazilian keyboards + INTERNATIONAL_1 = 0x73, // on Brazilian and Japanese keyboards + INTERNATIONAL_2 = 0x70, // on Japanese keyboards + INTERNATIONAL_3 = 0x7D, // on Japanese keyboards + INTERNATIONAL_4 = 0x79, // on Japanese keyboards + INTERNATIONAL_5 = 0x7B, // on Japanese keyboards + INTERNATIONAL_6 = 0x5C, + + // These are all the same physical key + LANG_1 = 0x72, // key release event only, NOT used by legacy keyboard messages + LANG_1_LEGACY = 0xF2, // key release event only, ONLY used by legacy keyboard messages + + // These are all the same physical key + LANG_2 = 0x71, // key release event only, NOT used by legacy keyboard messages + LANG_2_LEGACY = 0xF1, // key release event only, ONLY used by legacy keyboard messages + + LANG3 = 0x78, + LANG4 = 0x77, + LANG5 = 0x76, + L_CONTROL = 0x1D, + L_SHIFT = 0x2A, + L_ALT = 0x38, + L_GUI = 0xE05B, + R_CONTROL = 0xE01D, + R_SHIFT = 0x36, + R_ALT = 0xE038, + R_GUI = 0xE05C, + + NEXT_TRACK = 0xE019, + PREVIOUS_TRACK = 0xE010, + STOP = 0xE024, + PLAY_PAUSE = 0xE022, + MUTE = 0xE020, + VOLUME_INCREMENT = 0xE030, + VOLUME_DECREMENT = 0xE02E, + + // AL stands for "application launch" + AL_CONSUMER_CONTROL_CONFIGURATION = 0xE06D, + AL_EMAIL_READER = 0xE06C, + AL_CALCULATOR = 0xE021, + AL_LOCAL_MACHINE_BROWSER = 0xE06B, + + // AC stands for "application control" + AC_SEARCH = 0xE065, + AC_HOME = 0xE032, + AC_BACK = 0xE06A, + AC_FORWARD = 0xE069, + AC_STOP = 0xE068, + AC_REFRESH = 0xE067, + AC_BOOKMARKS = 0xE066, +} diff --git a/src/types.cpp b/src/types.cpp index 19df3de9d..0e885afab 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1248,6 +1248,10 @@ gb_internal bool is_type_unsigned(Type *t) { if (t->kind == Type_Basic) { return (t->Basic.flags & BasicFlag_Unsigned) != 0; } + if (t->kind == Type_Enum) { + // TODO(slowhei): Is an enum's base type guaranteed to be TypeKind::Basic? Even if its backing type is implicitly int? + return (t->Enum.base_type->Basic.flags & BasicFlag_Unsigned) != 0; + } return false; } gb_internal bool is_type_integer_128bit(Type *t) { -- cgit v1.2.3 From 1fbc5641c0212dc30ea514f69a640a6d1fb5bd11 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 26 Jun 2025 14:47:38 +0200 Subject: Add to `tests/internal` Turn repro code into a proper test, and delete superfluous files from Odin root. --- minimal_test.odin | 31 ---- real_test.odin | 227 ------------------------ src/types.cpp | 1 - tests/internal/test_signedness_comparisons.odin | 34 ++++ 4 files changed, 34 insertions(+), 259 deletions(-) delete mode 100644 minimal_test.odin delete mode 100644 real_test.odin create mode 100644 tests/internal/test_signedness_comparisons.odin (limited to 'src/types.cpp') diff --git a/minimal_test.odin b/minimal_test.odin deleted file mode 100644 index 29ed6f123..000000000 --- a/minimal_test.odin +++ /dev/null @@ -1,31 +0,0 @@ -package test - -import "core:fmt" - -main :: proc() { - // The bug involves the compiler handling enums backed by unsigned ints as signed ints - - // I have never seen it happen when working with enums directly - Test_Enum :: enum u32 { - SMALL_VALUE = 0xFF, - BIG_VALUE = 0xFF00_0000, // negative if interpreted as i32 - } - // These will all evaluate to false - fmt.printfln("Should be false. Got %t.", Test_Enum.SMALL_VALUE > Test_Enum.BIG_VALUE) - fmt.printfln("Should be false. Got %t.", Test_Enum(0xF) > Test_Enum.BIG_VALUE) - fmt.printfln("Should be false. Got %t.", Test_Enum(0xF) > Test_Enum(0xF000_0000)) - fmt.printfln("Should be false. Got %t.", Test_Enum.SMALL_VALUE > max(Test_Enum)) - fmt.printfln("Should be false. Got %t.", Test_Enum(0xF) > max(Test_Enum)) - - // But I have seen it happen when working with enums generically - test_proc :: proc(lhs: $T, rhs: T) -> bool { - return lhs > rhs - } - // The enum value comparisons below are the same as the comparisons in the block above - // These will all evaluate to true - fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum.SMALL_VALUE, Test_Enum.BIG_VALUE)) - fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum(0xF), Test_Enum.BIG_VALUE)) - fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum(0xF), Test_Enum(0xF000_0000))) - fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum.SMALL_VALUE, max(Test_Enum))) - fmt.printfln("Should be false. Got %t.", test_proc(Test_Enum(0xF), max(Test_Enum))) -} diff --git a/real_test.odin b/real_test.odin deleted file mode 100644 index d715708a4..000000000 --- a/real_test.odin +++ /dev/null @@ -1,227 +0,0 @@ -package test - -import "core:fmt" -import refl "core:reflect" - -main :: proc() { - // This test recreates how I discovered the bug - // I've copy-pasted some code from a project I'm working on - - // This will evaluate to false - should_be_true := refl.enum_value_has_name(Scancode.NUM_LOCK) - fmt.printfln("Should be true. Got %v.", should_be_true) -} - -/* -Scancodes for Win32 keyboard input: -https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes - -Win32 scancodes are derived from PS/2 Set 1 scancodes. -Win32 scancodes are 2 bytes, but PS/2 Set 1 contains scancodes longer than 2 bytes. -Win32 invents its own 2 byte scancodes to replace these long scancodes. -(E.g. Print Screen is 0xE02AE037 in PS/2 Set 1, but is 0xE037 in Win32.) -Table of Win32 scancodes: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes - -⚠️ WARNING: Some keys sort of share the same scancode due to new and legacy scancode definitions. -For some reason, Microsoft uses different scancodes in the context of keyboard messages (WM_KEYDOWN, and everything else in this -list: https://learn.microsoft.com/en-us/windows/win32/inputdev/keyboard-input-notifications). I call these "legacy scancodes." -E.g. Num Lock's scancode is 0xE045 in the context of WM_KEYDOWN, but MapVirtualKeyW(VK_NUMLOCK) returns 0x45. -This causes multiple physical keys to share the same scancode. -E.g. Num Lock's new scancode is 0x45, but Pause's legacy scancode is also 0x45. -But so long as you strictly use new scancodes are strictly use legacy scancodes, no physical keys will share a scancode. -Thanks Microsoft... >:^( - -⚠️ WARNING: There are still other edge cases: some physical keys have multiple scancodes, some scancodes are over 2 bytes, etc. -All such edge cases are commented next to the relevant enum value definitions. -Again, thanks Microsoft... >:^( -*/ -Scancode :: enum u16 { - POWER_DOWN = 0xE05E, - SLEEP = 0xE05F, - WAKE_UP = 0xE063, - - ERROR_ROLL_OVER = 0xFF, - - A = 0x1E, - B = 0x30, - C = 0x2E, - D = 0x20, - E = 0x12, - F = 0x21, - G = 0x22, - H = 0x23, - I = 0x17, - J = 0x24, - K = 0x25, - L = 0x26, - M = 0x32, - N = 0x31, - O = 0x18, - P = 0x19, - Q = 0x10, - R = 0x13, - S = 0x1F, - T = 0x14, - U = 0x16, - V = 0x2F, - W = 0x11, - X = 0x2D, - Y = 0x15, - Z = 0x2C, - _1 = 0x02, - _2 = 0x03, - _3 = 0x04, - _4 = 0x05, - _5 = 0x06, - _6 = 0x07, - _7 = 0x08, - _8 = 0x09, - _9 = 0x0A, - _0 = 0x0B, - - ENTER = 0x1C, - ESCAPE = 0x01, - DELETE = 0x0E, - TAB = 0x0F, - SPACEBAR = 0x39, - MINUS = 0x0C, - EQUALS = 0x0D, - L_BRACE = 0x1A, - R_BRACE = 0x1B, - BACKSLASH = 0x2B, - NON_US_HASH = 0x2B, - SEMICOLON = 0x27, - APOSTROPHE = 0x28, - GRAVE_ACCENT = 0x29, - COMMA = 0x33, - PERIOD = 0x34, - FORWARD_SLASH = 0x35, - CAPS_LOCK = 0x3A, - - F1 = 0x3B, - F2 = 0x3C, - F3 = 0x3D, - F4 = 0x3E, - F5 = 0x3F, - F6 = 0x40, - F7 = 0x41, - F8 = 0x42, - F9 = 0x43, - F10 = 0x44, - F11 = 0x57, - F12 = 0x58, - - // These are all the same physical key - // Windows maps Print Screen and Sys Rq to system-level shortcuts, so by default, WM_KEYDOWN does not report Print Screen or Sys Rq - PRINT_SCREEN = 0xE037, - PRINT_SCREEN_SYS_RQ = 0x54, // Alt + PrintScreen - - SCROLL_LOCK = 0x46, - - // These are all the same physical key - PAUSE = 0xE11D, // NOT used by legacy keyboard messages - // Win32 scancodes are 2 bytes, the documentation says Pause's scancode is 0xE11D45, which is 3 bytes??? - // MapVirtualKeyW(VK_PAUSE) returns 0xE11D, so we're just gonna use that - PAUSE_BREAK = 0xE046, // Ctrl + Pause - PAUSE_LEGACY = 0x45, // ONLY used by legacy keyboard messages - - INSERT = 0xE052, - HOME = 0xE047, - PAGE_UP = 0xE049, - DELETE_FORWARD = 0xE053, - END = 0xE04F, - PAGE_DOWN = 0xE051, - RIGHT_ARROW = 0xE04D, - LEFT_ARROW = 0xE04B, - DOWN_ARROW = 0xE050, - UP_ARROW = 0xE048, - - // These are all the same physical key - NUM_LOCK = 0x45, // NOT used by legacy keyboard messages - NUM_LOCK_LEGACY = 0xE045, // ONLY used by legacy keyboard messages - - KEYPAD_FORWARD_SLASH = 0xE035, - KEYPAD_STAR = 0x37, - KEYPAD_DASH = 0x4A, - KEYPAD_PLUS = 0x4E, - KEYPAD_ENTER = 0xE01C, - KEYPAD_1 = 0x4F, - KEYPAD_2 = 0x50, - KEYPAD_3 = 0x51, - KEYPAD_4 = 0x4B, - KEYPAD_5 = 0x4C, - KEYPAD_6 = 0x4D, - KEYPAD_7 = 0x47, - KEYPAD_8 = 0x48, - KEYPAD_9 = 0x49, - KEYPAD_0 = 0x52, - KEYPAD_PERIOD = 0x53, - NON_US_BACKSLASH = 0x56, - APPLICATION = 0xE05D, - POWER = 0xE05E, - KEYPAD_EQUALS = 0x59, - - F13 = 0x64, - F14 = 0x65, - F15 = 0x66, - F16 = 0x67, - F17 = 0x68, - F18 = 0x69, - F19 = 0x6A, - F20 = 0x6B, - F21 = 0x6C, - F22 = 0x6D, - F23 = 0x6E, - F24 = 0x76, - - KEYPAD_COMMA = 0x7E, // on Brazilian keyboards - INTERNATIONAL_1 = 0x73, // on Brazilian and Japanese keyboards - INTERNATIONAL_2 = 0x70, // on Japanese keyboards - INTERNATIONAL_3 = 0x7D, // on Japanese keyboards - INTERNATIONAL_4 = 0x79, // on Japanese keyboards - INTERNATIONAL_5 = 0x7B, // on Japanese keyboards - INTERNATIONAL_6 = 0x5C, - - // These are all the same physical key - LANG_1 = 0x72, // key release event only, NOT used by legacy keyboard messages - LANG_1_LEGACY = 0xF2, // key release event only, ONLY used by legacy keyboard messages - - // These are all the same physical key - LANG_2 = 0x71, // key release event only, NOT used by legacy keyboard messages - LANG_2_LEGACY = 0xF1, // key release event only, ONLY used by legacy keyboard messages - - LANG3 = 0x78, - LANG4 = 0x77, - LANG5 = 0x76, - L_CONTROL = 0x1D, - L_SHIFT = 0x2A, - L_ALT = 0x38, - L_GUI = 0xE05B, - R_CONTROL = 0xE01D, - R_SHIFT = 0x36, - R_ALT = 0xE038, - R_GUI = 0xE05C, - - NEXT_TRACK = 0xE019, - PREVIOUS_TRACK = 0xE010, - STOP = 0xE024, - PLAY_PAUSE = 0xE022, - MUTE = 0xE020, - VOLUME_INCREMENT = 0xE030, - VOLUME_DECREMENT = 0xE02E, - - // AL stands for "application launch" - AL_CONSUMER_CONTROL_CONFIGURATION = 0xE06D, - AL_EMAIL_READER = 0xE06C, - AL_CALCULATOR = 0xE021, - AL_LOCAL_MACHINE_BROWSER = 0xE06B, - - // AC stands for "application control" - AC_SEARCH = 0xE065, - AC_HOME = 0xE032, - AC_BACK = 0xE06A, - AC_FORWARD = 0xE069, - AC_STOP = 0xE068, - AC_REFRESH = 0xE067, - AC_BOOKMARKS = 0xE066, -} diff --git a/src/types.cpp b/src/types.cpp index 0e885afab..74da7f6aa 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1249,7 +1249,6 @@ gb_internal bool is_type_unsigned(Type *t) { return (t->Basic.flags & BasicFlag_Unsigned) != 0; } if (t->kind == Type_Enum) { - // TODO(slowhei): Is an enum's base type guaranteed to be TypeKind::Basic? Even if its backing type is implicitly int? return (t->Enum.base_type->Basic.flags & BasicFlag_Unsigned) != 0; } return false; diff --git a/tests/internal/test_signedness_comparisons.odin b/tests/internal/test_signedness_comparisons.odin new file mode 100644 index 000000000..5e7431c7a --- /dev/null +++ b/tests/internal/test_signedness_comparisons.odin @@ -0,0 +1,34 @@ +package test_internal + +import "core:testing" + +@test +test_comparisons_5408 :: proc(t: ^testing.T) { + // See: https://github.com/odin-lang/Odin/pull/5408 + test_proc :: proc(lhs: $T, rhs: T) -> bool { + return lhs > rhs + } + + Test_Enum :: enum u32 { + SMALL_VALUE = 0xFF, + BIG_VALUE = 0xFF00_0000, // negative if interpreted as i32 + } + + testing.expect_value(t, test_proc(Test_Enum.SMALL_VALUE, Test_Enum.BIG_VALUE), false) + testing.expect_value(t, test_proc(Test_Enum(0xF), Test_Enum.BIG_VALUE), false) + testing.expect_value(t, test_proc(Test_Enum(0xF), Test_Enum(0xF000_0000)), false) + testing.expect_value(t, test_proc(Test_Enum.SMALL_VALUE, max(Test_Enum)), false) + testing.expect_value(t, test_proc(Test_Enum(0xF), max(Test_Enum)), false) +} + +test_signedness :: proc(t: ^testing.T) { + { + a, b := i16(32767), i16(0) + testing.expect_value(t, a > b, true) + } + + { + a, b := u16(65535), u16(0) + testing.expect_value(t, a > b, true) + } +} \ No newline at end of file -- cgit v1.2.3