From 842788e59ddc5788ddbacb9ccc0df6cb7b00753a Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 18 Jun 2025 17:02:56 +0200 Subject: [PATCH] `x += 1` and `x -= 1` works propertly on pointer vectors #2222. --- releasenotes.md | 1 + src/compiler/compiler_internal.h | 28 ++++- src/compiler/llvm_codegen_expr.c | 8 +- src/compiler/sema_expr.c | 74 +++++++----- .../test_suite/distinct/disntinct_add_fail.c3 | 4 +- .../enumerations/inc_assign_fail.c3 | 2 +- .../expressions/vector_ptr.assign_add_sub.c3t | 113 ++++++++++++++++++ 7 files changed, 192 insertions(+), 38 deletions(-) create mode 100644 test/test_suite/expressions/vector_ptr.assign_add_sub.c3t diff --git a/releasenotes.md b/releasenotes.md index 5230fc178..086993d0c 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -50,6 +50,7 @@ - Linker errors when shadowing @local with public function #2198 - Bug when offsetting pointers of large structs using ++ and --. - `x++` and `x--` works on pointer vectors #2222. +- `x += 1` and `x -= 1` works propertly on pointer vectors #2222. ### Stdlib changes - Deprecate `String.is_zstr` and `String.quick_zstr` #2188. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index bffb7d6cf..87467c024 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2536,7 +2536,7 @@ INLINE bool type_is_integer_or_bool_kind(Type *type); INLINE bool type_is_numeric(Type *type); INLINE bool type_is_inferred(Type *type); INLINE bool type_underlying_is_numeric(Type *type); -INLINE bool type_underlying_may_add_sub(Type *type); +INLINE bool type_underlying_may_add_sub(CanonicalType *type); INLINE bool type_is_pointer(Type *type); INLINE bool type_is_arraylike(Type *type); INLINE bool type_is_any_arraylike(Type *type); @@ -2989,6 +2989,7 @@ static inline Type *type_base(Type *type) } } } + static inline Type *type_flat_distinct_inline(Type *type) { do @@ -3169,6 +3170,26 @@ INLINE bool type_is_number(Type *type) return (kind >= TYPE_I8) && (kind <= TYPE_FLOAT_LAST); } +static inline Type *type_flat_for_arithmethics(Type *type) +{ + do + { + type = type->canonical; + if (type->type_kind != TYPE_DISTINCT) break; + Decl *decl = type->decl; + Type *inner = decl->distinct->type; + if (decl->is_substruct) + { + type = inner; + continue; + } + inner = type_flat_for_arithmethics(inner); + if (type_is_number_or_bool(inner)) return inner; + break; + } while (1); + return type; +} + INLINE bool type_is_numeric(Type *type) { RETRY:; @@ -3187,10 +3208,9 @@ INLINE bool type_underlying_is_numeric(Type *type) return type_is_numeric(type_flatten(type)); } -INLINE bool type_underlying_may_add_sub(Type *type) +INLINE bool type_underlying_may_add_sub(CanonicalType *type) { - type = type_flatten(type); - return type->type_kind == TYPE_ENUM || type->type_kind == TYPE_POINTER || type_is_numeric(type_flatten(type)); + return type->type_kind == TYPE_ENUM || type->type_kind == TYPE_POINTER || type_is_numeric(type); } INLINE bool type_flat_is_vector(Type *type) diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index c955b1beb..4acb81ce8 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -4057,7 +4057,7 @@ void llvm_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs val = llvm_emit_pointer_gep_raw(c, llvm_get_type(c, element_type), lhs_value, rhs_value); break; } - else if (lhs_type->type_kind == TYPE_POINTER) + if (lhs_type->type_kind == TYPE_POINTER) { if (lhs_type == rhs_type) { @@ -4079,6 +4079,12 @@ void llvm_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs val = llvm_emit_sub_int(c, lhs_type, lhs_value, rhs_value, expr->span); break; case BINARYOP_ADD: + if (type_is_pointer_vector(lhs_type)) + { + Type *element_type = lhs_type->array.base->pointer; + val = llvm_emit_pointer_gep_raw(c, llvm_get_type(c, element_type), lhs_value, rhs_value); + break; + } if (lhs_type->type_kind == TYPE_POINTER) { ASSERT(type_is_integer(rhs_type)); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index a5f6ee3a4..5f161bc9f 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -6703,7 +6703,6 @@ static bool sema_expr_analyse_op_assign(SemaContext *context, Expr *expr, Expr * if (!sema_cast_rvalue(context, left, false)) return false; Type *no_fail = type_no_optional(left->type); - Type *flat = type_flatten(no_fail); Type *canonical = no_fail->canonical; if (type_is_user_defined(canonical)) @@ -6741,16 +6740,19 @@ static bool sema_expr_analyse_op_assign(SemaContext *context, Expr *expr, Expr * return sema_rewrite_op_assign(context, expr, left, right, underlying_op); } } -SKIP_OVERLOAD_CHECK: +SKIP_OVERLOAD_CHECK:; // 3. If this is only defined for ints (^= |= &= %=) verify that this is an int. - if (int_only && !type_flat_is_intlike(flat)) + Type *flat = type_flat_for_arithmethics(no_fail); + Type *base = flat->type_kind == TYPE_VECTOR ? type_flat_for_arithmethics(flat->array.base) : flat; + + if (int_only && !type_is_integer(base)) { if (is_bit_op && (flat->type_kind == TYPE_BITSTRUCT || flat == type_bool || type_flat_is_bool_vector(flat))) goto BITSTRUCT_OK; RETURN_SEMA_ERROR(left, "Expected an integer here, not a value of type %s.", type_quoted_error_string(left->type)); } // 4. In any case, these ops are only defined on numbers. - if (!type_underlying_is_numeric(flat) && !(is_add_sub && type_underlying_may_add_sub(left->type))) + if (!type_is_numeric(base) && !(is_add_sub && type_underlying_may_add_sub(base))) { RETURN_SEMA_ERROR(left, "Expected a numeric type here, not a value of type %s.", type_quoted_error_string(left->type)); } @@ -6777,48 +6779,60 @@ BITSTRUCT_OK: expr->type = left->type; bool optional = IS_OPTIONAL(left) || IS_OPTIONAL(right); + Type *type_rhs_inline = type_flat_distinct_inline(type_no_optional(right->type->canonical)); + // 5. In the pointer case we have to treat this differently. - if (flat->type_kind == TYPE_ENUM) + if (base->type_kind == TYPE_ENUM) { - if (type_flat_distinct_inline(no_fail)->type_kind != TYPE_ENUM) - { - RETURN_SEMA_ERROR(expr, "A value of type %s cannot be added to or subtracted from.", type_quoted_error_string(left->type)); - } // 7. Finally, check that the right side is indeed an integer. - if (!type_is_integer(right->type->canonical)) + if (flat == base) { - RETURN_SEMA_ERROR(right, - "The right side was '%s' but only integers are valid on the right side of %s when the left side is an enum.", - type_to_error_string(right->type), - token_type_to_string(binaryop_to_token(expr->binary_expr.operator))); + if (!type_is_integer(type_rhs_inline)) + { + RETURN_SEMA_ERROR(right, + "The right side was '%s' but only integers are valid on the right side of %s when the left side is an enum.", + type_to_error_string(right->type), + token_type_to_string(binaryop_to_token(expr->binary_expr.operator))); + } + } + else + { + if (!type_is_integer(type_rhs_inline) && (type_rhs_inline->type_kind != TYPE_VECTOR || !type_is_integer(type_rhs_inline->array.base))) + { + RETURN_SEMA_ERROR(right, + "The right side was '%s' but only integers or integer vectors are valid on the right side of %s when the left side is an enum vector.", + type_to_error_string(right->type), + token_type_to_string(binaryop_to_token(expr->binary_expr.operator))); + } } if (!cast_implicit(context, right, flat->decl->enums.type_info->type, false)) return false; goto END; } - if (type_is_pointer_like(flat)) + if (base->type_kind == TYPE_POINTER) { - // Not inline pointer-like - if (!type_is_pointer_like(no_fail)) + if (flat == base) { - RETURN_SEMA_ERROR(expr, "A value of type %s cannot be added to or subtracted from.", type_quoted_error_string(left->type)); + if (!type_is_integer(type_rhs_inline)) + { + RETURN_SEMA_ERROR(right, + "The right side was '%s' but only integers are valid on the right side of %s when the left side is a pointer.", + type_to_error_string(right->type), + token_type_to_string(binaryop_to_token(expr->binary_expr.operator))); + } } - // 7. Finally, check that the right side is indeed an integer. - if (!type_is_integer(right->type->canonical)) + else { - RETURN_SEMA_ERROR(right, - "The right side was '%s' but only integers are valid on the right side of %s when the left side is a pointer.", - type_to_error_string(right->type), - token_type_to_string(binaryop_to_token(expr->binary_expr.operator))); + if (!type_is_integer(type_rhs_inline) && (type_rhs_inline->type_kind != TYPE_VECTOR || !type_is_integer(type_rhs_inline->array.base))) + { + RETURN_SEMA_ERROR(right, + "The right side was '%s' but only integer vectors are valid on the right side of %s when the left side is a pointer vector.", + type_to_error_string(right->type), + token_type_to_string(binaryop_to_token(expr->binary_expr.operator))); + } } goto END; } - if (flat->type_kind == TYPE_ENUM) - { - if (!cast_implicit(context, right, type_base(flat), false)) return false; - goto END; - } - if (is_shift) { if (!sema_expr_check_shift_rhs(context, expr, left->type, type_flatten(left->type), right, type_flatten(right->type), failed_ref)) diff --git a/test/test_suite/distinct/disntinct_add_fail.c3 b/test/test_suite/distinct/disntinct_add_fail.c3 index d90e42d62..1185d3c6d 100644 --- a/test/test_suite/distinct/disntinct_add_fail.c3 +++ b/test/test_suite/distinct/disntinct_add_fail.c3 @@ -7,6 +7,6 @@ fn void main() DString y; y ^= 1; // #error: Expected an integer here - y += 1.0; // #error: A value of type 'DString' - y += 1; // #error: A value of type 'DString' + y += 1.0; // #error: Expected a numeric type here + y += 1; // #error: Expected a numeric type here } diff --git a/test/test_suite/enumerations/inc_assign_fail.c3 b/test/test_suite/enumerations/inc_assign_fail.c3 index b9c76521b..54604483c 100644 --- a/test/test_suite/enumerations/inc_assign_fail.c3 +++ b/test/test_suite/enumerations/inc_assign_fail.c3 @@ -16,5 +16,5 @@ fn void main() y ^= 1; // #error: Expected an integer here, not a value of type y += 1.0; // #error: The right side was 'double' Bob gh; - gh += 1; // #error: A value of type 'Bob' + gh += 1; // #error: value of type 'Bob' } diff --git a/test/test_suite/expressions/vector_ptr.assign_add_sub.c3t b/test/test_suite/expressions/vector_ptr.assign_add_sub.c3t new file mode 100644 index 000000000..ca40dbf67 --- /dev/null +++ b/test/test_suite/expressions/vector_ptr.assign_add_sub.c3t @@ -0,0 +1,113 @@ +// #target: macos-x64 +module test; +import std::io; +const FOO_SIZE = 500; + +alias Foo = char[FOO_SIZE]; + +fn int main() +{ + const NUM = 10; + + Foo* x = calloc(Foo.sizeof * NUM); + Foo*[<1>] z = { x }; + Foo*[<1>] g = { x }; + Foo*[<2>] z2 = { x, x }; + Foo*[<2>] g2 = { x, x }; + for (int i = 0; i < 10; i++) + { + z++; + g--; + z2 += 1; + g2 -= 1; + z2 = z2 + 1; + g2 = g2 - 1; + z++; + g--; + assert(z2.x == z.x); + assert(g2.x == g.x); + } + return 0; +} + +/* #expect: test.ll + +define i32 @main() #0 { +entry: + %x = alloca ptr, align 8 + %z = alloca <1 x ptr>, align 8 + %g = alloca <1 x ptr>, align 8 + %z2 = alloca <2 x ptr>, align 16 + %g2 = alloca <2 x ptr>, align 16 + %i = alloca i32, align 4 + %0 = call ptr @std.core.mem.calloc(i64 5000) #2 + store ptr %0, ptr %x, align 8 + %1 = load ptr, ptr %x, align 8 + %2 = insertelement <1 x ptr> undef, ptr %1, i64 0 + store <1 x ptr> %2, ptr %z, align 8 + %3 = load ptr, ptr %x, align 8 + %4 = insertelement <1 x ptr> undef, ptr %3, i64 0 + store <1 x ptr> %4, ptr %g, align 8 + %5 = load ptr, ptr %x, align 8 + %6 = insertelement <2 x ptr> undef, ptr %5, i64 0 + %7 = load ptr, ptr %x, align 8 + %8 = insertelement <2 x ptr> %6, ptr %7, i64 1 + store <2 x ptr> %8, ptr %z2, align 16 + %9 = load ptr, ptr %x, align 8 + %10 = insertelement <2 x ptr> undef, ptr %9, i64 0 + %11 = load ptr, ptr %x, align 8 + %12 = insertelement <2 x ptr> %10, ptr %11, i64 1 + store <2 x ptr> %12, ptr %g2, align 16 + store i32 0, ptr %i, align 4 + br label %loop.cond + +loop.cond: ; preds = %loop.body, %entry + %13 = load i32, ptr %i, align 4 + %lt = icmp slt i32 %13, 10 + br i1 %lt, label %loop.body, label %loop.exit + +loop.body: ; preds = %loop.cond + %14 = load <1 x ptr>, ptr %z, align 8 + %ptroffset_any = getelementptr [500 x i8], <1 x ptr> %14, <1 x i64> + store <1 x ptr> %ptroffset_any, ptr %z, align 8 + %15 = load <1 x ptr>, ptr %g, align 8 + %ptroffset_any1 = getelementptr [500 x i8], <1 x ptr> %15, <1 x i64> + store <1 x ptr> %ptroffset_any1, ptr %g, align 8 + %16 = load <2 x ptr>, ptr %z2, align 16 + %ptradd_any = getelementptr i8, <2 x ptr> %16, i32 500 + store <2 x ptr> %ptradd_any, ptr %z2, align 16 + %17 = load <2 x ptr>, ptr %g2, align 16 + %ptradd_any2 = getelementptr i8, <2 x ptr> %17, i32 -500 + store <2 x ptr> %ptradd_any2, ptr %g2, align 16 + %18 = load <2 x ptr>, ptr %z2, align 16 + %ptradd_any3 = getelementptr i8, <2 x ptr> %18, <2 x i64> + store <2 x ptr> %ptradd_any3, ptr %z2, align 16 + %19 = load <2 x ptr>, ptr %g2, align 16 + %ptradd_any4 = getelementptr i8, <2 x ptr> %19, <2 x i64> + store <2 x ptr> %ptradd_any4, ptr %g2, align 16 + %20 = load <1 x ptr>, ptr %z, align 8 + %ptroffset_any5 = getelementptr [500 x i8], <1 x ptr> %20, <1 x i64> + store <1 x ptr> %ptroffset_any5, ptr %z, align 8 + %21 = load <1 x ptr>, ptr %g, align 8 + %ptroffset_any6 = getelementptr [500 x i8], <1 x ptr> %21, <1 x i64> + store <1 x ptr> %ptroffset_any6, ptr %g, align 8 + %22 = load <2 x ptr>, ptr %z2, align 16 + %23 = extractelement <2 x ptr> %22, i64 0 + %24 = load <1 x ptr>, ptr %z, align 8 + %25 = extractelement <1 x ptr> %24, i64 0 + %eq = icmp eq ptr %23, %25 + call void @llvm.assume(i1 %eq) + %26 = load <2 x ptr>, ptr %g2, align 16 + %27 = extractelement <2 x ptr> %26, i64 0 + %28 = load <1 x ptr>, ptr %g, align 8 + %29 = extractelement <1 x ptr> %28, i64 0 + %eq7 = icmp eq ptr %27, %29 + call void @llvm.assume(i1 %eq7) + %30 = load i32, ptr %i, align 4 + %add = add i32 %30, 1 + store i32 %add, ptr %i, align 4 + br label %loop.cond + +loop.exit: ; preds = %loop.cond + ret i32 0 +}