| ... | @@ -2666,6 +2666,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As | ... | @@ -2666,6 +2666,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2666 | .validate_deref, | 2666 | .validate_deref, |
| 2667 | .save_err_ret_index, | 2667 | .save_err_ret_index, |
| 2668 | .restore_err_ret_index, | 2668 | .restore_err_ret_index, |
| | 2669 | .for_check_lens, |
| 2669 | => break :b true, | 2670 | => break :b true, |
| 2670 | | 2671 | |
| 2671 | .@"defer" => unreachable, | 2672 | .@"defer" => unreachable, |
| ... | @@ -6294,37 +6295,35 @@ fn forExpr( | ... | @@ -6294,37 +6295,35 @@ fn forExpr( |
| 6294 | try astgen.checkLabelRedefinition(scope, label_token); | 6295 | try astgen.checkLabelRedefinition(scope, label_token); |
| 6295 | } | 6296 | } |
| 6296 | | 6297 | |
| 6297 | // Set up variables and constants. | | |
| 6298 | const is_inline = parent_gz.force_comptime or for_full.inline_token != null; | 6298 | const is_inline = parent_gz.force_comptime or for_full.inline_token != null; |
| 6299 | const tree = astgen.tree; | 6299 | const tree = astgen.tree; |
| 6300 | const token_tags = tree.tokens.items(.tag); | 6300 | const token_tags = tree.tokens.items(.tag); |
| 6301 | const node_tags = tree.nodes.items(.tag); | 6301 | const node_tags = tree.nodes.items(.tag); |
| 6302 | const node_data = tree.nodes.items(.data); | 6302 | const node_data = tree.nodes.items(.data); |
| | 6303 | const gpa = astgen.gpa; |
| 6303 | | 6304 | |
| 6304 | // Check for unterminated ranges. | 6305 | const allocs = try gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len); |
| 6305 | { | 6306 | defer gpa.free(allocs); |
| 6306 | var unterminated: ?Ast.Node.Index = null; | 6307 | // elements of this array can be `none`, indicating no length check. |
| 6307 | for (for_full.ast.inputs) |input| { | 6308 | const lens = try gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len); |
| 6308 | if (node_tags[input] != .for_range) break; | 6309 | defer gpa.free(lens); |
| 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(); | | |
| 6322 | | 6310 | |
| 6323 | const counter_alloc_tag: Zir.Inst.Tag = if (is_inline) .alloc_comptime_mut else .alloc; | 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 | var payload = for_full.payload_token; | 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 | const payload_is_ref = token_tags[payload] == .asterisk; | 6327 | const payload_is_ref = token_tags[payload] == .asterisk; |
| 6329 | const ident_tok = payload + @boolToInt(payload_is_ref); | 6328 | const ident_tok = payload + @boolToInt(payload_is_ref); |
| 6330 | | 6329 | |
| ... | @@ -6339,59 +6338,101 @@ fn forExpr( | ... | @@ -6339,59 +6338,101 @@ fn forExpr( |
| 6339 | return astgen.failTok(ident_tok, "cannot capture reference to range", .{}); | 6338 | return astgen.failTok(ident_tok, "cannot capture reference to range", .{}); |
| 6340 | } | 6339 | } |
| 6341 | const counter_ptr = try parent_gz.addUnNode(counter_alloc_tag, .usize_type, node); | 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 | _ = try parent_gz.addBin(.store, counter_ptr, start_val); | 6343 | _ = try parent_gz.addBin(.store, counter_ptr, start_val); |
| 6344 | indexables[i] = counter_ptr; | | |
| 6345 | try counters.append(counter_ptr); | | |
| 6346 | | 6344 | |
| 6347 | const end_node = node_data[input].rhs; | 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; | 6346 | const end_val = if (end_node != 0) |
| 6349 | const range_len = try parent_gz.addPlNode(.for_range_len, input, Zir.Inst.Bin{ | 6347 | try expr(parent_gz, scope, .{ .rl = .none }, node_data[input].rhs) |
| 6350 | .lhs = start_val, | 6348 | else |
| 6351 | .rhs = end_val, | 6349 | .none; |
| 6352 | }); | 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 | lens[i] = range_len; | 6365 | lens[i] = range_len; |
| 6354 | } else { | 6366 | } else { |
| 6355 | const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none }; | 6367 | const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none }; |
| 6356 | const indexable = try expr(parent_gz, scope, cond_ri, input); | 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); | 6374 | if (end_input_index == null) { |
| 6360 | lens[i] = indexable_len; | 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: { | 6396 | // We use a dedicated ZIR instruction to assert the lengths to assist with |
| 6368 | // Future optimization: | 6397 | // nicer error reporting as well as fewer ZIR bytes emitted. |
| 6369 | // for loops with only ranges don't need a separate index variable. | 6398 | if (end_input_index != null) { |
| 6370 | const index_ptr = try parent_gz.addUnNode(counter_alloc_tag, .usize_type, node); | 6399 | const lens_len = @intCast(u32, lens.len); |
| 6371 | // initialize to zero | 6400 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.MultiOp).Struct.fields.len + lens_len); |
| 6372 | _ = try parent_gz.addBin(.store, index_ptr, .zero_usize); | 6401 | _ = try parent_gz.addPlNode(.for_check_lens, node, Zir.Inst.MultiOp{ |
| 6373 | try counters.append(index_ptr); | 6402 | .operands_len = lens_len, |
| 6374 | break :blk index_ptr; | 6403 | }); |
| 6375 | }; | 6404 | appendRefsAssumeCapacity(astgen, lens); |
| | 6405 | } |
| 6376 | | 6406 | |
| 6377 | const loop_tag: Zir.Inst.Tag = if (is_inline) .block_inline else .loop; | 6407 | const loop_tag: Zir.Inst.Tag = if (is_inline) .block_inline else .loop; |
| 6378 | const loop_block = try parent_gz.makeBlockInst(loop_tag, node); | 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 | var loop_scope = parent_gz.makeSubBlock(scope); | 6411 | var loop_scope = parent_gz.makeSubBlock(scope); |
| 6382 | loop_scope.is_inline = is_inline; | 6412 | loop_scope.is_inline = is_inline; |
| 6383 | loop_scope.setBreakResultInfo(ri); | 6413 | loop_scope.setBreakResultInfo(ri); |
| 6384 | defer loop_scope.unstack(); | 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 | var cond_scope = parent_gz.makeSubBlock(&loop_scope.base); | 6417 | var cond_scope = parent_gz.makeSubBlock(&loop_scope.base); |
| 6388 | defer cond_scope.unstack(); | 6418 | defer cond_scope.unstack(); |
| 6389 | | 6419 | |
| 6390 | // check condition i < array_expr.len | 6420 | // Load all the iterables. |
| 6391 | const index = try cond_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr); | 6421 | const loaded_ptrs = try gpa.alloc(Zir.Inst.Ref, allocs.len); |
| 6392 | const cond = try cond_scope.addPlNode(.cmp_lt, for_full.ast.cond_expr, Zir.Inst.Bin{ | 6422 | defer gpa.free(loaded_ptrs); |
| 6393 | .lhs = index, | 6423 | for (allocs) |alloc, i| { |
| 6394 | .rhs = len, | 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 | const condbr_tag: Zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; | 6438 | const condbr_tag: Zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; |
| ... | @@ -6400,16 +6441,15 @@ fn forExpr( | ... | @@ -6400,16 +6441,15 @@ fn forExpr( |
| 6400 | const cond_block = try loop_scope.makeBlockInst(block_tag, node); | 6441 | const cond_block = try loop_scope.makeBlockInst(block_tag, node); |
| 6401 | try cond_scope.setBlockBody(cond_block); | 6442 | try cond_scope.setBlockBody(cond_block); |
| 6402 | // cond_block unstacked now, can add new instructions to loop_scope | 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. | 6446 | // Increment the loop variables. |
| 6406 | for (counters) |counter_ptr| { | 6447 | for (allocs) |alloc, i| { |
| 6407 | const counter = try loop_scope.addUnNode(.load, counter_ptr, for_full.ast.cond_expr); | 6448 | const incremented = try loop_scope.addPlNode(.add, node, Zir.Inst.Bin{ |
| 6408 | const counter_plus_one = try loop_scope.addPlNode(.add, node, Zir.Inst.Bin{ | 6449 | .lhs = loaded_ptrs[i], |
| 6409 | .lhs = counter, | | |
| 6410 | .rhs = .one_usize, | 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 | const repeat_tag: Zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; | 6454 | const repeat_tag: Zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; |
| 6415 | _ = try loop_scope.addNode(repeat_tag, node); | 6455 | _ = try loop_scope.addNode(repeat_tag, node); |
| ... | @@ -8960,6 +9000,25 @@ comptime { | ... | @@ -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 | fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_res_ty: bool) bool { | 9022 | fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_res_ty: bool) bool { |
| 8964 | const node_tags = tree.nodes.items(.tag); | 9023 | const node_tags = tree.nodes.items(.tag); |
| 8965 | const node_datas = tree.nodes.items(.data); | 9024 | const node_datas = tree.nodes.items(.data); |