| ... | ... | @@ -2666,6 +2666,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2666 | 2666 | .validate_deref, |
| 2667 | 2667 | .save_err_ret_index, |
| 2668 | 2668 | .restore_err_ret_index, |
| 2669 | .for_check_lens, |
| 2669 | 2670 | => break :b true, |
| 2670 | 2671 | |
| 2671 | 2672 | .@"defer" => unreachable, |
| ... | ... | @@ -6294,37 +6295,35 @@ fn forExpr( |
| 6294 | 6295 | try astgen.checkLabelRedefinition(scope, label_token); |
| 6295 | 6296 | } |
| 6296 | 6297 | |
| 6297 | | // Set up variables and constants. |
| 6298 | 6298 | const is_inline = parent_gz.force_comptime or for_full.inline_token != null; |
| 6299 | 6299 | const tree = astgen.tree; |
| 6300 | 6300 | const token_tags = tree.tokens.items(.tag); |
| 6301 | 6301 | const node_tags = tree.nodes.items(.tag); |
| 6302 | 6302 | const node_data = tree.nodes.items(.data); |
| 6303 | const gpa = astgen.gpa; |
| 6303 | 6304 | |
| 6304 | | // Check for unterminated ranges. |
| 6305 | | { |
| 6306 | | var unterminated: ?Ast.Node.Index = null; |
| 6307 | | for (for_full.ast.inputs) |input| { |
| 6308 | | if (node_tags[input] != .for_range) break; |
| 6309 | | if (node_data[input].rhs != 0) break; |
| 6310 | | unterminated = unterminated orelse input; |
| 6311 | | } else { |
| 6312 | | return astgen.failNode(unterminated.?, "unterminated for range", .{}); |
| 6313 | | } |
| 6314 | | } |
| 6315 | | |
| 6316 | | var lens = astgen.gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len); |
| 6317 | | defer astgen.gpa.free(lens); |
| 6318 | | var indexables = astgen.gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len); |
| 6319 | | defer astgen.gpa.free(indexables); |
| 6320 | | var counters = std.ArrayList(Zir.Inst.Ref).init(astgen.gpa); |
| 6321 | | defer counters.deinit(); |
| 6305 | const allocs = try gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len); |
| 6306 | defer gpa.free(allocs); |
| 6307 | // elements of this array can be `none`, indicating no length check. |
| 6308 | const lens = try gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len); |
| 6309 | defer gpa.free(lens); |
| 6322 | 6310 | |
| 6323 | 6311 | const counter_alloc_tag: Zir.Inst.Tag = if (is_inline) .alloc_comptime_mut else .alloc; |
| 6324 | 6312 | |
| 6313 | // Tracks the index of allocs/lens that has a length to be checked and is |
| 6314 | // used for the end value. |
| 6315 | // If this is null, there are no len checks. |
| 6316 | var end_input_index: ?u32 = null; |
| 6317 | // This is a value to use to find out if the for loop has reached the end |
| 6318 | // yet. It prefers to use a counter since the end value is provided directly, |
| 6319 | // and otherwise falls back to adding ptr+len of a slice to compute end. |
| 6320 | // Corresponds to end_input_index and will be .none in case that value is null. |
| 6321 | var cond_end_val: Zir.Inst.Ref = .none; |
| 6322 | |
| 6325 | 6323 | { |
| 6326 | 6324 | var payload = for_full.payload_token; |
| 6327 | | for (for_full.ast.inputs) |input, i| { |
| 6325 | for (for_full.ast.inputs) |input, i_usize| { |
| 6326 | const i = @intCast(u32, i_usize); |
| 6328 | 6327 | const payload_is_ref = token_tags[payload] == .asterisk; |
| 6329 | 6328 | const ident_tok = payload + @boolToInt(payload_is_ref); |
| 6330 | 6329 | |
| ... | ... | @@ -6339,59 +6338,101 @@ fn forExpr( |
| 6339 | 6338 | return astgen.failTok(ident_tok, "cannot capture reference to range", .{}); |
| 6340 | 6339 | } |
| 6341 | 6340 | const counter_ptr = try parent_gz.addUnNode(counter_alloc_tag, .usize_type, node); |
| 6342 | | const start_val = try expr(parent_gz, scope, node_data[input].lhs, input); |
| 6341 | const start_node = node_data[input].lhs; |
| 6342 | const start_val = try expr(parent_gz, scope, .{ .rl = .none }, start_node); |
| 6343 | 6343 | _ = try parent_gz.addBin(.store, counter_ptr, start_val); |
| 6344 | | indexables[i] = counter_ptr; |
| 6345 | | try counters.append(counter_ptr); |
| 6346 | 6344 | |
| 6347 | 6345 | const end_node = node_data[input].rhs; |
| 6348 | | const end_val = if (end_node != 0) try expr(parent_gz, scope, node_data[input].rhs, input) else .none; |
| 6349 | | const range_len = try parent_gz.addPlNode(.for_range_len, input, Zir.Inst.Bin{ |
| 6350 | | .lhs = start_val, |
| 6351 | | .rhs = end_val, |
| 6352 | | }); |
| 6346 | const end_val = if (end_node != 0) |
| 6347 | try expr(parent_gz, scope, .{ .rl = .none }, node_data[input].rhs) |
| 6348 | else |
| 6349 | .none; |
| 6350 | |
| 6351 | const range_len = if (end_val == .none or nodeIsTriviallyZero(tree, start_node)) |
| 6352 | end_val |
| 6353 | else |
| 6354 | try parent_gz.addPlNode(.sub, input, Zir.Inst.Bin{ |
| 6355 | .lhs = end_val, |
| 6356 | .rhs = start_val, |
| 6357 | }); |
| 6358 | |
| 6359 | if (range_len != .none and cond_end_val == .none) { |
| 6360 | end_input_index = i; |
| 6361 | cond_end_val = end_val; |
| 6362 | } |
| 6363 | |
| 6364 | allocs[i] = counter_ptr; |
| 6353 | 6365 | lens[i] = range_len; |
| 6354 | 6366 | } else { |
| 6355 | 6367 | const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none }; |
| 6356 | 6368 | const indexable = try expr(parent_gz, scope, cond_ri, input); |
| 6357 | | indexables[i] = indexable; |
| 6369 | const base_ptr = try parent_gz.addPlNode(.elem_ptr_imm, input, Zir.Inst.ElemPtrImm{ |
| 6370 | .ptr = indexable, |
| 6371 | .index = 0, |
| 6372 | }); |
| 6358 | 6373 | |
| 6359 | | const indexable_len = try parent_gz.addUnNode(.indexable_ptr_len, indexable, input); |
| 6360 | | lens[i] = indexable_len; |
| 6374 | if (end_input_index == null) { |
| 6375 | end_input_index = i; |
| 6376 | assert(cond_end_val == .none); |
| 6377 | } |
| 6378 | |
| 6379 | allocs[i] = base_ptr; |
| 6380 | lens[i] = try parent_gz.addUnNode(.indexable_ptr_len, indexable, input); |
| 6361 | 6381 | } |
| 6362 | 6382 | } |
| 6363 | 6383 | } |
| 6364 | 6384 | |
| 6365 | | const len = "check_for_lens"; |
| 6385 | // In case there are no counters which already have an end computed, we |
| 6386 | // compute an end from base pointer plus length. |
| 6387 | if (end_input_index) |i| { |
| 6388 | if (cond_end_val == .none) { |
| 6389 | cond_end_val = try parent_gz.addPlNode(.add, for_full.ast.inputs[i], Zir.Inst.Bin{ |
| 6390 | .lhs = allocs[i], |
| 6391 | .rhs = lens[i], |
| 6392 | }); |
| 6393 | } |
| 6394 | } |
| 6366 | 6395 | |
| 6367 | | const index_ptr = blk: { |
| 6368 | | // Future optimization: |
| 6369 | | // for loops with only ranges don't need a separate index variable. |
| 6370 | | const index_ptr = try parent_gz.addUnNode(counter_alloc_tag, .usize_type, node); |
| 6371 | | // initialize to zero |
| 6372 | | _ = try parent_gz.addBin(.store, index_ptr, .zero_usize); |
| 6373 | | try counters.append(index_ptr); |
| 6374 | | break :blk index_ptr; |
| 6375 | | }; |
| 6396 | // We use a dedicated ZIR instruction to assert the lengths to assist with |
| 6397 | // nicer error reporting as well as fewer ZIR bytes emitted. |
| 6398 | if (end_input_index != null) { |
| 6399 | const lens_len = @intCast(u32, lens.len); |
| 6400 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.MultiOp).Struct.fields.len + lens_len); |
| 6401 | _ = try parent_gz.addPlNode(.for_check_lens, node, Zir.Inst.MultiOp{ |
| 6402 | .operands_len = lens_len, |
| 6403 | }); |
| 6404 | appendRefsAssumeCapacity(astgen, lens); |
| 6405 | } |
| 6376 | 6406 | |
| 6377 | 6407 | const loop_tag: Zir.Inst.Tag = if (is_inline) .block_inline else .loop; |
| 6378 | 6408 | const loop_block = try parent_gz.makeBlockInst(loop_tag, node); |
| 6379 | | try parent_gz.instructions.append(astgen.gpa, loop_block); |
| 6409 | try parent_gz.instructions.append(gpa, loop_block); |
| 6380 | 6410 | |
| 6381 | 6411 | var loop_scope = parent_gz.makeSubBlock(scope); |
| 6382 | 6412 | loop_scope.is_inline = is_inline; |
| 6383 | 6413 | loop_scope.setBreakResultInfo(ri); |
| 6384 | 6414 | defer loop_scope.unstack(); |
| 6385 | | defer loop_scope.labeled_breaks.deinit(astgen.gpa); |
| 6415 | defer loop_scope.labeled_breaks.deinit(gpa); |
| 6386 | 6416 | |
| 6387 | 6417 | var cond_scope = parent_gz.makeSubBlock(&loop_scope.base); |
| 6388 | 6418 | defer cond_scope.unstack(); |
| 6389 | 6419 | |
| 6390 | | // check condition i < array_expr.len |
| 6391 | | const index = try cond_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr); |
| 6392 | | const cond = try cond_scope.addPlNode(.cmp_lt, for_full.ast.cond_expr, Zir.Inst.Bin{ |
| 6393 | | .lhs = index, |
| 6394 | | .rhs = len, |
| 6420 | // Load all the iterables. |
| 6421 | const loaded_ptrs = try gpa.alloc(Zir.Inst.Ref, allocs.len); |
| 6422 | defer gpa.free(loaded_ptrs); |
| 6423 | for (allocs) |alloc, i| { |
| 6424 | loaded_ptrs[i] = try cond_scope.addUnNode(.load, alloc, for_full.ast.inputs[i]); |
| 6425 | } |
| 6426 | |
| 6427 | // Check the condition. |
| 6428 | const input_index = end_input_index orelse { |
| 6429 | return astgen.failNode(node, "TODO: handle infinite for loop", .{}); |
| 6430 | }; |
| 6431 | assert(cond_end_val != .none); |
| 6432 | |
| 6433 | const cond = try cond_scope.addPlNode(.cmp_neq, for_full.ast.inputs[input_index], Zir.Inst.Bin{ |
| 6434 | .lhs = loaded_ptrs[input_index], |
| 6435 | .rhs = cond_end_val, |
| 6395 | 6436 | }); |
| 6396 | 6437 | |
| 6397 | 6438 | const condbr_tag: Zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; |
| ... | ... | @@ -6400,16 +6441,15 @@ fn forExpr( |
| 6400 | 6441 | const cond_block = try loop_scope.makeBlockInst(block_tag, node); |
| 6401 | 6442 | try cond_scope.setBlockBody(cond_block); |
| 6402 | 6443 | // cond_block unstacked now, can add new instructions to loop_scope |
| 6403 | | try loop_scope.instructions.append(astgen.gpa, cond_block); |
| 6444 | try loop_scope.instructions.append(gpa, cond_block); |
| 6404 | 6445 | |
| 6405 | | // Increment the index variable and ranges. |
| 6406 | | for (counters) |counter_ptr| { |
| 6407 | | const counter = try loop_scope.addUnNode(.load, counter_ptr, for_full.ast.cond_expr); |
| 6408 | | const counter_plus_one = try loop_scope.addPlNode(.add, node, Zir.Inst.Bin{ |
| 6409 | | .lhs = counter, |
| 6446 | // Increment the loop variables. |
| 6447 | for (allocs) |alloc, i| { |
| 6448 | const incremented = try loop_scope.addPlNode(.add, node, Zir.Inst.Bin{ |
| 6449 | .lhs = loaded_ptrs[i], |
| 6410 | 6450 | .rhs = .one_usize, |
| 6411 | 6451 | }); |
| 6412 | | _ = try loop_scope.addBin(.store, counter_ptr, counter_plus_one); |
| 6452 | _ = try loop_scope.addBin(.store, alloc, incremented); |
| 6413 | 6453 | } |
| 6414 | 6454 | const repeat_tag: Zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; |
| 6415 | 6455 | _ = try loop_scope.addNode(repeat_tag, node); |
| ... | ... | @@ -8960,6 +9000,25 @@ comptime { |
| 8960 | 9000 | } |
| 8961 | 9001 | } |
| 8962 | 9002 | |
| 9003 | fn nodeIsTriviallyZero(tree: *const Ast, node: Ast.Node.Index) bool { |
| 9004 | const node_tags = tree.nodes.items(.tag); |
| 9005 | const main_tokens = tree.nodes.items(.main_token); |
| 9006 | |
| 9007 | switch (node_tags[node]) { |
| 9008 | .number_literal => { |
| 9009 | const ident = main_tokens[node]; |
| 9010 | return switch (std.zig.parseNumberLiteral(tree.tokenSlice(ident))) { |
| 9011 | .int => |number| switch (number) { |
| 9012 | 0 => true, |
| 9013 | else => false, |
| 9014 | }, |
| 9015 | else => false, |
| 9016 | }; |
| 9017 | }, |
| 9018 | else => return false, |
| 9019 | } |
| 9020 | } |
| 9021 | |
| 8963 | 9022 | fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_res_ty: bool) bool { |
| 8964 | 9023 | const node_tags = tree.nodes.items(.tag); |
| 8965 | 9024 | const node_datas = tree.nodes.items(.data); |