diff --git a/blog/v-vaporware-2019-06-23.markdown b/blog/v-vaporware-2019-06-23.markdown new file mode 100644 index 0000000..de6baa6 --- /dev/null +++ b/blog/v-vaporware-2019-06-23.markdown @@ -0,0 +1,436 @@ +--- +title: V is for Vaporware +date: 2019-06-23 +--- + +# V is for Vaporware + +[V](http://vlang.io) is a programming language that has been hyped a lot. As it's +recently had its first alpha release, I figured it would be a good idea to step +through it and see if it lives up to the promises that the author has been +claiming for months. + +The V website claims the following on the front page: + +- The compiler compiles 1.2 million lines of code compiled per CPU core per second +- The resulting code is as fast as C +- Built-in serialization without runtime reflection +- Minimal amount of allocations +- Zero dependencies +- Requires only 0.4 MB of space to build +- Able to translate arbitrary C/C++ code to V and build it faster than C/C++ +- Hot code reloading +- 2d/3d graphics support in the standard library +- Effortless cross-compilation +- A powerful built-in web framework +- The compiler generates direct machine code + +As far as I can tell, all of the above features are either "work-in-progress" +or completely absent from the source repository. + +## Speed + +The author mentions that the compiler is fast, stating the following: + +> Fast compilation +> +> V compiles ≈1.2 million lines of code per second per CPU core. (Intel +> i5-7500 @ 3.40GHz, SM0256L SSD, no optimization) +> +> Such speed is achieved by direct machine code generation [wip] and a strong +> modularity. +> +> V can also emit C, then the compilation speed drops to ≈100k lines/second/CPU. +> +> Direct machine code generation is at a very early stage. Right now only +> x64/Mach-O is supported. This means that for now emitting C has to be used. By +> the end of this year x64 generation should be stable enough. + +This has a few pretty fantastic claims. Let's see if they can be replicated. +Creating a 1.2 million line of code file should be pretty easy: + +``` +-- lua +print "fn main() {" + +for i = 0, 1200000, 1 +do + print "println('hello, world ')" +end + +print "}" +``` + +Then let's run this script to generate the 1.2 million lines of code: + +``` +$ time lua5.3 ./gencode.lua > 1point2mil.v + 4.29 real 0.83 user 3.27 sys +``` + +And compile the resulting file: + +``` +$ time v 1point2mil.v +pass=2 fn=`main` +panic: 1point2mil.v:50003 +more than 50 000 statements in function `main` + 2.43 real 2.13 user 0.15 sys +``` + +Oh boy. It's also worth noting that it was more than 2 seconds to only compile +50,000 lines of code on my Core m7 12" MacBook. + +## No Dependencies + +V claims to have zero dependencies. Again quoting from the website: + +> 400 KB compiler with zero [wip] dependencies +> +> The entire language and its standard library are less than 400 KB. V is written +> in V, and you can build it in 0.4 seconds. +> +> (By the end of this year this number will drop to ≈0.15 seconds.) + +... + +> Right now the V compiler does have one dependency: a C compiler. But it's +> needed to bootstrap the language anyway, and if you are doing development, +> chances are you already have a C compiler installed. +> +> It's a small dependency, and it's not going to be needed once x64 generation +> is mature enough. + +AMD64 is not the only CPU architecture that exists, but okay I'll take that you +are only targeting the most common one. + +Digging through the [readme](https://github.com/vlang/v/blob/8b08bf636acfba5af7f10e2bd0a646aaa71c16f5/README.md), +its graphics library and HTTP support require some dependencies: + +> In order to build Tetris and anything else using the graphics module, you will need to install glfw and freetype. +> +> If you plan to use the http package, you also need to install libcurl. +> +> glfw and libcurl dependencies will be removed soon. +> +> Ubuntu: +> sudo apt install glfw libglfw3-dev libfreetype6-dev libcurl3-dev +> +> macOS: +> brew install glfw freetype curl + +I'm sorry, but this combined with the explicit dependency on a C compiler means +that V has dependencies. Now, breaking the grammar down pretty literally it says +the _compiler_ has zero dependencies. Let's see what `ldd` says about the compiler +when built on Linux: + +``` +$ ldd v + linux-vdso.so.1 (0x00007ffc0f02e000) + libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f356c6cc000) + libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f356c2db000) + /lib64/ld-linux-x86-64.so.2 (0x00007f356cb25000) +``` + +So the compiler with "zero dependencies" is a _dynamically linked binary_ with +dependencies on libpthread and libc (the other two are glibc-specific). + +Also of note, I had to modify the [Makefile](https://github.com/vlang/v/blob/master/compiler/Makefile) +in order to get it to build on Linux without segfaulting every time it tried +to compile code: + +``` +$ git diff +diff --git a/compiler/Makefile b/compiler/Makefile +index e29d30d..353824d 100644 +--- a/compiler/Makefile ++++ b/compiler/Makefile +@@ -4,7 +4,7 @@ v: vc + ./vc -o v . + + vc: v.c +- cc -std=c11 -w -o vc v.c ++ clang -Dlinux -std=c11 -w -o vc v.c + + v.c: + wget https://vlang.io/v.c +``` + +Otherwise it would segfault every time I tried to run it with: + +``` +$ ./v --help +fish: “./v --help” terminated by signal SIGSEGV (Address boundary error) +``` + +Before I added the `-Dlinux` flag, it also failed compile with the following +error: + +``` +$ make +clang -std=c11 -w -o vc v.c +./vc -o v . +cc: error: unrecognized command line option ‘-mmacosx-version-min=10.7’ +V panic: clang error +Makefile:4: recipe for target 'v' failed +make: *** [v] Error 1 +``` + +Implying that the compiler was _falsely detecting Linux as macOS_. + +## Memory Safety + +V claims to be memory-safe: + +> Memory management +> +> There's no garbage collection or reference counting. V cleans up what it can +> during compilation. + +So I made a simple "hello world" program: + +``` +fn main() { + println('hello world!') // V only supports single quoted strings +} +``` + +and built it on my Linux box with valgrind installed. Surely a "hello world" +program has no good reason to leak memory, right? + +``` +$ time v hello.v +0.02user 0.00system 0:00.32elapsed 9%CPU (0avgtext+0avgdata 6196maxresident)k +0inputs+104outputs (0major+1162minor)pagefaults 0swaps + +$ valgrind ./hello +==5860== Memcheck, a memory error detector +==5860== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +==5860== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info +==5860== Command: ./hello +==5860== +hello, world +==5860== +==5860== HEAP SUMMARY: +==5860== in use at exit: 1,000 bytes in 1 blocks +==5860== total heap usage: 2 allocs, 1 frees, 2,024 bytes allocated +==5860== +==5860== LEAK SUMMARY: +==5860== definitely lost: 0 bytes in 0 blocks +==5860== indirectly lost: 0 bytes in 0 blocks +==5860== possibly lost: 0 bytes in 0 blocks +==5860== still reachable: 1,000 bytes in 1 blocks +==5860== suppressed: 0 bytes in 0 blocks +==5860== Rerun with --leak-check=full to see details of leaked memory +==5860== +==5860== For counts of detected and suppressed errors, rerun with: -v +==5860== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) +``` + +Looking at the [generated C code](https://gist.github.com/Xe/1afdd4c7e7c9cfa23d1aa87194ee5190#file-hello-c-L3698-L3705) +it's plainly obvious to see this memory leak. `init_consts` creates a 1000 byte +allocation and never frees it. This is a memory leak that is unavoidable in +any program compiled with V. This is potentially confusing for people who are +trying to debug memory leaks in their V code. They will always be off by 1 +allocation and 1000 bytes leaked without an easy way to tell why that is the +case. The compiler itself also leaks memory: + +``` +$ valgrind v hello.v +==9096== Memcheck, a memory error detector +==9096== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +==9096== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info +==9096== Command: v hello.v +==9096== +==9096== +==9096== HEAP SUMMARY: +==9096== in use at exit: 3,861,785 bytes in 24,843 blocks +==9096== total heap usage: 25,588 allocs, 745 frees, 4,286,917 bytes allocated +==9096== +==9096== LEAK SUMMARY: +==9096== definitely lost: 778,354 bytes in 18,773 blocks +==9096== indirectly lost: 3,077,104 bytes in 6,020 blocks +==9096== possibly lost: 0 bytes in 0 blocks +==9096== still reachable: 6,327 bytes in 50 blocks +==9096== suppressed: 0 bytes in 0 blocks +==9096== Rerun with --leak-check=full to see details of leaked memory +==9096== +==9096== For counts of detected and suppressed errors, rerun with: -v +==9096== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) +``` + +## Space Required to Build + +V also claims to only require 400-ish kilobytes of disk space to build itself. +Let's test this claim with a minimal Dockerfile: + +``` +FROM xena/alpine + +RUN apk --no-cache add build-base libexecinfo-dev clang git \ + && git clone https://github.com/vlang/v /root/code/v \ + && cd /root/code/v/compiler \ + && wget https://vlang.io/v.c \ + && clang -Dlinux -std=c11 -w -o vc v.c \ + && ./vc -o v . \ + && du -sh /root/code/v /root/.vlang0.0.12 \ + && apk del clang +``` + +Except it doesn't build on Alpine: + +``` +/usr/bin/ld: /tmp/v-c9fb07.o: in function `os__print_backtrace': +v.c:(.text+0x84d9): undefined reference to `backtrace' +/usr/bin/ld: v.c:(.text+0x8514): undefined reference to `backtrace_symbols_fd' +clang-8: error: linker command failed with exit code 1 (use -v to see invocation) +``` + +It looks like `backtrace()` is a glibc-specific addon. Let's link against +[`libexecinfo`](https://www.freshports.org/devel/libexecinfo) to fix this: + +``` + && clang -Dlinux -lexecinfo -std=c11 -w -o vc v.c \ +``` + +``` +Cloning into '/root/code/v'... +Connecting to vlang.io (3.91.188.13:443) +v.c 100% |********************************| 310k 0:00:00 ETA +Segmentation fault (core dumped) +``` + +Annoying, but we can adjust to Ubuntu fairly easily: + +``` +FROM ubuntu:latest + +RUN apt update \ + && apt -y install wget build-essential clang git \ + && git clone https://github.com/vlang/v /root/code/v \ + && cd /root/code/v/compiler \ + && wget https://vlang.io/v.c \ + && clang -Dlinux -std=c11 -w -o vc v.c \ + && ./vc -o v . \ + && du -sh /root/code/v /root/.vlang0.0.12 \ + && apt -y remove clang +``` + +As of the time of writing this article, the image `ubuntu:latest` has an +uncompressed size of `64.2MB`. If the V compiler only requires 400 KB to build +like it claims, the resulting image size for this Dockerfile should be around +65 MB at worst, right? +the resulting `du` command should show 400 KB in total, right? + +``` +3.4M /root/code/v +304K /root/.vlang0.0.12 +``` + +3.7 MB. That means the 400 KB claim is either a lie or "work-in-progress". +Coincidentally, the compiler uses about as much disk space as it leaks during +the compilation of "Hello, world". + +## HTTP Module + +V has a [http module](https://github.com/vlang/v/tree/master/http). It leaves a +lot to be desired. My favorite part is the implementation of [`download_file` on macOS](https://github.com/vlang/v/blob/master/http/download_mac.v#L60-L67): + +``` +fn download_file(url, out string) { + // println('\nDOWNLOAD FILE $out url=$url') + // -L follow redirects + // println('curl -L -o "$out" "$url"') + os.system2('curl -s -L -o "$out" "$url"') + // res := os.system('curl -s -L -o "$out" "$url"') + // println(res) +} +``` + +This has no error checking (the function `os.system2` returns the exit code of +curl) and it _shells out to curl instead of using libcurl_. +[Other parts of the http module use libcurl](https://github.com/vlang/v/blob/master/http/http_mac.v#L79-L191) +correctly (though the HTTP status code, headers and other important metadata +are not returned). There is also no support for overriding the HTTP transport, +setting a custom TLS configuration or many other basic features that +_libcurl provides for free_. + +I wasn't expecting it to have HTTP support out of the box, but even then I still +feel disappointed. + +## Random Number Generation + +Randomness is important for programming languages to get right. Here is how V +implements randomness: + +``` +module rand + +#include +// #include +fn seed() { + # time_t t; + # srand((unsigned) time(&t)); +} + +fn next(max int) int { + # return rand() % max; + return 0 +} +``` + +I mean I guess this is technically a valid implementation of randomness, but +this is how you get security vulnerabilities because people +[thought random values were random](https://www.rapid7.com/db/vulnerabilities/openssl-debian-weak-keys). +A correct implementation is commented out. Yay. + +## Suggestions for Improvement + +I would like to see V be a tool for productive development. I can't see it doing +that in the near future though. I would like to suggest the following to the V +developer in order for them to be able to improve in the future: + +Firstly, do not make claims about disk space, speed or dependencies without +explaining what you mean by that _in detail_. + +Do not shell out to arbitrary commands in the standard library for any reason. +If an attacker can somehow run code on a server with a V binary that uses the +`download_file` function, they can replace `curl` with a malicious binary that +is able to do anything the attacker wants. This feels like a huge vulnerability, +especially given that the playground allows you to run this function. + +AMD64 is not the only processor architecture that exists. It's nice that you're +supporting it, but this means that any program compiled with V will be stuck on +that architecture. This also means that V cannot currently be used for systems +programming like building a system-level package manager. + +Do not leak memory in "Hello world". You could solve the 1000 kilobyte leak by +adding the following generated C code and calling it after the user-written +main() function: + +``` +void destroy_consts() { free(g_str_buf); } +``` + +If you claim your compiler can support 1.2 million lines of code, do not make it +have a limit of 50,000 statements in one function. Yes it is somewhat crazy to +have 1.2 million statements in a single function, but as a compiler author it's +generally not your position to make these kinds of judgments. If the user wants +to have 1.2 million statements in a function, let them. + +Do not give code examples for libraries that you have not released. This means +don't show anything about the "built-in web framework" until you have code to +back your claim. If there is no code to back it up, you have backed yourself +into a corner where you are looking like you are lying. I would have loved to +benchmark V's web framework against Nim's Jester and Go's net/http, but I can't. + +Please fix the implementation of randomness. Holy crap that is a billion +security bugs waiting to happen. + +Thanks for reading this far. I hope this feedback can help make V a productive +tool for programming. It's a shame it seems to have been hyped so much for +comparatively so little as a result. The developer has been hyping and selling +this language like it's the new sliced bread. It is not. This is a very alpha +product. I bet you could use it for productive development as is if you really +stuck your head into it, but as it stands I recommend against using it for +anything.