| author | |
| committer | |
| log | e69cb9105a716dfd4a8cc2684417545b2438f606 |
| tree | d80540e1ff8f325f753ee3537daac3d8cbf0f975 |
| parent | f4e051e35d8019c9a8d99ccae8f2e9d8f032629a |
| parent | 3145fa97c21704d8822db928e5f988f22497b1b8 |
| signature |
stage2: implement airArrayToSlice for x86_644 files changed, 137 insertions(+), 46 deletions(-)
src/arch/x86_64/CodeGen.zig+27-6| ... | ... | @@ -1659,6 +1659,18 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type |
| 1659 | 1659 | }, |
| 1660 | 1660 | } |
| 1661 | 1661 | }, |
| 1662 | .register => |src_reg| { | |
| 1663 | const abi_size = value_ty.abiSize(self.target.*); | |
| 1664 | _ = try self.addInst(.{ | |
| 1665 | .tag = .mov, | |
| 1666 | .ops = (Mir.Ops{ | |
| 1667 | .reg1 = reg.to64(), | |
| 1668 | .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), | |
| 1669 | .flags = 0b10, | |
| 1670 | }).encode(), | |
| 1671 | .data = .{ .imm = 0 }, | |
| 1672 | }); | |
| 1673 | }, | |
| 1662 | 1674 | else => |other| { |
| 1663 | 1675 | return self.fail("TODO implement set pointee with {}", .{other}); |
| 1664 | 1676 | }, |
| ... | ... | @@ -1822,7 +1834,7 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: |
| 1822 | 1834 | const dst_ty = self.air.typeOfIndex(inst); |
| 1823 | 1835 | const air_tags = self.air.instructions.items(.tag); |
| 1824 | 1836 | switch (air_tags[inst]) { |
| 1825 | .add, .addwrap => try self.genBinMathOpMir(.add, dst_ty, .unsigned, dst_mcv, src_mcv), | |
| 1837 | .add, .addwrap, .ptr_add => try self.genBinMathOpMir(.add, dst_ty, .unsigned, dst_mcv, src_mcv), | |
| 1826 | 1838 | .bool_or, .bit_or => try self.genBinMathOpMir(.@"or", dst_ty, .unsigned, dst_mcv, src_mcv), |
| 1827 | 1839 | .bool_and, .bit_and => try self.genBinMathOpMir(.@"and", dst_ty, .unsigned, dst_mcv, src_mcv), |
| 1828 | 1840 | .sub, .subwrap => try self.genBinMathOpMir(.sub, dst_ty, .unsigned, dst_mcv, src_mcv), |
| ... | ... | @@ -3125,7 +3137,6 @@ fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void { |
| 3125 | 3137 | fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { |
| 3126 | 3138 | switch (mcv) { |
| 3127 | 3139 | .dead => unreachable, |
| 3128 | .ptr_stack_offset => unreachable, | |
| 3129 | 3140 | .ptr_embedded_in_code => unreachable, |
| 3130 | 3141 | .unreach, .none => return, // Nothing to do. |
| 3131 | 3142 | .undef => { |
| ... | ... | @@ -3241,6 +3252,10 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro |
| 3241 | 3252 | } |
| 3242 | 3253 | return self.fail("TODO implement memcpy for setting stack from {}", .{mcv}); |
| 3243 | 3254 | }, |
| 3255 | .ptr_stack_offset => { | |
| 3256 | const reg = try self.copyToTmpRegister(ty, mcv); | |
| 3257 | return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); | |
| 3258 | }, | |
| 3244 | 3259 | .stack_offset => |off| { |
| 3245 | 3260 | if (stack_offset == off) { |
| 3246 | 3261 | // Copy stack variable to itself; nothing to do. |
| ... | ... | @@ -3618,10 +3633,16 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void { |
| 3618 | 3633 | |
| 3619 | 3634 | fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void { |
| 3620 | 3635 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 3621 | const result: MCValue = if (self.liveness.isUnused(inst)) | |
| 3622 | .dead | |
| 3623 | else | |
| 3624 | return self.fail("TODO implement airArrayToSlice for {}", .{self.target.cpu.arch}); | |
| 3636 | const ptr_ty = self.air.typeOf(ty_op.operand); | |
| 3637 | const ptr = try self.resolveInst(ty_op.operand); | |
| 3638 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else blk: { | |
| 3639 | const stack_offset = try self.allocMem(inst, 16, 16); | |
| 3640 | const array_ty = ptr_ty.childType(); | |
| 3641 | const array_len = array_ty.arrayLenIncludingSentinel(); | |
| 3642 | try self.genSetStack(Type.initTag(.usize), stack_offset + 8, ptr); | |
| 3643 | try self.genSetStack(Type.initTag(.u64), stack_offset + 16, .{ .immediate = array_len }); | |
| 3644 | break :blk .{ .stack_offset = stack_offset }; | |
| 3645 | }; | |
| 3625 | 3646 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
| 3626 | 3647 | } |
| 3627 | 3648 |
test/behavior.zig+2-2| ... | ... | @@ -16,11 +16,11 @@ test { |
| 16 | 16 | _ = @import("behavior/type.zig"); |
| 17 | 17 | _ = @import("behavior/bugs/655.zig"); |
| 18 | 18 | _ = @import("behavior/bool.zig"); |
| 19 | _ = @import("behavior/align.zig"); | |
| 20 | _ = @import("behavior/array.zig"); | |
| 19 | 21 | |
| 20 | 22 | if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) { |
| 21 | 23 | // Tests that pass for stage1, llvm backend, C backend, wasm backend. |
| 22 | _ = @import("behavior/align.zig"); | |
| 23 | _ = @import("behavior/array.zig"); | |
| 24 | 24 | _ = @import("behavior/basic.zig"); |
| 25 | 25 | _ = @import("behavior/bitcast.zig"); |
| 26 | 26 | _ = @import("behavior/bugs/624.zig"); |
test/behavior/align.zig+66-38| ... | ... | @@ -6,6 +6,8 @@ const native_arch = builtin.target.cpu.arch; |
| 6 | 6 | var foo: u8 align(4) = 100; |
| 7 | 7 | |
| 8 | 8 | test "global variable alignment" { |
| 9 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 10 | ||
| 9 | 11 | comptime try expect(@typeInfo(@TypeOf(&foo)).Pointer.alignment == 4); |
| 10 | 12 | comptime try expect(@TypeOf(&foo) == *align(4) u8); |
| 11 | 13 | { |
| ... | ... | @@ -20,10 +22,14 @@ test "global variable alignment" { |
| 20 | 22 | } |
| 21 | 23 | |
| 22 | 24 | test "default alignment allows unspecified in type syntax" { |
| 25 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 26 | ||
| 23 | 27 | try expect(*u32 == *align(@alignOf(u32)) u32); |
| 24 | 28 | } |
| 25 | 29 | |
| 26 | 30 | test "implicitly decreasing pointer alignment" { |
| 31 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 32 | ||
| 27 | 33 | const a: u32 align(4) = 3; |
| 28 | 34 | const b: u32 align(8) = 4; |
| 29 | 35 | try expect(addUnaligned(&a, &b) == 7); |
| ... | ... | @@ -33,16 +39,9 @@ fn addUnaligned(a: *align(1) const u32, b: *align(1) const u32) u32 { |
| 33 | 39 | return a.* + b.*; |
| 34 | 40 | } |
| 35 | 41 | |
| 36 | test "implicitly decreasing slice alignment" { | |
| 37 | const a: u32 align(4) = 3; | |
| 38 | const b: u32 align(8) = 4; | |
| 39 | try expect(addUnalignedSlice(@as(*const [1]u32, &a)[0..], @as(*const [1]u32, &b)[0..]) == 7); | |
| 40 | } | |
| 41 | fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) u32 { | |
| 42 | return a[0] + b[0]; | |
| 43 | } | |
| 44 | ||
| 45 | 42 | test "@alignCast pointers" { |
| 43 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 44 | ||
| 46 | 45 | var x: u32 align(4) = 1; |
| 47 | 46 | expectsOnly1(&x); |
| 48 | 47 | try expect(x == 2); |
| ... | ... | @@ -54,48 +53,25 @@ fn expects4(x: *align(4) u32) void { |
| 54 | 53 | x.* += 1; |
| 55 | 54 | } |
| 56 | 55 | |
| 57 | test "specifying alignment allows pointer cast" { | |
| 58 | try testBytesAlign(0x33); | |
| 59 | } | |
| 60 | fn testBytesAlign(b: u8) !void { | |
| 61 | var bytes align(4) = [_]u8{ b, b, b, b }; | |
| 62 | const ptr = @ptrCast(*u32, &bytes[0]); | |
| 63 | try expect(ptr.* == 0x33333333); | |
| 64 | } | |
| 65 | ||
| 66 | test "@alignCast slices" { | |
| 67 | var array align(4) = [_]u32{ 1, 1 }; | |
| 68 | const slice = array[0..]; | |
| 69 | sliceExpectsOnly1(slice); | |
| 70 | try expect(slice[0] == 2); | |
| 71 | } | |
| 72 | fn sliceExpectsOnly1(slice: []align(1) u32) void { | |
| 73 | sliceExpects4(@alignCast(4, slice)); | |
| 74 | } | |
| 75 | fn sliceExpects4(slice: []align(4) u32) void { | |
| 76 | slice[0] += 1; | |
| 77 | } | |
| 78 | ||
| 79 | 56 | test "alignment of structs" { |
| 57 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 58 | ||
| 80 | 59 | try expect(@alignOf(struct { |
| 81 | 60 | a: i32, |
| 82 | 61 | b: *i32, |
| 83 | 62 | }) == @alignOf(usize)); |
| 84 | 63 | } |
| 85 | 64 | |
| 86 | test "return error union with 128-bit integer" { | |
| 87 | try expect(3 == try give()); | |
| 88 | } | |
| 89 | fn give() anyerror!u128 { | |
| 90 | return 3; | |
| 91 | } | |
| 92 | ||
| 93 | 65 | test "alignment of >= 128-bit integer type" { |
| 66 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 67 | ||
| 94 | 68 | try expect(@alignOf(u128) == 16); |
| 95 | 69 | try expect(@alignOf(u129) == 16); |
| 96 | 70 | } |
| 97 | 71 | |
| 98 | 72 | test "alignment of struct with 128-bit field" { |
| 73 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 74 | ||
| 99 | 75 | try expect(@alignOf(struct { |
| 100 | 76 | x: u128, |
| 101 | 77 | }) == 16); |
| ... | ... | @@ -108,6 +84,8 @@ test "alignment of struct with 128-bit field" { |
| 108 | 84 | } |
| 109 | 85 | |
| 110 | 86 | test "size of extern struct with 128-bit field" { |
| 87 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 88 | ||
| 111 | 89 | try expect(@sizeOf(extern struct { |
| 112 | 90 | x: u128, |
| 113 | 91 | y: u8, |
| ... | ... | @@ -122,12 +100,16 @@ test "size of extern struct with 128-bit field" { |
| 122 | 100 | } |
| 123 | 101 | |
| 124 | 102 | test "@ptrCast preserves alignment of bigger source" { |
| 103 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 104 | ||
| 125 | 105 | var x: u32 align(16) = 1234; |
| 126 | 106 | const ptr = @ptrCast(*u8, &x); |
| 127 | 107 | try expect(@TypeOf(ptr) == *align(16) u8); |
| 128 | 108 | } |
| 129 | 109 | |
| 130 | 110 | test "alignstack" { |
| 111 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 112 | ||
| 131 | 113 | try expect(fnWithAlignedStack() == 1234); |
| 132 | 114 | } |
| 133 | 115 | |
| ... | ... | @@ -135,3 +117,49 @@ fn fnWithAlignedStack() i32 { |
| 135 | 117 | @setAlignStack(256); |
| 136 | 118 | return 1234; |
| 137 | 119 | } |
| 120 | ||
| 121 | test "implicitly decreasing slice alignment" { | |
| 122 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 123 | ||
| 124 | const a: u32 align(4) = 3; | |
| 125 | const b: u32 align(8) = 4; | |
| 126 | try expect(addUnalignedSlice(@as(*const [1]u32, &a)[0..], @as(*const [1]u32, &b)[0..]) == 7); | |
| 127 | } | |
| 128 | fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) u32 { | |
| 129 | return a[0] + b[0]; | |
| 130 | } | |
| 131 | ||
| 132 | test "specifying alignment allows pointer cast" { | |
| 133 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 134 | ||
| 135 | try testBytesAlign(0x33); | |
| 136 | } | |
| 137 | fn testBytesAlign(b: u8) !void { | |
| 138 | var bytes align(4) = [_]u8{ b, b, b, b }; | |
| 139 | const ptr = @ptrCast(*u32, &bytes[0]); | |
| 140 | try expect(ptr.* == 0x33333333); | |
| 141 | } | |
| 142 | ||
| 143 | test "@alignCast slices" { | |
| 144 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 145 | ||
| 146 | var array align(4) = [_]u32{ 1, 1 }; | |
| 147 | const slice = array[0..]; | |
| 148 | sliceExpectsOnly1(slice); | |
| 149 | try expect(slice[0] == 2); | |
| 150 | } | |
| 151 | fn sliceExpectsOnly1(slice: []align(1) u32) void { | |
| 152 | sliceExpects4(@alignCast(4, slice)); | |
| 153 | } | |
| 154 | fn sliceExpects4(slice: []align(4) u32) void { | |
| 155 | slice[0] += 1; | |
| 156 | } | |
| 157 | ||
| 158 | test "return error union with 128-bit integer" { | |
| 159 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 160 | ||
| 161 | try expect(3 == try give()); | |
| 162 | } | |
| 163 | fn give() anyerror!u128 { | |
| 164 | return 3; | |
| 165 | } |
test/behavior/array.zig+42| ... | ... | @@ -5,7 +5,23 @@ const mem = std.mem; |
| 5 | 5 | const expect = testing.expect; |
| 6 | 6 | const expectEqual = testing.expectEqual; |
| 7 | 7 | |
| 8 | test "array to slice" { | |
| 9 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 10 | ||
| 11 | const a: u32 align(4) = 3; | |
| 12 | const b: u32 align(8) = 4; | |
| 13 | const a_slice: []align(1) const u32 = @as(*const [1]u32, &a)[0..]; | |
| 14 | const b_slice: []align(1) const u32 = @as(*const [1]u32, &b)[0..]; | |
| 15 | try expect(a_slice[0] + b_slice[0] == 7); | |
| 16 | ||
| 17 | const d: []const u32 = &[2]u32{ 1, 2 }; | |
| 18 | const e: []const u32 = &[3]u32{ 3, 4, 5 }; | |
| 19 | try expect(d[0] + e[0] + d[1] + e[1] == 10); | |
| 20 | } | |
| 21 | ||
| 8 | 22 | test "arrays" { |
| 23 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 24 | ||
| 9 | 25 | var array: [5]u32 = undefined; |
| 10 | 26 | |
| 11 | 27 | var i: u32 = 0; |
| ... | ... | @@ -30,6 +46,8 @@ fn getArrayLen(a: []const u32) usize { |
| 30 | 46 | } |
| 31 | 47 | |
| 32 | 48 | test "array init with mult" { |
| 49 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 50 | ||
| 33 | 51 | const a = 'a'; |
| 34 | 52 | var i: [8]u8 = [2]u8{ a, 'b' } ** 4; |
| 35 | 53 | try expect(std.mem.eql(u8, &i, "abababab")); |
| ... | ... | @@ -39,6 +57,8 @@ test "array init with mult" { |
| 39 | 57 | } |
| 40 | 58 | |
| 41 | 59 | test "array literal with explicit type" { |
| 60 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 61 | ||
| 42 | 62 | const hex_mult: [4]u16 = .{ 4096, 256, 16, 1 }; |
| 43 | 63 | |
| 44 | 64 | try expect(hex_mult.len == 4); |
| ... | ... | @@ -46,6 +66,8 @@ test "array literal with explicit type" { |
| 46 | 66 | } |
| 47 | 67 | |
| 48 | 68 | test "array literal with inferred length" { |
| 69 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 70 | ||
| 49 | 71 | const hex_mult = [_]u16{ 4096, 256, 16, 1 }; |
| 50 | 72 | |
| 51 | 73 | try expect(hex_mult.len == 4); |
| ... | ... | @@ -53,6 +75,8 @@ test "array literal with inferred length" { |
| 53 | 75 | } |
| 54 | 76 | |
| 55 | 77 | test "array dot len const expr" { |
| 78 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 79 | ||
| 56 | 80 | try expect(comptime x: { |
| 57 | 81 | break :x some_array.len == 4; |
| 58 | 82 | }); |
| ... | ... | @@ -64,12 +88,16 @@ const ArrayDotLenConstExpr = struct { |
| 64 | 88 | const some_array = [_]u8{ 0, 1, 2, 3 }; |
| 65 | 89 | |
| 66 | 90 | test "array literal with specified size" { |
| 91 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 92 | ||
| 67 | 93 | var array = [2]u8{ 1, 2 }; |
| 68 | 94 | try expect(array[0] == 1); |
| 69 | 95 | try expect(array[1] == 2); |
| 70 | 96 | } |
| 71 | 97 | |
| 72 | 98 | test "array len field" { |
| 99 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 100 | ||
| 73 | 101 | var arr = [4]u8{ 0, 0, 0, 0 }; |
| 74 | 102 | var ptr = &arr; |
| 75 | 103 | try expect(arr.len == 4); |
| ... | ... | @@ -79,6 +107,8 @@ test "array len field" { |
| 79 | 107 | } |
| 80 | 108 | |
| 81 | 109 | test "array with sentinels" { |
| 110 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 111 | ||
| 82 | 112 | const S = struct { |
| 83 | 113 | fn doTheTest(is_ct: bool) !void { |
| 84 | 114 | if (is_ct or builtin.zig_is_stage2) { |
| ... | ... | @@ -106,6 +136,8 @@ test "array with sentinels" { |
| 106 | 136 | } |
| 107 | 137 | |
| 108 | 138 | test "void arrays" { |
| 139 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 140 | ||
| 109 | 141 | var array: [4]void = undefined; |
| 110 | 142 | array[0] = void{}; |
| 111 | 143 | array[1] = array[2]; |
| ... | ... | @@ -114,6 +146,8 @@ test "void arrays" { |
| 114 | 146 | } |
| 115 | 147 | |
| 116 | 148 | test "nested arrays" { |
| 149 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 150 | ||
| 117 | 151 | if (builtin.zig_backend == .stage2_wasm) { |
| 118 | 152 | // TODO this is a recent stage2 test case regression due to an enhancement; |
| 119 | 153 | // now arrays are properly detected as comptime. This exercised a new code |
| ... | ... | @@ -132,6 +166,8 @@ test "nested arrays" { |
| 132 | 166 | } |
| 133 | 167 | |
| 134 | 168 | test "implicit comptime in array type size" { |
| 169 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 170 | ||
| 135 | 171 | var arr: [plusOne(10)]bool = undefined; |
| 136 | 172 | try expect(arr.len == 11); |
| 137 | 173 | } |
| ... | ... | @@ -141,6 +177,8 @@ fn plusOne(x: u32) u32 { |
| 141 | 177 | } |
| 142 | 178 | |
| 143 | 179 | test "single-item pointer to array indexing and slicing" { |
| 180 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 181 | ||
| 144 | 182 | try testSingleItemPtrArrayIndexSlice(); |
| 145 | 183 | comptime try testSingleItemPtrArrayIndexSlice(); |
| 146 | 184 | } |
| ... | ... | @@ -164,6 +202,8 @@ fn doSomeMangling(array: *[4]u8) void { |
| 164 | 202 | } |
| 165 | 203 | |
| 166 | 204 | test "implicit cast zero sized array ptr to slice" { |
| 205 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 206 | ||
| 167 | 207 | { |
| 168 | 208 | var b = "".*; |
| 169 | 209 | const c: []const u8 = &b; |
| ... | ... | @@ -177,6 +217,8 @@ test "implicit cast zero sized array ptr to slice" { |
| 177 | 217 | } |
| 178 | 218 | |
| 179 | 219 | test "anonymous list literal syntax" { |
| 220 | if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 221 | ||
| 180 | 222 | const S = struct { |
| 181 | 223 | fn doTheTest() !void { |
| 182 | 224 | var array: [4]u8 = .{ 1, 2, 3, 4 }; |