Commit 3ee9d06cbd

Andrew Kelley <andrew@ziglang.org>
2019-02-21 04:40:41
packed structs support comptime bitcasting
* `type_size_store` is no longer a thing. loading and storing a pointer to a value may dereference up to `@sizeOf(T)` bytes, even for integers such as `u24`. * fix `types_have_same_zig_comptime_repr` to not think that the same `ZigTypeId` means the `ConstExprValue` neccesarily has the same representation. * implement `buf_write_value_bytes` and `buf_read_value_bytes` for `ContainerLayoutPacked` closes #1120
1 parent 0797287
src/analyze.cpp
@@ -359,28 +359,6 @@ uint64_t type_size(CodeGen *g, ZigType *type_entry) {
     return LLVMABISizeOfType(g->target_data_ref, type_entry->type_ref);
 }
 
-uint64_t type_size_store(CodeGen *g, ZigType *type_entry) {
-    assert(type_is_complete(type_entry));
-
-    if (!type_has_bits(type_entry))
-        return 0;
-
-    if (type_entry->id == ZigTypeIdStruct && type_entry->data.structure.layout == ContainerLayoutPacked) {
-        uint64_t size_in_bits = type_size_bits(g, type_entry);
-        return (size_in_bits + 7) / 8;
-    } else if (type_entry->id == ZigTypeIdArray) {
-        ZigType *child_type = type_entry->data.array.child_type;
-        if (child_type->id == ZigTypeIdStruct &&
-            child_type->data.structure.layout == ContainerLayoutPacked)
-        {
-            uint64_t size_in_bits = type_size_bits(g, type_entry);
-            return (size_in_bits + 7) / 8;
-        }
-    }
-
-    return LLVMStoreSizeOfType(g->target_data_ref, type_entry->type_ref);
-}
-
 uint64_t type_size_bits(CodeGen *g, ZigType *type_entry) {
     assert(type_is_complete(type_entry));
 
src/analyze.hpp
@@ -19,7 +19,6 @@ ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const);
 ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_const,
         bool is_volatile, PtrLen ptr_len, uint32_t byte_alignment, uint32_t bit_offset, uint32_t unaligned_bit_count);
 uint64_t type_size(CodeGen *g, ZigType *type_entry);
-uint64_t type_size_store(CodeGen *g, ZigType *type_entry);
 uint64_t type_size_bits(CodeGen *g, ZigType *type_entry);
 ZigType *get_int_type(CodeGen *g, bool is_signed, uint32_t size_in_bits);
 ZigType *get_vector_type(CodeGen *g, uint32_t len, ZigType *elem_type);
