Brian Robert Callahan

academic, developer, with an eye towards a brighter techno-social life


Let's try V on OpenBSD

A long time ago, I submitted a patch for V. And then I mostly forgot about V. GitHub recently reminded me of its existence. Let's see if V still works on OpenBSD. I am going to build it, see if it is easily able to be made into a port, and then try some of their upstream-developed programs.

About V

V bills itself as a simple, fast, safe, and compiled language for developing maintainable software. Somehow, it has already produced a 408-page textbook on the language, garnered nearly 30,000 stars on GitHub (as of this blog post), and created a developer community of over 500 contributors who have committed nearly 13,000 commits. For comparison, D has a little under 60,000 commits, with a contributor list somewhere between 400 and 800 (D splits their code over three repos which makes exact numbers more difficult to ascertain) and about 4,400 stars on GitHub. But D has been around since 1999, whereas V began life in 2019.

I do not know much about V or its community, but it did not take very long to discover that apparently V has been a bit controversial. Xe Iaso wrote a series of three blog posts detailing harsh but fair criticism, which ultimately led to her being blocked by the V team on (at least) Twitter. That last part seems very unacceptable; accepting, confronting, interacting, and responding to criticism, even harsh criticism, is something we need to do in order to be better and I don't read Xe's posts as crossing the line into vindictive. As a side note: if you read my blog and you don't read hers, you should.

The other large criticism I see is that V and its creator overpromise and underdeliver. And yes, a quick perusal of the website suggests this. But so long as the rest of the V community is enjoying working on V and no one is getting hurt, then whatever there are more important things to worry about.

Building V

It was nice to see that building V was very straightforward. All I had to do was clone the repository and run make. The Makefile took care of building the bootstrap compiler and then building a stage 2 and stage 3 V compiler. Running ./v after make finished worked fine and brought me to the V REPL:

/home/brian/v $ make
rm -rf vc/
git clone --depth 1 --quiet
cc -O2 -pipe  -std=gnu11 -w -o v1 vc/v.c -lm -lexecinfo -lpthread
./v1 -no-parallel -o v2  cmd/v
./v2 -o v  cmd/v
rm -rf v1 v2 vc/
V has been successfully built
./v run ./cmd/tools/detect_tcc.v

Note: `tcc` was not used, so unless you install it yourself, your backend
C compiler will be `cc`, which is usually either `clang`, `gcc` or `msvc`.

These C compilers, are several times slower at compiling C source code,
compared to `tcc`. They do produce more optimised executables, but that
is done at the cost of compilation speed.

/home/brian/v $ ./v
 ____    ____
 \   \  /   /  |  Welcome to the V REPL (for help with V itself, type  exit , then run  v help ).
  \   \/   /   |  Note: the REPL is highly experimental. For best V experience, use a text editor,
   \      /    |  save your code in a  main.v  file and execute:  v run main.v
    \    /     |  V 0.2.4 5c0917b . Use  list  to see the accumulated program so far.
     \__/      |  Use Ctrl-C or  exit  to exit, or  help  to see other available commands.


Alright fine, they want me to use v as a compiler. No problem. They also really seem to want us to use TCC as the backend C compiler. V is one of those languages that transpiles to C and then uses a C compiler to generate native code. This is the same technique we used for our PL/0 compiler.

I am guessing that this is how the V team gets away with claiming a compilation speed of approximately one million lines of code per second. I am not very convinced that compilation speed is so important that it needs to be elevated to the second item in the key features list, unless V is a simple prototyping language. Doubly true if we are using tcc to achieve those speeds, since while it is well known that tcc is extremely fast, it is not quite as performant as clang or gcc (though tcc by my unscientific testing is more performant than I thought it would be). I would much rather have a safe language that creates performant binaries first and then the compilation speed is a nice bonus. I don't think there are are millions of lines of V out in the world at the moment.

In any event, I used the OpenBSD clang compiler that comes with the system, and I did not experience anything near what was promised in compilation speed, even taking into consideration the approximately 110,000 lines per second compilation speed claimed when using clang:

/home/brian/v $ gtime -v make
rm -rf vc/
git clone --depth 1 --quiet
cc -O2 -pipe  -std=gnu11 -w -o v1 vc/v.c -lm -lexecinfo -lpthread
./v1 -no-parallel -o v2  cmd/v
./v2 -o v  cmd/v
rm -rf v1 v2 vc/
V has been successfully built
./v run ./cmd/tools/detect_tcc.v

Note: `tcc` was not used, so unless you install it yourself, your backend
C compiler will be `cc`, which is usually either `clang`, `gcc` or `msvc`.

These C compilers, are several times slower at compiling C source code,
compared to `tcc`. They do produce more optimised executables, but that
is done at the cost of compilation speed.

        Command being timed: "make"
        User time (seconds): 110.03
        System time (seconds): 5.12
        Percent of CPU this job got: 97%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 1:57.53
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 464824
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 12241
        Minor (reclaiming a frame) page faults: 747402
        Voluntary context switches: 41264
        Involuntary context switches: 3657
        Swaps: 0
        File system inputs: 165
        File system outputs: 2550
        Socket messages sent: 16
        Socket messages received: 1682
        Signals delivered: 13
        Page size (bytes): 4096
        Exit status: 0

