| author | |
| committer | |
| log | 2dd5e8b6f865a24498da06a9a0ce3609d23662c9 |
| tree | 96de992b22f7cfc4bcb672892c64fb2cdf621913 |
| parent | e1375942e584d4fffd3ecd06277ced7cf35d7e6c |
| parent | d5100dc81555f0e8197d5f189b1432070e8d72dd |
| signature |
stage2: tuple/slice mul/cat17 files changed, 331 insertions(+), 70 deletions(-)
lib/std/hash_map.zig+2-2| ... | ... | @@ -738,7 +738,7 @@ pub fn HashMapUnmanaged( |
| 738 | 738 | value: V, |
| 739 | 739 | }; |
| 740 | 740 | |
| 741 | const Header = packed struct { | |
| 741 | const Header = struct { | |
| 742 | 742 | values: [*]V, |
| 743 | 743 | keys: [*]K, |
| 744 | 744 | capacity: Size, |
| ... | ... | @@ -932,7 +932,7 @@ pub fn HashMapUnmanaged( |
| 932 | 932 | } |
| 933 | 933 | |
| 934 | 934 | fn header(self: *const Self) *Header { |
| 935 | return @ptrCast(*Header, @ptrCast([*]Header, self.metadata.?) - 1); | |
| 935 | return @ptrCast(*Header, @ptrCast([*]Header, @alignCast(@alignOf(Header), self.metadata.?)) - 1); | |
| 936 | 936 | } |
| 937 | 937 | |
| 938 | 938 | fn keys(self: *const Self) [*]K { |
lib/std/heap/general_purpose_allocator.zig+19-6| ... | ... | @@ -341,9 +341,15 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 341 | 341 | const slot_index = @intCast(SlotIndex, used_bits_byte * 8 + bit_index); |
| 342 | 342 | const stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc); |
| 343 | 343 | const addr = bucket.page + slot_index * size_class; |
| 344 | log.err("memory address 0x{x} leaked: {s}", .{ | |
| 345 | @ptrToInt(addr), stack_trace, | |
| 346 | }); | |
| 344 | if (builtin.zig_backend == .stage1) { | |
| 345 | log.err("memory address 0x{x} leaked: {s}", .{ | |
| 346 | @ptrToInt(addr), stack_trace, | |
| 347 | }); | |
| 348 | } else { // TODO | |
| 349 | log.err("memory address 0x{x} leaked", .{ | |
| 350 | @ptrToInt(addr), | |
| 351 | }); | |
| 352 | } | |
| 347 | 353 | leaks = true; |
| 348 | 354 | } |
| 349 | 355 | if (bit_index == math.maxInt(u3)) |
| ... | ... | @@ -372,9 +378,16 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 372 | 378 | var it = self.large_allocations.valueIterator(); |
| 373 | 379 | while (it.next()) |large_alloc| { |
| 374 | 380 | if (config.retain_metadata and large_alloc.freed) continue; |
| 375 | log.err("memory address 0x{x} leaked: {s}", .{ | |
| 376 | @ptrToInt(large_alloc.bytes.ptr), large_alloc.getStackTrace(.alloc), | |
| 377 | }); | |
| 381 | const stack_trace = large_alloc.getStackTrace(.alloc); | |
| 382 | if (builtin.zig_backend == .stage1) { | |
| 383 | log.err("memory address 0x{x} leaked: {s}", .{ | |
| 384 | @ptrToInt(large_alloc.bytes.ptr), stack_trace, | |
| 385 | }); | |
| 386 | } else { // TODO | |
| 387 | log.err("memory address 0x{x} leaked", .{ | |
| 388 | @ptrToInt(large_alloc.bytes.ptr), | |
| 389 | }); | |
| 390 | } | |
| 378 | 391 | leaks = true; |
| 379 | 392 | } |
| 380 | 393 | return leaks; |
lib/std/special/test_runner.zig+3-2| ... | ... | @@ -46,9 +46,10 @@ pub fn main() void { |
| 46 | 46 | |
| 47 | 47 | var leaks: usize = 0; |
| 48 | 48 | for (test_fn_list) |test_fn, i| { |
| 49 | if (builtin.zig_backend != .stage2_llvm) std.testing.allocator_instance = .{}; | |
| 49 | const gpa_works = builtin.zig_backend == .stage1 or builtin.os.tag != .macos; | |
| 50 | if (gpa_works) std.testing.allocator_instance = .{}; | |
| 50 | 51 | defer { |
| 51 | if (builtin.zig_backend != .stage2_llvm and std.testing.allocator_instance.deinit()) { | |
| 52 | if (gpa_works and std.testing.allocator_instance.deinit()) { | |
| 52 | 53 | leaks += 1; |
| 53 | 54 | } |
| 54 | 55 | } |
src/Air.zig+4| ... | ... | @@ -218,6 +218,9 @@ pub const Inst = struct { |
| 218 | 218 | /// Yields the return address of the current function. |
| 219 | 219 | /// Uses the `no_op` field. |
| 220 | 220 | ret_addr, |
| 221 | /// Implements @frameAddress builtin. | |
| 222 | /// Uses the `no_op` field. | |
| 223 | frame_addr, | |
| 221 | 224 | /// Function call. |
| 222 | 225 | /// Result type is the return type of the function being called. |
| 223 | 226 | /// Uses the `pl_op` field with the `Call` payload. operand is the callee. |
| ... | ... | @@ -939,6 +942,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { |
| 939 | 942 | .ptrtoint, |
| 940 | 943 | .slice_len, |
| 941 | 944 | .ret_addr, |
| 945 | .frame_addr, | |
| 942 | 946 | => return Type.initTag(.usize), |
| 943 | 947 | |
| 944 | 948 | .bool_to_int => return Type.initTag(.u1), |
src/Liveness.zig+1| ... | ... | @@ -317,6 +317,7 @@ fn analyzeInst( |
| 317 | 317 | .unreach, |
| 318 | 318 | .fence, |
| 319 | 319 | .ret_addr, |
| 320 | .frame_addr, | |
| 320 | 321 | => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), |
| 321 | 322 | |
| 322 | 323 | .not, |
src/Sema.zig+187-22| ... | ... | @@ -8059,6 +8059,79 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. |
| 8059 | 8059 | return block.addTyOp(.not, operand_type, operand); |
| 8060 | 8060 | } |
| 8061 | 8061 | |
| 8062 | fn analyzeTupleCat( | |
| 8063 | sema: *Sema, | |
| 8064 | block: *Block, | |
| 8065 | src_node: i32, | |
| 8066 | lhs: Air.Inst.Ref, | |
| 8067 | rhs: Air.Inst.Ref, | |
| 8068 | ) CompileError!Air.Inst.Ref { | |
| 8069 | const lhs_ty = sema.typeOf(lhs); | |
| 8070 | const rhs_ty = sema.typeOf(rhs); | |
| 8071 | const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node }; | |
| 8072 | const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node }; | |
| 8073 | ||
| 8074 | const lhs_tuple = lhs_ty.tupleFields(); | |
| 8075 | const rhs_tuple = rhs_ty.tupleFields(); | |
| 8076 | const dest_fields = lhs_tuple.types.len + rhs_tuple.types.len; | |
| 8077 | ||
| 8078 | if (dest_fields == 0) { | |
| 8079 | return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value)); | |
| 8080 | } | |
| 8081 | const final_len = try sema.usizeCast(block, rhs_src, dest_fields); | |
| 8082 | ||
| 8083 | const types = try sema.arena.alloc(Type, final_len); | |
| 8084 | const values = try sema.arena.alloc(Value, final_len); | |
| 8085 | ||
| 8086 | const opt_runtime_src = rs: { | |
| 8087 | var runtime_src: ?LazySrcLoc = null; | |
| 8088 | for (lhs_tuple.types) |ty, i| { | |
| 8089 | types[i] = ty; | |
| 8090 | values[i] = lhs_tuple.values[i]; | |
| 8091 | const operand_src = lhs_src; // TODO better source location | |
| 8092 | if (values[i].tag() == .unreachable_value) { | |
| 8093 | runtime_src = operand_src; | |
| 8094 | } | |
| 8095 | } | |
| 8096 | const offset = lhs_tuple.types.len; | |
| 8097 | for (rhs_tuple.types) |ty, i| { | |
| 8098 | types[i + offset] = ty; | |
| 8099 | values[i + offset] = rhs_tuple.values[i]; | |
| 8100 | const operand_src = rhs_src; // TODO better source location | |
| 8101 | if (rhs_tuple.values[i].tag() == .unreachable_value) { | |
| 8102 | runtime_src = operand_src; | |
| 8103 | } | |
| 8104 | } | |
| 8105 | break :rs runtime_src; | |
| 8106 | }; | |
| 8107 | ||
| 8108 | const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ | |
| 8109 | .types = types, | |
| 8110 | .values = values, | |
| 8111 | }); | |
| 8112 | ||
| 8113 | const runtime_src = opt_runtime_src orelse { | |
| 8114 | const tuple_val = try Value.Tag.@"struct".create(sema.arena, values); | |
| 8115 | return sema.addConstant(tuple_ty, tuple_val); | |
| 8116 | }; | |
| 8117 | ||
| 8118 | try sema.requireRuntimeBlock(block, runtime_src); | |
| 8119 | ||
| 8120 | const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len); | |
| 8121 | for (lhs_tuple.types) |_, i| { | |
| 8122 | const operand_src = lhs_src; // TODO better source location | |
| 8123 | element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, @intCast(u32, i), lhs_ty); | |
| 8124 | } | |
| 8125 | const offset = lhs_tuple.types.len; | |
| 8126 | for (rhs_tuple.types) |_, i| { | |
| 8127 | const operand_src = rhs_src; // TODO better source location | |
| 8128 | element_refs[i + offset] = | |
| 8129 | try sema.tupleFieldValByIndex(block, operand_src, rhs, @intCast(u32, i), rhs_ty); | |
| 8130 | } | |
| 8131 | ||
| 8132 | return block.addAggregateInit(tuple_ty, element_refs); | |
| 8133 | } | |
| 8134 | ||
| 8062 | 8135 | fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 8063 | 8136 | const tracy = trace(@src()); |
| 8064 | 8137 | defer tracy.end(); |
| ... | ... | @@ -8069,12 +8142,17 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8069 | 8142 | const rhs = sema.resolveInst(extra.rhs); |
| 8070 | 8143 | const lhs_ty = sema.typeOf(lhs); |
| 8071 | 8144 | const rhs_ty = sema.typeOf(rhs); |
| 8145 | ||
| 8146 | if (lhs_ty.isTuple() and rhs_ty.isTuple()) { | |
| 8147 | return sema.analyzeTupleCat(block, inst_data.src_node, lhs, rhs); | |
| 8148 | } | |
| 8149 | ||
| 8072 | 8150 | const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; |
| 8073 | 8151 | const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; |
| 8074 | 8152 | |
| 8075 | const lhs_info = getArrayCatInfo(lhs_ty) orelse | |
| 8153 | const lhs_info = (try sema.getArrayCatInfo(block, lhs_src, lhs)) orelse | |
| 8076 | 8154 | return sema.fail(block, lhs_src, "expected array, found '{}'", .{lhs_ty}); |
| 8077 | const rhs_info = getArrayCatInfo(rhs_ty) orelse | |
| 8155 | const rhs_info = (try sema.getArrayCatInfo(block, rhs_src, rhs)) orelse | |
| 8078 | 8156 | return sema.fail(block, rhs_src, "expected array, found '{}'", .{rhs_ty}); |
| 8079 | 8157 | if (!lhs_info.elem_type.eql(rhs_info.elem_type)) { |
| 8080 | 8158 | return sema.fail(block, rhs_src, "expected array of type '{}', found '{}'", .{ lhs_info.elem_type, rhs_ty }); |
| ... | ... | @@ -8095,9 +8173,10 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8095 | 8173 | const rhs_len = try sema.usizeCast(block, lhs_src, rhs_info.len); |
| 8096 | 8174 | const final_len = lhs_len + rhs_len; |
| 8097 | 8175 | const final_len_including_sent = final_len + @boolToInt(res_sent != null); |
| 8098 | const is_pointer = lhs_ty.zigTypeTag() == .Pointer; | |
| 8099 | const lhs_sub_val = if (is_pointer) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val; | |
| 8100 | const rhs_sub_val = if (is_pointer) (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).? else rhs_val; | |
| 8176 | const lhs_single_ptr = lhs_ty.zigTypeTag() == .Pointer and !lhs_ty.isSlice(); | |
| 8177 | const rhs_single_ptr = rhs_ty.zigTypeTag() == .Pointer and !rhs_ty.isSlice(); | |
| 8178 | const lhs_sub_val = if (lhs_single_ptr) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val; | |
| 8179 | const rhs_sub_val = if (rhs_single_ptr) (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).? else rhs_val; | |
| 8101 | 8180 | var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded); |
| 8102 | 8181 | defer anon_decl.deinit(); |
| 8103 | 8182 | |
| ... | ... | @@ -8129,7 +8208,7 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8129 | 8208 | }); |
| 8130 | 8209 | const val = try Value.Tag.array.create(anon_decl.arena(), buf); |
| 8131 | 8210 | const decl = try anon_decl.finish(ty, val); |
| 8132 | if (is_pointer) { | |
| 8211 | if (lhs_single_ptr or rhs_single_ptr) { | |
| 8133 | 8212 | return sema.analyzeDeclRef(decl); |
| 8134 | 8213 | } else { |
| 8135 | 8214 | return sema.analyzeDeclVal(block, .unneeded, decl); |
| ... | ... | @@ -8142,11 +8221,20 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8142 | 8221 | } |
| 8143 | 8222 | } |
| 8144 | 8223 | |
| 8145 | fn getArrayCatInfo(t: Type) ?Type.ArrayInfo { | |
| 8224 | fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, inst: Air.Inst.Ref) !?Type.ArrayInfo { | |
| 8225 | const t = sema.typeOf(inst); | |
| 8146 | 8226 | return switch (t.zigTypeTag()) { |
| 8147 | 8227 | .Array => t.arrayInfo(), |
| 8148 | 8228 | .Pointer => blk: { |
| 8149 | 8229 | const ptrinfo = t.ptrInfo().data; |
| 8230 | if (ptrinfo.size == .Slice) { | |
| 8231 | const val = try sema.resolveConstValue(block, src, inst); | |
| 8232 | return Type.ArrayInfo{ | |
| 8233 | .elem_type = t.childType(), | |
| 8234 | .sentinel = t.sentinel(), | |
| 8235 | .len = val.sliceLen(), | |
| 8236 | }; | |
| 8237 | } | |
| 8150 | 8238 | if (ptrinfo.pointee_type.zigTypeTag() != .Array) return null; |
| 8151 | 8239 | if (ptrinfo.size != .One) return null; |
| 8152 | 8240 | break :blk ptrinfo.pointee_type.arrayInfo(); |
| ... | ... | @@ -8155,6 +8243,73 @@ fn getArrayCatInfo(t: Type) ?Type.ArrayInfo { |
| 8155 | 8243 | }; |
| 8156 | 8244 | } |
| 8157 | 8245 | |
| 8246 | fn analyzeTupleMul( | |
| 8247 | sema: *Sema, | |
| 8248 | block: *Block, | |
| 8249 | src_node: i32, | |
| 8250 | operand: Air.Inst.Ref, | |
| 8251 | factor: u64, | |
| 8252 | ) CompileError!Air.Inst.Ref { | |
| 8253 | const operand_ty = sema.typeOf(operand); | |
| 8254 | const operand_tuple = operand_ty.tupleFields(); | |
| 8255 | const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node }; | |
| 8256 | const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node }; | |
| 8257 | ||
| 8258 | const tuple_len = operand_tuple.types.len; | |
| 8259 | const final_len_u64 = std.math.mul(u64, tuple_len, factor) catch | |
| 8260 | return sema.fail(block, rhs_src, "operation results in overflow", .{}); | |
| 8261 | ||
| 8262 | if (final_len_u64 == 0) { | |
| 8263 | return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value)); | |
| 8264 | } | |
| 8265 | const final_len = try sema.usizeCast(block, rhs_src, final_len_u64); | |
| 8266 | ||
| 8267 | const types = try sema.arena.alloc(Type, final_len); | |
| 8268 | const values = try sema.arena.alloc(Value, final_len); | |
| 8269 | ||
| 8270 | const opt_runtime_src = rs: { | |
| 8271 | var runtime_src: ?LazySrcLoc = null; | |
| 8272 | for (operand_tuple.types) |ty, i| { | |
| 8273 | types[i] = ty; | |
| 8274 | values[i] = operand_tuple.values[i]; | |
| 8275 | const operand_src = lhs_src; // TODO better source location | |
| 8276 | if (values[i].tag() == .unreachable_value) { | |
| 8277 | runtime_src = operand_src; | |
| 8278 | } | |
| 8279 | } | |
| 8280 | var i: usize = 1; | |
| 8281 | while (i < factor) : (i += 1) { | |
| 8282 | mem.copy(Type, types[tuple_len * i ..], operand_tuple.types); | |
| 8283 | mem.copy(Value, values[tuple_len * i ..], operand_tuple.values); | |
| 8284 | } | |
| 8285 | break :rs runtime_src; | |
| 8286 | }; | |
| 8287 | ||
| 8288 | const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ | |
| 8289 | .types = types, | |
| 8290 | .values = values, | |
| 8291 | }); | |
| 8292 | ||
| 8293 | const runtime_src = opt_runtime_src orelse { | |
| 8294 | const tuple_val = try Value.Tag.@"struct".create(sema.arena, values); | |
| 8295 | return sema.addConstant(tuple_ty, tuple_val); | |
| 8296 | }; | |
| 8297 | ||
| 8298 | try sema.requireRuntimeBlock(block, runtime_src); | |
| 8299 | ||
| 8300 | const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len); | |
| 8301 | for (operand_tuple.types) |_, i| { | |
| 8302 | const operand_src = lhs_src; // TODO better source location | |
| 8303 | element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, operand, @intCast(u32, i), operand_ty); | |
| 8304 | } | |
| 8305 | var i: usize = 1; | |
| 8306 | while (i < factor) : (i += 1) { | |
| 8307 | mem.copy(Air.Inst.Ref, element_refs[tuple_len * i ..], element_refs[0..tuple_len]); | |
| 8308 | } | |
| 8309 | ||
| 8310 | return block.addAggregateInit(tuple_ty, element_refs); | |
| 8311 | } | |
| 8312 | ||
| 8158 | 8313 | fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 8159 | 8314 | const tracy = trace(@src()); |
| 8160 | 8315 | defer tracy.end(); |
| ... | ... | @@ -8169,7 +8324,12 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8169 | 8324 | |
| 8170 | 8325 | // In `**` rhs has to be comptime-known, but lhs can be runtime-known |
| 8171 | 8326 | const factor = try sema.resolveInt(block, rhs_src, extra.rhs, Type.usize); |
| 8172 | const mulinfo = getArrayCatInfo(lhs_ty) orelse | |
| 8327 | ||
| 8328 | if (lhs_ty.isTuple()) { | |
| 8329 | return sema.analyzeTupleMul(block, inst_data.src_node, lhs, factor); | |
| 8330 | } | |
| 8331 | ||
| 8332 | const mulinfo = (try sema.getArrayCatInfo(block, lhs_src, lhs)) orelse | |
| 8173 | 8333 | return sema.fail(block, lhs_src, "expected array, found '{}'", .{lhs_ty}); |
| 8174 | 8334 | |
| 8175 | 8335 | const final_len_u64 = std.math.mul(u64, mulinfo.len, factor) catch |
| ... | ... | @@ -8180,7 +8340,8 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8180 | 8340 | const final_len_including_sent = final_len + @boolToInt(mulinfo.sentinel != null); |
| 8181 | 8341 | const lhs_len = try sema.usizeCast(block, lhs_src, mulinfo.len); |
| 8182 | 8342 | |
| 8183 | const lhs_sub_val = if (lhs_ty.zigTypeTag() == .Pointer) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val; | |
| 8343 | const is_single_ptr = lhs_ty.zigTypeTag() == .Pointer and !lhs_ty.isSlice(); | |
| 8344 | const lhs_sub_val = if (is_single_ptr) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val; | |
| 8184 | 8345 | |
| 8185 | 8346 | var anon_decl = try block.startAnonDecl(src); |
| 8186 | 8347 | defer anon_decl.deinit(); |
| ... | ... | @@ -8220,7 +8381,7 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 8220 | 8381 | break :blk try Value.Tag.array.create(anon_decl.arena(), buf); |
| 8221 | 8382 | }; |
| 8222 | 8383 | const decl = try anon_decl.finish(final_ty, val); |
| 8223 | if (lhs_ty.zigTypeTag() == .Pointer) { | |
| 8384 | if (is_single_ptr) { | |
| 8224 | 8385 | return sema.analyzeDeclRef(decl); |
| 8225 | 8386 | } else { |
| 8226 | 8387 | return sema.analyzeDeclVal(block, .unneeded, decl); |
| ... | ... | @@ -9832,14 +9993,21 @@ fn zirRetAddr( |
| 9832 | 9993 | block: *Block, |
| 9833 | 9994 | extended: Zir.Inst.Extended.InstData, |
| 9834 | 9995 | ) CompileError!Air.Inst.Ref { |
| 9835 | const tracy = trace(@src()); | |
| 9836 | defer tracy.end(); | |
| 9837 | ||
| 9838 | 9996 | const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; |
| 9839 | 9997 | try sema.requireRuntimeBlock(block, src); |
| 9840 | 9998 | return try block.addNoOp(.ret_addr); |
| 9841 | 9999 | } |
| 9842 | 10000 | |
| 10001 | fn zirFrameAddress( | |
| 10002 | sema: *Sema, | |
| 10003 | block: *Block, | |
| 10004 | extended: Zir.Inst.Extended.InstData, | |
| 10005 | ) CompileError!Air.Inst.Ref { | |
| 10006 | const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; | |
| 10007 | try sema.requireRuntimeBlock(block, src); | |
| 10008 | return try block.addNoOp(.frame_addr); | |
| 10009 | } | |
| 10010 | ||
| 9843 | 10011 | fn zirBuiltinSrc( |
| 9844 | 10012 | sema: *Sema, |
| 9845 | 10013 | block: *Block, |
| ... | ... | @@ -11728,15 +11896,6 @@ fn zirFrame( |
| 11728 | 11896 | return sema.fail(block, src, "TODO: Sema.zirFrame", .{}); |
| 11729 | 11897 | } |
| 11730 | 11898 | |
| 11731 | fn zirFrameAddress( | |
| 11732 | sema: *Sema, | |
| 11733 | block: *Block, | |
| 11734 | extended: Zir.Inst.Extended.InstData, | |
| 11735 | ) CompileError!Air.Inst.Ref { | |
| 11736 | const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; | |
| 11737 | return sema.fail(block, src, "TODO: Sema.zirFrameAddress", .{}); | |
| 11738 | } | |
| 11739 | ||
| 11740 | 11899 | fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 11741 | 11900 | const inst_data = sema.code.instructions.items(.data)[inst].un_node; |
| 11742 | 11901 | const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; |
| ... | ... | @@ -14743,6 +14902,11 @@ fn tupleFieldVal( |
| 14743 | 14902 | tuple_ty, field_name, @errorName(err), |
| 14744 | 14903 | }); |
| 14745 | 14904 | }; |
| 14905 | if (field_index >= tuple_ty.structFieldCount()) { | |
| 14906 | return sema.fail(block, field_name_src, "tuple {} has no such field '{s}'", .{ | |
| 14907 | tuple_ty, field_name, | |
| 14908 | }); | |
| 14909 | } | |
| 14746 | 14910 | return tupleFieldValByIndex(sema, block, src, tuple_byval, field_index, tuple_ty); |
| 14747 | 14911 | } |
| 14748 | 14912 | |
| ... | ... | @@ -19093,6 +19257,7 @@ fn pointerDeref(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, ptr |
| 19093 | 19257 | /// Used to convert a u64 value to a usize value, emitting a compile error if the number |
| 19094 | 19258 | /// is too big to fit. |
| 19095 | 19259 | fn usizeCast(sema: *Sema, block: *Block, src: LazySrcLoc, int: u64) CompileError!usize { |
| 19260 | if (@bitSizeOf(u64) <= @bitSizeOf(usize)) return int; | |
| 19096 | 19261 | return std.math.cast(usize, int) catch |err| switch (err) { |
| 19097 | 19262 | error.Overflow => return sema.fail(block, src, "expression produces integer value {d} which is too big for this compiler implementation to handle", .{int}), |
| 19098 | 19263 | }; |
src/arch/aarch64/CodeGen.zig+10-3| ... | ... | @@ -579,7 +579,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 579 | 579 | .block => try self.airBlock(inst), |
| 580 | 580 | .br => try self.airBr(inst), |
| 581 | 581 | .breakpoint => try self.airBreakpoint(), |
| 582 | .ret_addr => try self.airRetAddr(), | |
| 582 | .ret_addr => try self.airRetAddr(inst), | |
| 583 | .frame_addr => try self.airFrameAddress(inst), | |
| 583 | 584 | .fence => try self.airFence(), |
| 584 | 585 | .call => try self.airCall(inst), |
| 585 | 586 | .cond_br => try self.airCondBr(inst), |
| ... | ... | @@ -2178,8 +2179,14 @@ fn airBreakpoint(self: *Self) !void { |
| 2178 | 2179 | return self.finishAirBookkeeping(); |
| 2179 | 2180 | } |
| 2180 | 2181 | |
| 2181 | fn airRetAddr(self: *Self) !void { | |
| 2182 | return self.fail("TODO implement airRetAddr for {}", .{self.target.cpu.arch}); | |
| 2182 | fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void { | |
| 2183 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for aarch64", .{}); | |
| 2184 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 2185 | } | |
| 2186 | ||
| 2187 | fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void { | |
| 2188 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for aarch64", .{}); | |
| 2189 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 2183 | 2190 | } |
| 2184 | 2191 | |
| 2185 | 2192 | fn airFence(self: *Self) !void { |
src/arch/arm/CodeGen.zig+10-3| ... | ... | @@ -565,7 +565,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 565 | 565 | .block => try self.airBlock(inst), |
| 566 | 566 | .br => try self.airBr(inst), |
| 567 | 567 | .breakpoint => try self.airBreakpoint(), |
| 568 | .ret_addr => try self.airRetAddr(), | |
| 568 | .ret_addr => try self.airRetAddr(inst), | |
| 569 | .frame_addr => try self.airFrameAddress(inst), | |
| 569 | 570 | .fence => try self.airFence(), |
| 570 | 571 | .call => try self.airCall(inst), |
| 571 | 572 | .cond_br => try self.airCondBr(inst), |
| ... | ... | @@ -2449,8 +2450,14 @@ fn airBreakpoint(self: *Self) !void { |
| 2449 | 2450 | return self.finishAirBookkeeping(); |
| 2450 | 2451 | } |
| 2451 | 2452 | |
| 2452 | fn airRetAddr(self: *Self) !void { | |
| 2453 | return self.fail("TODO implement airRetAddr for {}", .{self.target.cpu.arch}); | |
| 2453 | fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void { | |
| 2454 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for arm", .{}); | |
| 2455 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 2456 | } | |
| 2457 | ||
| 2458 | fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void { | |
| 2459 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for arm", .{}); | |
| 2460 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 2454 | 2461 | } |
| 2455 | 2462 | |
| 2456 | 2463 | fn airFence(self: *Self) !void { |
src/arch/riscv64/CodeGen.zig+10-3| ... | ... | @@ -550,7 +550,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 550 | 550 | .block => try self.airBlock(inst), |
| 551 | 551 | .br => try self.airBr(inst), |
| 552 | 552 | .breakpoint => try self.airBreakpoint(), |
| 553 | .ret_addr => try self.airRetAddr(), | |
| 553 | .ret_addr => try self.airRetAddr(inst), | |
| 554 | .frame_addr => try self.airFrameAddress(inst), | |
| 554 | 555 | .fence => try self.airFence(), |
| 555 | 556 | .call => try self.airCall(inst), |
| 556 | 557 | .cond_br => try self.airCondBr(inst), |
| ... | ... | @@ -1438,8 +1439,14 @@ fn airBreakpoint(self: *Self) !void { |
| 1438 | 1439 | return self.finishAirBookkeeping(); |
| 1439 | 1440 | } |
| 1440 | 1441 | |
| 1441 | fn airRetAddr(self: *Self) !void { | |
| 1442 | return self.fail("TODO implement airRetAddr for {}", .{self.target.cpu.arch}); | |
| 1442 | fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void { | |
| 1443 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for riscv64", .{}); | |
| 1444 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 1445 | } | |
| 1446 | ||
| 1447 | fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void { | |
| 1448 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for riscv64", .{}); | |
| 1449 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 1443 | 1450 | } |
| 1444 | 1451 | |
| 1445 | 1452 | fn airFence(self: *Self) !void { |
src/arch/wasm/CodeGen.zig+1| ... | ... | @@ -1683,6 +1683,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1683 | 1683 | .assembly, |
| 1684 | 1684 | .shl_sat, |
| 1685 | 1685 | .ret_addr, |
| 1686 | .frame_addr, | |
| 1686 | 1687 | .clz, |
| 1687 | 1688 | .ctz, |
| 1688 | 1689 | .popcount, |
src/arch/x86_64/CodeGen.zig+10-3| ... | ... | @@ -662,7 +662,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 662 | 662 | .block => try self.airBlock(inst), |
| 663 | 663 | .br => try self.airBr(inst), |
| 664 | 664 | .breakpoint => try self.airBreakpoint(), |
| 665 | .ret_addr => try self.airRetAddr(), | |
| 665 | .ret_addr => try self.airRetAddr(inst), | |
| 666 | .frame_addr => try self.airFrameAddress(inst), | |
| 666 | 667 | .fence => try self.airFence(), |
| 667 | 668 | .call => try self.airCall(inst), |
| 668 | 669 | .cond_br => try self.airCondBr(inst), |
| ... | ... | @@ -3127,8 +3128,14 @@ fn airBreakpoint(self: *Self) !void { |
| 3127 | 3128 | return self.finishAirBookkeeping(); |
| 3128 | 3129 | } |
| 3129 | 3130 | |
| 3130 | fn airRetAddr(self: *Self) !void { | |
| 3131 | return self.fail("TODO implement airRetAddr for {}", .{self.target.cpu.arch}); | |
| 3131 | fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void { | |
| 3132 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for x86_64", .{}); | |
| 3133 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 3134 | } | |
| 3135 | ||
| 3136 | fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void { | |
| 3137 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for x86_64", .{}); | |
| 3138 | return self.finishAir(inst, result, .{ .none, .none, .none }); | |
| 3132 | 3139 | } |
| 3133 | 3140 | |
| 3134 | 3141 | fn airFence(self: *Self) !void { |
src/codegen/c.zig+11-2| ... | ... | @@ -1588,7 +1588,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO |
| 1588 | 1588 | .arg => airArg(f), |
| 1589 | 1589 | |
| 1590 | 1590 | .breakpoint => try airBreakpoint(f), |
| 1591 | .ret_addr => try airRetAddr(f), | |
| 1591 | .ret_addr => try airRetAddr(f, inst), | |
| 1592 | .frame_addr => try airFrameAddress(f, inst), | |
| 1592 | 1593 | .unreach => try airUnreach(f), |
| 1593 | 1594 | .fence => try airFence(f, inst), |
| 1594 | 1595 | |
| ... | ... | @@ -2717,12 +2718,20 @@ fn airBreakpoint(f: *Function) !CValue { |
| 2717 | 2718 | return CValue.none; |
| 2718 | 2719 | } |
| 2719 | 2720 | |
| 2720 | fn airRetAddr(f: *Function) !CValue { | |
| 2721 | fn airRetAddr(f: *Function, inst: Air.Inst.Index) !CValue { | |
| 2722 | if (f.liveness.isUnused(inst)) return CValue.none; | |
| 2721 | 2723 | const local = try f.allocLocal(Type.usize, .Const); |
| 2722 | 2724 | try f.object.writer().writeAll(" = zig_return_address();\n"); |
| 2723 | 2725 | return local; |
| 2724 | 2726 | } |
| 2725 | 2727 | |
| 2728 | fn airFrameAddress(f: *Function, inst: Air.Inst.Index) !CValue { | |
| 2729 | if (f.liveness.isUnused(inst)) return CValue.none; | |
| 2730 | const local = try f.allocLocal(Type.usize, .Const); | |
| 2731 | try f.object.writer().writeAll(" = zig_frame_address();\n"); | |
| 2732 | return local; | |
| 2733 | } | |
| 2734 | ||
| 2726 | 2735 | fn airFence(f: *Function, inst: Air.Inst.Index) !CValue { |
| 2727 | 2736 | const atomic_order = f.air.instructions.items(.data)[inst].fence; |
| 2728 | 2737 | const writer = f.object.writer(); |
src/codegen/llvm.zig+19-5| ... | ... | @@ -2135,6 +2135,7 @@ pub const FuncGen = struct { |
| 2135 | 2135 | .switch_br => try self.airSwitchBr(inst), |
| 2136 | 2136 | .breakpoint => try self.airBreakpoint(inst), |
| 2137 | 2137 | .ret_addr => try self.airRetAddr(inst), |
| 2138 | .frame_addr => try self.airFrameAddress(inst), | |
| 2138 | 2139 | .call => try self.airCall(inst), |
| 2139 | 2140 | .cond_br => try self.airCondBr(inst), |
| 2140 | 2141 | .intcast => try self.airIntCast(inst), |
| ... | ... | @@ -4100,12 +4101,25 @@ pub const FuncGen = struct { |
| 4100 | 4101 | } |
| 4101 | 4102 | |
| 4102 | 4103 | fn airRetAddr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
| 4103 | _ = inst; | |
| 4104 | const i32_zero = self.context.intType(32).constNull(); | |
| 4105 | const usize_llvm_ty = try self.dg.llvmType(Type.usize); | |
| 4104 | if (self.liveness.isUnused(inst)) return null; | |
| 4105 | ||
| 4106 | const llvm_i32 = self.context.intType(32); | |
| 4106 | 4107 | const llvm_fn = self.getIntrinsic("llvm.returnaddress", &.{}); |
| 4107 | const ptr_val = self.builder.buildCall(llvm_fn, &[_]*const llvm.Value{i32_zero}, 1, .Fast, .Auto, ""); | |
| 4108 | return self.builder.buildPtrToInt(ptr_val, usize_llvm_ty, ""); | |
| 4108 | const params = [_]*const llvm.Value{llvm_i32.constNull()}; | |
| 4109 | const ptr_val = self.builder.buildCall(llvm_fn, &params, params.len, .Fast, .Auto, ""); | |
| 4110 | const llvm_usize = try self.dg.llvmType(Type.usize); | |
| 4111 | return self.builder.buildPtrToInt(ptr_val, llvm_usize, ""); | |
| 4112 | } | |
| 4113 | ||
| 4114 | fn airFrameAddress(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | |
| 4115 | if (self.liveness.isUnused(inst)) return null; | |
| 4116 | ||
| 4117 | const llvm_i32 = self.context.intType(32); | |
| 4118 | const llvm_fn = self.getIntrinsic("llvm.frameaddress", &.{llvm_i32}); | |
| 4119 | const params = [_]*const llvm.Value{llvm_i32.constNull()}; | |
| 4120 | const ptr_val = self.builder.buildCall(llvm_fn, &params, params.len, .Fast, .Auto, ""); | |
| 4121 | const llvm_usize = try self.dg.llvmType(Type.usize); | |
| 4122 | return self.builder.buildPtrToInt(ptr_val, llvm_usize, ""); | |
| 4109 | 4123 | } |
| 4110 | 4124 | |
| 4111 | 4125 | fn airFence(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
src/print_air.zig+1| ... | ... | @@ -171,6 +171,7 @@ const Writer = struct { |
| 171 | 171 | .breakpoint, |
| 172 | 172 | .unreach, |
| 173 | 173 | .ret_addr, |
| 174 | .frame_addr, | |
| 174 | 175 | => try w.writeNoOp(s, inst), |
| 175 | 176 | |
| 176 | 177 | .const_ty, |
src/type.zig+13-3| ... | ... | @@ -4531,7 +4531,15 @@ pub const Type = extern union { |
| 4531 | 4531 | }; |
| 4532 | 4532 | |
| 4533 | 4533 | pub fn isTuple(ty: Type) bool { |
| 4534 | return ty.tag() == .tuple; | |
| 4534 | return ty.tag() == .tuple or ty.tag() == .empty_struct_literal; | |
| 4535 | } | |
| 4536 | ||
| 4537 | pub fn tupleFields(ty: Type) Payload.Tuple.Data { | |
| 4538 | return switch (ty.tag()) { | |
| 4539 | .tuple => ty.castTag(.tuple).?.data, | |
| 4540 | .empty_struct_literal => .{ .types = &.{}, .values = &.{} }, | |
| 4541 | else => unreachable, | |
| 4542 | }; | |
| 4535 | 4543 | } |
| 4536 | 4544 | |
| 4537 | 4545 | /// The sub-types are named after what fields they contain. |
| ... | ... | @@ -4683,11 +4691,13 @@ pub const Type = extern union { |
| 4683 | 4691 | |
| 4684 | 4692 | pub const Tuple = struct { |
| 4685 | 4693 | base: Payload = .{ .tag = .tuple }, |
| 4686 | data: struct { | |
| 4694 | data: Data, | |
| 4695 | ||
| 4696 | pub const Data = struct { | |
| 4687 | 4697 | types: []Type, |
| 4688 | 4698 | /// unreachable_value elements are used to indicate runtime-known. |
| 4689 | 4699 | values: []Value, |
| 4690 | }, | |
| 4700 | }; | |
| 4691 | 4701 | }; |
| 4692 | 4702 | |
| 4693 | 4703 | pub const Union = struct { |
src/value.zig+21-7| ... | ... | @@ -1829,7 +1829,7 @@ pub const Value = extern union { |
| 1829 | 1829 | assert(a_tag != .undef); |
| 1830 | 1830 | assert(b_tag != .undef); |
| 1831 | 1831 | if (a_tag == b_tag) switch (a_tag) { |
| 1832 | .void_value, .null_value, .the_only_possible_value => return true, | |
| 1832 | .void_value, .null_value, .the_only_possible_value, .empty_struct_value => return true, | |
| 1833 | 1833 | .enum_literal => { |
| 1834 | 1834 | const a_name = a.castTag(.enum_literal).?.data; |
| 1835 | 1835 | const b_name = b.castTag(.enum_literal).?.data; |
| ... | ... | @@ -1892,10 +1892,18 @@ pub const Value = extern union { |
| 1892 | 1892 | return a_payload == b_payload; |
| 1893 | 1893 | }, |
| 1894 | 1894 | .@"struct" => { |
| 1895 | const fields = ty.structFields().values(); | |
| 1896 | 1895 | const a_field_vals = a.castTag(.@"struct").?.data; |
| 1897 | 1896 | const b_field_vals = b.castTag(.@"struct").?.data; |
| 1898 | 1897 | assert(a_field_vals.len == b_field_vals.len); |
| 1898 | if (ty.isTuple()) { | |
| 1899 | const types = ty.tupleFields().types; | |
| 1900 | assert(types.len == a_field_vals.len); | |
| 1901 | for (types) |field_ty, i| { | |
| 1902 | if (!eql(a_field_vals[i], b_field_vals[i], field_ty)) return false; | |
| 1903 | } | |
| 1904 | return true; | |
| 1905 | } | |
| 1906 | const fields = ty.structFields().values(); | |
| 1899 | 1907 | assert(fields.len == a_field_vals.len); |
| 1900 | 1908 | for (fields) |field, i| { |
| 1901 | 1909 | if (!eql(a_field_vals[i], b_field_vals[i], field.ty)) return false; |
| ... | ... | @@ -1967,11 +1975,10 @@ pub const Value = extern union { |
| 1967 | 1975 | return true; |
| 1968 | 1976 | }, |
| 1969 | 1977 | .Struct => { |
| 1970 | // must be a struct with no fields since we checked for if | |
| 1971 | // both have the struct tag above. | |
| 1972 | const fields = ty.structFields().values(); | |
| 1973 | assert(fields.len == 0); | |
| 1974 | return true; | |
| 1978 | // A tuple can be represented with .empty_struct_value, | |
| 1979 | // the_one_possible_value, .@"struct" in which case we could | |
| 1980 | // end up here and the values are equal if the type has zero fields. | |
| 1981 | return ty.structFieldCount() != 0; | |
| 1975 | 1982 | }, |
| 1976 | 1983 | else => return order(a, b).compare(.eq), |
| 1977 | 1984 | } |
| ... | ... | @@ -2024,6 +2031,13 @@ pub const Value = extern union { |
| 2024 | 2031 | } |
| 2025 | 2032 | }, |
| 2026 | 2033 | .Struct => { |
| 2034 | if (ty.isTuple()) { | |
| 2035 | const fields = ty.tupleFields(); | |
| 2036 | for (fields.values) |field_val, i| { | |
| 2037 | field_val.hash(fields.types[i], hasher); | |
| 2038 | } | |
| 2039 | return; | |
| 2040 | } | |
| 2027 | 2041 | const fields = ty.structFields().values(); |
| 2028 | 2042 | if (fields.len == 0) return; |
| 2029 | 2043 | const field_values = val.castTag(.@"struct").?.data; |
test/behavior/tuple.zig+9-9| ... | ... | @@ -23,28 +23,30 @@ test "tuple concatenation" { |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | 25 | test "tuple multiplication" { |
| 26 | if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO | |
| 27 | ||
| 28 | 26 | const S = struct { |
| 29 | 27 | fn doTheTest() !void { |
| 30 | 28 | { |
| 31 | 29 | const t = .{} ** 4; |
| 32 | try expectEqual(0, @typeInfo(@TypeOf(t)).Struct.fields.len); | |
| 30 | try expect(@typeInfo(@TypeOf(t)).Struct.fields.len == 0); | |
| 33 | 31 | } |
| 34 | 32 | { |
| 35 | 33 | const t = .{'a'} ** 4; |
| 36 | try expectEqual(4, @typeInfo(@TypeOf(t)).Struct.fields.len); | |
| 37 | inline for (t) |x| try expectEqual('a', x); | |
| 34 | try expect(@typeInfo(@TypeOf(t)).Struct.fields.len == 4); | |
| 35 | inline for (t) |x| try expect(x == 'a'); | |
| 38 | 36 | } |
| 39 | 37 | { |
| 40 | 38 | const t = .{ 1, 2, 3 } ** 4; |
| 41 | try expectEqual(12, @typeInfo(@TypeOf(t)).Struct.fields.len); | |
| 42 | inline for (t) |x, i| try expectEqual(1 + i % 3, x); | |
| 39 | try expect(@typeInfo(@TypeOf(t)).Struct.fields.len == 12); | |
| 40 | inline for (t) |x, i| try expect(x == 1 + i % 3); | |
| 43 | 41 | } |
| 44 | 42 | } |
| 45 | 43 | }; |
| 46 | 44 | try S.doTheTest(); |
| 47 | 45 | comptime try S.doTheTest(); |
| 46 | } | |
| 47 | ||
| 48 | test "tuple concatenation" { | |
| 49 | if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO | |
| 48 | 50 | |
| 49 | 51 | const T = struct { |
| 50 | 52 | fn consume_tuple(tuple: anytype, len: usize) !void { |
| ... | ... | @@ -86,8 +88,6 @@ test "tuple multiplication" { |
| 86 | 88 | } |
| 87 | 89 | |
| 88 | 90 | test "pass tuple to comptime var parameter" { |
| 89 | if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO | |
| 90 | ||
| 91 | 91 | const S = struct { |
| 92 | 92 | fn Foo(comptime args: anytype) !void { |
| 93 | 93 | try expect(args[0] == 1); |