borsh-rs icon indicating copy to clipboard operation
borsh-rs copied to clipboard

Add BorshSize trait

Open Longarithm opened this issue 3 years ago • 3 comments

Discussion started here: https://github.com/near/nearcore/pull/4107#discussion_r594754389

The point is to wrap object' serialization and length computation in one method.

CC: @nearmax

Longarithm avatar Mar 18 '21 18:03 Longarithm

80% solution:

pub fn borsh_len(value: &impl BorshSerialize) -> Result<usize> {
    struct Writer {
        len: usize,
    }
    impl Write for Writer {
        #[inline]
        fn write(&mut self, buf: &[u8]) -> Result<usize> {
            self.len += buf.len();
            Ok(buf.len())
        }
        #[inline]
        fn flush(&mut self) -> Result<()> {
            Ok(())
        }
    }
    let mut w = Writer { len: 0 };
    value.serialize(&mut w)?;
    Ok(w.len)
}

This won't do exactly what we want for types that use extra heap-allocated buffers for serilization, but my gut feeling is that that's rare.

EDIT: didnt' resist the temptation to look at the asm, looks right for simple cases:

15:22:23|~/projects/borsh-rs|master⚡✚*
λ git diff --cached
diff --git a/borsh/examples/len.rs b/borsh/examples/len.rs
new file mode 100644
index 00000000..69b84a0c
--- /dev/null
+++ b/borsh/examples/len.rs
@@ -0,0 +1,17 @@
+#[derive(borsh::BorshSerialize)]
+pub struct Foo {
+    x: u8,
+    y: u32
+}
+
+#[no_mangle]
+#[inline(never)]
+pub fn size_of_foo(foo: &Foo) -> usize {
+    borsh::len(foo).unwrap_or_default()
+}
+
+fn main() {
+    let foo = Foo { x: 62, y: 92 };
+    let res = size_of_foo(&foo);
+    eprintln!("{}", res);
+}
diff --git a/borsh/src/lib.rs b/borsh/src/lib.rs
index 51c81919..55088283 100644
--- a/borsh/src/lib.rs
+++ b/borsh/src/lib.rs
@@ -45,3 +45,23 @@ pub mod maybestd {
         pub use hashbrown::hash_map::Entry;
     }
 }
+
+pub fn len(value: &impl BorshSerialize) -> std::io::Result<usize> {
+    struct Writer {
+        len: usize,
+    }
+    impl std::io::Write for Writer {
+        #[inline]
+        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+            self.len += buf.len();
+            Ok(buf.len())
+        }
+        #[inline]
+        fn flush(&mut self) -> std::io::Result<()> {
+            Ok(())
+        }
+    }
+    let mut w = Writer { len: 0 };
+    value.serialize(&mut w)?;
+    Ok(w.len)
+}

15:22:28|~/projects/borsh-rs|master⚡✚*
λ cargo build --example len --release -q

15:22:41|~/projects/borsh-rs|master⚡✚*
λ dis ./target/release/examples/len size_of_foo
Dump of assembler code for function size_of_foo:
   0x0000000000011b70 <+0>:	mov    eax,0x5
   0x0000000000011b75 <+5>:	ret    
End of assembler dump.

matklad avatar Apr 09 '21 12:04 matklad

And output for slightly more intersting

#[derive(borsh::BorshSerialize)]
pub struct Foo {
    x: u8,
    y: u32,
    zs: Vec<u32>,
}
Dump of assembler code for function size_of_foo:
   0x0000000000011be0 <+0>:	mov    rax,QWORD PTR [rdi+0x10]
   0x0000000000011be4 <+4>:	mov    rcx,rax
   0x0000000000011be7 <+7>:	shr    rcx,0x20
   0x0000000000011beb <+11>:	test   rax,rax
   0x0000000000011bee <+14>:	lea    rax,[rax*4+0x9]
   0x0000000000011bf6 <+22>:	mov    edx,0x9
   0x0000000000011bfb <+27>:	cmovne rdx,rax
   0x0000000000011bff <+31>:	xor    eax,eax
   0x0000000000011c01 <+33>:	test   rcx,rcx
   0x0000000000011c04 <+36>:	cmove  rax,rdx
   0x0000000000011c08 <+40>:	ret    
End of assembler dump.

matklad avatar Apr 09 '21 12:04 matklad

What a trick :D

maxzaver avatar Apr 13 '21 18:04 maxzaver