Hacker News new | comments | show | ask | jobs | submit login
Go Assembly by Example (davidwong.fr)
197 points by baby on Sept 5, 2017 | hide | past | web | favorite | 36 comments

A few days ago I was reading through a few Wikipedia article related to assembly, and I found out Go has its own assembler [0]:

> Some others self-hosted native-targeted language implementations (like Golang, Free Pascal, SBCL) have their own assemblers with multiple targets. They may be used for inline assembly inside language, or even included as a library, but not always suitable for standalone application - no command-line tool exists, or only intermediate representation used as a source, or support for targets very limited.

Despite receive so much criticism, Go seems to be doing pretty well at carving out its own place. It's nice to see a language pick certain categories of problems and just focus on that. I haven't had any chance to write something serious in Go, but I've had to dig through a few projects and it's always refreshingly simple to read and understand. Really wish I could say the same thing about C++.

[0] https://en.wikipedia.org/wiki/Comparison_of_assemblers#As_pa...

> I've had to dig through a few projects and it's always refreshingly simple to read and understand. Really wish I could say the same thing about C++.

That's just not a design goal of C++. Different languages have different priorities and appeal to different folks. Personally, while writing C++ can be frustrating at times and the language still has too many arbitrary restrictions for my taste, every time I go through a Go codebase I feel glad I haven't had to write any Go myself. The amount of mindless, repetitive boilerplate one has to write because the language tries to be "simple" at the expense of abstraction, genericity and expressiveness looks intolerable to me.

But, clearly, there are people that enjoy writing Go and that's great. More diversity in language design can only be a good thing.

There is certainly some of that. Personally, I find that the Go language can be quite limiting, but there are often workarounds for the problems. That said, workarounds aren't a good excuse for failing to have first-class support for various use cases, and sometimes Go simply doesn't have a suitable workaround. These are my criticisms for Go the language. Go the ecosystem is top-notch; there's virtually no learning curve, and almost everything Just Works. Testing, benchmarking, and profiling are all supported out of the box. No project metadata files or build script files to learn or maintain. Everyone's code looks the same (`gofmt`), and the constraints imposed by the language make for boring, super-easy-to-read code, even if it is tedious for the writer. No special documentation syntax to learn--docs are just comments. http://godoc.org is bar-none the best documentation site for any language I've used (everything is in one place, clean layout, easy to navigate, etc). Even if code isn't well documented, documentation tools can use the static type structure of the program to give you enough information to piece together how to use a library.

Depending on the application, the utility of the ecosystem often outweighs the constraints of the language. But inevitably, Go will have to improve as a language before some other language's ecosystem improves and eats Go's lunch.

Please report the limitations you have to offer, so Golang2 can get better!


Well damn. Someone added my 2014 article to the "generics" section. I didn't even know this wiki page existed.

> Depending on the application, the utility of the ecosystem often outweighs the constraints of the language.

