Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: port dbg!() from Rust #3296

Open
vi opened this issue Sep 22, 2019 · 9 comments
Open

Proposal: port dbg!() from Rust #3296

vi opened this issue Sep 22, 2019 · 9 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@vi
Copy link

vi commented Sep 22, 2019

Primary tool for "printf debugging" in Zig seems to be std.debug.warn. This works, but cumbersome for some use cases where you want to temporarily insert tracing of some [sub]expression without chaning the code around too much.

For example, you have this code:

const warn=@import("std").debug.warn;

fn foo(x:u64) u32 {
    var a : u64 = switch(x) {
        4 => 4,
        0...3 => 8*x + (x<<4)*3,
        5...10 => 5*x + (x<<2)*3,
        11...19 => 2*x + x*x,
        else => 0xFFFFFFFF,
    };
    return @intCast(u32, (((a*a) % 4294967291) * a) % 4294967291 );
}

pub fn main() void {
    for ([_]u64{1,4,11,14,19,23}) |x| {
        warn("{}\n", foo(x));
    }
}
175616
64
1331
2744
6859
64

Now you want to see what's the value of 2*x + x*x and how often is it called (without being bothered by other switch branches).

const warn=@import("std").debug.warn;

fn foo(x:u64) u32 {
    var a : u64 = switch(x) {
        4 => 4,
        0...3 => 8*x + (x<<4)*3,
        5...10 => 5*x + (x<<2)*3,
        11...19 => blk:{var xx=2*x + x*x; warn("QQQ {}\n",xx); break :blk x;},
        else => 0xFFFFFFFF,
    };
    return @intCast(u32, (((a*a) % 4294967291) * a) % 4294967291 );
}

pub fn main() void {
    for ([_]u64{1,4,11,14,19,23}) |x| {
        warn("{}\n", foo(x));
    }
}
175616
64
QQQ 143
1331
QQQ 224
2744
QQQ 399
6859
64

It is 10 additional things to think of (blk:, {} block, var, new identifier name xx, warn, format string that is identifiable among other output, not forgotten \n, break, :blk, final semicolon before }) just for trivial one-off debug run.

This should be nicer. Therefore I propose to adopt dbg!(x) approach from Rust.

This is what it should look like:

const warn=@import("std").debug.warn;

fn foo(x:u64) u32 {
    var a : u64 = switch(x) {
        4 => 4,
        0...3 => 8*x + (x<<4)*3,
        5...10 => 5*x + (x<<2)*3,
        11...19 => @dbg(2*x + x*x),
        else => 0xFFFFFFFF,
    };
    return @intCast(u32, (((a*a) % 4294967291) * a) % 4294967291 );
}

pub fn main() void {
    for ([_]u64{1,4,11,14,19,23}) |x| {
        warn("{}\n", foo(x));
    }
}
175616
64
[myfile.zig:8] 2*x + x*x = 143
1331
[myfile.zig:8] 2*x + x*x = 224
2744
[myfile.zig:8] 2*x + x*x = 399
6859
64
  • Filename (or filepath) is included

  • Line number

  • Citation from source code

  • Actual value, if it can ever be printed;

  • No import needed (nice to have)

  • Semantically @dbg acts like an identity function (fn dbg(x: var) @typeOf(x) { return x; })

Such function should be either dummied out (turns an into actual identity function) or just forbidden for non-debug usages (--sloppy mode only). Obviously, this rule should not be tangled with LLVM optimisation level.

See also: #2029.

@andrewrk andrewrk added this to the 0.6.0 milestone Sep 24, 2019
@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Sep 24, 2019
@m-r-hunt
Copy link
Contributor

m-r-hunt commented Oct 3, 2019

You can write this today as normal zig code:

fn dbg(comptime T: type, value: T) T {
    if (builtin.mode == builtin.Mode.Debug) {
        std.debug.warn("{}", value);
    }
    return value;
}

I think dbg(type, expr) is probably good enough. It could go in std.debug perhaps?

@vi
Copy link
Author

vi commented Oct 3, 2019

It may work (especially with value: var instead of comptime T: type, value: T), but:

  1. Requires import or specifying long-ish name;
  2. May compile-fail for non-printable type T (if there are unprintable types in Zig);
  3. No filename:linenum information. No citation of expression.

I indent this to be a trading maintainability for quick modification-series feature, maybe available only in special mode, so ergonomy is a priority.

@andrewrk andrewrk modified the milestones: 0.6.0, 0.7.0 Feb 15, 2020
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 27, 2020
@nektro
Copy link
Contributor

nektro commented Nov 4, 2020

Would this not be closer to the original proposal?

