Live data from Hacker News

Show HN: Tiny 1k Rust binary on Windows

github.com

41–50 of 66 posts

Re: Show HN: Tiny 1k Rust binary on Windows

#42

I'm disappointed. In C I can get a 1k Hello Word binary simply be writing int main(void) { puts("Hello World!"); return 0; } The Rust version is a mess [0] [0] https://github.com/mcountryman/min-sized-rust-windows/blob/m...

Actually, the equivalent Rust code to this is just the following:

    println!("Hello world!");
Exactly that text. Compile it and it comes in at exactly 13.6% of the C program you have that compiles to 1k.

Re: Show HN: Tiny 1k Rust binary on Windows

#43

If you're willing to rely on undocumented behavior, you can always skip kernel32 and use ntdll.dll directly. Or you could go even further and direct invoke the right syscalls. But either way, you'd be relying on behavior that Microsoft can change without notice.

The Win32 loader will always load kernel32 no matter what. You don't even need it in your import table. It's just a matter of finding where it's loaded and calling into the right functions.

Re: Show HN: Tiny 1k Rust binary on Windows

#44
Note that a typical "Hello World" executable written in C does not contain any code capable of actually printing anything. It links to libc shipped with the operating system. That is about 30MB of code that C executables get for free.

You can link libc statically and dead-code-eliminate everything except print. That's what Rust does too, but Rust has fancier machinery for formatting and handling of panics with backtraces. These bits don't get dead-code-eliminated as thoroughly in Rust (probably because the print could theoretically fail).

Re: Show HN: Tiny 1k Rust binary on Windows

#45
post #37
post #16

Earlier quoted context omitted.

You're comparing C+StdLib vs. Rust without StdLib. Disable stdlib for C version and use the following program: #include int _start() { const char str[] = "Hello world!\n"; HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE); DWORD written; WriteFile(stdout, str, sizeof(str), &written, NULL); ExitProcess(12); return 0; } https://stackoverflow.com/a/42536990 Which is similar to what the Rust program does. The result should…

I tried your program with MSVC using the following command: cl small2.c /MD /link /entry:_start /subsystem:CONSOLE kernel32.lib It's 5kb.

If I remember correctly you need a /nodefaultlib flag to disable stdlibs

https://docs.microsoft.com/en-us/cpp/build/reference/nodefau...

Re: Show HN: Tiny 1k Rust binary on Windows

#46
post #45
post #37

Earlier quoted context omitted.

I tried your program with MSVC using the following command: cl small2.c /MD /link /entry:_start /subsystem:CONSOLE kernel32.lib It's 5kb.

If I remember correctly you need a /nodefaultlib flag to disable stdlibs https://docs.microsoft.com/en-us/cpp/build/reference/nodefau...

Ok, I've just tried that but I also needed to add `/force` otherwise it complained about missing symbols. So the full compile line is:

    cl small2.c /link /force /nodefaultlib /entry:_start /subsystem:CONSOLE kernel32.lib
This gets it down to 3kb.

Re: Show HN: Tiny 1k Rust binary on Windows

#47
post #44

Note that a typical "Hello World" executable written in C does not contain any code capable of actually printing anything. It links to libc shipped with the operating system. That is about 30MB of code that C executables get for free. You can link libc statically and dead-code-eliminate everything except print. That's what Rust does too, but Rust has fancier machinery for formatting and handling of panics with backtr…

Rant:

And below libc is probably X or Wayland which tells the kernel where to put pixels. And below that is the kernel and graphics driver which figures out how to do that and communicates it to the GPU. And below that is the actual GPU which figures out how to modulate an array of pixels into an HDMI signal. When do we stop quibbling about the size of binaries? It’s pointless to point out that small code relies on other abstractions. No one complains about tiny demo scene programs using tons of other stuff built into the OS. So why here?

Re: Show HN: Tiny 1k Rust binary on Windows

#48

If you're willing to rely on undocumented behavior, you can always skip kernel32 and use ntdll.dll directly. Or you could go even further and direct invoke the right syscalls. But either way, you'd be relying on behavior that Microsoft can change without notice.

I thought of that but couldn't figure out how to get the syscalls for my system and the assembly around it. Being on win10, kernel32 links to api-console-... and I haven't been able to find it on my system. A few online downloads had it but, they don't export `GetStdHandle` and `WriteFile`. I thought maybe there was dynamic export generation going on because they did have those names as strings.

Edit: Also I believe ntdll doesn't export `GetStdHandle` anymore.

Re: Show HN: Tiny 1k Rust binary on Windows

#49
post #43

If you're willing to rely on undocumented behavior, you can always skip kernel32 and use ntdll.dll directly. Or you could go even further and direct invoke the right syscalls. But either way, you'd be relying on behavior that Microsoft can change without notice.

The Win32 loader will always load kernel32 no matter what. You don't even need it in your import table. It's just a matter of finding where it's loaded and calling into the right functions.

Between a 40b strcmp method (If I had/if it was more widely supported I could use sse4.2 strcmp) and ~74b of resolving said exports a couple syscalls sounds pretty nice provided the context switch isn't more expensive than resolving the imports.

Re: Show HN: Tiny 1k Rust binary on Windows

#50
post #22

Zig actually does a good job of cutting final output bytes automatically , even with standard library use. I think (?) its compiler sort-of implements a more advanced version of Unix strip. Andrew Kelley could probably chime in more on it.

It's very easy and fast to try: using 0.7.0+51d7c14ce from https://ziglang.org/download/ and: const std = @import("std"); pub fn main() void { std.debug.print("Hello, world!\n", .{}); } ---------- zig build-exe hello.zig -O ReleaseSmall --strip --single-threaded -target x86_64-windows Resulting hello.exe is 3072 bytes. From these 2560 are zero bytes . The only system calls from the resulting binary are to ExitProcess…

@Edit2 I saw that, and that's pretty cool in regards to how the zig std is a wrapper rather than a rewrite. Although, if we were optimizing for size in this case it might be more optimal to have only one entry in the import table :D
Post reply on HN