By the way, gtime is my OpenBSD port of GNU time; you can get a copy of the port here. Also, this is using the Internet connection from $DAYJOB, so the git cloning time was fairly negligible.

So is that 110,000 lines per second compilation speed with clang only valid when using -O0? If so, why not say that? But then what's the point? The whole point of using these production-quality compilers is to use their production-quality optimizers. And clearly, that takes some significant amount of time. The v compiler does have a -cflags flag that you can use to pass your CFLAGS to the backend C compiler but then the V team should take benchmarks of gcc and clang with actual production-code quality CFLAGS.

Addendum: the founder of V reached out to me to say that he felt I should be using gtime -v v self for a fair comparison, as their website does say these compilation speed claims are for unoptimized builds only. Here is the GNU time output for that:

/home/brian $ gtime -v v self
V self compiling ...
V built successfully as executable "v".
        Command being timed: "v self"
        User time (seconds): 10.31
        System time (seconds): 0.80
        Percent of CPU this job got: 103%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:10.75
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 297472
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 3829
        Minor (reclaiming a frame) page faults: 165835
        Voluntary context switches: 31100
        Involuntary context switches: 1307
        Swaps: 0
        File system inputs: 51
        File system outputs: 778
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 2
        Page size (bytes): 4096
        Exit status: 0

A failed attempt at making an OpenBSD V port

I tried making a port for V. But this failed. I am not entirely sure why. The v compiler complains that it is unable to find some files. It doesn't have this problem when v is built in my $HOME directory. I can post the WIP OpenBSD port if there are some enterprising individuals who want to try to fix the problem before I get back around to it.

