| author | |
| committer | |
| log | fea8659b82ea1a785f933c58ba9d65ceb05a4094 |
| tree | 91d33abe8ae357bc5aecc06e9fd2423bd83a10a8 |
| parent | fb37c1b0912c65d72b82f32df8bc7e780ab1ad80 |
* Function calls that happen in a comptime scope get called at
compile-time. We do this by putting the parameters in place as
constant values and then running regular function analysis on the
body.
* Added `Scope.Block.dump()` for debugging purposes.
* Fixed some code to call `identifierTokenString` rather than
`tokenSlice`, making it work for `@""` syntax.
* Implemented `Value.copy` for big integers.
Follow-up issues to tackle:
* Adding compile errors to the callsite instead of the callee Decl.
* Proper error notes for "called from here".
- Related: #7555
* Branch quotas.
* ZIR support?6 files changed, 257 insertions(+), 46 deletions(-)
src/Module.zig+35-7| ... | ... | @@ -268,6 +268,11 @@ pub const Decl = struct { |
| 268 | 268 | } |
| 269 | 269 | } |
| 270 | 270 | |
| 271 | /// Asserts that the `Decl` is part of AST and not ZIRModule. | |
| 272 | pub fn getFileScope(self: *Decl) *Scope.File { | |
| 273 | return self.scope.cast(Scope.Container).?.file_scope; | |
| 274 | } | |
| 275 | ||
| 271 | 276 | fn removeDependant(self: *Decl, other: *Decl) void { |
| 272 | 277 | self.dependants.removeAssertDiscard(other); |
| 273 | 278 | } |
| ... | ... | @@ -776,6 +781,11 @@ pub const Scope = struct { |
| 776 | 781 | results: ArrayListUnmanaged(*Inst), |
| 777 | 782 | block_inst: *Inst.Block, |
| 778 | 783 | }; |
| 784 | ||
| 785 | /// For debugging purposes. | |
| 786 | pub fn dump(self: *Block, mod: Module) void { | |
| 787 | zir.dumpBlock(mod, self); | |
| 788 | } | |
| 779 | 789 | }; |
| 780 | 790 | |
| 781 | 791 | /// This is a temporary structure, references to it are valid only |
| ... | ... | @@ -992,11 +1002,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 992 | 1002 | defer tracy.end(); |
| 993 | 1003 | |
| 994 | 1004 | const container_scope = decl.scope.cast(Scope.Container).?; |
| 995 | const tree = try self.getAstTree(container_scope); | |
| 1005 | const tree = try self.getAstTree(container_scope.file_scope); | |
| 996 | 1006 | const ast_node = tree.root_node.decls()[decl.src_index]; |
| 997 | 1007 | switch (ast_node.tag) { |
| 998 | 1008 | .FnProto => { |
| 999 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", ast_node); | |
| 1009 | const fn_proto = ast_node.castTag(.FnProto).?; | |
| 1000 | 1010 | |
| 1001 | 1011 | decl.analysis = .in_progress; |
| 1002 | 1012 | |
| ... | ... | @@ -1131,7 +1141,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1131 | 1141 | for (fn_proto.params()) |param, i| { |
| 1132 | 1142 | const name_token = param.name_token.?; |
| 1133 | 1143 | const src = tree.token_locs[name_token].start; |
| 1134 | const param_name = tree.tokenSlice(name_token); // TODO: call identifierTokenString | |
| 1144 | const param_name = try self.identifierTokenString(&gen_scope.base, name_token); | |
| 1135 | 1145 | const arg = try gen_scope_arena.allocator.create(zir.Inst.Arg); |
| 1136 | 1146 | arg.* = .{ |
| 1137 | 1147 | .base = .{ |
| ... | ... | @@ -1496,12 +1506,10 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { |
| 1496 | 1506 | } |
| 1497 | 1507 | } |
| 1498 | 1508 | |
| 1499 | fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree { | |
| 1509 | pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { | |
| 1500 | 1510 | const tracy = trace(@src()); |
| 1501 | 1511 | defer tracy.end(); |
| 1502 | 1512 | |
| 1503 | const root_scope = container_scope.file_scope; | |
| 1504 | ||
| 1505 | 1513 | switch (root_scope.status) { |
| 1506 | 1514 | .never_loaded, .unloaded_success => { |
| 1507 | 1515 | try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); |
| ... | ... | @@ -1549,7 +1557,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void |
| 1549 | 1557 | |
| 1550 | 1558 | // We may be analyzing it for the first time, or this may be |
| 1551 | 1559 | // an incremental update. This code handles both cases. |
| 1552 | const tree = try self.getAstTree(container_scope); | |
| 1560 | const tree = try self.getAstTree(container_scope.file_scope); | |
| 1553 | 1561 | const decls = tree.root_node.decls(); |
| 1554 | 1562 | |
| 1555 | 1563 | try self.comp.work_queue.ensureUnusedCapacity(decls.len); |
| ... | ... | @@ -3427,3 +3435,23 @@ pub fn validateVarType(mod: *Module, scope: *Scope, src: usize, ty: Type) !void |
| 3427 | 3435 | return mod.fail(scope, src, "variable of type '{}' must be const or comptime", .{ty}); |
| 3428 | 3436 | } |
| 3429 | 3437 | } |
| 3438 | ||
| 3439 | /// Identifier token -> String (allocated in scope.arena()) | |
| 3440 | pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { | |
| 3441 | const tree = scope.tree(); | |
| 3442 | ||
| 3443 | const ident_name = tree.tokenSlice(token); | |
| 3444 | if (mem.startsWith(u8, ident_name, "@")) { | |
| 3445 | const raw_string = ident_name[1..]; | |
| 3446 | var bad_index: usize = undefined; | |
| 3447 | return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) { | |
| 3448 | error.InvalidCharacter => { | |
| 3449 | const bad_byte = raw_string[bad_index]; | |
| 3450 | const src = tree.token_locs[token].start; | |
| 3451 | return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte}); | |
| 3452 | }, | |
| 3453 | else => |e| return e, | |
| 3454 | }; | |
| 3455 | } | |
| 3456 | return ident_name; | |
| 3457 | } |
src/astgen.zig+9-29| ... | ... | @@ -384,7 +384,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr |
| 384 | 384 | .local_val => scope = scope.cast(Scope.LocalVal).?.parent, |
| 385 | 385 | .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, |
| 386 | 386 | else => if (node.getLabel()) |break_label| { |
| 387 | const label_name = try identifierTokenString(mod, parent_scope, break_label); | |
| 387 | const label_name = try mod.identifierTokenString(parent_scope, break_label); | |
| 388 | 388 | return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); |
| 389 | 389 | } else { |
| 390 | 390 | return mod.failTok(parent_scope, src, "break expression outside loop", .{}); |
| ... | ... | @@ -426,7 +426,7 @@ fn continueExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowE |
| 426 | 426 | .local_val => scope = scope.cast(Scope.LocalVal).?.parent, |
| 427 | 427 | .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, |
| 428 | 428 | else => if (node.getLabel()) |break_label| { |
| 429 | const label_name = try identifierTokenString(mod, parent_scope, break_label); | |
| 429 | const label_name = try mod.identifierTokenString(parent_scope, break_label); | |
| 430 | 430 | return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); |
| 431 | 431 | } else { |
| 432 | 432 | return mod.failTok(parent_scope, src, "continue expression outside loop", .{}); |
| ... | ... | @@ -551,7 +551,7 @@ fn varDecl( |
| 551 | 551 | } |
| 552 | 552 | const tree = scope.tree(); |
| 553 | 553 | const name_src = tree.token_locs[node.name_token].start; |
| 554 | const ident_name = try identifierTokenString(mod, scope, node.name_token); | |
| 554 | const ident_name = try mod.identifierTokenString(scope, node.name_token); | |
| 555 | 555 | |
| 556 | 556 | // Local variables shadowing detection, including function parameters. |
| 557 | 557 | { |
| ... | ... | @@ -843,7 +843,7 @@ fn typeInixOp(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp, op_ins |
| 843 | 843 | fn enumLiteral(mod: *Module, scope: *Scope, node: *ast.Node.EnumLiteral) !*zir.Inst { |
| 844 | 844 | const tree = scope.tree(); |
| 845 | 845 | const src = tree.token_locs[node.name].start; |
| 846 | const name = try identifierTokenString(mod, scope, node.name); | |
| 846 | const name = try mod.identifierTokenString(scope, node.name); | |
| 847 | 847 | |
| 848 | 848 | return addZIRInst(mod, scope, src, zir.Inst.EnumLiteral, .{ .name = name }, .{}); |
| 849 | 849 | } |
| ... | ... | @@ -864,7 +864,7 @@ fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Erro |
| 864 | 864 | |
| 865 | 865 | for (decls) |decl, i| { |
| 866 | 866 | const tag = decl.castTag(.ErrorTag).?; |
| 867 | fields[i] = try identifierTokenString(mod, scope, tag.name_token); | |
| 867 | fields[i] = try mod.identifierTokenString(scope, tag.name_token); | |
| 868 | 868 | } |
| 869 | 869 | |
| 870 | 870 | // analyzing the error set results in a decl ref, so we might need to dereference it |
| ... | ... | @@ -988,36 +988,16 @@ fn orelseCatchExpr( |
| 988 | 988 | /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. |
| 989 | 989 | /// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used. |
| 990 | 990 | fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool { |
| 991 | const ident_name_1 = try identifierTokenString(mod, scope, token1); | |
| 992 | const ident_name_2 = try identifierTokenString(mod, scope, token2); | |
| 991 | const ident_name_1 = try mod.identifierTokenString(scope, token1); | |
| 992 | const ident_name_2 = try mod.identifierTokenString(scope, token2); | |
| 993 | 993 | return mem.eql(u8, ident_name_1, ident_name_2); |
| 994 | 994 | } |
| 995 | 995 | |
| 996 | /// Identifier token -> String (allocated in scope.arena()) | |
| 997 | fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { | |
| 998 | const tree = scope.tree(); | |
| 999 | ||
| 1000 | const ident_name = tree.tokenSlice(token); | |
| 1001 | if (mem.startsWith(u8, ident_name, "@")) { | |
| 1002 | const raw_string = ident_name[1..]; | |
| 1003 | var bad_index: usize = undefined; | |
| 1004 | return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) { | |
| 1005 | error.InvalidCharacter => { | |
| 1006 | const bad_byte = raw_string[bad_index]; | |
| 1007 | const src = tree.token_locs[token].start; | |
| 1008 | return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte}); | |
| 1009 | }, | |
| 1010 | else => |e| return e, | |
| 1011 | }; | |
| 1012 | } | |
| 1013 | return ident_name; | |
| 1014 | } | |
| 1015 | ||
| 1016 | 996 | pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst { |
| 1017 | 997 | const tree = scope.tree(); |
| 1018 | 998 | const src = tree.token_locs[node.token].start; |
| 1019 | 999 | |
| 1020 | const ident_name = try identifierTokenString(mod, scope, node.token); | |
| 1000 | const ident_name = try mod.identifierTokenString(scope, node.token); | |
| 1021 | 1001 | |
| 1022 | 1002 | return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{}); |
| 1023 | 1003 | } |
| ... | ... | @@ -1936,7 +1916,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo |
| 1936 | 1916 | defer tracy.end(); |
| 1937 | 1917 | |
| 1938 | 1918 | const tree = scope.tree(); |
| 1939 | const ident_name = try identifierTokenString(mod, scope, ident.token); | |
| 1919 | const ident_name = try mod.identifierTokenString(scope, ident.token); | |
| 1940 | 1920 | const src = tree.token_locs[ident.token].start; |
| 1941 | 1921 | if (mem.eql(u8, ident_name, "_")) { |
| 1942 | 1922 | return mod.failNode(scope, &ident.base, "TODO implement '_' identifier", .{}); |
src/value.zig+8-5| ... | ... | @@ -330,11 +330,14 @@ pub const Value = extern union { |
| 330 | 330 | .int_type => return self.copyPayloadShallow(allocator, Payload.IntType), |
| 331 | 331 | .int_u64 => return self.copyPayloadShallow(allocator, Payload.U64), |
| 332 | 332 | .int_i64 => return self.copyPayloadShallow(allocator, Payload.I64), |
| 333 | .int_big_positive => { | |
| 334 | @panic("TODO implement copying of big ints"); | |
| 335 | }, | |
| 336 | .int_big_negative => { | |
| 337 | @panic("TODO implement copying of big ints"); | |
| 333 | .int_big_positive, .int_big_negative => { | |
| 334 | const old_payload = self.cast(Payload.BigInt).?; | |
| 335 | const new_payload = try allocator.create(Payload.BigInt); | |
| 336 | new_payload.* = .{ | |
| 337 | .base = .{ .tag = self.ptr_otherwise.tag }, | |
| 338 | .data = try allocator.dupe(std.math.big.Limb, old_payload.data), | |
| 339 | }; | |
| 340 | return Value{ .ptr_otherwise = &new_payload.base }; | |
| 338 | 341 | }, |
| 339 | 342 | .function => return self.copyPayloadShallow(allocator, Payload.Function), |
| 340 | 343 | .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl), |
src/zir.zig+70| ... | ... | @@ -1885,6 +1885,46 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { |
| 1885 | 1885 | module.dump(); |
| 1886 | 1886 | } |
| 1887 | 1887 | |
| 1888 | /// For debugging purposes, prints a function representation to stderr. | |
| 1889 | pub fn dumpBlock(old_module: IrModule, module_block: *IrModule.Scope.Block) void { | |
| 1890 | const allocator = old_module.gpa; | |
| 1891 | var ctx: EmitZIR = .{ | |
| 1892 | .allocator = allocator, | |
| 1893 | .decls = .{}, | |
| 1894 | .arena = std.heap.ArenaAllocator.init(allocator), | |
| 1895 | .old_module = &old_module, | |
| 1896 | .next_auto_name = 0, | |
| 1897 | .names = std.StringArrayHashMap(void).init(allocator), | |
| 1898 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), | |
| 1899 | .indent = 0, | |
| 1900 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), | |
| 1901 | .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), | |
| 1902 | .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), | |
| 1903 | .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), | |
| 1904 | }; | |
| 1905 | defer ctx.metadata.deinit(); | |
| 1906 | defer ctx.body_metadata.deinit(); | |
| 1907 | defer ctx.block_table.deinit(); | |
| 1908 | defer ctx.loop_table.deinit(); | |
| 1909 | defer ctx.decls.deinit(allocator); | |
| 1910 | defer ctx.names.deinit(); | |
| 1911 | defer ctx.primitive_table.deinit(); | |
| 1912 | defer ctx.arena.deinit(); | |
| 1913 | ||
| 1914 | _ = ctx.emitBlock(module_block, 0) catch |err| { | |
| 1915 | std.debug.print("unable to dump function: {}\n", .{err}); | |
| 1916 | return; | |
| 1917 | }; | |
| 1918 | var module = Module{ | |
| 1919 | .decls = ctx.decls.items, | |
| 1920 | .arena = ctx.arena, | |
| 1921 | .metadata = ctx.metadata, | |
| 1922 | .body_metadata = ctx.body_metadata, | |
| 1923 | }; | |
| 1924 | ||
| 1925 | module.dump(); | |
| 1926 | } | |
| 1927 | ||
| 1888 | 1928 | const EmitZIR = struct { |
| 1889 | 1929 | allocator: *Allocator, |
| 1890 | 1930 | arena: std.heap.ArenaAllocator, |
| ... | ... | @@ -2065,6 +2105,36 @@ const EmitZIR = struct { |
| 2065 | 2105 | return &declref_inst.base; |
| 2066 | 2106 | } |
| 2067 | 2107 | |
| 2108 | fn emitBlock(self: *EmitZIR, module_block: *IrModule.Scope.Block, src: usize) Allocator.Error!*Decl { | |
| 2109 | var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); | |
| 2110 | defer inst_table.deinit(); | |
| 2111 | ||
| 2112 | var instructions = std.ArrayList(*Inst).init(self.allocator); | |
| 2113 | defer instructions.deinit(); | |
| 2114 | ||
| 2115 | const body: ir.Body = .{ .instructions = module_block.instructions.items }; | |
| 2116 | try self.emitBody(body, &inst_table, &instructions); | |
| 2117 | ||
| 2118 | const fn_type = try self.emitType(src, Type.initTag(.void)); | |
| 2119 | ||
| 2120 | const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); | |
| 2121 | mem.copy(*Inst, arena_instrs, instructions.items); | |
| 2122 | ||
| 2123 | const fn_inst = try self.arena.allocator.create(Inst.Fn); | |
| 2124 | fn_inst.* = .{ | |
| 2125 | .base = .{ | |
| 2126 | .src = src, | |
| 2127 | .tag = Inst.Fn.base_tag, | |
| 2128 | }, | |
| 2129 | .positionals = .{ | |
| 2130 | .fn_type = fn_type.inst, | |
| 2131 | .body = .{ .instructions = arena_instrs }, | |
| 2132 | }, | |
| 2133 | .kw_args = .{}, | |
| 2134 | }; | |
| 2135 | return self.emitUnnamedDecl(&fn_inst.base); | |
| 2136 | } | |
| 2137 | ||
| 2068 | 2138 | fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl { |
| 2069 | 2139 | var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); |
| 2070 | 2140 | defer inst_table.deinit(); |
src/zir_sema.zig+111-4| ... | ... | @@ -25,6 +25,8 @@ const trace = @import("tracy.zig").trace; |
| 25 | 25 | const Scope = Module.Scope; |
| 26 | 26 | const InnerError = Module.InnerError; |
| 27 | 27 | const Decl = Module.Decl; |
| 28 | const astgen = @import("astgen.zig"); | |
| 29 | const ast = std.zig.ast; | |
| 28 | 30 | |
| 29 | 31 | pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { |
| 30 | 32 | switch (old_inst.tag) { |
| ... | ... | @@ -826,7 +828,112 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError |
| 826 | 828 | |
| 827 | 829 | const ret_type = func.ty.fnReturnType(); |
| 828 | 830 | |
| 829 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 831 | const b = try mod.requireFunctionBlock(scope, inst.base.src); | |
| 832 | if (b.is_comptime) { | |
| 833 | const fn_val = try mod.resolveConstValue(scope, func); | |
| 834 | const module_fn = switch (fn_val.tag()) { | |
| 835 | .function => fn_val.castTag(.function).?.data, | |
| 836 | .extern_fn => return mod.fail(scope, inst.base.src, "comptime call of extern function", .{}), | |
| 837 | else => unreachable, | |
| 838 | }; | |
| 839 | const callee_decl = module_fn.owner_decl; | |
| 840 | const callee_file_scope = callee_decl.getFileScope(); | |
| 841 | const tree = mod.getAstTree(callee_file_scope) catch |err| switch (err) { | |
| 842 | error.OutOfMemory => return error.OutOfMemory, | |
| 843 | error.AnalysisFail => return error.AnalysisFail, | |
| 844 | // TODO: make sure this gets retried and not cached | |
| 845 | else => return mod.fail(scope, inst.base.src, "failed to load {s}: {s}", .{ | |
| 846 | callee_file_scope.sub_file_path, @errorName(err), | |
| 847 | }), | |
| 848 | }; | |
| 849 | const ast_node = tree.root_node.decls()[callee_decl.src_index]; | |
| 850 | const fn_proto = ast_node.castTag(.FnProto).?; | |
| 851 | ||
| 852 | var call_arena = std.heap.ArenaAllocator.init(mod.gpa); | |
| 853 | defer call_arena.deinit(); | |
| 854 | ||
| 855 | var gen_scope: Scope.GenZIR = .{ | |
| 856 | .decl = callee_decl, | |
| 857 | .arena = &call_arena.allocator, | |
| 858 | .parent = callee_decl.scope, | |
| 859 | }; | |
| 860 | defer gen_scope.instructions.deinit(mod.gpa); | |
| 861 | ||
| 862 | // Add a const instruction for each parameter. | |
| 863 | var params_scope = &gen_scope.base; | |
| 864 | for (fn_proto.params()) |param, i| { | |
| 865 | const name_token = param.name_token.?; | |
| 866 | const src = tree.token_locs[name_token].start; | |
| 867 | const param_name = try mod.identifierTokenString(scope, name_token); | |
| 868 | const arg_val = try mod.resolveConstValue(scope, casted_args[i]); | |
| 869 | const arg = try astgen.addZIRInstConst(mod, params_scope, src, .{ | |
| 870 | .ty = casted_args[i].ty, | |
| 871 | .val = arg_val, | |
| 872 | }); | |
| 873 | const sub_scope = try call_arena.allocator.create(Scope.LocalVal); | |
| 874 | sub_scope.* = .{ | |
| 875 | .parent = params_scope, | |
| 876 | .gen_zir = &gen_scope, | |
| 877 | .name = param_name, | |
| 878 | .inst = arg, | |
| 879 | }; | |
| 880 | params_scope = &sub_scope.base; | |
| 881 | } | |
| 882 | ||
| 883 | const body_node = fn_proto.getBodyNode().?; // We handle extern functions above. | |
| 884 | const body_block = body_node.cast(ast.Node.Block).?; | |
| 885 | ||
| 886 | try astgen.blockExpr(mod, params_scope, body_block); | |
| 887 | ||
| 888 | if (gen_scope.instructions.items.len == 0 or | |
| 889 | !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()) | |
| 890 | { | |
| 891 | const src = tree.token_locs[body_block.rbrace].start; | |
| 892 | _ = try astgen.addZIRNoOp(mod, &gen_scope.base, src, .returnvoid); | |
| 893 | } | |
| 894 | ||
| 895 | if (mod.comp.verbose_ir) { | |
| 896 | zir.dumpZir(mod.gpa, "fn_body_callee", callee_decl.name, gen_scope.instructions.items) catch {}; | |
| 897 | } | |
| 898 | ||
| 899 | // Analyze the ZIR. | |
| 900 | var inner_block: Scope.Block = .{ | |
| 901 | .parent = null, | |
| 902 | .func = module_fn, | |
| 903 | .decl = callee_decl, | |
| 904 | .instructions = .{}, | |
| 905 | .arena = &call_arena.allocator, | |
| 906 | .is_comptime = true, | |
| 907 | }; | |
| 908 | defer inner_block.instructions.deinit(mod.gpa); | |
| 909 | ||
| 910 | // TODO make sure compile errors that happen from this analyzeBody are reported correctly | |
| 911 | // and attach to the caller Decl not the callee. | |
| 912 | try analyzeBody(mod, &inner_block.base, .{ | |
| 913 | .instructions = gen_scope.instructions.items, | |
| 914 | }); | |
| 915 | ||
| 916 | if (mod.comp.verbose_ir) { | |
| 917 | inner_block.dump(mod.*); | |
| 918 | } | |
| 919 | ||
| 920 | assert(inner_block.instructions.items.len == 1); | |
| 921 | const only_inst = inner_block.instructions.items[0]; | |
| 922 | switch (only_inst.tag) { | |
| 923 | .ret => { | |
| 924 | const ret_inst = only_inst.castTag(.ret).?; | |
| 925 | const operand = ret_inst.operand; | |
| 926 | const callee_arena = scope.arena(); | |
| 927 | return mod.constInst(scope, inst.base.src, .{ | |
| 928 | .ty = try operand.ty.copy(callee_arena), | |
| 929 | .val = try operand.value().?.copy(callee_arena), | |
| 930 | }); | |
| 931 | }, | |
| 932 | .retvoid => return mod.constVoid(scope, inst.base.src), | |
| 933 | else => unreachable, | |
| 934 | } | |
| 935 | } | |
| 936 | ||
| 830 | 937 | return mod.addCall(b, inst.base.src, ret_type, func, casted_args); |
| 831 | 938 | } |
| 832 | 939 | |
| ... | ... | @@ -1509,7 +1616,7 @@ fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr |
| 1509 | 1616 | return mod.fail(scope, inst.base.src, "unable to find '{s}'", .{operand}); |
| 1510 | 1617 | }, |
| 1511 | 1618 | else => { |
| 1512 | // TODO user friendly error to string | |
| 1619 | // TODO: make sure this gets retried and not cached | |
| 1513 | 1620 | return mod.fail(scope, inst.base.src, "unable to open '{s}': {s}", .{ operand, @errorName(err) }); |
| 1514 | 1621 | }, |
| 1515 | 1622 | }; |
| ... | ... | @@ -1912,12 +2019,12 @@ fn analyzeInstUnreachable( |
| 1912 | 2019 | |
| 1913 | 2020 | fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { |
| 1914 | 2021 | const operand = try resolveInst(mod, scope, inst.positionals.operand); |
| 1915 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 2022 | const b = try mod.requireFunctionBlock(scope, inst.base.src); | |
| 1916 | 2023 | return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); |
| 1917 | 2024 | } |
| 1918 | 2025 | |
| 1919 | 2026 | fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { |
| 1920 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 2027 | const b = try mod.requireFunctionBlock(scope, inst.base.src); | |
| 1921 | 2028 | if (b.func) |func| { |
| 1922 | 2029 | // Need to emit a compile error if returning void is not allowed. |
| 1923 | 2030 | const void_inst = try mod.constVoid(scope, inst.base.src); |
test/stage2/test.zig+24-1| ... | ... | @@ -318,7 +318,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 318 | 318 | } |
| 319 | 319 | |
| 320 | 320 | { |
| 321 | var case = ctx.exe("adding numbers at runtime", linux_x64); | |
| 321 | var case = ctx.exe("adding numbers at runtime and comptime", linux_x64); | |
| 322 | 322 | case.addCompareOutput( |
| 323 | 323 | \\export fn _start() noreturn { |
| 324 | 324 | \\ add(3, 4); |
| ... | ... | @@ -342,6 +342,29 @@ pub fn addCases(ctx: *TestContext) !void { |
| 342 | 342 | , |
| 343 | 343 | "", |
| 344 | 344 | ); |
| 345 | case.addCompareOutput( | |
| 346 | \\export fn _start() noreturn { | |
| 347 | \\ exit(); | |
| 348 | \\} | |
| 349 | \\ | |
| 350 | \\fn add(a: u32, b: u32) u32 { | |
| 351 | \\ return a + b; | |
| 352 | \\} | |
| 353 | \\ | |
| 354 | \\const x = add(3, 4); | |
| 355 | \\ | |
| 356 | \\fn exit() noreturn { | |
| 357 | \\ asm volatile ("syscall" | |
| 358 | \\ : | |
| 359 | \\ : [number] "{rax}" (231), | |
| 360 | \\ [arg1] "{rdi}" (x - 7) | |
| 361 | \\ : "rcx", "r11", "memory" | |
| 362 | \\ ); | |
| 363 | \\ unreachable; | |
| 364 | \\} | |
| 365 | , | |
| 366 | "", | |
| 367 | ); | |
| 345 | 368 | } |
| 346 | 369 | |
| 347 | 370 | { |