This is the one part where I generally tend to disagree. Basic language constraints are immutable, whereas ecosystem pain points can be fixed if people care enough. Everything you cited is true, the Go people seem to have done a great job at making the ecosystem convenient to use, but language issues are the one thing users can't fix on their own. The C++ ecosystem is fragmented, everyone has their own preferences and we'd all like to see it improved, but individual developers (as well as organizations, just look at Google's internal ecosystem) find a way to make it work well. If there's a dealbreaker in language design, on the other hand, there's just nothing you can do.

I absolutely agree; this is why I prefixed with "Depending on the application". If your application doesn't run into the prohibitive language issues, then you can benefit from the ecosystem with minimum drawbacks.

It's also worth noting that these "prohibitive language issues" can be solved with code generation in the worst case, and while this is often unjustifiably tedious, it's still far simpler than basic C++ project management (maintaining CMake, integrating an editor into your project, setting up testing, cross compiling, dealing with templates and compiler errors, etc). And I say all of this as a former C++ developer. If you can make C++ work in the basic cases, Go's worst case is still far nicer for all but the most performance-demanding applications.

I'm biased due to the performance constraints I work with and actually liking templates for the most part (apart from a few silly restrictions on specialization), but here's how I've solved some of those pain points for myself. I can't stand CMake, but quite enjoy using Meson (though small projects are just fine with a simple build script in bash or whatever you prefer). Error messages and editor integration are largely fixed by the Clang ecosystem, which have also enabled some excellent editor plugins (I use EasyClangComplete for Sublime, there are equivalents for every major editor now). When I care about Windows compatibility, I also use VS2017, which is a great improvement over previous versions and doesn't impose major constraints in terms of standard compliance any more. Perf and VTune satisfy all of my profiling needs.

Overall, I feel like the ecosystem utility / tooling situation has improved hugely over the past few years, to the point where it's comparable to any other language out there, though at the expense of requiring some user effort in choosing tools and getting a bit used to them. That's just my personal opinion, though, YMMV.

This seems reasonable. It's been more than a few years since I got out of the C++ game, and I never really kicked the tires on Meson. I'm truly happy to hear that things are improving for C++; I really liked the language (complex as it was), but I couldn't get anything done because of the ecosystem.

> The amount of mindless, repetitive boilerplate one has to write because the language tries to be "simple" at the expense of abstraction, genericity and expressiveness looks intolerable to me.

After wasting half a day staring at a totally nonsense generic class definition in Java spanning more than ten lines of code (after having worked in with Java for many years) I do not want any programmer to be clever any more, except he or she has so much social (and code) proof, that I will be delighted to learn something in such an exercise.

For the rest - I might add as a hyperbole - even Golang is too expressive.

Well, let's try to remember criticism isn't a bad thing. Both directly, and the fact that if nobody is doing it then nobody cares.

Is Go's assembly intended to be mostly platform-independent or platform-specific?

I clicked through to the AES example, and found that it "makes use of Go Assembly to leverage Intel's hardware support for AES, calling the AES-NI CPU instructions." So does that only work on Intel CPUs?

I'm confused about why Go has its own assembly syntax if you're going to be writing platform-specific code anyway. Or can you write Go assembly that works well on both x86 and ARM, say?

EDIT to put the question a different way: what are some use cases where you'd use Go assembly? In C, you'd typically drop down to platform-specific assembly for two reasons: 1) to muck about with the stack, for example in a threading library, or 2) to use platform-specific instructions, like NEON or SSE. (In that second case you can often get away with intrinsics rather than assembly, though.) Does Go assembly have the same uses, or different ones?

For another view: http://dtrace.org/blogs/wesolows/2014/12/29/golang-is-trash/

I'm partly with the dtrace guy, but it is nice to see somebody not propagating that gcc inline asm shite... so I'm prepared to forgive it a certain amount.

I'm not a Go fan, but I'm not ready to dismiss it as "trash" -- I mean, it works! And plenty of people like it.

But I can't quite figure out whether this Go assembly stuff is a real feature that makes sense and is well-designed, or if it's just a quirk of the implementation, stemming from its background in Plan 9.

The assembly section in the Go docs literally says "go read the Plan 9 docs first". That's kind of weird.

OK, another commenter linked to crc32 in the Go standard library:


That has different .s files for different architectures, and a .go file with the platform-independent version, so the assembly clearly isn't platform-independent.

It seems a strange decision to disguise e.g. arm64 assembly behind an x86-like syntax, so I'm going to go with "quirk of the implementation."

As I understand it, Go's assembly is an artifact of its heritage--it was initially built by developers who were more familiar with the Plan 9 toolchain than they were with other toolchains.

I believe the people behind Go are the people behind Plan 9?

This has been my consistent frustration with the development philosophy. I like to describe it as deciding to build a sweet new car to get from SF to LA and starting by paving the roads. Using 30 year old asphalt. Focus on what you bring to the table, and use LLVM or, god forgive me even GCC ;) as your foundation. Great writeup, thanks for sharing.

