authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-10-01 00:07:21-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-10-02 13:15:28-04:00
logd657b6c0e2ab7c47f5416dc4df1abb2bfbecd4b6
tree1c28a341c2b8094468bac5121a3dc955f16f8582
parent53775b0999527ced0550f3abb32c4a4a715af74e

sema: support reinterpreting extern/packed unions at comptime via field access

My previous change for reading / writing to unions at comptime did not handle union field read/writes correctly in all cases. Previously, if a field was written to a union, it would overwrite the entire value. This is problematic when a field of a larger size is subsequently read, because the value would not be long enough, causing a panic. Additionally, the writing behaviour itself was incorrect. Writing to a field of a packed or extern union should only overwrite the bits corresponding to that field, allowing for memory reintepretation via field writes / reads. I addressed these problems as follows: Add the concept of a "backing type" for extern / packed unions (`Type.unionBackingType`). For extern unions, this is a `u8` array, for packed unions it's an integer matching the `bitSize` of the union. Whenever union memory is read at comptime, it's read as this type. When union memory is written at comptime, the tag may still be known. If so, the memory is written using the tagged type. If the tag is unknown (because this union had previously been read from memory), it's simply written back out as the backing type. I added `write_packed` to the `reinterpret` field of `ComptimePtrMutationKit`. This causes writes of the operand to be packed - which is necessary when writing to a field of a packed union. Without this, writing a value to a `u1` field would overwrite the entire byte it occupied. The final case to address was reading a different (potentially larger) field from a union when it was written with a known tag. To handle this, a new kind of bitcast was introduced (`bitCastUnionFieldVal`) which supports reading a larger field by using a backing buffer that has the unwritten bits set to undefined. The reason to support this (vs always just writing the union as it's backing type), is that no reads to larger fields ever occur at comptime, it would be strictly worse to have spent time writing the full backing type.

6 files changed, 271 insertions(+), 73 deletions(-)

