| author | |
| committer | |
| log | 62d178e91af57c19d0ac000fe6930039a23e53a3 |
| tree | f849674dd8aa13839319e05585b8513a0192b7ba |
| parent | 412d863ba5801c1376af7ab8f04a71b839a820a6 |
* add nested packed struct/union behavior tests
* use ptr_info.packed_offset rather than trying to duplicate the logic from Sema.structFieldPtrByIndex()
* use the container_ptr_info.packed_offset to account for non-aligned nested structs.
* dedup type.packedStructFieldBitOffset() and module.structPackedFieldBitOffset()7 files changed, 362 insertions(+), 65 deletions(-)
src/arch/wasm/CodeGen.zig+11-4| ... | @@ -3090,12 +3090,19 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue | ... | @@ -3090,12 +3090,19 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue |
| 3090 | return func.lowerParentPtr(elem.base.toValue(), @as(u32, @intCast(elem_offset + offset))); | 3090 | return func.lowerParentPtr(elem.base.toValue(), @as(u32, @intCast(elem_offset + offset))); |
| 3091 | }, | 3091 | }, |
| 3092 | .field => |field| { | 3092 | .field => |field| { |
| 3093 | const parent_ty = mod.intern_pool.typeOf(field.base).toType().childType(mod); | 3093 | const parent_ptr_ty = mod.intern_pool.typeOf(field.base).toType(); |
| 3094 | const parent_ty = parent_ptr_ty.childType(mod); | ||
| 3095 | const field_index: u32 = @intCast(field.index); | ||
| 3094 | 3096 | ||
| 3095 | const field_offset = switch (parent_ty.zigTypeTag(mod)) { | 3097 | const field_offset = switch (parent_ty.zigTypeTag(mod)) { |
| 3096 | .Struct => switch (parent_ty.containerLayout(mod)) { | 3098 | .Struct => blk: { |
| 3097 | .Packed => parent_ty.packedStructFieldByteOffset(@as(usize, @intCast(field.index)), mod), | 3099 | if (mod.typeToPackedStruct(parent_ty)) |struct_type| { |
| 3098 | else => parent_ty.structFieldOffset(@as(usize, @intCast(field.index)), mod), | 3100 | if (ptr.ty.toType().ptrInfo(mod).packed_offset.host_size == 0) |
| 3101 | break :blk @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) | ||
| 3102 | else | ||
| 3103 | break :blk 0; | ||
| 3104 | } | ||
| 3105 | break :blk parent_ty.structFieldOffset(field_index, mod); | ||
| 3099 | }, | 3106 | }, |
| 3100 | .Union => switch (parent_ty.containerLayout(mod)) { | 3107 | .Union => switch (parent_ty.containerLayout(mod)) { |
| 3101 | .Packed => 0, | 3108 | .Packed => 0, |
src/arch/x86_64/CodeGen.zig+9-8| ... | @@ -5774,14 +5774,15 @@ fn fieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32 | ... | @@ -5774,14 +5774,15 @@ fn fieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32 |
| 5774 | const ptr_container_ty_info = ptr_container_ty.ptrInfo(mod); | 5774 | const ptr_container_ty_info = ptr_container_ty.ptrInfo(mod); |
| 5775 | const container_ty = ptr_container_ty.childType(mod); | 5775 | const container_ty = ptr_container_ty.childType(mod); |
| 5776 | 5776 | ||
| 5777 | const field_offset: i32 = @intCast(switch (container_ty.containerLayout(mod)) { | 5777 | const field_offset: i32 = blk: { |
| 5778 | .Auto, .Extern => container_ty.structFieldOffset(index, mod), | 5778 | if (mod.typeToPackedStruct(container_ty)) |struct_type| { |
| 5779 | .Packed => if (container_ty.zigTypeTag(mod) == .Struct and | 5779 | break :blk if (ptr_field_ty.ptrInfo(mod).packed_offset.host_size == 0) |
| 5780 | ptr_field_ty.ptrInfo(mod).packed_offset.host_size == 0) | 5780 | @divExact(mod.structPackedFieldBitOffset(struct_type, index) + ptr_container_ty_info.packed_offset.bit_offset, 8) |
| 5781 | container_ty.packedStructFieldByteOffset(index, mod) + @divExact(ptr_container_ty_info.packed_offset.bit_offset, 8) | 5781 | else |
| 5782 | else | 5782 | 0; |
| 5783 | 0, | 5783 | } |
| 5784 | }); | 5784 | break :blk @intCast(container_ty.structFieldOffset(index, mod)); |
| 5785 | }; | ||
| 5785 | 5786 | ||
| 5786 | const src_mcv = try self.resolveInst(operand); | 5787 | const src_mcv = try self.resolveInst(operand); |
| 5787 | const dst_mcv = if (switch (src_mcv) { | 5788 | const dst_mcv = if (switch (src_mcv) { |
src/codegen/c.zig+12-8| ... | @@ -5269,22 +5269,26 @@ fn fieldLocation( | ... | @@ -5269,22 +5269,26 @@ fn fieldLocation( |
| 5269 | const ip = &mod.intern_pool; | 5269 | const ip = &mod.intern_pool; |
| 5270 | const container_ty = container_ptr_ty.childType(mod); | 5270 | const container_ty = container_ptr_ty.childType(mod); |
| 5271 | return switch (container_ty.zigTypeTag(mod)) { | 5271 | return switch (container_ty.zigTypeTag(mod)) { |
| 5272 | .Struct => switch (container_ty.containerLayout(mod)) { | 5272 | .Struct => blk: { |
| 5273 | .Auto, .Extern => for (field_index..container_ty.structFieldCount(mod)) |next_field_index_usize| { | 5273 | if (mod.typeToPackedStruct(container_ty)) |struct_type| { |
| 5274 | if (field_ptr_ty.ptrInfo(mod).packed_offset.host_size == 0) | ||
| 5275 | break :blk .{ .byte_offset = @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + container_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) } | ||
| 5276 | else | ||
| 5277 | break :blk .begin; | ||
| 5278 | } | ||
| 5279 | |||
| 5280 | for (field_index..container_ty.structFieldCount(mod)) |next_field_index_usize| { | ||
| 5274 | const next_field_index: u32 = @intCast(next_field_index_usize); | 5281 | const next_field_index: u32 = @intCast(next_field_index_usize); |
| 5275 | if (container_ty.structFieldIsComptime(next_field_index, mod)) continue; | 5282 | if (container_ty.structFieldIsComptime(next_field_index, mod)) continue; |
| 5276 | const field_ty = container_ty.structFieldType(next_field_index, mod); | 5283 | const field_ty = container_ty.structFieldType(next_field_index, mod); |
| 5277 | if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; | 5284 | if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; |
| 5278 | 5285 | ||
| 5279 | break .{ .field = if (container_ty.isSimpleTuple(mod)) | 5286 | break :blk .{ .field = if (container_ty.isSimpleTuple(mod)) |
| 5280 | .{ .field = next_field_index } | 5287 | .{ .field = next_field_index } |
| 5281 | else | 5288 | else |
| 5282 | .{ .identifier = ip.stringToSlice(container_ty.legacyStructFieldName(next_field_index, mod)) } }; | 5289 | .{ .identifier = ip.stringToSlice(container_ty.legacyStructFieldName(next_field_index, mod)) } }; |
| 5283 | } else if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin, | 5290 | } |
| 5284 | .Packed => if (field_ptr_ty.ptrInfo(mod).packed_offset.host_size == 0) | 5291 | break :blk if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin; |
| 5285 | .{ .byte_offset = container_ty.packedStructFieldByteOffset(field_index, mod) + @divExact(container_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) } | ||
| 5286 | else | ||
| 5287 | .begin, | ||
| 5288 | }, | 5292 | }, |
| 5289 | .Union => { | 5293 | .Union => { |
| 5290 | const union_obj = mod.typeToUnion(container_ty).?; | 5294 | const union_obj = mod.typeToUnion(container_ty).?; |
src/codegen/llvm.zig+17-25| ... | @@ -4,7 +4,6 @@ const assert = std.debug.assert; | ... | @@ -4,7 +4,6 @@ const assert = std.debug.assert; |
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | const log = std.log.scoped(.codegen); | 5 | const log = std.log.scoped(.codegen); |
| 6 | const math = std.math; | 6 | const math = std.math; |
| 7 | const native_endian = builtin.cpu.arch.endian(); | ||
| 8 | const DW = std.dwarf; | 7 | const DW = std.dwarf; |
| 9 | 8 | ||
| 10 | const Builder = @import("llvm/Builder.zig"); | 9 | const Builder = @import("llvm/Builder.zig"); |
| ... | @@ -3770,7 +3769,7 @@ pub const Object = struct { | ... | @@ -3770,7 +3769,7 @@ pub const Object = struct { |
| 3770 | .opt_payload, | 3769 | .opt_payload, |
| 3771 | .elem, | 3770 | .elem, |
| 3772 | .field, | 3771 | .field, |
| 3773 | => try o.lowerParentPtr(val, ty.ptrInfo(mod).packed_offset.bit_offset % 8 == 0), | 3772 | => try o.lowerParentPtr(val), |
| 3774 | .comptime_field => unreachable, | 3773 | .comptime_field => unreachable, |
| 3775 | }; | 3774 | }; |
| 3776 | switch (ptr.len) { | 3775 | switch (ptr.len) { |
| ... | @@ -4230,15 +4229,16 @@ pub const Object = struct { | ... | @@ -4230,15 +4229,16 @@ pub const Object = struct { |
| 4230 | return o.lowerDeclRefValue(ptr_ty, decl_index); | 4229 | return o.lowerDeclRefValue(ptr_ty, decl_index); |
| 4231 | } | 4230 | } |
| 4232 | 4231 | ||
| 4233 | fn lowerParentPtr(o: *Object, ptr_val: Value, byte_aligned: bool) Allocator.Error!Builder.Constant { | 4232 | fn lowerParentPtr(o: *Object, ptr_val: Value) Allocator.Error!Builder.Constant { |
| 4234 | const mod = o.module; | 4233 | const mod = o.module; |
| 4235 | const ip = &mod.intern_pool; | 4234 | const ip = &mod.intern_pool; |
| 4236 | return switch (ip.indexToKey(ptr_val.toIntern()).ptr.addr) { | 4235 | const ptr = ip.indexToKey(ptr_val.toIntern()).ptr; |
| 4236 | return switch (ptr.addr) { | ||
| 4237 | .decl => |decl| o.lowerParentPtrDecl(decl), | 4237 | .decl => |decl| o.lowerParentPtrDecl(decl), |
| 4238 | .mut_decl => |mut_decl| o.lowerParentPtrDecl(mut_decl.decl), | 4238 | .mut_decl => |mut_decl| o.lowerParentPtrDecl(mut_decl.decl), |
| 4239 | .int => |int| try o.lowerIntAsPtr(int), | 4239 | .int => |int| try o.lowerIntAsPtr(int), |
| 4240 | .eu_payload => |eu_ptr| { | 4240 | .eu_payload => |eu_ptr| { |
| 4241 | const parent_ptr = try o.lowerParentPtr(eu_ptr.toValue(), true); | 4241 | const parent_ptr = try o.lowerParentPtr(eu_ptr.toValue()); |
| 4242 | 4242 | ||
| 4243 | const eu_ty = ip.typeOf(eu_ptr).toType().childType(mod); | 4243 | const eu_ty = ip.typeOf(eu_ptr).toType().childType(mod); |
| 4244 | const payload_ty = eu_ty.errorUnionPayload(mod); | 4244 | const payload_ty = eu_ty.errorUnionPayload(mod); |
| ... | @@ -4256,7 +4256,7 @@ pub const Object = struct { | ... | @@ -4256,7 +4256,7 @@ pub const Object = struct { |
| 4256 | }); | 4256 | }); |
| 4257 | }, | 4257 | }, |
| 4258 | .opt_payload => |opt_ptr| { | 4258 | .opt_payload => |opt_ptr| { |
| 4259 | const parent_ptr = try o.lowerParentPtr(opt_ptr.toValue(), true); | 4259 | const parent_ptr = try o.lowerParentPtr(opt_ptr.toValue()); |
| 4260 | 4260 | ||
| 4261 | const opt_ty = ip.typeOf(opt_ptr).toType().childType(mod); | 4261 | const opt_ty = ip.typeOf(opt_ptr).toType().childType(mod); |
| 4262 | const payload_ty = opt_ty.optionalChild(mod); | 4262 | const payload_ty = opt_ty.optionalChild(mod); |
| ... | @@ -4274,7 +4274,7 @@ pub const Object = struct { | ... | @@ -4274,7 +4274,7 @@ pub const Object = struct { |
| 4274 | }, | 4274 | }, |
| 4275 | .comptime_field => unreachable, | 4275 | .comptime_field => unreachable, |
| 4276 | .elem => |elem_ptr| { | 4276 | .elem => |elem_ptr| { |
| 4277 | const parent_ptr = try o.lowerParentPtr(elem_ptr.base.toValue(), true); | 4277 | const parent_ptr = try o.lowerParentPtr(elem_ptr.base.toValue()); |
| 4278 | const elem_ty = ip.typeOf(elem_ptr.base).toType().elemType2(mod); | 4278 | const elem_ty = ip.typeOf(elem_ptr.base).toType().elemType2(mod); |
| 4279 | 4279 | ||
| 4280 | return o.builder.gepConst(.inbounds, try o.lowerType(elem_ty), parent_ptr, null, &.{ | 4280 | return o.builder.gepConst(.inbounds, try o.lowerType(elem_ty), parent_ptr, null, &.{ |
| ... | @@ -4282,9 +4282,9 @@ pub const Object = struct { | ... | @@ -4282,9 +4282,9 @@ pub const Object = struct { |
| 4282 | }); | 4282 | }); |
| 4283 | }, | 4283 | }, |
| 4284 | .field => |field_ptr| { | 4284 | .field => |field_ptr| { |
| 4285 | const parent_ptr = try o.lowerParentPtr(field_ptr.base.toValue(), byte_aligned); | 4285 | const parent_ptr = try o.lowerParentPtr(field_ptr.base.toValue()); |
| 4286 | const parent_ty = ip.typeOf(field_ptr.base).toType().childType(mod); | 4286 | const parent_ptr_ty = ip.typeOf(field_ptr.base).toType(); |
| 4287 | 4287 | const parent_ty = parent_ptr_ty.childType(mod); | |
| 4288 | const field_index: u32 = @intCast(field_ptr.index); | 4288 | const field_index: u32 = @intCast(field_ptr.index); |
| 4289 | switch (parent_ty.zigTypeTag(mod)) { | 4289 | switch (parent_ty.zigTypeTag(mod)) { |
| 4290 | .Union => { | 4290 | .Union => { |
| ... | @@ -4309,22 +4309,14 @@ pub const Object = struct { | ... | @@ -4309,22 +4309,14 @@ pub const Object = struct { |
| 4309 | }, | 4309 | }, |
| 4310 | .Struct => { | 4310 | .Struct => { |
| 4311 | if (mod.typeToPackedStruct(parent_ty)) |struct_type| { | 4311 | if (mod.typeToPackedStruct(parent_ty)) |struct_type| { |
| 4312 | if (!byte_aligned) return parent_ptr; | 4312 | const ptr_info = ptr.ty.toType().ptrInfo(mod); |
| 4313 | if (ptr_info.packed_offset.host_size != 0) return parent_ptr; | ||
| 4314 | |||
| 4315 | const parent_ptr_info = parent_ptr_ty.ptrInfo(mod); | ||
| 4316 | const bit_offset = mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_info.packed_offset.bit_offset; | ||
| 4313 | const llvm_usize = try o.lowerType(Type.usize); | 4317 | const llvm_usize = try o.lowerType(Type.usize); |
| 4314 | const base_addr = | 4318 | const base_addr = try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize); |
| 4315 | try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize); | 4319 | const byte_offset = try o.builder.intConst(llvm_usize, @divExact(bit_offset, 8)); |
| 4316 | // count bits of fields before this one | ||
| 4317 | // TODO https://github.com/ziglang/zig/issues/17178 | ||
| 4318 | const prev_bits = b: { | ||
| 4319 | var b: usize = 0; | ||
| 4320 | for (0..field_index) |i| { | ||
| 4321 | const field_ty = struct_type.field_types.get(ip)[i].toType(); | ||
| 4322 | if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; | ||
| 4323 | b += @intCast(field_ty.bitSize(mod)); | ||
| 4324 | } | ||
| 4325 | break :b b; | ||
| 4326 | }; | ||
| 4327 | const byte_offset = try o.builder.intConst(llvm_usize, prev_bits / 8); | ||
| 4328 | const field_addr = try o.builder.binConst(.add, base_addr, byte_offset); | 4320 | const field_addr = try o.builder.binConst(.add, base_addr, byte_offset); |
| 4329 | return o.builder.castConst(.inttoptr, field_addr, .ptr); | 4321 | return o.builder.castConst(.inttoptr, field_addr, .ptr); |
| 4330 | } | 4322 | } |
src/type.zig+2-16| ... | @@ -3028,24 +3028,10 @@ pub const Type = struct { | ... | @@ -3028,24 +3028,10 @@ pub const Type = struct { |
| 3028 | }; | 3028 | }; |
| 3029 | } | 3029 | } |
| 3030 | 3030 | ||
| 3031 | pub fn packedStructFieldBitOffset(ty: Type, field_index: usize, mod: *Module) u32 { | 3031 | pub fn packedStructFieldByteOffset(ty: Type, field_index: u32, mod: *Module) u32 { |
| 3032 | const ip = &mod.intern_pool; | 3032 | const ip = &mod.intern_pool; |
| 3033 | const struct_type = ip.indexToKey(ty.toIntern()).struct_type; | 3033 | const struct_type = ip.indexToKey(ty.toIntern()).struct_type; |
| 3034 | assert(struct_type.layout == .Packed); | 3034 | return @divExact(mod.structPackedFieldBitOffset(struct_type, field_index), 8); |
| 3035 | comptime assert(Type.packed_struct_layout_version == 2); | ||
| 3036 | |||
| 3037 | var running_bits: u32 = 0; | ||
| 3038 | for (struct_type.field_types.get(ip), 0..) |field_ty, i| { | ||
| 3039 | if (i == field_index) break; | ||
| 3040 | if (!field_ty.toType().hasRuntimeBits(mod)) continue; | ||
| 3041 | const field_bits: u32 = @intCast(field_ty.toType().bitSize(mod)); | ||
| 3042 | running_bits += field_bits; | ||
| 3043 | } | ||
| 3044 | return running_bits; | ||
| 3045 | } | ||
| 3046 | |||
| 3047 | pub fn packedStructFieldByteOffset(ty: Type, field_index: usize, mod: *Module) u32 { | ||
| 3048 | return packedStructFieldBitOffset(ty, field_index, mod) / 8; | ||
| 3049 | } | 3035 | } |
| 3050 | 3036 | ||
| 3051 | pub const FieldOffset = struct { | 3037 | pub const FieldOffset = struct { |
test/behavior/packed-struct.zig+264-4| ... | @@ -253,6 +253,79 @@ test "regular in irregular packed struct" { | ... | @@ -253,6 +253,79 @@ test "regular in irregular packed struct" { |
| 253 | try expectEqual(@as(u8, 42), foo.bar.b); | 253 | try expectEqual(@as(u8, 42), foo.bar.b); |
| 254 | } | 254 | } |
| 255 | 255 | ||
| 256 | test "nested packed struct unaligned" { | ||
| 257 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | ||
| 258 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | ||
| 259 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | ||
| 260 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | ||
| 261 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | ||
| 262 | if (native_endian != .Little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet | ||
| 263 | |||
| 264 | const S1 = packed struct { | ||
| 265 | a: u4, | ||
| 266 | b: u4, | ||
| 267 | c: u8, | ||
| 268 | }; | ||
| 269 | const S2 = packed struct { | ||
| 270 | base: u8, | ||
| 271 | p0: S1, | ||
| 272 | bit0: u1, | ||
| 273 | p1: packed struct { | ||
| 274 | a: u8, | ||
| 275 | }, | ||
| 276 | p2: packed struct { | ||
| 277 | a: u7, | ||
| 278 | b: u8, | ||
| 279 | }, | ||
| 280 | p3: S1, | ||
| 281 | |||
| 282 | var s: @This() = .{ | ||
| 283 | .base = 1, | ||
| 284 | .p0 = .{ .a = 2, .b = 3, .c = 4 }, | ||
| 285 | .bit0 = 0, | ||
| 286 | .p1 = .{ .a = 5 }, | ||
| 287 | .p2 = .{ .a = 6, .b = 7 }, | ||
| 288 | .p3 = .{ .a = 8, .b = 9, .c = 10 }, | ||
| 289 | }; | ||
| 290 | }; | ||
| 291 | |||
| 292 | try expect(S2.s.base == 1); | ||
| 293 | try expect(S2.s.p0.a == 2); | ||
| 294 | try expect(S2.s.p0.b == 3); | ||
| 295 | try expect(S2.s.p0.c == 4); | ||
| 296 | try expect(S2.s.bit0 == 0); | ||
| 297 | try expect(S2.s.p1.a == 5); | ||
| 298 | try expect(S2.s.p2.a == 6); | ||
| 299 | try expect(S2.s.p2.b == 7); | ||
| 300 | try expect(S2.s.p3.a == 8); | ||
| 301 | try expect(S2.s.p3.b == 9); | ||
| 302 | try expect(S2.s.p3.c == 10); | ||
| 303 | |||
| 304 | const S3 = packed struct { | ||
| 305 | pad: u8, | ||
| 306 | v: u2, | ||
| 307 | s: packed struct { | ||
| 308 | v: u3, | ||
| 309 | s: packed struct { | ||
| 310 | v: u2, | ||
| 311 | s: packed struct { | ||
| 312 | bit0: u1, | ||
| 313 | byte: u8, | ||
| 314 | bit1: u1, | ||
| 315 | }, | ||
| 316 | }, | ||
| 317 | }, | ||
| 318 | var v0: @This() = .{ .pad = 0, .v = 1, .s = .{ .v = 2, .s = .{ .v = 3, .s = .{ .bit0 = 0, .byte = 4, .bit1 = 1 } } } }; | ||
| 319 | }; | ||
| 320 | |||
| 321 | try expect(S3.v0.v == 1); | ||
| 322 | try expect(S3.v0.s.v == 2); | ||
| 323 | try expect(S3.v0.s.s.v == 3); | ||
| 324 | try expect(S3.v0.s.s.s.bit0 == 0); | ||
| 325 | try expect(S3.v0.s.s.s.byte == 4); | ||
| 326 | try expect(S3.v0.s.s.s.bit1 == 1); | ||
| 327 | } | ||
| 328 | |||
| 256 | test "byte-aligned field pointer offsets" { | 329 | test "byte-aligned field pointer offsets" { |
| 257 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | 330 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 258 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 331 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| ... | @@ -354,6 +427,45 @@ test "byte-aligned field pointer offsets" { | ... | @@ -354,6 +427,45 @@ test "byte-aligned field pointer offsets" { |
| 354 | try comptime S.doTheTest(); | 427 | try comptime S.doTheTest(); |
| 355 | } | 428 | } |
| 356 | 429 | ||
| 430 | test "nested packed struct field pointers" { | ||
| 431 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | ||
| 432 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | ||
| 433 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | ||
| 434 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | ||
| 435 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // ubsan unaligned pointer access | ||
| 436 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | ||
| 437 | if (native_endian != .Little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet | ||
| 438 | |||
| 439 | const S2 = packed struct { | ||
| 440 | base: u8, | ||
| 441 | p0: packed struct { | ||
| 442 | a: u4, | ||
| 443 | b: u4, | ||
| 444 | c: u8, | ||
| 445 | }, | ||
| 446 | bit: u1, | ||
| 447 | p1: packed struct { | ||
| 448 | a: u7, | ||
| 449 | b: u8, | ||
| 450 | }, | ||
| 451 | |||
| 452 | var s: @This() = .{ .base = 1, .p0 = .{ .a = 2, .b = 3, .c = 4 }, .bit = 0, .p1 = .{ .a = 5, .b = 6 } }; | ||
| 453 | }; | ||
| 454 | |||
| 455 | const ptr_base = &S2.s.base; | ||
| 456 | const ptr_p0_a = &S2.s.p0.a; | ||
| 457 | const ptr_p0_b = &S2.s.p0.b; | ||
| 458 | const ptr_p0_c = &S2.s.p0.c; | ||
| 459 | const ptr_p1_a = &S2.s.p1.a; | ||
| 460 | const ptr_p1_b = &S2.s.p1.b; | ||
| 461 | try expectEqual(@as(u8, 1), ptr_base.*); | ||
| 462 | try expectEqual(@as(u4, 2), ptr_p0_a.*); | ||
| 463 | try expectEqual(@as(u4, 3), ptr_p0_b.*); | ||
| 464 | try expectEqual(@as(u8, 4), ptr_p0_c.*); | ||
| 465 | try expectEqual(@as(u7, 5), ptr_p1_a.*); | ||
| 466 | try expectEqual(@as(u8, 6), ptr_p1_b.*); | ||
| 467 | } | ||
| 468 | |||
| 357 | test "load pointer from packed struct" { | 469 | test "load pointer from packed struct" { |
| 358 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | 470 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 359 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 471 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| ... | @@ -380,6 +492,7 @@ test "@intFromPtr on a packed struct field" { | ... | @@ -380,6 +492,7 @@ test "@intFromPtr on a packed struct field" { |
| 380 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 492 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 381 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 493 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 382 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | 494 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; |
| 495 | if (native_endian != .Little) return error.SkipZigTest; | ||
| 383 | 496 | ||
| 384 | const S = struct { | 497 | const S = struct { |
| 385 | const P = packed struct { | 498 | const P = packed struct { |
| ... | @@ -387,6 +500,7 @@ test "@intFromPtr on a packed struct field" { | ... | @@ -387,6 +500,7 @@ test "@intFromPtr on a packed struct field" { |
| 387 | y: u8, | 500 | y: u8, |
| 388 | z: u32, | 501 | z: u32, |
| 389 | }; | 502 | }; |
| 503 | |||
| 390 | var p0: P = P{ | 504 | var p0: P = P{ |
| 391 | .x = 1, | 505 | .x = 1, |
| 392 | .y = 2, | 506 | .y = 2, |
| ... | @@ -396,6 +510,138 @@ test "@intFromPtr on a packed struct field" { | ... | @@ -396,6 +510,138 @@ test "@intFromPtr on a packed struct field" { |
| 396 | try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 2); | 510 | try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 2); |
| 397 | } | 511 | } |
| 398 | 512 | ||
| 513 | test "@intFromPtr on a packed struct field unaligned and nested" { | ||
| 514 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | ||
| 515 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | ||
| 516 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | ||
| 517 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | ||
| 518 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | ||
| 519 | if (native_endian != .Little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet | ||
| 520 | |||
| 521 | const S1 = packed struct { | ||
| 522 | a: u4, | ||
| 523 | b: u4, | ||
| 524 | c: u8, | ||
| 525 | }; | ||
| 526 | const S2 = packed struct { | ||
| 527 | base: u8, | ||
| 528 | p0: S1, | ||
| 529 | bit0: u1, | ||
| 530 | p1: packed struct { | ||
| 531 | a: u8, | ||
| 532 | }, | ||
| 533 | p2: packed struct { | ||
| 534 | a: u7, | ||
| 535 | b: u8, | ||
| 536 | }, | ||
| 537 | p3: S1, | ||
| 538 | |||
| 539 | var s: @This() = .{ | ||
| 540 | .base = 1, | ||
| 541 | .p0 = .{ .a = 2, .b = 3, .c = 4 }, | ||
| 542 | .bit0 = 0, | ||
| 543 | .p1 = .{ .a = 5 }, | ||
| 544 | .p2 = .{ .a = 6, .b = 7 }, | ||
| 545 | .p3 = .{ .a = 8, .b = 9, .c = 10 }, | ||
| 546 | }; | ||
| 547 | }; | ||
| 548 | |||
| 549 | switch (comptime @alignOf(S2)) { | ||
| 550 | 4 => { | ||
| 551 | comptime assert(@TypeOf(&S2.s.base) == *align(4) u8); | ||
| 552 | comptime assert(@TypeOf(&S2.s.p0.a) == *align(1:0:2) u4); | ||
| 553 | comptime assert(@TypeOf(&S2.s.p0.b) == *align(1:4:2) u4); | ||
| 554 | comptime assert(@TypeOf(&S2.s.p0.c) == *u8); | ||
| 555 | comptime assert(@TypeOf(&S2.s.bit0) == *align(4:24:8) u1); | ||
| 556 | comptime assert(@TypeOf(&S2.s.p1.a) == *align(4:25:8) u8); | ||
| 557 | comptime assert(@TypeOf(&S2.s.p2.a) == *align(4:33:8) u7); | ||
| 558 | comptime assert(@TypeOf(&S2.s.p2.b) == *u8); | ||
| 559 | comptime assert(@TypeOf(&S2.s.p3.a) == *align(2:0:2) u4); | ||
| 560 | comptime assert(@TypeOf(&S2.s.p3.b) == *align(2:4:2) u4); | ||
| 561 | comptime assert(@TypeOf(&S2.s.p3.c) == *u8); | ||
| 562 | }, | ||
| 563 | 8 => { | ||
| 564 | comptime assert(@TypeOf(&S2.s.base) == *align(8) u8); | ||
| 565 | comptime assert(@TypeOf(&S2.s.p0.a) == *align(1:0:2) u4); | ||
| 566 | comptime assert(@TypeOf(&S2.s.p0.b) == *align(1:4:2) u4); | ||
| 567 | comptime assert(@TypeOf(&S2.s.p0.c) == *u8); | ||
| 568 | comptime assert(@TypeOf(&S2.s.bit0) == *align(8:24:8) u1); | ||
| 569 | comptime assert(@TypeOf(&S2.s.p1.a) == *align(8:25:8) u8); | ||
| 570 | comptime assert(@TypeOf(&S2.s.p2.a) == *align(8:33:8) u7); | ||
| 571 | comptime assert(@TypeOf(&S2.s.p2.b) == *u8); | ||
| 572 | comptime assert(@TypeOf(&S2.s.p3.a) == *align(2:0:2) u4); | ||
| 573 | comptime assert(@TypeOf(&S2.s.p3.b) == *align(2:4:2) u4); | ||
| 574 | comptime assert(@TypeOf(&S2.s.p3.c) == *u8); | ||
| 575 | }, | ||
| 576 | else => {}, | ||
| 577 | } | ||
| 578 | try expect(@intFromPtr(&S2.s.base) - @intFromPtr(&S2.s) == 0); | ||
| 579 | try expect(@intFromPtr(&S2.s.p0.a) - @intFromPtr(&S2.s) == 1); | ||
| 580 | try expect(@intFromPtr(&S2.s.p0.b) - @intFromPtr(&S2.s) == 1); | ||
| 581 | try expect(@intFromPtr(&S2.s.p0.c) - @intFromPtr(&S2.s) == 2); | ||
| 582 | try expect(@intFromPtr(&S2.s.bit0) - @intFromPtr(&S2.s) == 0); | ||
| 583 | try expect(@intFromPtr(&S2.s.p1.a) - @intFromPtr(&S2.s) == 0); | ||
| 584 | try expect(@intFromPtr(&S2.s.p2.a) - @intFromPtr(&S2.s) == 0); | ||
| 585 | try expect(@intFromPtr(&S2.s.p2.b) - @intFromPtr(&S2.s) == 5); | ||
| 586 | try expect(@intFromPtr(&S2.s.p3.a) - @intFromPtr(&S2.s) == 6); | ||
| 587 | try expect(@intFromPtr(&S2.s.p3.b) - @intFromPtr(&S2.s) == 6); | ||
| 588 | try expect(@intFromPtr(&S2.s.p3.c) - @intFromPtr(&S2.s) == 7); | ||
| 589 | |||
| 590 | const S3 = packed struct { | ||
| 591 | pad: u8, | ||
| 592 | v: u2, | ||
| 593 | s: packed struct { | ||
| 594 | v: u3, | ||
| 595 | s: packed struct { | ||
| 596 | v: u2, | ||
| 597 | s: packed struct { | ||
| 598 | bit0: u1, | ||
| 599 | byte: u8, | ||
| 600 | bit1: u1, | ||
| 601 | }, | ||
| 602 | }, | ||
| 603 | }, | ||
| 604 | var v0: @This() = .{ .pad = 0, .v = 1, .s = .{ .v = 2, .s = .{ .v = 3, .s = .{ .bit0 = 0, .byte = 4, .bit1 = 1 } } } }; | ||
| 605 | }; | ||
| 606 | |||
| 607 | comptime assert(@TypeOf(&S3.v0.v) == *align(4:8:4) u2); | ||
| 608 | comptime assert(@TypeOf(&S3.v0.s.v) == *align(4:10:4) u3); | ||
| 609 | comptime assert(@TypeOf(&S3.v0.s.s.v) == *align(4:13:4) u2); | ||
| 610 | comptime assert(@TypeOf(&S3.v0.s.s.s.bit0) == *align(4:15:4) u1); | ||
| 611 | comptime assert(@TypeOf(&S3.v0.s.s.s.byte) == *align(2) u8); | ||
| 612 | comptime assert(@TypeOf(&S3.v0.s.s.s.bit1) == *align(4:24:4) u1); | ||
| 613 | try expect(@intFromPtr(&S3.v0.v) - @intFromPtr(&S3.v0) == 0); | ||
| 614 | try expect(@intFromPtr(&S3.v0.s) - @intFromPtr(&S3.v0) == 0); | ||
| 615 | try expect(@intFromPtr(&S3.v0.s.v) - @intFromPtr(&S3.v0) == 0); | ||
| 616 | try expect(@intFromPtr(&S3.v0.s.s) - @intFromPtr(&S3.v0) == 0); | ||
| 617 | try expect(@intFromPtr(&S3.v0.s.s.v) - @intFromPtr(&S3.v0) == 0); | ||
| 618 | try expect(@intFromPtr(&S3.v0.s.s.s) - @intFromPtr(&S3.v0) == 0); | ||
| 619 | try expect(@intFromPtr(&S3.v0.s.s.s.bit0) - @intFromPtr(&S3.v0) == 0); | ||
| 620 | try expect(@intFromPtr(&S3.v0.s.s.s.byte) - @intFromPtr(&S3.v0) == 2); | ||
| 621 | try expect(@intFromPtr(&S3.v0.s.s.s.bit1) - @intFromPtr(&S3.v0) == 0); | ||
| 622 | } | ||
| 623 | |||
| 624 | test "packed struct fields modification" { | ||
| 625 | const Small = packed struct { | ||
| 626 | val: u8 = 0, | ||
| 627 | lo: u4 = 0, | ||
| 628 | hi: u4 = 0, | ||
| 629 | |||
| 630 | var p: @This() = undefined; | ||
| 631 | }; | ||
| 632 | Small.p = .{ | ||
| 633 | .val = 0x12, | ||
| 634 | .lo = 3, | ||
| 635 | .hi = 4, | ||
| 636 | }; | ||
| 637 | try expect(@as(u16, @bitCast(Small.p)) == 0x4312); | ||
| 638 | |||
| 639 | Small.p.val -= Small.p.lo; | ||
| 640 | Small.p.val += Small.p.hi; | ||
| 641 | Small.p.hi -= Small.p.lo; | ||
| 642 | try expect(@as(u16, @bitCast(Small.p)) == 0x1313); | ||
| 643 | } | ||
| 644 | |||
| 399 | test "optional pointer in packed struct" { | 645 | test "optional pointer in packed struct" { |
| 400 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | 646 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 401 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 647 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| ... | @@ -410,7 +656,7 @@ test "optional pointer in packed struct" { | ... | @@ -410,7 +656,7 @@ test "optional pointer in packed struct" { |
| 410 | 656 | ||
| 411 | test "nested packed struct field access test" { | 657 | test "nested packed struct field access test" { |
| 412 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO | 658 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 413 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | 659 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits |
| 414 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO | 660 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO |
| 415 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO | 661 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| 416 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 662 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| ... | @@ -562,7 +808,7 @@ test "nested packed struct at non-zero offset 2" { | ... | @@ -562,7 +808,7 @@ test "nested packed struct at non-zero offset 2" { |
| 562 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 808 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 563 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 809 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 564 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | 810 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; |
| 565 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | 811 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits |
| 566 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; | 812 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| 567 | 813 | ||
| 568 | const S = struct { | 814 | const S = struct { |
| ... | @@ -700,7 +946,6 @@ test "packed struct initialized in bitcast" { | ... | @@ -700,7 +946,6 @@ test "packed struct initialized in bitcast" { |
| 700 | test "pointer to container level packed struct field" { | 946 | test "pointer to container level packed struct field" { |
| 701 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | 947 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 702 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 948 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 703 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | ||
| 704 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 949 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 705 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | 950 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; |
| 706 | 951 | ||
| ... | @@ -727,7 +972,6 @@ test "store undefined to packed result location" { | ... | @@ -727,7 +972,6 @@ test "store undefined to packed result location" { |
| 727 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | 972 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 728 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | 973 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 729 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | 974 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; |
| 730 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | ||
| 731 | 975 | ||
| 732 | var x: u4 = 0; | 976 | var x: u4 = 0; |
| 733 | var s = packed struct { x: u4, y: u4 }{ .x = x, .y = if (x > 0) x else undefined }; | 977 | var s = packed struct { x: u4, y: u4 }{ .x = x, .y = if (x > 0) x else undefined }; |
| ... | @@ -743,3 +987,19 @@ test "bitcast back and forth" { | ... | @@ -743,3 +987,19 @@ test "bitcast back and forth" { |
| 743 | try expect(s.one == s2.one); | 987 | try expect(s.one == s2.one); |
| 744 | try expect(s.two == s2.two); | 988 | try expect(s.two == s2.two); |
| 745 | } | 989 | } |
| 990 | |||
| 991 | test "field access of packed struct smaller than its abi size inside struct initialized with rls" { | ||
| 992 | if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .arm) return error.SkipZigTest; | ||
| 993 | const S = struct { | ||
| 994 | ps: packed struct { x: i2, y: i2 }, | ||
| 995 | |||
| 996 | fn init(cond: bool) @This() { | ||
| 997 | return .{ .ps = .{ .x = 0, .y = if (cond) 1 else 0 } }; | ||
| 998 | } | ||
| 999 | }; | ||
| 1000 | |||
| 1001 | var s = S.init(true); | ||
| 1002 | // note: this bug is triggered by the == operator, expectEqual will hide it | ||
| 1003 | try expect(@as(i2, 0) == s.ps.x); | ||
| 1004 | try expect(@as(i2, 1) == s.ps.y); | ||
| 1005 | } |
test/behavior/packed-union.zig+47| ... | @@ -38,3 +38,50 @@ test "flags in packed union" { | ... | @@ -38,3 +38,50 @@ test "flags in packed union" { |
| 38 | try expectEqual(true, test_bits.enable_1); | 38 | try expectEqual(true, test_bits.enable_1); |
| 39 | try expectEqual(false, test_bits.other_flags.flags.enable_1); | 39 | try expectEqual(false, test_bits.other_flags.flags.enable_1); |
| 40 | } | 40 | } |
| 41 | |||
| 42 | test "flags in packed union at offset" { | ||
| 43 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | ||
| 44 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | ||
| 45 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; | ||
| 46 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | ||
| 47 | |||
| 48 | const FlagBits = packed union { | ||
| 49 | base_flags: packed union { | ||
| 50 | flags: packed struct(u4) { | ||
| 51 | enable_1: bool = true, | ||
| 52 | enable_2: bool = false, | ||
| 53 | enable_3: bool = false, | ||
| 54 | enable_4: bool = false, | ||
| 55 | }, | ||
| 56 | bits: u4, | ||
| 57 | }, | ||
| 58 | adv_flags: packed struct(u12) { | ||
| 59 | pad: u8 = 0, | ||
| 60 | adv: packed union { | ||
| 61 | flags: packed struct(u4) { | ||
| 62 | enable_1: bool = true, | ||
| 63 | enable_2: bool = false, | ||
| 64 | enable_3: bool = false, | ||
| 65 | enable_4: bool = false, | ||
| 66 | }, | ||
| 67 | bits: u4, | ||
| 68 | }, | ||
| 69 | }, | ||
| 70 | }; | ||
| 71 | var test_bits: FlagBits = .{ .adv_flags = .{ .adv = .{ .flags = .{} } } }; | ||
| 72 | |||
| 73 | try expectEqual(@as(u8, 0), test_bits.adv_flags.pad); | ||
| 74 | try expectEqual(true, test_bits.adv_flags.adv.flags.enable_1); | ||
| 75 | try expectEqual(false, test_bits.adv_flags.adv.flags.enable_2); | ||
| 76 | |||
| 77 | test_bits.adv_flags.adv.flags.enable_1 = false; | ||
| 78 | test_bits.adv_flags.adv.flags.enable_2 = true; | ||
| 79 | try expectEqual(@as(u8, 0), test_bits.adv_flags.pad); | ||
| 80 | try expectEqual(false, test_bits.adv_flags.adv.flags.enable_1); | ||
| 81 | try expectEqual(true, test_bits.adv_flags.adv.flags.enable_2); | ||
| 82 | |||
| 83 | test_bits.adv_flags.adv.bits = 12; | ||
| 84 | try expectEqual(@as(u8, 0), test_bits.adv_flags.pad); | ||
| 85 | try expectEqual(false, test_bits.adv_flags.adv.flags.enable_1); | ||
| 86 | try expectEqual(false, test_bits.adv_flags.adv.flags.enable_2); | ||
| 87 | } |