diff --git a/src/Compilation.zig b/src/Compilation.zig index bf8fd73e93..4efb845a82 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1351,6 +1351,9 @@ pub fn totalErrorCount(self: *Compilation) usize { module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); + for (module.compile_log_decls.items()) |entry| { + total += entry.value.items.len; + } } // The "no entry point found" error only counts if there are no other errors. @@ -1407,6 +1410,15 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }); try AllErrors.addPlain(&arena, &errors, msg); } + for (module.compile_log_decls.items()) |entry| { + const decl = entry.key; + const path = decl.scope.subFilePath(); + const source = try decl.scope.getSource(module); + for (entry.value.items) |src_loc| { + const err_msg = ErrorMsg{ .byte_offset = src_loc, .msg = "found compile log statement" }; + try AllErrors.add(&arena, &errors, path, source, err_msg); + } + } } if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) { diff --git a/src/Module.zig b/src/Module.zig index 6395a7f3a5..c53efb7a2d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -61,6 +61,8 @@ failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, /// emit-h failing for that Decl. This table is also how we tell if a Decl has /// failed emit-h or succeeded. emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, +/// A Decl can have multiple compileLogs, but only one error, so we map a Decl to a the src locs of all the compileLogs +compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, ArrayListUnmanaged(usize)) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, @@ -936,6 +938,11 @@ pub fn deinit(self: *Module) void { } self.failed_exports.deinit(gpa); + for (self.compile_log_decls.items()) |*entry| { + entry.value.deinit(gpa); + } + self.compile_log_decls.deinit(gpa); + for (self.decl_exports.items()) |entry| { const export_list = entry.value; gpa.free(export_list); @@ -1881,6 +1888,9 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { if (self.emit_h_failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); } + if (self.compile_log_decls.remove(decl)) |*entry| { + entry.value.deinit(self.gpa); + } self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); @@ -1971,6 +1981,9 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { if (self.emit_h_failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); } + if (self.compile_log_decls.remove(decl)) |*entry| { + entry.value.deinit(self.gpa); + } decl.analysis = .outdated; } @@ -3151,6 +3164,44 @@ pub fn failNode( return self.fail(scope, src, format, args); } +fn addCompileLog(self: *Module, decl: *Decl, src: usize) error{OutOfMemory}!void { + const entry = try self.compile_log_decls.getOrPutValue(self.gpa, decl, .{}); + try entry.value.append(self.gpa, src); +} + +pub fn failCompileLog( + self: *Module, + scope: *Scope, + src: usize, +) InnerError!void { + switch (scope.tag) { + .decl => { + const decl = scope.cast(Scope.DeclAnalysis).?.decl; + try self.addCompileLog(decl, src); + }, + .block => { + const block = scope.cast(Scope.Block).?; + try self.addCompileLog(block.decl, src); + }, + .gen_zir => { + const gen_zir = scope.cast(Scope.GenZIR).?; + try self.addCompileLog(gen_zir.decl, src); + }, + .local_val => { + const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; + try self.addCompileLog(gen_zir.decl, src); + }, + .local_ptr => { + const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; + try self.addCompileLog(gen_zir.decl, src); + }, + .zir_module, + .file, + .container, + => unreachable, + } +} + fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Compilation.ErrorMsg) InnerError { { errdefer err_msg.destroy(self.gpa); diff --git a/src/astgen.zig b/src/astgen.zig index 8275a05d77..7f5d596dda 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -2346,6 +2346,16 @@ fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCal items[param_i] = try expr(mod, scope, .none, param); return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.TypeOfPeer, .{ .items = items }, .{})); } +fn compileLog(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + const tree = scope.tree(); + const arena = scope.arena(); + const src = tree.token_locs[call.builtin_token].start; + const params = call.params(); + var targets = try arena.alloc(*zir.Inst, params.len); + for (params) |param, param_i| + targets[param_i] = try expr(mod, scope, .none, param); + return addZIRInst(mod, scope, src, zir.Inst.CompileLog, .{ .to_log = targets }, .{}); +} fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); @@ -2377,6 +2387,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built return compileError(mod, scope, call); } else if (mem.eql(u8, builtin_name, "@setEvalBranchQuota")) { return setEvalBranchQuota(mod, scope, call); + } else if (mem.eql(u8, builtin_name, "@compileLog")) { + return compileLog(mod, scope, call); } else { return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{s}'", .{builtin_name}); } diff --git a/src/zir.zig b/src/zir.zig index 8019d0e030..63850d67db 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -127,9 +127,8 @@ pub const Inst = struct { coerce_to_ptr_elem, /// Emit an error message and fail compilation. compileerror, - /// Changes the maximum number of backwards branches that compile-time - /// code execution can use before giving up and making a compile error. - set_eval_branch_quota, + /// Log compile time variables and emit an error message. + compilelog, /// Conditional branch. Splits control flow based on a boolean condition value. condbr, /// Special case, has no textual representation. @@ -223,6 +222,9 @@ pub const Inst = struct { @"return", /// Same as `return` but there is no operand; the operand is implicitly the void value. returnvoid, + /// Changes the maximum number of backwards branches that compile-time + /// code execution can use before giving up and making a compile error. + set_eval_branch_quota, /// Integer shift-left. Zeroes are shifted in from the right hand side. shl, /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type. @@ -407,6 +409,7 @@ pub const Inst = struct { .declval => DeclVal, .declval_in_module => DeclValInModule, .coerce_result_block_ptr => CoerceResultBlockPtr, + .compilelog => CompileLog, .loop => Loop, .@"const" => Const, .str => Str, @@ -540,6 +543,7 @@ pub const Inst = struct { .typeof_peer, .resolve_inferred_alloc, .set_eval_branch_quota, + .compilelog, => false, .@"break", @@ -723,6 +727,19 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const CompileLog = struct { + pub const base_tag = Tag.compilelog; + base: Inst, + + positionals: struct { + to_log: []*Inst, + }, + kw_args: struct { + /// If we have seen it already so don't make another error + seen: bool = false, + }, + }; + pub const Const = struct { pub const base_tag = Tag.@"const"; base: Inst, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index dbe10c4bde..596cd5b24e 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -57,6 +57,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?), .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?), .compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?), + .compilelog => return analyzeInstCompileLog(mod, scope, old_inst.castTag(.compilelog).?), .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?), .dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?), .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), @@ -630,6 +631,27 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In return mod.fail(scope, inst.base.src, "{s}", .{msg}); } +fn analyzeInstCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst { + std.debug.print("| ", .{}); + for (inst.positionals.to_log) |item, i| { + const to_log = try resolveInst(mod, scope, item); + if (to_log.value()) |val| { + std.debug.print("{}", .{val}); + } else { + std.debug.print("(runtime value)", .{}); + } + if (i != inst.positionals.to_log.len - 1) std.debug.print(", ", .{}); + } + std.debug.print("\n", .{}); + if (!inst.kw_args.seen) { + + // so that we do not give multiple compile errors if it gets evaled twice + inst.kw_args.seen = true; + try mod.failCompileLog(scope, inst.base.src); + } + return mod.constVoid(scope, inst.base.src); +} + fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 6e25dc283b..829ab421ea 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1243,6 +1243,24 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":3:9: error: redefinition of 'testing'"}); } + ctx.compileError("compileLog", linux_x64, + \\export fn _start() noreturn { + \\ const b = true; + \\ var f: u32 = 1; + \\ @compileLog(b, 20, f, x); + \\ @compileLog(1000); + \\ var bruh: usize = true; + \\ unreachable; + \\} + \\fn x() void {} + , &[_][]const u8{ + ":4:3: error: found compile log statement", + ":5:3: error: found compile log statement", + ":6:21: error: expected usize, found bool", + }); + // TODO if this is here it invalidates the compile error checker: + // "| true, 20, (runtime value), (function)" + // "| 1000" { var case = ctx.obj("extern variable has no type", linux_x64);