docs/slides/dbg.md
__dbgDebug trait__dbg(...) is the Sway version of Rust dbg!(...). It prints the current line; it pretty prints its argument; it allows customization by the Debug trait; the compiler auto implement the Debug trait for all types; and in the end it returns its argument, allowing it to be used anywhere inside an expression.
> forc test --path sway-lib-std ok_all_primitives_must_be_debug --logs
[src/debug.sw:2679:13] = true
[src/debug.sw:2680:13] = false
[src/debug.sw:2691:13] = 0
[src/debug.sw:2692:13] = 18446744073709551615
[src/debug.sw:2697:13] = 0x0
[src/debug.sw:2698:13] = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
[src/debug.sw:2701:13] = "A"
[src/debug.sw:2703:13] = ("A", 0)
[src/debug.sw:2705:13] = [0, 1]
let a = __dbg(1u8);
is desugared into:
let a = {
let mut f = Formatter { };
f.print_str("[{current_file}:{current_line}:{current_col}] = ");
let arg = arg;
arg.fmt(f);
arg
};
And we can call fmt on a u8 because of this impl.
pub trait Debug {
fn fmt(self, ref mut f: Formatter);
}
impl Debug for u8 {
fn fmt(self, ref mut f: Formatter) {
f.print_u8(self);
}
}
All other primitive impls live in sway-lib-std/src/debug.sw.
Inside Formatter, we transform the number into a string; and call syscall_write.
pub fn print_u8(self, value: u8) {
let mut value = value;
let mut digits = [48u8; 64];
let mut i = 63;
while true {
digits[i] = (value % 10) + 48; // ascii zero = 48
value = value / 10;
if value == 0 {
break;
}
i -= 1;
}
syscall_write(STDERR, __addr_of(digits).add::<u8>(i), 64 - i);
}
And syscall_write is very similar to write(2) (https://man7.org/linux/man-pages/man2/write.2.html), but instead of calling an actual syscall, we use ecal with the first argument as 1000.
We reserved [0, 1000), for the VM, [1000, 2000) for the compiler and [2000, 3000) for the tooling purposes. This way, we can avoid a program having different behavior depending on the interpreter configuration.
So far, only 1000 is used.
// ssize_t write(int fd, const void buf[.count], size_t count);
fn syscall_write(fd: u64, buf: raw_ptr, count: u64) {
asm(id: 1000, fd: fd, buf: buf, count: count) {
ecal id fd buf count;
}
}
And inside the interpreter, this ecal calls:
impl EcalHandler for EcalSyscallHandler {
fn ecal<M: fuel_vm::prelude::Memory, S, Tx>(
vm: &mut Interpreter<M, S, Tx, Self>, a: RegId, b: RegId, c: RegId, d: RegId,
) -> fuel_vm::error::SimpleResult<()>
{
let regs = vm.registers();
let syscall = match regs[a.to_u8() as usize] {
1000 => { ... }
_ => { ... }
};
Ok(())
}
}
Real implementation in forc-test/src/ecal.rs
This means that output can vary by the interpreter configuration. The output shown below is from forc-test. Others interpreters can have different output, or none at all, as this is the default behavior.
> forc test --path sway-lib-std ok_all_primitives_must_be_debug --logs
[src/debug.sw:2679:13] = true
[src/debug.sw:2680:13] = false
[src/debug.sw:2691:13] = 0
[src/debug.sw:2692:13] = 18446744073709551615
[src/debug.sw:2697:13] = 0x0
[src/debug.sw:2698:13] = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
[src/debug.sw:2701:13] = "A"
[src/debug.sw:2703:13] = ("A", 0)
[src/debug.sw:2705:13] = [0, 1]
Debug traitThe compiler already generates Debug impl for all structs and enums, but this implementation can be customized like this:
impl<T> Debug for Vec<T> where T: Debug
{
fn fmt(self, ref mut f: Formatter) {
let mut l = f.debug_list();
for elem in self.iter() {
let _ = l.entry(elem);
}
l.finish();
}
}
This is the default implementation for the Vec struct;
[src/vec.sw:918:13] = Vec { buf: RawVec { ptr: 67108857, cap: 4 }, len: 3 }
versus the customized:
[src/vec.sw:931:13] = [1, 2, 3]
Different from AbiEncode, Debug auto implementation works for any primitive data type, including raw_ptr, raw_slice, references, etc. So the compiler will generate auto implementation for any type, unless a custom implementation is found.
__dbg will never have any use when running on chain. The VM will never implement the EcalHandler as we saw above.
So deploying binaries with it would be wasteful.
For this reason we completely strip everything when compiling in --release mode. So in release:
let a = __dbg(1u8);
desugars into
let a = 1u8;
But, if there is a troubleshooting needed in release build, one can force the compiler to emit __dbg by using:
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
entry = "main.sw"
name = "some-project"
force-dbg-in-release = true
end