authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-06 19:40:55-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-06 19:53:04-07:00
log7d0de54ad44832589379a4bcbba493db2087bebf
tree5efb0a82c4ad399e25f603ba25cc6e36566ca845
parente974d4c4295c4fbdbc239caa2cf2d653f65662f1

stage2: fix return pointer result locations

* Introduce `ret_load` ZIR instruction which does return semantics based on a corresponding `ret_ptr` instruction. If the return type of the function has storage for the return type, it simply returns. However if the return type of the function is by-value, it loads the return value from the `ret_ptr` allocation and returns that. * AstGen: improve `finishThenElseBlock` to not emit break instructions after a return instruction in the same block. * Sema: `ret_ptr` instruction works correctly in comptime contexts. Same with `alloc_mut`. The test case with a recursive inline function having an implicitly comptime return value now has a runtime return value because of the fact that it calls a function in a non-comptime context.

5 files changed, 92 insertions(+), 56 deletions(-)

src/AstGen.zig+25-9
...@@ -2131,6 +2131,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner...@@ -2131,6 +2131,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
2131 .condbr_inline,2131 .condbr_inline,
2132 .compile_error,2132 .compile_error,
2133 .ret_node,2133 .ret_node,
2134 .ret_load,
2134 .ret_coerce,2135 .ret_coerce,
2135 .ret_err_value,2136 .ret_err_value,
2136 .@"unreachable",2137 .@"unreachable",
...@@ -4791,11 +4792,10 @@ fn finishThenElseBlock(...@@ -4791,11 +4792,10 @@ fn finishThenElseBlock(
4791 const strat = rl.strategy(block_scope);4792 const strat = rl.strategy(block_scope);
4792 switch (strat.tag) {4793 switch (strat.tag) {
4793 .break_void => {4794 .break_void => {
4794 if (!parent_gz.refIsNoReturn(then_result)) {4795 if (!then_scope.endsWithNoReturn()) {
4795 _ = try then_scope.addBreak(break_tag, then_break_block, .void_value);4796 _ = try then_scope.addBreak(break_tag, then_break_block, .void_value);
4796 }4797 }
4797 const elide_else = if (else_result != .none) parent_gz.refIsNoReturn(else_result) else false;4798 if (!else_scope.endsWithNoReturn()) {
4798 if (!elide_else) {
4799 _ = try else_scope.addBreak(break_tag, main_block, .void_value);4799 _ = try else_scope.addBreak(break_tag, main_block, .void_value);
4800 }4800 }
4801 assert(!strat.elide_store_to_block_ptr_instructions);4801 assert(!strat.elide_store_to_block_ptr_instructions);
...@@ -4803,11 +4803,11 @@ fn finishThenElseBlock(...@@ -4803,11 +4803,11 @@ fn finishThenElseBlock(
4803 return indexToRef(main_block);4803 return indexToRef(main_block);
4804 },4804 },
4805 .break_operand => {4805 .break_operand => {
4806 if (!parent_gz.refIsNoReturn(then_result)) {4806 if (!then_scope.endsWithNoReturn()) {
4807 _ = try then_scope.addBreak(break_tag, then_break_block, then_result);4807 _ = try then_scope.addBreak(break_tag, then_break_block, then_result);
4808 }4808 }
4809 if (else_result != .none) {4809 if (else_result != .none) {
4810 if (!parent_gz.refIsNoReturn(else_result)) {4810 if (!else_scope.endsWithNoReturn()) {
4811 _ = try else_scope.addBreak(break_tag, main_block, else_result);4811 _ = try else_scope.addBreak(break_tag, main_block, else_result);
4812 }4812 }
4813 } else {4813 } else {
...@@ -6236,7 +6236,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6236,7 +6236,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
6236 // Value is always an error. Emit both error defers and regular defers.6236 // Value is always an error. Emit both error defers and regular defers.
6237 const err_code = try gz.addUnNode(.err_union_code, operand, node);6237 const err_code = try gz.addUnNode(.err_union_code, operand, node);
6238 try genDefers(gz, defer_outer, scope, .{ .both = err_code });6238 try genDefers(gz, defer_outer, scope, .{ .both = err_code });
6239 _ = try gz.addUnNode(.ret_node, operand, node);6239 try gz.addRet(rl, operand, node);
6240 return Zir.Inst.Ref.unreachable_value;6240 return Zir.Inst.Ref.unreachable_value;
6241 },6241 },
6242 .maybe => {6242 .maybe => {
...@@ -6244,7 +6244,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6244,7 +6244,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
6244 if (!defer_counts.have_err) {6244 if (!defer_counts.have_err) {
6245 // Only regular defers; no branch needed.6245 // Only regular defers; no branch needed.
6246 try genDefers(gz, defer_outer, scope, .normal_only);6246 try genDefers(gz, defer_outer, scope, .normal_only);
6247 _ = try gz.addUnNode(.ret_node, operand, node);6247 try gz.addRet(rl, operand, node);
6248 return Zir.Inst.Ref.unreachable_value;6248 return Zir.Inst.Ref.unreachable_value;
6249 }6249 }
62506250
...@@ -6256,7 +6256,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6256,7 +6256,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
6256 defer then_scope.instructions.deinit(astgen.gpa);6256 defer then_scope.instructions.deinit(astgen.gpa);
62576257
6258 try genDefers(&then_scope, defer_outer, scope, .normal_only);6258 try genDefers(&then_scope, defer_outer, scope, .normal_only);
6259 _ = try then_scope.addUnNode(.ret_node, operand, node);6259 try then_scope.addRet(rl, operand, node);
62606260
6261 var else_scope = gz.makeSubBlock(scope);6261 var else_scope = gz.makeSubBlock(scope);
6262 defer else_scope.instructions.deinit(astgen.gpa);6262 defer else_scope.instructions.deinit(astgen.gpa);
...@@ -6265,7 +6265,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6265,7 +6265,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
6265 .both = try else_scope.addUnNode(.err_union_code, operand, node),6265 .both = try else_scope.addUnNode(.err_union_code, operand, node),
6266 };6266 };
6267 try genDefers(&else_scope, defer_outer, scope, which_ones);6267 try genDefers(&else_scope, defer_outer, scope, which_ones);
6268 _ = try else_scope.addUnNode(.ret_node, operand, node);6268 try else_scope.addRet(rl, operand, node);
62696269
6270 try setCondBrPayload(condbr, is_non_err, &then_scope, &else_scope);6270 try setCondBrPayload(condbr, is_non_err, &then_scope, &else_scope);
62716271
...@@ -9003,6 +9003,14 @@ const GenZir = struct {...@@ -9003,6 +9003,14 @@ const GenZir = struct {
9003 used: bool = false,9003 used: bool = false,
9004 };9004 };
90059005
9006 fn endsWithNoReturn(gz: GenZir) bool {
9007 const tags = gz.astgen.instructions.items(.tag);
9008 if (gz.instructions.items.len == 0) return false;
9009 const last_inst = gz.instructions.items[gz.instructions.items.len - 1];
9010 return tags[last_inst].isNoReturn();
9011 }
9012
9013 /// TODO all uses of this should be replaced with uses of `endsWithNoReturn`.
9006 fn refIsNoReturn(gz: GenZir, inst_ref: Zir.Inst.Ref) bool {9014 fn refIsNoReturn(gz: GenZir, inst_ref: Zir.Inst.Ref) bool {
9007 if (inst_ref == .unreachable_value) return true;9015 if (inst_ref == .unreachable_value) return true;
9008 if (refToIndex(inst_ref)) |inst_index| {9016 if (refToIndex(inst_ref)) |inst_index| {
...@@ -9977,6 +9985,14 @@ const GenZir = struct {...@@ -9977,6 +9985,14 @@ const GenZir = struct {
9977 gz.instructions.appendAssumeCapacity(new_index);9985 gz.instructions.appendAssumeCapacity(new_index);
9978 return new_index;9986 return new_index;
9979 }9987 }
9988
9989 fn addRet(gz: *GenZir, rl: ResultLoc, operand: Zir.Inst.Ref, node: ast.Node.Index) !void {
9990 switch (rl) {
9991 .ptr => |ret_ptr| _ = try gz.addUnNode(.ret_load, ret_ptr, node),
9992 .ty => _ = try gz.addUnNode(.ret_node, operand, node),
9993 else => unreachable,
9994 }
9995 }
9980};9996};
99819997
9982/// This can only be for short-lived references; the memory becomes invalidated9998/// This can only be for short-lived references; the memory becomes invalidated
src/Sema.zig+49-17
...@@ -366,6 +366,7 @@ pub fn analyzeBody(...@@ -366,6 +366,7 @@ pub fn analyzeBody(
366 .compile_error => return sema.zirCompileError(block, inst),366 .compile_error => return sema.zirCompileError(block, inst),
367 .ret_coerce => return sema.zirRetCoerce(block, inst),367 .ret_coerce => return sema.zirRetCoerce(block, inst),
368 .ret_node => return sema.zirRetNode(block, inst),368 .ret_node => return sema.zirRetNode(block, inst),
369 .ret_load => return sema.zirRetLoad(block, inst),
369 .ret_err_value => return sema.zirRetErrValue(block, inst),370 .ret_err_value => return sema.zirRetErrValue(block, inst),
370 .@"unreachable" => return sema.zirUnreachable(block, inst),371 .@"unreachable" => return sema.zirUnreachable(block, inst),
371 .repeat => return sema.zirRepeat(block, inst),372 .repeat => return sema.zirRepeat(block, inst),
...@@ -718,8 +719,8 @@ fn resolveMaybeUndefValAllowVariables(...@@ -718,8 +719,8 @@ fn resolveMaybeUndefValAllowVariables(
718 if (try sema.typeHasOnePossibleValue(block, src, sema.typeOf(inst))) |opv| {719 if (try sema.typeHasOnePossibleValue(block, src, sema.typeOf(inst))) |opv| {
719 return opv;720 return opv;
720 }721 }
721722 const air_tags = sema.air_instructions.items(.tag);
722 switch (sema.air_instructions.items(.tag)[i]) {723 switch (air_tags[i]) {
723 .constant => {724 .constant => {
724 const ty_pl = sema.air_instructions.items(.data)[i].ty_pl;725 const ty_pl = sema.air_instructions.items(.data)[i].ty_pl;
725 return sema.air_values.items[ty_pl.payload];726 return sema.air_values.items[ty_pl.payload];
...@@ -1248,6 +1249,11 @@ fn zirRetPtr(...@@ -1248,6 +1249,11 @@ fn zirRetPtr(
12481249
1249 const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };1250 const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
1250 try sema.requireFunctionBlock(block, src);1251 try sema.requireFunctionBlock(block, src);
1252
1253 if (block.is_comptime) {
1254 return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty);
1255 }
1256
1251 const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One);1257 const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One);
1252 return block.addTy(.alloc, ptr_type);1258 return block.addTy(.alloc, ptr_type);
1253}1259}
...@@ -1375,21 +1381,7 @@ fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp...@@ -1375,21 +1381,7 @@ fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp
1375 const inst_data = sema.code.instructions.items(.data)[inst].un_node;1381 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
1376 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };1382 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
1377 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);1383 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
1378 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);1384 return sema.analyzeComptimeAlloc(block, var_type);
1379
1380 var anon_decl = try block.startAnonDecl();
1381 defer anon_decl.deinit();
1382 const decl = try anon_decl.finish(
1383 try var_type.copy(anon_decl.arena()),
1384 // AstGen guarantees there will be a store before the first load, so we put a value
1385 // here indicating there is no valid value.
1386 Value.initTag(.unreachable_value),
1387 );
1388 try sema.mod.declareDeclDependency(sema.owner_decl, decl);
1389 return sema.addConstant(ptr_type, try Value.Tag.decl_ref_mut.create(sema.arena, .{
1390 .runtime_index = block.runtime_index,
1391 .decl = decl,
1392 }));
1393}1385}
13941386
1395fn zirAllocInferredComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {1387fn zirAllocInferredComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -1419,6 +1411,9 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr...@@ -1419,6 +1411,9 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
1419 const var_decl_src = inst_data.src();1411 const var_decl_src = inst_data.src();
1420 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };1412 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
1421 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);1413 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
1414 if (block.is_comptime) {
1415 return sema.analyzeComptimeAlloc(block, var_type);
1416 }
1422 try sema.validateVarType(block, ty_src, var_type);1417 try sema.validateVarType(block, ty_src, var_type);
1423 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);1418 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);
1424 try sema.requireRuntimeBlock(block, var_decl_src);1419 try sema.requireRuntimeBlock(block, var_decl_src);
...@@ -6280,6 +6275,21 @@ fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr...@@ -6280,6 +6275,21 @@ fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
6280 return sema.analyzeRet(block, operand, src, false);6275 return sema.analyzeRet(block, operand, src, false);
6281}6276}
62826277
6278fn zirRetLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
6279 const tracy = trace(@src());
6280 defer tracy.end();
6281
6282 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
6283 const src = inst_data.src();
6284 // TODO: when implementing functions that accept a result location pointer,
6285 // this logic will be updated to only do a load in case that the function's return
6286 // type in fact does not need a result location pointer. Until then we assume
6287 // the `ret_ptr` is the same as an `alloc` and do a load here.
6288 const ret_ptr = sema.resolveInst(inst_data.operand);
6289 const operand = try sema.analyzeLoad(block, src, ret_ptr, src);
6290 return sema.analyzeRet(block, operand, src, false);
6291}
6292
6283fn analyzeRet(6293fn analyzeRet(
6284 sema: *Sema,6294 sema: *Sema,
6285 block: *Scope.Block,6295 block: *Scope.Block,
...@@ -9416,3 +9426,25 @@ fn isComptimeKnown(...@@ -9416,3 +9426,25 @@ fn isComptimeKnown(
9416) !bool {9426) !bool {
9417 return (try sema.resolveMaybeUndefVal(block, src, inst)) != null;9427 return (try sema.resolveMaybeUndefVal(block, src, inst)) != null;
9418}9428}
9429
9430fn analyzeComptimeAlloc(
9431 sema: *Sema,
9432 block: *Scope.Block,
9433 var_type: Type,
9434) CompileError!Air.Inst.Ref {
9435 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);
9436
9437 var anon_decl = try block.startAnonDecl();
9438 defer anon_decl.deinit();
9439 const decl = try anon_decl.finish(
9440 try var_type.copy(anon_decl.arena()),
9441 // AstGen guarantees there will be a store before the first load, so we put a value
9442 // here indicating there is no valid value.
9443 Value.initTag(.unreachable_value),
9444 );
9445 try sema.mod.declareDeclDependency(sema.owner_decl, decl);
9446 return sema.addConstant(ptr_type, try Value.Tag.decl_ref_mut.create(sema.arena, .{
9447 .runtime_index = block.runtime_index,
9448 .decl = decl,
9449 }));
9450}
src/Zir.zig+8
...@@ -465,6 +465,11 @@ pub const Inst = struct {...@@ -465,6 +465,11 @@ pub const Inst = struct {
465 /// Uses the `un_node` union field.465 /// Uses the `un_node` union field.
466 ret_node,466 ret_node,
467 /// Sends control flow back to the function's callee.467 /// Sends control flow back to the function's callee.
468 /// The operand is a `ret_ptr` instruction, where the return value can be found.
469 /// Includes an AST node source location.
470 /// Uses the `un_node` union field.
471 ret_load,
472 /// Sends control flow back to the function's callee.
468 /// Includes an operand as the return value.473 /// Includes an operand as the return value.
469 /// Includes a token source location.474 /// Includes a token source location.
470 /// Uses the `un_tok` union field.475 /// Uses the `un_tok` union field.
...@@ -1231,6 +1236,7 @@ pub const Inst = struct {...@@ -1231,6 +1236,7 @@ pub const Inst = struct {
1231 .condbr_inline,1236 .condbr_inline,
1232 .compile_error,1237 .compile_error,
1233 .ret_node,1238 .ret_node,
1239 .ret_load,
1234 .ret_coerce,1240 .ret_coerce,
1235 .ret_err_value,1241 .ret_err_value,
1236 .@"unreachable",1242 .@"unreachable",
...@@ -1335,6 +1341,7 @@ pub const Inst = struct {...@@ -1335,6 +1341,7 @@ pub const Inst = struct {
1335 .param_type = .param_type,1341 .param_type = .param_type,
1336 .ref = .un_tok,1342 .ref = .un_tok,
1337 .ret_node = .un_node,1343 .ret_node = .un_node,
1344 .ret_load = .un_node,
1338 .ret_coerce = .un_tok,1345 .ret_coerce = .un_tok,
1339 .ret_err_value = .str_tok,1346 .ret_err_value = .str_tok,
1340 .ret_err_value_code = .str_tok,1347 .ret_err_value_code = .str_tok,
...@@ -2912,6 +2919,7 @@ const Writer = struct {...@@ -2912,6 +2919,7 @@ const Writer = struct {
2912 .ensure_result_used,2919 .ensure_result_used,
2913 .ensure_result_non_error,2920 .ensure_result_non_error,
2914 .ret_node,2921 .ret_node,
2922 .ret_load,
2915 .resolve_inferred_alloc,2923 .resolve_inferred_alloc,
2916 .optional_type,2924 .optional_type,
2917 .optional_payload_safe,2925 .optional_payload_safe,
test/behavior/generics.zig+5-29
...@@ -28,16 +28,7 @@ test "simple generic fn" {...@@ -28,16 +28,7 @@ test "simple generic fn" {
28}28}
2929
30fn max(comptime T: type, a: T, b: T) T {30fn max(comptime T: type, a: T, b: T) T {
31 if (!builtin.zig_is_stage2) {31 return if (a > b) a else b;
32 // TODO: stage2 is incorrectly emitting AIR that allocates a result
33 // value, stores to it, but then returns void instead of the result.
34 return if (a > b) a else b;
35 }
36 if (a > b) {
37 return a;
38 } else {
39 return b;
40 }
41}32}
4233
43fn add(comptime a: i32, b: i32) i32 {34fn add(comptime a: i32, b: i32) i32 {
...@@ -70,29 +61,14 @@ test "fn with comptime args" {...@@ -70,29 +61,14 @@ test "fn with comptime args" {
70test "anytype params" {61test "anytype params" {
71 try expect(max_i32(12, 34) == 34);62 try expect(max_i32(12, 34) == 34);
72 try expect(max_f64(1.2, 3.4) == 3.4);63 try expect(max_f64(1.2, 3.4) == 3.4);
73 if (!builtin.zig_is_stage2) {64 comptime {
74 // TODO: stage2 is incorrectly hitting the following problem:65 try expect(max_i32(12, 34) == 34);
75 // error: unable to resolve comptime value66 try expect(max_f64(1.2, 3.4) == 3.4);
76 // return max_anytype(a, b);
77 // ^
78 comptime {
79 try expect(max_i32(12, 34) == 34);
80 try expect(max_f64(1.2, 3.4) == 3.4);
81 }
82 }67 }
83}68}
8469
85fn max_anytype(a: anytype, b: anytype) @TypeOf(a, b) {70fn max_anytype(a: anytype, b: anytype) @TypeOf(a, b) {
86 if (!builtin.zig_is_stage2) {71 return if (a > b) a else b;
87 // TODO: stage2 is incorrectly emitting AIR that allocates a result
88 // value, stores to it, but then returns void instead of the result.
89 return if (a > b) a else b;
90 }
91 if (a > b) {
92 return a;
93 } else {
94 return b;
95 }
96}72}
9773
98fn max_i32(a: i32, b: i32) i32 {74fn max_i32(a: i32, b: i32) i32 {
test/stage2/cbe.zig+5-1
...@@ -240,6 +240,10 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -240,6 +240,10 @@ pub fn addCases(ctx: *TestContext) !void {
240 if (host_supports_custom_stack_size) {240 if (host_supports_custom_stack_size) {
241 var case = ctx.exeFromCompiledC("@setEvalBranchQuota", .{});241 var case = ctx.exeFromCompiledC("@setEvalBranchQuota", .{});
242242
243 // TODO when adding result location support to function calls, revisit this test
244 // case. It can go back to what it was before, with `y` being comptime known.
245 // Because the ret_ptr will passed in with the inline fn call, and there will
246 // only be 1 store to it, and it will be comptime known.
243 case.addCompareOutput(247 case.addCompareOutput(
244 \\pub export fn main() i32 {248 \\pub export fn main() i32 {
245 \\ @setEvalBranchQuota(1001);249 \\ @setEvalBranchQuota(1001);
...@@ -247,7 +251,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -247,7 +251,7 @@ pub fn addCases(ctx: *TestContext) !void {
247 \\ return y - 1;251 \\ return y - 1;
248 \\}252 \\}
249 \\253 \\
250 \\fn rec(n: usize) callconv(.Inline) usize {254 \\inline fn rec(n: i32) i32 {
251 \\ if (n <= 1) return n;255 \\ if (n <= 1) return n;
252 \\ return rec(n - 1);256 \\ return rec(n - 1);
253 \\}257 \\}