Commit ab5f7b5156

g-w1 <jacoblevgw@gmail.com>
2020-11-22 03:12:33
stage2: add compile log statement
1 parent 38572ee
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});
     }
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) {
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);
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,
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();
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);