var warned = false;
fn dbg(value: anyvalue) @TypeOf(value) {
    std.debug.warn("[dbg]: {}", value);
    return value;
}

@Mouvedia
Copy link

@nektro what's anyvalue? Is it dependent on another proposal?

@daurnimator
Copy link
Collaborator

@nektro what's anyvalue? Is it dependent on another proposal?

They probably meant anytype

@tauoverpi
Copy link
Sponsor Contributor

tauoverpi commented Apr 21, 2021

You can get the expression too and the result isn't too far from the proposal though for blocks it'd require a bit more work to get the display right.

Result:

/tmp/scratch λ zig test original.zig
[/tmp/scratch/original.zig:12]  2 * x + x * x = 143
[/tmp/scratch/example.zig:55]  @as(u32, (4 + 5) + 9) = 18
All 2 tests passed.
/tmp/scratch λ zig test example.zig
[/tmp/scratch/example.zig:55]  @as(u32, (4 + 5) + 9) = 18
All 1 tests passed.

Code (it's not pretty):

original.zig

const dbg = @import("example.zig").dbg;

test {
    _ = foo(11);
}

fn foo(x: u64) u32 {
    var a: u64 = switch (x) {
        4 => 4,
        0...3 => 8 * x + (x << 4) * 3,
        5...10 => 5 * x + (x << 2) * 3,
        11...19 => dbg(@src(), 2 * x + x * x),
        else => 0xFFFFFFFF,
    };
    return @intCast(u32, (((a * a) % 4294967291) * a) % 4294967291);
}

example.zig

const std = @import("std");
const mem = std.mem;

pub fn dbg(comptime loc: std.builtin.SourceLocation, val: anytype) @TypeOf(val) {
    var src = @embedFile(loc.file);
    var start: usize = 0;
    var line: usize = 0;

    while (mem.indexOfScalarPos(u8, src, start, '\n')) |pos| {
        line += 1;
        if (line >= loc.line) break;
        start = pos + 1;
    }

    start += loc.column - 1;

    var it = std.zig.Tokenizer.init(src[start..]);

    const code_start = start + while (true) {
        const token = it.next();
        if (token.tag == .comma) break it.index;
    } else unreachable;

    var depth: usize = 0;
    const code_end = start + while (true) {
        const token = it.next();
        switch (token.tag) {
            .r_paren => if (depth != 0) {
                depth -= 1;
            } else break it.index,
            .l_paren => depth += 1,
            else => {},
        }
    } else unreachable;

    std.debug.print("[{s}:{d}] {s} = {any}\n", .{
        loc.file,
        loc.line,
        src[code_start..code_end - 1],
        val,
    });

    return val;
}

test {
    _ = dbg(@src(), @as(u32, (4 + 5) + 9));
}

@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 Nov 23, 2021
@andrewrk andrewrk modified the milestones: 0.10.0, 0.11.0 Apr 16, 2022
@wooster0
Copy link
Contributor

wooster0 commented Oct 16, 2022

Some more thoughts on this:

  • @dbg should compile-error if @import("builtin").mode != .Debug.
  • Maybe we can remove std.debug.print or std.log.debug in favor of @dbg. Not sure about this though.
  • I think it should be a builtin. It avoids having to import it and it would avoid having to pass @src() to dbg on every invocation.
  • comptime @dbg could replace @compileLog, in which case compile @dbg will always compile-error (and print the output) like @compileLog does currently.
  • @dbg should take varargs (args: ... like @compileLog does right now) so that we can take the code at comptime (like 1 + 1 or x + 1) and then print something like 1 + 1 = 2. I don't think it should be a tuple (e.g. @dbg(.{ 123, x + 5 })).

Here's roughly how I imagine it:

pub fn main() void {
    var x: u8 = 5;
    @dbg("hello", x + 1, 123 * 5);
}
$ zig run x.zig
src/main.zig:3: "hello" = "hello"
src/main.zig:3: x + 1   = 6
src/main.zig:3: 123 * 5 = 615

All expressions are on separate lines and all lines only of the same @dbg statement are aligned like this (notice the equals sign's alignment).

This means now we would be able to write hello worlds without the std, which might feel weird but I think it's ok because it's not a production-ready hello world anyway and you can only use it in debug mode. And of course this compile-errors in any environment without an stderr available.

@nektro
Copy link
Contributor

nektro commented Oct 20, 2022

@vi
Copy link
Author

vi commented Oct 20, 2022

@nektro , This one seems to rely on stack walking and availability of symbols, so may break in embedded or exotic scenarios.

Another approach is to embed caller information into the format string itself at compile time. As far as I understand, Rust uses this approach and have special annotation for functions that would redirect Rust's analogue of @src to caller instead of the function itself.

@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jul 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

8 participants