This is relevant https://www.youtube.com/watch?v=KINIAgRpkDA Its Rob Pike talking about the go assembler and he answers a few of your questions.

Do you happen to know of a transcript for those of us who consume text better than video?

Here are the slides for the talk:


The particular slide linked above answers the question "what are the advantages of having their own assembler." Turns out, they'll be able to abstract away the differences across the different manufacturers' assemblers to where they can just feed in the PDFs to be able to support that platform.

The video has a transcript.

Its not platform-independent. However they do use the legacy plan 9 code so some of the opcode and conventions are consistent across different platforms but they can't hide all the differences.

It is possible, though, as LLVM assembly shows us.

Tangentially related, recently came across this: https://go.godbolt.org/

It's an interactive compiler allowing you fiddle around with the generated assembly.

Does Go support macro assembler programming? It is a bit harsh to have to program assembler in a fully "write all the instructions" way, particularly in 2017...

Additionally, i see that the opcodes are not machine language (target) machine opcodes... It feels a bit ackward to have to manually program into an assembler that is not really targeting the actual CPU; kind of defeats one of the reasons to write in assembler in the first place.

As for Go, i always recall this article, which I found interesting:


Its a pretty basic assembler built in. Most of the Go library is written in Go itself with assembly for a few critical functions so that is the intended use. You can still use a proper macro assembler for full blown assembly and link Go code against that.

Neat, but I don't quite get it. How do I pass values between Go code and assembly? The format of this site is a little strange: it looks like an example Go snippet, followed by what I assume is the equivalent assembly code--but where is the connection? Or, is the point simply to teach us Go devs assembly?

Also, when is an appropriate time to use assembly in a Go program? After the failed trig experiment blog post that was featured here last week, the benefits aren't very clear to me.

EDIT: ok, I see it now. The empty function declaration. What an odd syntax. I get the tutorial now, but still wondering about the benefits.

Author of the failed trig experiment here.

After looking at the comments from people more knowledgeable than me, I realized that it had failed mostly because the assembly that I wrote was pretty bad. You have to know assembly language pretty well to beat the compiler.

I have been spending more time with this. And I understand my faults a bit better now. There are a lot of places which can be improved. And I have sent a CL to add some commands to the Go assembler (so that I don't have to write the ugly hex codes :P)

I plan to revisit the code once the CL is merged. And see how it goes.

To answer your question - You might get benefits if:

* You really understand what the compiler is doing.

* You know the current shortcomings of the Go assembler and the latest amd64 instruction set.

* You know assembly language well enough to write custom code for your problem and beat the compiler.

From what I have seen - the Go assembler attempts to generate binary code which supports old amd64 processors too. As a result of which you cannot take advantage of the latest instructions which come as part of AVX2 extensions. If you are doing high performance math stuff and know the AVX2 instruction set like the back of your hand, then you are sure to reap benefits.


Like in any programs to get better optimisation / performance on very specific functions.

Not sure if you saw the blog post I referenced that was trending here last week. These types of optimizations often turn out to be underwhelming.

Not true at all, there are 20-50x speedups for some of the functions that have been optimized into Go assembly. Specific cryptography routines for which CPUs have dedicated instructions. Those are the kinds of places where you'd use it. If you want to write assembly because you think you can generate better code than the compiler, you probably can, but it also almost certainly will be underwhelming as you say. It's only really worth it when you're using instructions the compiler won't emit, like specialized crypto, math, bit counting instructions, or SIMD instructions.

What is the command line to assemble and run ???

Simply drop the assembly as a .s file in a Go package and run `go build`. See Go's hash/crc32 package for a real example [0].

There's a lower level `go tool` command, of course, but I don't know it off the top of my head.

[0]: https://github.com/golang/go/tree/master/src/hash/crc32


Thanks alot!

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact