diff --git a/lib/std/compression/qoi.c3 b/lib/std/compression/qoi.c3 index 4d55d6290..1b4a1fb09 100644 --- a/lib/std/compression/qoi.c3 +++ b/lib/std/compression/qoi.c3 @@ -471,7 +471,7 @@ bitstruct OpDiff : char char diff_green : 2..3; char diff_blue : 0..1; } -bitstruct OpLuma : ushort +bitstruct OpLuma : ushort @align(1) { char tag : 6..7; char diff_green : 0..5; diff --git a/lib/std/hash/md5.c3 b/lib/std/hash/md5.c3 index 50d57b39b..74f71479c 100644 --- a/lib/std/hash/md5.c3 +++ b/lib/std/hash/md5.c3 @@ -108,7 +108,7 @@ macro @i(x, y, z) => y ^ (x | ~z); macro @step(#f, a, b, c, d, ptr, n, t, s) { - *a += #f(b, c, d) + *(uint *)&ptr[n * 4] + t; + *a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t; *a = (*a << s) | ((*a & 0xffffffff) >> (32 - s)); *a += b; } diff --git a/releasenotes.md b/releasenotes.md index 83e0a4191..373b70cbc 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -22,6 +22,7 @@ - Allow `fn int test() => @pool() { return 1; }` short function syntax usage #1906. - Test runner will also check for leaks. - Improve inference on `??` #1943. +- Detect unaligned loads #1951. ### Fixes - Fix issue requiring prefix on a generic interface declaration. @@ -59,6 +60,7 @@ - `write` of qoi would leak memory. - Issue when having an empty `Path` or just "." - `set_env` would leak memory. +- Fix issue where aligned bitstructs did not store/load with the given alignment. ### Stdlib changes - Added '%h' and '%H' for printing out binary data in hexadecimal using the formatter. diff --git a/src/compiler/codegen_internal.h b/src/compiler/codegen_internal.h index ce09955c9..49c8b4a0e 100644 --- a/src/compiler/codegen_internal.h +++ b/src/compiler/codegen_internal.h @@ -13,7 +13,6 @@ enum IntrospectIndex }; bool type_is_homogenous_aggregate(Type *type, Type **base, unsigned *elements); -static inline Type *type_reduced_from_expr(Expr *expr); static inline bool abi_type_is_type(AbiType type); static inline bool abi_type_is_valid(AbiType type); @@ -90,11 +89,6 @@ static inline Type *type_lowering(Type *type) } } -static inline Type *type_reduced_from_expr(Expr *expr) -{ - return type_lowering(expr->type); -} - static inline bool abi_type_is_type(AbiType type) { return !(type.int_bits_plus_1 & 0x01); diff --git a/src/compiler/llvm_codegen_builtins.c b/src/compiler/llvm_codegen_builtins.c index 71b87feda..bdeb50478 100644 --- a/src/compiler/llvm_codegen_builtins.c +++ b/src/compiler/llvm_codegen_builtins.c @@ -160,12 +160,15 @@ INLINE void llvm_emit_atomic_store(GenContext *c, BEValue *result_value, Expr *e INLINE void llvm_emit_unaligned_store(GenContext *c, BEValue *result_value, Expr *expr) { + bool emit_check = c->emitting_load_store_check; + c->emitting_load_store_check = true; BEValue value; llvm_emit_expr(c, &value, expr->call_expr.arguments[0]); llvm_emit_expr(c, result_value, expr->call_expr.arguments[1]); llvm_value_deref(c, &value); value.alignment = expr->call_expr.arguments[2]->const_expr.ixx.i.low; llvm_store(c, &value, result_value); + c->emitting_load_store_check = emit_check; } INLINE void llvm_emit_atomic_fetch(GenContext *c, BuiltinFunction func, BEValue *result_value, Expr *expr) @@ -241,10 +244,13 @@ INLINE void llvm_emit_atomic_load(GenContext *c, BEValue *result_value, Expr *ex INLINE void llvm_emit_unaligned_load(GenContext *c, BEValue *result_value, Expr *expr) { + bool emit_check = c->emitting_load_store_check; + c->emitting_load_store_check = true; llvm_emit_expr(c, result_value, expr->call_expr.arguments[0]); llvm_value_deref(c, result_value); result_value->alignment = expr->call_expr.arguments[1]->const_expr.ixx.i.low; llvm_value_rvalue(c, result_value); + c->emitting_load_store_check = emit_check; } static inline LLVMValueRef llvm_syscall_asm(LLVMTypeRef func_type, char *call) diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 1a741be74..698988fd0 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -2286,6 +2286,7 @@ static inline void llvm_emit_deref(GenContext *c, BEValue *value, Expr *inner, T } llvm_emit_expr(c, value, inner); llvm_value_rvalue(c, value); + AlignSize alignment = type_abi_alignment(type); if (safe_mode_enabled()) { LLVMValueRef check = LLVMBuildICmp(c->builder, LLVMIntEQ, value->value, llvm_get_zero(c, inner->type), "checknull"); @@ -2294,11 +2295,28 @@ static inline void llvm_emit_deref(GenContext *c, BEValue *value, Expr *inner, T span_to_scratch(inner->span); scratch_buffer_append("' was null."); llvm_emit_panic_on_true(c, check, scratch_buffer_to_string(), inner->span, NULL, NULL, NULL); + if (alignment > 1 && !c->emitting_load_store_check) + { + LLVMValueRef as_int = LLVMBuildPtrToInt(c->builder, value->value, llvm_get_type(c, type_usz), ""); + LLVMValueRef align = llvm_const_int(c, type_usz, alignment); + LLVMValueRef rem = LLVMBuildURem(c->builder, as_int, align, ""); + LLVMValueRef is_not_zero = LLVMBuildICmp(c->builder, LLVMIntNE, rem, llvm_get_zero(c, type_usz), ""); + c->emitting_load_store_check = true; + BEValue value1; + BEValue value2; + if (inner->type->name ) + llvm_value_set(&value1, align, type_usz); + llvm_value_set(&value2, rem, type_usz); + llvm_emit_panic_on_true(c, is_not_zero, "Unaligned pointer access detected", inner->span, "Unaligned access: ptr %% %s = %s, use @unaligned_load / @unaligned_store for unaligned access.", + &value1, &value2); + c->emitting_load_store_check = false; + } + } // Convert pointer to address value->kind = BE_ADDRESS; - value->type = type; - value->alignment = type_abi_alignment(type); + value->type = type_lowering(type); + value->alignment = alignment; } /** @@ -2342,7 +2360,7 @@ static void llvm_emit_dynamic_method_addr(GenContext *c, BEValue *value, Expr *e static void llvm_emit_unary_expr(GenContext *c, BEValue *value, Expr *expr) { - Type *type = type_reduced_from_expr(expr->unary_expr.expr); + Type *type = type_lowering(expr->unary_expr.expr->type); Expr *inner = expr->unary_expr.expr; switch (expr->unary_expr.operator) @@ -2430,7 +2448,7 @@ static void llvm_emit_unary_expr(GenContext *c, BEValue *value, Expr *expr) value->type = type_lowering(expr->type); return; case UNARYOP_DEREF: - llvm_emit_deref(c, value, inner, type_lowering(expr->type)); + llvm_emit_deref(c, value, inner, expr->type); return; case UNARYOP_INC: llvm_emit_pre_inc_dec(c, value, inner, 1, !expr->unary_expr.no_wrap); @@ -4506,7 +4524,7 @@ static inline void llvm_emit_const_initializer_list_expr(GenContext *c, BEValue static void llvm_emit_const_expr(GenContext *c, BEValue *be_value, Expr *expr) { - Type *type = type_reduced_from_expr(expr)->canonical; + Type *type = type_lowering(expr->type)->canonical; bool is_bytes = false; switch (expr->const_expr.const_kind) { diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 34c357e08..c3a626c9c 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -84,6 +84,7 @@ typedef struct GenContext_ bool shared_context; bool in_init_ref; bool weaken; + bool emitting_load_store_check; LLVMModuleRef module; LLVMBuilderRef global_builder; LLVMTargetMachineRef machine; diff --git a/src/compiler/types.c b/src/compiler/types.c index c694b5f21..9654e8fef 100644 --- a/src/compiler/types.c +++ b/src/compiler/types.c @@ -683,6 +683,7 @@ AlignSize type_abi_alignment(Type *type) case TYPE_WILDCARD: UNREACHABLE; case TYPE_BITSTRUCT: + if (type->decl->alignment) return type->decl->alignment; type = type->decl->strukt.container_type->type; goto RETRY; case TYPE_INFERRED_VECTOR: diff --git a/test/test_suite/bitstruct/bitstruct_align.c3t b/test/test_suite/bitstruct/bitstruct_align.c3t new file mode 100644 index 000000000..67e353273 --- /dev/null +++ b/test/test_suite/bitstruct/bitstruct_align.c3t @@ -0,0 +1,40 @@ +// #target: macos-x64 +module test; +import std::io; + +Foo y; +bitstruct Foo : ushort @align(1) +{ + int a : 0..10; +} +fn int main(String[] args) +{ + Foo z = y; + Foo* y = &z; + y.a = 123; + return 0; +} + +/* #expect: test.ll + +@test.y = local_unnamed_addr global i16 0, align 1 + +define i32 @test.main(ptr %0, i64 %1) #0 { +entry: + %args = alloca %"char[][]", align 8 + %z = alloca i16, align 1 + %y = alloca ptr, align 8 + store ptr %0, ptr %args, align 8 + %ptradd = getelementptr inbounds i8, ptr %args, i64 8 + store i64 %1, ptr %ptradd, align 8 + %2 = load i16, ptr @test.y, align 1 + store i16 %2, ptr %z, align 1 + store ptr %z, ptr %y, align 8 + %3 = load ptr, ptr %y, align 8 + %4 = load i16, ptr %3, align 1 + %5 = and i16 %4, -2048 + %6 = or i16 %5, 123 + store i16 %6, ptr %3, align 1 + ret i32 0 +} + diff --git a/test/test_suite/builtins/unaligned_access.c3t b/test/test_suite/builtins/unaligned_access.c3t new file mode 100644 index 000000000..4963b6fcf --- /dev/null +++ b/test/test_suite/builtins/unaligned_access.c3t @@ -0,0 +1,82 @@ +// #target: macos-x64 +// #safe: yes +module test; +import std::io; + +fn int main() +{ + char[10] buf; + + int* i = (int*)(&buf[1]); + int z = *i; + int z2 = @unaligned_load(*i, 1); + *i = 5; + @unaligned_store(*i, 6, 1); + int y = *i; + + return 0; +} + +/* #expect: test.ll + + store ptr %ptradd9, ptr %i, align 8 + %0 = load ptr, ptr %i, align 8 + %checknull = icmp eq ptr %0, null + %1 = call i1 @llvm.expect.i1(i1 %checknull, i1 false) + br i1 %1, label %panic, label %checkok + +checkok: ; preds = %entry + %2 = ptrtoint ptr %0 to i64 + %3 = urem i64 %2, 4 + %4 = icmp ne i64 %3, 0 + %5 = call i1 @llvm.expect.i1(i1 %4, i1 false) + br i1 %5, label %panic10, label %checkok13 + +checkok13: ; preds = %checkok + %6 = load i32, ptr %0, align 4 + store i32 %6, ptr %z, align 4 + %7 = load ptr, ptr %i, align 8 + %checknull14 = icmp eq ptr %7, null + %8 = call i1 @llvm.expect.i1(i1 %checknull14, i1 false) + br i1 %8, label %panic15, label %checkok16 + +checkok16: ; preds = %checkok13 + %9 = load i32, ptr %7, align 1 + store i32 %9, ptr %z2, align 4 + %10 = load ptr, ptr %i, align 8 + %checknull17 = icmp eq ptr %10, null + %11 = call i1 @llvm.expect.i1(i1 %checknull17, i1 false) + br i1 %11, label %panic18, label %checkok19 + +checkok19: ; preds = %checkok16 + %12 = ptrtoint ptr %10 to i64 + %13 = urem i64 %12, 4 + %14 = icmp ne i64 %13, 0 + %15 = call i1 @llvm.expect.i1(i1 %14, i1 false) + br i1 %15, label %panic20, label %checkok27 + +checkok27: ; preds = %checkok19 + store i32 5, ptr %10, align 4 + %16 = load ptr, ptr %i, align 8 + %checknull28 = icmp eq ptr %16, null + %17 = call i1 @llvm.expect.i1(i1 %checknull28, i1 false) + br i1 %17, label %panic29, label %checkok30 + +checkok30: ; preds = %checkok27 + store i32 6, ptr %16, align 1 + %18 = load ptr, ptr %i, align 8 + %checknull31 = icmp eq ptr %18, null + %19 = call i1 @llvm.expect.i1(i1 %checknull31, i1 false) + br i1 %19, label %panic32, label %checkok33 + +checkok33: ; preds = %checkok30 + %20 = ptrtoint ptr %18 to i64 + %21 = urem i64 %20, 4 + %22 = icmp ne i64 %21, 0 + %23 = call i1 @llvm.expect.i1(i1 %22, i1 false) + br i1 %23, label %panic34, label %checkok41 + +checkok41: ; preds = %checkok33 + %24 = load i32, ptr %18, align 4 + store i32 %24, ptr %y, align 4 + ret i32 0 diff --git a/test/test_suite/safe/deref.c3t b/test/test_suite/safe/deref.c3t index b2d6fb6f2..b97045d65 100644 --- a/test/test_suite/safe/deref.c3t +++ b/test/test_suite/safe/deref.c3t @@ -14,18 +14,47 @@ define void @foo.main() #0 { entry: %x = alloca ptr, align 8 %y = alloca i32, align 4 + %taddr = alloca i64, align 8 + %taddr2 = alloca i64, align 8 + %varargslots = alloca [2 x %any], align 16 + %indirectarg = alloca %"any[]", align 8 store ptr null, ptr %x, align 8 %0 = load ptr, ptr %x, align 8 %checknull = icmp eq ptr %0, null %1 = call i1 @llvm.expect.i1(i1 %checknull, i1 false) br i1 %1, label %panic, label %checkok + checkok: ; preds = %entry - %2 = load i32, ptr %0, align 4 - store i32 %2, ptr %y, align 4 + %2 = ptrtoint ptr %0 to i64 + %3 = urem i64 %2, 4 + %4 = icmp ne i64 %3, 0 + %5 = call i1 @llvm.expect.i1(i1 %4, i1 false) + br i1 %5, label %panic1, label %checkok3 + +checkok3: ; preds = %checkok + %6 = load i32, ptr %0, align 4 + store i32 %6, ptr %y, align 4 ret void + panic: ; preds = %entry - %3 = load ptr, ptr @std.core.builtin.panic, align 8 - call void %3(ptr @.panic_msg, i64 42, ptr @.file, i64 8, ptr @.func, i64 4, i32 6) + %7 = load ptr, ptr @std.core.builtin.panic, align 8 + call void %7(ptr @.panic_msg, i64 42, ptr @.file, i64 8, ptr @.func, i64 4, i32 6) #2 + unreachable + +panic1: ; preds = %checkok + store i64 4, ptr %taddr, align 8 + %8 = insertvalue %any undef, ptr %taddr, 0 + %9 = insertvalue %any %8, i64 ptrtoint (ptr @"$ct.ulong" to i64), 1 + store i64 %3, ptr %taddr2, align 8 + %10 = insertvalue %any undef, ptr %taddr2, 0 + %11 = insertvalue %any %10, i64 ptrtoint (ptr @"$ct.ulong" to i64), 1 + store %any %9, ptr %varargslots, align 16 + %ptradd = getelementptr inbounds i8, ptr %varargslots, i64 16 + store %any %11, ptr %ptradd, align 16 + %12 = insertvalue %"any[]" undef, ptr %varargslots, 0 + %"$$temp" = insertvalue %"any[]" %12, i64 2, 1 + store %"any[]" %"$$temp", ptr %indirectarg, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg.1, i64 94, ptr @.file, i64 8, ptr @.func, i64 4, i32 6, ptr byval(%"any[]") align 8 %indirectarg) #2 unreachable }