src/codegen.cpp
@@ -3070,7 +3070,10 @@ static LLVMValueRef ir_render_bit_cast(CodeGen *g, IrExecutable *executable,
 {
     ZigType *wanted_type = instruction->base.value.type;
     LLVMValueRef value = ir_llvm_value(g, instruction->value);
-    return LLVMBuildBitCast(g->builder, value, wanted_type->type_ref, "");
+    // We either bitcast the value directly or bitcast the pointer which does a pointer cast
+    LLVMTypeRef wanted_type_ref = handle_is_ptr(wanted_type) ?
+        LLVMPointerType(wanted_type->type_ref, 0) : wanted_type->type_ref;
+    return LLVMBuildBitCast(g->builder, value, wanted_type_ref, "");
 }
 
 static LLVMValueRef ir_render_widen_or_shorten(CodeGen *g, IrExecutable *executable,
src/ir.cpp
@@ -198,10 +198,11 @@ static ConstExprValue *const_ptr_pointee_unchecked(CodeGen *g, ConstExprValue *c
             result = &array_val->data.x_array.data.s_none.elements[const_val->data.x_ptr.data.base_array.elem_index];
             break;
         }
-        case ConstPtrSpecialBaseStruct:
-            result = &const_val->data.x_ptr.data.base_struct.struct_val->data.x_struct.fields[
-                const_val->data.x_ptr.data.base_struct.field_index];
+        case ConstPtrSpecialBaseStruct: {
+            ConstExprValue *struct_val = const_val->data.x_ptr.data.base_struct.struct_val;
+            result = &struct_val->data.x_struct.fields[const_val->data.x_ptr.data.base_struct.field_index];
             break;
+        }
         case ConstPtrSpecialBaseErrorUnionCode:
             result = const_val->data.x_ptr.data.base_err_union_code.err_union_val->data.x_err_union.error_set;
             break;
@@ -230,20 +231,55 @@ static bool is_opt_err_set(ZigType *ty) {
         (ty->id == ZigTypeIdOptional && ty->data.maybe.child_type->id == ZigTypeIdErrorSet);
 }
 
+// This function returns true when you can change the type of a ConstExprValue and the
+// value remains meaningful.
 static bool types_have_same_zig_comptime_repr(ZigType *a, ZigType *b) {
     if (a == b)
         return true;
 
-    if (a->id == b->id)
-        return true;
-
     if (get_codegen_ptr_type(a) != nullptr && get_codegen_ptr_type(b) != nullptr)
         return true;
 
     if (is_opt_err_set(a) && is_opt_err_set(b))
         return true;
 
-    return false;
+    if (a->id != b->id)
+        return false;
+
+    switch (a->id) {
+        case ZigTypeIdInvalid:
+        case ZigTypeIdUnreachable:
+            zig_unreachable();
+        case ZigTypeIdMetaType:
+        case ZigTypeIdVoid:
+        case ZigTypeIdBool:
+        case ZigTypeIdComptimeFloat:
+        case ZigTypeIdComptimeInt:
+        case ZigTypeIdPointer:
+        case ZigTypeIdUndefined:
+        case ZigTypeIdNull:
+        case ZigTypeIdNamespace:
+        case ZigTypeIdBoundFn:
+        case ZigTypeIdErrorSet:
+        case ZigTypeIdOpaque:
+            return true;
+        case ZigTypeIdFloat:
+            return a->data.floating.bit_count == b->data.floating.bit_count;
+        case ZigTypeIdInt:
+            return a->data.integral.is_signed == b->data.integral.is_signed;
+        case ZigTypeIdArray:
+        case ZigTypeIdStruct:
+        case ZigTypeIdOptional:
+        case ZigTypeIdErrorUnion:
+        case ZigTypeIdEnum:
+        case ZigTypeIdUnion:
+        case ZigTypeIdFn:
+        case ZigTypeIdArgTuple:
+        case ZigTypeIdPromise:
+        case ZigTypeIdVector:
+            return false;
+    }
+    zig_unreachable();
 }
 
 static bool ir_should_inline(IrExecutable *exec, Scope *scope) {
@@ -14421,12 +14457,11 @@ static Error ir_read_const_ptr(IrAnalyze *ira, CodeGen *codegen, AstNode *source
     if ((err = type_resolve(codegen, out_val->type, ResolveStatusSizeKnown)))
         return ErrorSemanticAnalyzeFail;
 
-    // We don't need to read the padding bytes, so we look at type_size_store bytes
-    size_t src_size = type_size_store(codegen, pointee->type);
-    size_t dst_size = type_size_store(codegen, out_val->type);
+    size_t src_size = type_size(codegen, pointee->type);
+    size_t dst_size = type_size(codegen, out_val->type);
 
     if (dst_size <= src_size) {
-        if (types_have_same_zig_comptime_repr(pointee->type, out_val->type)) {
+        if (src_size == dst_size && types_have_same_zig_comptime_repr(pointee->type, out_val->type)) {
             copy_const_val(out_val, pointee, ptr_val->data.x_ptr.mut == ConstPtrMutComptimeConst);
             return ErrorNone;
         }
@@ -20885,7 +20920,68 @@ static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue
         case ZigTypeIdVector:
             return buf_write_value_bytes_array(codegen, buf, val, val->type->data.vector.len);
         case ZigTypeIdStruct:
-            zig_panic("TODO buf_write_value_bytes struct type");
+            switch (val->type->data.structure.layout) {
+                case ContainerLayoutAuto:
+                    zig_unreachable();
+                case ContainerLayoutExtern:
+                    zig_panic("TODO buf_write_value_bytes extern struct");
+                case ContainerLayoutPacked: {
+                    size_t src_field_count = val->type->data.structure.src_field_count;
+                    size_t gen_field_count = val->type->data.structure.gen_field_count;
+                    size_t gen_i = 0;
+                    size_t src_i = 0;
+                    size_t offset = 0;
+                    bool is_big_endian = codegen->is_big_endian;
+                    uint8_t child_buf_prealloc[16];
+                    size_t child_buf_len = 16;
+                    uint8_t *child_buf = child_buf_prealloc;
+                    while (gen_i < gen_field_count) {
+                        LLVMTypeRef gen_llvm_int_type = LLVMStructGetTypeAtIndex(val->type->type_ref,
+                                    (unsigned)gen_i);
+                        size_t big_int_bit_count = LLVMGetIntTypeWidth(gen_llvm_int_type);
+                        size_t big_int_byte_count = big_int_bit_count / 8;
+                        if (big_int_byte_count > child_buf_len) {
+                            child_buf = allocate_nonzero<uint8_t>(big_int_byte_count);
+                            child_buf_len = big_int_byte_count;
+                        }
+                        BigInt big_int;
+                        bigint_init_unsigned(&big_int, 0);
+                        size_t used_bits = 0;
+                        while (src_i < src_field_count) {
+                            TypeStructField *field = &val->type->data.structure.fields[src_i];
+                            assert(field->gen_index != SIZE_MAX);
+                            if (field->gen_index != gen_i)
+                                break;
+                            uint32_t packed_bits_size = type_size_bits(codegen, field->type_entry);
+                            buf_write_value_bytes(codegen, child_buf, &val->data.x_struct.fields[src_i]);
+                            BigInt child_val;
+                            bigint_read_twos_complement(&child_val, child_buf, packed_bits_size, is_big_endian,
+                                    false);
+                            if (is_big_endian) {
+                                BigInt shift_amt;
+                                bigint_init_unsigned(&shift_amt, packed_bits_size);
+                                BigInt shifted;
+                                bigint_shl(&shifted, &big_int, &shift_amt);
+                                bigint_or(&big_int, &shifted, &child_val);
+                            } else {
+                                BigInt shift_amt;
+                                bigint_init_unsigned(&shift_amt, used_bits);
+                                BigInt child_val_shifted;
+                                bigint_shl(&child_val_shifted, &child_val, &shift_amt);
+                                BigInt tmp;
+                                bigint_or(&tmp, &big_int, &child_val_shifted);
+                                big_int = tmp;
+                                used_bits += packed_bits_size;
+                            }
+                            src_i += 1;
+                        }
+                        bigint_write_twos_complement(&big_int, buf + offset, big_int_bit_count, is_big_endian);
+                        offset += big_int_byte_count;
+                        gen_i += 1;
+                    }
+                }
+            }
+            return;
         case ZigTypeIdOptional:
             zig_panic("TODO buf_write_value_bytes maybe type");
         case ZigTypeIdErrorUnion:
@@ -21012,8 +21108,62 @@ static Error buf_read_value_bytes(IrAnalyze *ira, CodeGen *codegen, AstNode *sou
                     }
                     return ErrorNone;
                 }
-                case ContainerLayoutPacked:
-                    zig_panic("TODO buf_read_value_bytes packed struct");
+                case ContainerLayoutPacked: {
+                    size_t src_field_count = val->type->data.structure.src_field_count;
+                    val->data.x_struct.fields = create_const_vals(src_field_count);
+                    size_t gen_field_count = val->type->data.structure.gen_field_count;
+                    size_t gen_i = 0;
+                    size_t src_i = 0;
+                    size_t offset = 0;
+                    bool is_big_endian = codegen->is_big_endian;
+                    uint8_t child_buf_prealloc[16];
+                    size_t child_buf_len = 16;
+                    uint8_t *child_buf = child_buf_prealloc;
+                    while (gen_i < gen_field_count) {
+                        LLVMTypeRef gen_llvm_int_type = LLVMStructGetTypeAtIndex(val->type->type_ref,
+                                    (unsigned)gen_i);
+                        size_t big_int_bit_count = LLVMGetIntTypeWidth(gen_llvm_int_type);
+                        size_t big_int_byte_count = big_int_bit_count / 8;
+                        if (big_int_byte_count > child_buf_len) {
+                            child_buf = allocate_nonzero<uint8_t>(big_int_byte_count);
+                            child_buf_len = big_int_byte_count;
+                        }
+                        BigInt big_int;
+                        bigint_read_twos_complement(&big_int, buf + offset, big_int_bit_count, is_big_endian, false);
+                        while (src_i < src_field_count) {
+                            TypeStructField *field = &val->type->data.structure.fields[src_i];
+                            assert(field->gen_index != SIZE_MAX);
+                            if (field->gen_index != gen_i)
+                                break;
+                            ConstExprValue *field_val = &val->data.x_struct.fields[src_i];
+                            field_val->special = ConstValSpecialStatic;
+                            field_val->type = field->type_entry;
+                            uint32_t packed_bits_size = type_size_bits(codegen, field->type_entry);
+
+                            BigInt child_val;
+                            if (is_big_endian) {
+                                zig_panic("TODO buf_read_value_bytes packed struct big endian");
+                            } else {
+                                BigInt packed_bits_size_bi;
+                                bigint_init_unsigned(&packed_bits_size_bi, packed_bits_size);
+                                bigint_truncate(&child_val, &big_int, packed_bits_size, false);
+                                BigInt tmp;
+                                bigint_shr(&tmp, &big_int, &packed_bits_size_bi);
+                                big_int = tmp;
+                            }
+
+                            bigint_write_twos_complement(&child_val, child_buf, big_int_bit_count, is_big_endian);
+                            if ((err = buf_read_value_bytes(ira, codegen, source_node, child_buf, field_val))) {
+                                return err;
+                            }
+
+                            src_i += 1;
+                        }
+                        offset += big_int_byte_count;
+                        gen_i += 1;
+                    }
+                    return ErrorNone;
+                }
             }
             zig_unreachable();
         case ZigTypeIdOptional:
test/stage1/behavior/bugs/1120.zig
@@ -0,0 +1,23 @@
+const std = @import("std");
+const expect = std.testing.expect;
+
+const A = packed struct {
+    a: u2,
+    b: u6,
+};
+const B = packed struct {
+    q: u8,
+    a: u2,
+    b: u6,
+};
+test "bug 1120" {
+    var a = A{ .a = 2, .b = 2 };
+    var b = B{ .q = 22, .a = 3, .b = 2 };
+    var t: usize = 0;
+    const ptr = switch (t) {
+        0 => &a.a,
+        1 => &b.a,
+        else => unreachable,
+    };
+    expect(ptr.* == 2);
+}
test/stage1/behavior/bitcast.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const builtin = @import("builtin");
 const expect = std.testing.expect;
 const maxInt = std.math.maxInt;
 
@@ -34,3 +35,34 @@ test "@bitCast extern enum to its integer type" {
     SOCK.testBitCastExternEnum();
     comptime SOCK.testBitCastExternEnum();
 }
+
+test "@bitCast packed structs at runtime and comptime" {
+    const Full = packed struct {
+        number: u16,
+    };
+    const Divided = packed struct {
+        half1: u8,
+        quarter3: u4,
+        quarter4: u4,
+    };
+    const S = struct {
+        fn doTheTest() void {
+            var full = Full{ .number = 0x1234 };
+            var two_halves = @bitCast(Divided, full);
+            switch (builtin.endian) {
+                builtin.Endian.Big => {
+                    expect(two_halves.half1 == 0x12);
+                    expect(two_halves.quarter3 == 0x3);
+                    expect(two_halves.quarter4 == 0x4);
+                },
+                builtin.Endian.Little => {
+                    expect(two_halves.half1 == 0x34);
+                    expect(two_halves.quarter3 == 0x2);
+                    expect(two_halves.quarter4 == 0x1);
+                },
+            }
+        }
+    };
+    S.doTheTest();
+    comptime S.doTheTest();
+}
test/stage1/behavior.zig
@@ -11,6 +11,7 @@ comptime {
     _ = @import("behavior/bswap.zig");
     _ = @import("behavior/bugs/1076.zig");
     _ = @import("behavior/bugs/1111.zig");
+    _ = @import("behavior/bugs/1120.zig");
     _ = @import("behavior/bugs/1277.zig");
     _ = @import("behavior/bugs/1322.zig");
     _ = @import("behavior/bugs/1381.zig");
test/compile_errors.zig
@@ -318,12 +318,12 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         "reading past end of pointer casted array",
         \\comptime {
         \\    const array = "aoeu";
-        \\    const slice = array[2..];
+        \\    const slice = array[1..];
         \\    const int_ptr = @ptrCast(*const u24, slice.ptr);
         \\    const deref = int_ptr.*;
         \\}
     ,
-        ".tmp_source.zig:5:26: error: attempt to read 3 bytes from [4]u8 at index 2 which is 2 bytes",
+        ".tmp_source.zig:5:26: error: attempt to read 4 bytes from [4]u8 at index 1 which is 3 bytes",
     );
 
     cases.add(