src/Module.zig+1
......@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
66076607 return field_ty.abiAlignment(mod);
66086608}
66096609
6610/// Returns the index of the active field, given the current tag value
66106611pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
66116612 const ip = &mod.intern_pool;
66126613 if (enum_tag.toIntern() == .none) return null;
src/Sema.zig+85-21
......@@ -27260,7 +27260,7 @@ fn unionFieldVal(
2726027260 else
2726127261 union_ty.unionFieldType(un.tag.toValue(), mod).?;
2726227262
27263 if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
27263 if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty)) |new_val| {
2726427264 return Air.internedToRef(new_val.toIntern());
2726527265 }
2726627266 }
......@@ -29781,13 +29781,19 @@ fn storePtrVal(
2978129781 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
2978229782 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
2978329783 };
29784 operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
29785 error.OutOfMemory => return error.OutOfMemory,
29786 error.ReinterpretDeclRef => unreachable,
29787 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
29788 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
29789 };
29790
29784 if (reinterpret.write_packed) {
29785 operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
29786 error.OutOfMemory => return error.OutOfMemory,
29787 error.ReinterpretDeclRef => unreachable,
29788 };
29789 } else {
29790 operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
29791 error.OutOfMemory => return error.OutOfMemory,
29792 error.ReinterpretDeclRef => unreachable,
29793 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
29794 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
29795 };
29796 }
2979129797 const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
2979229798 error.OutOfMemory => return error.OutOfMemory,
2979329799 error.IllDefinedMemoryLayout => unreachable,
......@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
2981929825 reinterpret: struct {
2982029826 val_ptr: *Value,
2982129827 byte_offset: usize,
29828 /// If set, write the operand to packed memory
29829 write_packed: bool = false,
2982229830 },
2982329831 /// If the root decl could not be used as parent, this means `ty` is the type that
2982429832 /// caused that by not having a well-defined layout.
......@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
3018230190 );
3018330191 },
3018430192 .@"union" => {
30185 // We need to set the active field of the union.
30186 const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);
30187
3018830193 const payload = &val_ptr.castTag(.@"union").?.data;
30189 payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
30194 const layout = base_child_ty.containerLayout(mod);
3019030195
30191 return beginComptimePtrMutationInner(
30192 sema,
30193 block,
30194 src,
30195 parent.ty.structFieldType(field_index, mod),
30196 &payload.val,
30197 ptr_elem_ty,
30198 parent.mut_decl,
30199 );
30196 const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
30197 const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
30198 if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
30199 // We need to set the active field of the union.
30200 payload.tag = hypothetical_tag;
30201
30202 const field_ty = parent.ty.structFieldType(field_index, mod);
30203 return beginComptimePtrMutationInner(
30204 sema,
30205 block,
30206 src,
30207 field_ty,
30208 &payload.val,
30209 ptr_elem_ty,
30210 parent.mut_decl,
30211 );
30212 } else {
30213 // Writing to a different field (a different or unknown tag is active) requires reinterpreting
30214 // memory of the entire union, which requires knowing its abiSize.
30215 try sema.resolveTypeLayout(parent.ty);
30216
30217 // This union value no longer has a well-defined tag type.
30218 // The reinterpretation will read it back out as .none.
30219 payload.val = try payload.val.unintern(sema.arena, mod);
30220 return ComptimePtrMutationKit{
30221 .mut_decl = parent.mut_decl,
30222 .pointee = .{ .reinterpret = .{
30223 .val_ptr = val_ptr,
30224 .byte_offset = 0,
30225 .write_packed = layout == .Packed,
30226 } },
30227 .ty = parent.ty,
30228 };
30229 }
3020030230 },
3020130231 .slice => switch (field_index) {
3020230232 Value.slice_ptr_index => return beginComptimePtrMutationInner(
......@@ -30697,6 +30727,7 @@ fn bitCastVal(
3069730727 // For types with well-defined memory layouts, we serialize them a byte buffer,
3069830728 // then deserialize to the new type.
3069930729 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));
30730
3070030731 const buffer = try sema.gpa.alloc(u8, abi_size);
3070130732 defer sema.gpa.free(buffer);
3070230733 val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
......@@ -30713,6 +30744,39 @@ fn bitCastVal(
3071330744 };
3071430745}
3071530746
30747fn bitCastUnionFieldVal(
30748 sema: *Sema,
30749 block: *Block,
30750 src: LazySrcLoc,
30751 val: Value,
30752 old_ty: Type,
30753 field_ty: Type,
30754) !?Value {
30755 const mod = sema.mod;
30756 if (old_ty.eql(field_ty, mod)) return val;
30757
30758 const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));
30759 const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod));
30760
30761 const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size));
30762 defer sema.gpa.free(buffer);
30763 val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
30764 error.OutOfMemory => return error.OutOfMemory,
30765 error.ReinterpretDeclRef => return null,
30766 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
30767 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}),
30768 };
30769
30770 // Reading a larger value means we need to reinterpret from undefined bytes
30771 if (field_size > old_size) @memset(buffer[old_size..], 0xaa);
30772
30773 return Value.readFromMemory(field_ty, mod, buffer[0..], sema.arena) catch |err| switch (err) {
30774 error.OutOfMemory => return error.OutOfMemory,
30775 error.IllDefinedMemoryLayout => unreachable,
30776 error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}),
30777 };
30778}
30779
3071630780fn coerceArrayPtrToSlice(
3071730781 sema: *Sema,
3071830782 block: *Block,
src/TypedValue.zig+17-7
......@@ -84,22 +84,27 @@ pub fn print(
8484 if (level == 0) {
8585 return writer.writeAll(".{ ... }");
8686 }
87 const union_val = val.castTag(.@"union").?.data;
87 const payload = val.castTag(.@"union").?.data;
8888 try writer.writeAll(".{ ");
8989
90 if (union_val.tag.toIntern() != .none) {
90 if (payload.tag) |tag| {
9191 try print(.{
9292 .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
93 .val = union_val.tag,
93 .val = tag,
9494 }, writer, level - 1, mod);
9595 try writer.writeAll(" = ");
96 const field_ty = ty.unionFieldType(union_val.tag, mod).?;
96 const field_ty = ty.unionFieldType(tag, mod).?;
9797 try print(.{
9898 .ty = field_ty,
99 .val = union_val.val,
99 .val = payload.val,
100100 }, writer, level - 1, mod);
101101 } else {
102 return writer.writeAll("(unknown tag)");
102 try writer.writeAll("(unknown tag) = ");
103 const backing_ty = try ty.unionBackingType(mod);
104 try print(.{
105 .ty = backing_ty,
106 .val = payload.val,
107 }, writer, level - 1, mod);
103108 }
104109
105110 return writer.writeAll(" }");
......@@ -421,7 +426,12 @@ pub fn print(
421426 .val = un.val.toValue(),
422427 }, writer, level - 1, mod);
423428 } else {
424 try writer.writeAll("(unknown tag)");
429 try writer.writeAll("(unknown tag) = ");
430 const backing_ty = try ty.unionBackingType(mod);
431 try print(.{
432 .ty = backing_ty,
433 .val = un.val.toValue(),
434 }, writer, level - 1, mod);
425435 }
426436 } else try writer.writeAll("...");
427437 return writer.writeAll(" }");
src/type.zig+10
......@@ -1954,6 +1954,16 @@ pub const Type = struct {
19541954 return true;
19551955 }
19561956
1957 /// Returns the type used for backing storage of this union during comptime operations.
1958 /// Asserts the type is either an extern or packed union.
1959 pub fn unionBackingType(ty: Type, mod: *Module) !Type {
1960 return switch (ty.containerLayout(mod)) {
1961 .Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
1962 .Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
1963 .Auto => unreachable,
1964 };
1965 }
1966
19571967 pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
19581968 const ip = &mod.intern_pool;
19591969 const union_type = ip.indexToKey(ty.toIntern()).union_type;
src/value.zig+25-22
......@@ -327,11 +327,19 @@ pub const Value = struct {
327327 },
328328 .@"union" => {
329329 const pl = val.castTag(.@"union").?.data;
330 return mod.intern(.{ .un = .{
331 .ty = ty.toIntern(),
332 .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
333 .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
334 } });
330 if (pl.tag) |pl_tag| {
331 return mod.intern(.{ .un = .{
332 .ty = ty.toIntern(),
333 .tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
334 .val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
335 } });
336 } else {
337 return mod.intern(.{ .un = .{
338 .ty = ty.toIntern(),
339 .tag = .none,
340 .val = try pl.val.intern(try ty.unionBackingType(mod), mod),
341 } });
342 }
335343 },
336344 }
337345 }
......@@ -399,10 +407,7 @@ pub const Value = struct {
399407
400408 .un => |un| Tag.@"union".create(arena, .{
401409 // toValue asserts that the value cannot be .none which is valid on unions.
402 .tag = .{
403 .ip_index = un.tag,
404 .legacy = undefined,
405 },
410 .tag = if (un.tag == .none) null else un.tag.toValue(),
406411 .val = un.val.toValue(),
407412 }),
408413
......@@ -709,21 +714,22 @@ pub const Value = struct {
709714 .Union => switch (ty.containerLayout(mod)) {
710715 .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
711716 .Extern => {
712 const union_obj = mod.typeToUnion(ty).?;
713717 if (val.unionTag(mod)) |union_tag| {
718 const union_obj = mod.typeToUnion(ty).?;
714719 const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
715720 const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
716721 const field_val = try val.fieldValue(mod, field_index);
717722 const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
718723 return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
719724 } else {
720 const union_size = ty.abiSize(mod);
721 const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
722 return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
725 const backing_ty = try ty.unionBackingType(mod);
726 const byte_count: usize = @intCast(backing_ty.abiSize(mod));
727 return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
723728 }
724729 },
725730 .Packed => {
726 const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
731 const backing_ty = try ty.unionBackingType(mod);
732 const byte_count: usize = @intCast(backing_ty.abiSize(mod));
727733 return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
728734 },
729735 },
......@@ -842,9 +848,8 @@ pub const Value = struct {
842848 const field_val = try val.fieldValue(mod, field_index);
843849 return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
844850 } else {
845 const union_bits: u16 = @intCast(ty.bitSize(mod));
846 const int_ty = try mod.intType(.unsigned, union_bits);
847 return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
851 const backing_ty = try ty.unionBackingType(mod);
852 return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
848853 }
849854 },
850855 }
......@@ -1146,10 +1151,8 @@ pub const Value = struct {
11461151 .Union => switch (ty.containerLayout(mod)) {
11471152 .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
11481153 .Packed => {
1149 const union_bits: u16 = @intCast(ty.bitSize(mod));
1150 assert(union_bits != 0);
1151 const int_ty = try mod.intType(.unsigned, union_bits);
1152 const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
1154 const backing_ty = try ty.unionBackingType(mod);
1155 const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
11531156 return (try mod.intern(.{ .un = .{
11541157 .ty = ty.toIntern(),
11551158 .tag = .none,
......@@ -4017,7 +4020,7 @@ pub const Value = struct {
40174020 data: Data,
40184021
40194022 pub const Data = struct {
4020 tag: Value,
4023 tag: ?Value,
40214024 val: Value,
40224025 };
40234026 };
test/behavior/comptime_memory.zig+133-23
......@@ -457,18 +457,60 @@ test "type pun null pointer-like optional" {
457457}
458458
459459test "reinterpret extern union" {
460 {
461 const U = extern union {
462 a: u32,
463 b: u8 align(8),
464 };
460 const U = extern union {
461 foo: u8,
462 baz: u32 align(8),
463 bar: u32,
464 };
465465
466 comptime var u: U = undefined;
467 comptime @memset(std.mem.asBytes(&u), 42);
468 try comptime testing.expect(0x2a2a2a2a == u.a);
469 try comptime testing.expect(42 == u.b);
470 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);
471 try testing.expectEqual(42, u.b);
466 comptime {
467 {
468 // Undefined initialization
469 const u = blk: {
470 var u: U = undefined;
471 @memset(std.mem.asBytes(&u), 0);
472 u.bar = 0xbbbbbbbb;
473 u.foo = 0x2a;
474 break :blk u;
475 };
476 try testing.expectEqual(@as(u8, 0x2a), u.foo);
477 try testing.expectEqual(@as(u32, 0xbbbbbb2a), u.bar);
478 try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz);
479 }
480
481 {
482 // Union initialization
483 var u: U = .{
484 .foo = 0x2a,
485 };
486 try testing.expectEqual(@as(u8, 0x2a), u.foo);
487 try testing.expectEqual(@as(u32, 0x2a), u.bar & 0xff);
488 try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff);
489
490 // Writing to a larger field
491 u = .{
492 .baz = 0xbbbbbbbb,
493 };
494 try testing.expectEqual(@as(u8, 0xbb), u.foo);
495 try testing.expectEqual(@as(u32, 0xbbbbbbbb), u.bar);
496 try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
497
498 // Writing to the same field
499 u = .{
500 .baz = 0xcccccccc,
501 };
502 try testing.expectEqual(@as(u8, 0xcc), u.foo);
503 try testing.expectEqual(@as(u32, 0xcccccccc), u.bar);
504 try testing.expectEqual(@as(u64, 0xcccccccc), u.baz);
505
506 // Writing to a smaller field
507 u = .{
508 .foo = 0xdd,
509 };
510 try testing.expectEqual(@as(u8, 0xdd), u.foo);
511 try testing.expectEqual(@as(u32, 0xccccccdd), u.bar);
512 try testing.expectEqual(@as(u64, 0xccccccdd), u.baz);
513 }
472514 }
473515}
474516
......@@ -479,12 +521,14 @@ test "reinterpret packed union" {
479521 b: u8 align(8),
480522 };
481523
482 comptime var u: U = undefined;
483 comptime @memset(std.mem.asBytes(&u), 42);
484 try comptime testing.expect(0x2a2a2a2a == u.a);
485 try comptime testing.expect(0x2a == u.b);
486 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);
487 try testing.expectEqual(0x2a, u.b);
524 comptime {
525 var u: U = undefined;
526 @memset(std.mem.asBytes(&u), 42);
527 try testing.expect(0x2a2a2a2a == u.a);
528 try testing.expect(0x2a == u.b);
529 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);
530 try testing.expectEqual(0x2a, u.b);
531 }
488532 }
489533
490534 {
......@@ -498,11 +542,77 @@ test "reinterpret packed union" {
498542 msb: U,
499543 };
500544
501 comptime var s: S = undefined;
502 comptime @memset(std.mem.asBytes(&s), 0xaa);
503 try comptime testing.expectEqual(@as(u7, 0x2a), s.lsb.a);
504 try comptime testing.expectEqual(@as(u1, 0), s.lsb.b);
505 try comptime testing.expectEqual(@as(u7, 0x55), s.msb.a);
506 try comptime testing.expectEqual(@as(u1, 1), s.msb.b);
545 comptime {
546 var s: S = undefined;
547 @memset(std.mem.asBytes(&s), 0x55);
548 try testing.expectEqual(@as(u7, 0x55), s.lsb.a);
549 try testing.expectEqual(@as(u1, 1), s.lsb.b);
550 try testing.expectEqual(@as(u7, 0x2a), s.msb.a);
551 try testing.expectEqual(@as(u1, 0), s.msb.b);
552
553 s.lsb.b = 0;
554 try testing.expectEqual(@as(u7, 0x54), s.lsb.a);
555 try testing.expectEqual(@as(u1, 0), s.lsb.b);
556 s.msb.b = 1;
557 try testing.expectEqual(@as(u7, 0x2b), s.msb.a);
558 try testing.expectEqual(@as(u1, 1), s.msb.b);
559 }
560 }
561
562 {
563 const U = packed union {
564 foo: u8,
565 bar: u29,
566 baz: u64,
567 };
568
569 comptime {
570 {
571 const u = blk: {
572 var u: U = undefined;
573 @memset(std.mem.asBytes(&u), 0);
574 u.baz = 0xbbbbbbbb;
575 u.foo = 0x2a;
576 break :blk u;
577 };
578 try testing.expectEqual(@as(u8, 0x2a), u.foo);
579 try testing.expectEqual(@as(u29, 0x1bbbbb2a), u.bar);
580 try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz);
581 }
582
583 {
584 // Union initialization
585 var u: U = .{
586 .foo = 0x2a,
587 };
588 try testing.expectEqual(@as(u8, 0x2a), u.foo);
589 try testing.expectEqual(@as(u29, 0x2a), u.bar & 0xff);
590 try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff);
591
592 // Writing to a larger field
593 u = .{
594 .baz = 0xbbbbbbbb,
595 };
596 try testing.expectEqual(@as(u8, 0xbb), u.foo);
597 try testing.expectEqual(@as(u29, 0x1bbbbbbb), u.bar);
598 try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
599
600 // Writing to the same field
601 u = .{
602 .baz = 0xcccccccc,
603 };
604 try testing.expectEqual(@as(u8, 0xcc), u.foo);
605 try testing.expectEqual(@as(u29, 0x0ccccccc), u.bar);
606 try testing.expectEqual(@as(u64, 0xcccccccc), u.baz);
607
608 // Writing to a smaller field
609 u = .{
610 .foo = 0xdd,
611 };
612 try testing.expectEqual(@as(u8, 0xdd), u.foo);
613 try testing.expectEqual(@as(u29, 0x0cccccdd), u.bar);
614 try testing.expectEqual(@as(u64, 0xccccccdd), u.baz);
615 }
616 }
507617 }
508618}