The V team also claims to have a native backend that, like tcc, spits out binaries directly. Unlike tcc, the ELF binaries that v produces are not well-formed, though I surmise they may work on Linux (they won't work on OpenBSD due to their malformedness). So the native backend, even if fast, is a non-starter. I looked into improving the native backend; adding the support apparatus for OpenBSD was very simple, but teaching v to generate ELF files that report more than zero sections and contain a section header table, among other things, was something I just didn't have the time for.

I suppose the C backend will be good enough for me. It is the default backend anyhow, and I cannot imagine anyone is seriously using the native backend.

Trying out the ved editor

I tried out two programs written in V: ved, a graphical text editor and Gitly, a lightweight GitHub/GitLab clone.

I tried building ved by following the instructions but I got a number of errors. The V standard library had to be taught how OpenBSD links X libraries, so I did that and submitted a Pull Request upstream. Once I had that fixed, I needed to copy uiold/ui_linux.c.v to uiold/ui_openbsd.c.v and then ved compiled successfully.

Running ved was a little strange. It hardcodes screen sizes. I guess someone has a 2560x1480 monitor (though I don't think that's a real resolution, though 2560x1440 is). This same person must also have a laptop with a 1440x900 resolution, which is at least a real resolution though I don't think a very common one. My machine has a 1920x1080 screen, so I used the smaller resolution, which can be had by issuing both the -window and the -laptop flags to ved. I also added the -dark flag to run in dark mode, which I found much easier on the eyes.

The editor itself seems to want to be both vi and emacs at the same time according to its keybindings. It's modal but you use C- combinations to do things like save and quit. I didn't really like it, but it does work and a text editor in your own language is a decent maturity test and a good self-containedness test. I could see someone who got very used to the ved keybindings, and had a monitor with exactly the right resolution, and only needed a simple text editor, and who wanted to "do it all in V" would probably be happy with ved. It even has rudimentary syntax highlighting, though the syntax highlighting is only for V (but it happened to do an OK job on C code).

Trying out Gitly

Gitly is a lightweight GitHub/GitLab clone written in V. They claim it can run well on the cheapest Amazon Lightsail virtual server. Which, for USD$3.50 per month (USD$42 per year) is actually a pretty good deal for self-hosting your open source projects if you want to do so in the cloud. You'd still need to pay for your own domain name, but those can be had very cheap depending on the TLD you select. But, this is only all a good deal if Gitly is a usable system. Unfortunately, it is not.

I built Gitly by following the instructions. And that's when I ran into my first problem: Gitly by default only runs on http://localhost:8080. Which is fine if you only want to connect from the same machine hosting the server, but I wanted to be able to connect to the server from all my home machines on my home network, which would mean the server listening on, its IP address on my home network. This was odd, as I eventually tracked it down to this line and then looked up the documentation only to be told that the run method starts a new VWeb server, listening to all available addresses, at the specified port. But that clearly wasn't happening in my case. Perhaps it is a simple fix to put OpenBSD somewhere in the VWeb code. I do not know.

I noticed the run_at method could specify a host, though it would be limited to just that host. That would be fine for me. However, the documentation gets the example wrong (the signature is correct): don't try to use vweb.run_at(app, 'localhost', 8099) like the example code says as it is wrong. Instead, you need to create a RunParams struct, in which you specify your host and port, and then use that.

The other change I had to make was a mechanical change to turn all references to the master branch into the main branch. All my repositories on GitHub use the main branch and Gitly will absolutely refuse to display any code if the branch names don't match.

All together, here is the diff to Gitly that I needed to get it to work on my machine:

diff --git a/branch.v b/branch.v
index 23dafaa..d3d1705 100644
--- a/branch.v
+++ b/branch.v
@@ -46,7 +46,7 @@ fn (mut app App) fetch_branches(r Repo) {
-	_ := r.git('checkout master')
+	_ := r.git('checkout main')
 fn (mut app App) update_branches(r &Repo) {
@@ -84,7 +84,7 @@ fn (mut app App) update_branches(r &Repo) {
-	_ := r.git('checkout master')
+	_ := r.git('checkout main')
 fn (branch Branch) relative() string {
diff --git a/gitly.v b/gitly.v
index 3e72119..4f6f559 100644
--- a/gitly.v
+++ b/gitly.v
@@ -51,11 +51,17 @@ mut:
 fn C.sqlite3_config(int)
 fn main() {
+	mut params := vweb.RunParams {
+		host: ''
+		port: 8080
+		family: .ip
+	}
 	C.sqlite3_config(3) // thread safe sqlite
 	if os.args.contains('ci_run') {
-, http_port)
+	vweb.run_at(new_app(), params) ?
 fn new_app() &App {
@@ -404,7 +410,7 @@ pub fn (mut app App) tree(user string, repo string, branch string, path string)
 		last_commit = app.find_repo_last_commit(
 	// println('app.tree() = ${time.ticks()-t}ms')
-	// branches := ['master'] TODO implemented usage
+	// branches := ['main'] TODO implemented usage
 	diff := int(time.ticks() - app.page_gen_start)
 	if diff == 0 {
 		app.page_gen_time = '<1ms'
@@ -440,7 +446,7 @@ pub fn (mut app App) update(user string, repo string) vweb.Result {
 	if app.user.is_admin {
 		// QTODO go
 		app.update_repo_data(mut app.repo)
-		app.slow_fetch_files_info('master', '.')
+		app.slow_fetch_files_info('main', '.')
 	return app.r_repo()
@@ -483,7 +489,7 @@ pub fn (mut app App) new_repo() vweb.Result {
 		name: name
 		git_dir: os.join_path(app.settings.repo_storage_path, app.user.username, name)
-		primary_branch: 'master'
+		primary_branch: 'main'
 		user_name: app.user.username
 		clone_url: clone_url
@@ -772,7 +778,7 @@ pub fn (mut app App) blob(user string, repo string, branch string, path string)
 	// Increase file's number of views
-	file := app.find_file_by_path(, 'master', blob_path) or {
+	file := app.find_file_by_path(, 'main', blob_path) or {
 		println('FILE NOT FOUND')
 		return vweb.Result{}
diff --git a/repo.v b/repo.v
index 99577d3..efdfd24 100644
--- a/repo.v
+++ b/repo.v
@@ -544,6 +544,6 @@ fn (mut r Repo) clone() {
 	r.git('config receive.denyCurrentBranch ignore')
 	r.git('config core.bare false')
-	r.git('checkout master')
+	r.git('checkout main')
 	r.status = .clone_done

And now I could run Gitly and connect to it on any of my machines.

It creates a new SQLite3 database on first launch. Once you navigate to the site using your browser of choice, it automagically detects that this is a new instance and has you set up an admin account.

And this is where the missing features began to seem too big to overcome for now. While I could create a new empty repository, I could not push anything to it. This is a known issue. But it also means no new repositories. You can import a GitHub repository, and I tested importing O and that worked fine.

There is some rudimentary syntax highlighting, but only for C, C++, Go, JavaScript, Python, TypeScript, and V. Additionally, there is no support for markdown formatting. I like the color scheme but would have preferred a dark mode option (yes, I know, glass houses on this blog; I intend to fix that).

But then the bigger issues begin to roll in: the Pull Requests button leads to a 404, a known issue; the Update button fatally crashes the server; files do not display on the repository page; the history page poorly formats commit messages so you can only see the first line; and there appears to be no way to clone a repository or even download individual raw files.

So it is for now just for displaying code to others. If Gitly implemented push and clone support, I could live with all the other flaws. Because at least then it would be a complete system to share code with the world. But as of now it is simply too incomplete to use. Which is a shame, because it really is lightweight and I really could see it becoming an excellent choice for lightweight self-hosted open source code servers.


V was an interesting language to try. It's important to remember that it is still a very immature language. It might be a very fruitful playground for someone who wants to be a part of the startup work of getting a programming language off the ground and establishing its first few killer apps. It right now probably isn't for someone who needs stability and battle-testedness, but if development keeps up at the pace it is then it may well become a good choice for those people sooner rather than later.

For me, I'd like to figure out why it is the V port isn't working and I'd like to see Gitly get to a point where it really can be used. I think I'll skip on ved because it's not for me (I'm a happy vi user) but it is clearly for someone.

I will keep V on my list of languages to keep tabs on and hopefully we can watch it develop into an interesting new language.