408 lines
14 KiB
Markdown
408 lines
14 KiB
Markdown
---
|
||
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.
|