site/blog/v-vaporware-2019-06-23.mark...

408 lines
14 KiB
Markdown
Raw Normal View History

---
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.
## 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.
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.