
Game Boy Advance “Hello World” Using Zig - WendigoJaeger
https://github.com/wendigojaeger/ZigGBAHelloWorld/
======
AndyKelley
The issue to get rid of the llvm-objcopy build dependency is
[https://github.com/ziglang/zig/issues/2826](https://github.com/ziglang/zig/issues/2826).
This appears to be the only thing that is currently required to be installed
besides Zig itself.

------
csande17
I tried to get Zig running on a Game Boy Advance a while ago, but never really
got to a point I was happy with. Great to see someone else working on it!

It's cool that you got the built-in linker to work. When I tried, I got a
bunch of "unsupported architecture" errors and had to use GNU ld. Have things
gotten better recently or is this an ARM-vs-Thumb thing?

I was excited to be able to use packed structs to represent the different IO
registers -- at last, no more OR-ing the wrong flag into the wrong register!
-- but unfortunately those don't quite work right on ARMv4T yet:
[https://github.com/ziglang/zig/issues/2767](https://github.com/ziglang/zig/issues/2767)

------
cpitman
For anyone else who doesn't know what Zig is:
[https://ziglang.org/](https://ziglang.org/)

~~~
pingyong
Hmm that's certainly an interesting project. But I have to wonder if writing
"defer file.close()" everywhere is really superior to RAII. Isn't defer also
kind of hidden control flow? At that point I feel like RAII simply offers
more, first of all you don't have to write what you want to defer everywhere,
and second of all you can actually move resources around and still rely on it.

Also the integer overflow section struck me as kinda odd - what if I _want_
wrapping behavior? That's not exactly a super uncommon scenario.

~~~
clarry
Wrapping is not a good default, and actually it's not that common a scenario.

When you want arithmetic to wrap, you should use some way to explicitly denote
that you want it to wrap. (I think it could look like an operation with a
modulo reduction or mask applied to it.)

defer is way more transparent than RAII, because your code is right there in
the function call, not hidden in a pile of classes that might change
underneath.

~~~
pingyong
I don't mind it not being default - but the section doesn't mention at all how
to get wrapping behavior. Just one sentence like "use this different operator
to get wrapping behavior" would be fine, but since they didn't mention it at
all I kind of went away with the idea that it is not supported at all, which
honestly would be a massive problem.

>defer is way more transparent than RAII, because your code is right there in
the function call, not hidden in a pile of classes that might change
underneath.

I mean, sort of? It seems way less composable though, if you have an object
which contains 5 different resources, now you have to defer 5 things? How does
this interact with return values, if you defer file.close() for example but
return the file, does it still get closed? And if you have a heap allocated
array of those objects, where objects get inserted at different places, defer
doesn't work at all anymore, since it's just bound to function scope? Because
that's usually the scenario that can be annoying to handle in C (opposed to
just closing whatever was opened in the same function scope), so it seems a
bit odd to me to kind of violate that "no hidden control flow" principle for
something that doesn't even solve the primary issue that C resource handling
has.

~~~
clarry
> I don't mind it not being default - but the section doesn't mention at all
> how to get wrapping behavior.

I think that piece would get too long if it described everything in depth.

[https://ziglang.org/documentation/master/#Wrapping-
Operation...](https://ziglang.org/documentation/master/#Wrapping-Operations)

> I mean, sort of? It seems way less composable though, if you have an object
> which contains 5 different resources, now you have to defer 5 things?

Defer is used for scope-local resources. If you create five different things
in the local scope that need to be cleaned up, then they are most likely not
part of the same object; yes, you will need to clean them all up. If you have
_an object_ with five different thing, then you would defer that object's
cleanup routine.

> How does this interact with return values, if you defer file.close() for
> example but return the file, does it still get closed?

Yes. Why would you defer close on a file that you are planning to return?
That's by definition not some scope-local resource with scope lifetime you
want to cleanup at the end of scope. That's like implementing malloc that
frees the block before returning.

Note that there is errdefer, which you can use to defer the release of
resources that would be returned normally but need to be cleaned up on error.
[https://ziglang.org/documentation/master/#defer](https://ziglang.org/documentation/master/#defer)

> And if you have a heap allocated array of those objects, where objects get
> inserted at different places, defer doesn't work at all anymore, since it's
> just bound to function scope?

I don't understand what you're asking about here. If you malloc() an array in
C, then you free() it when you're done with it. The exact same mechanism works
in Zig, but defer also helps you ensure free() actually gets called when you
go out of scope, so you can still use early returns instead of ret = foo; goto
err_bar;

> Because that's usually the scenario that can be annoying to handle in C
> (opposed to just closing whatever was opened in the same function scope), so
> it seems a bit odd to me to kind of violate that "no hidden control flow"
> principle for something that doesn't even solve the primary issue that C
> resource handling has.

I don't agree that it's violating the "no hidden control flow constraint."
It's explicit, right there in the scope, not stashed away in a bunch of
destructors. It's as transparent as return or goto or break.

Again, I don't really understand the rest of your complaints (or what's your
primary issue with C's resource handling). Defer is just a mechanism to make
it easier to release resources whose lifetime is tied to a scope.

~~~
pingyong
>I think that piece would get too long if it described everything in depth.

I don't think one sentence more about a very fundamental operation in a page-
long document would really be misplaced. But as far as the language itself
goes, those operators seem fine to me.

>Note that there is errdefer

That is definitely more useful, and covers the situation I was thinking of.

>I don't understand what you're asking about here. If you malloc() an array in
C, then you free() it when you're done with it.

This is about composed objects. So an allocated array of objects, which in
turn also hold allocated objects, which might in turn also hold allocated
objects.

>I don't agree that it's violating the "no hidden control flow constraint."
It's explicit, right there in the scope, not stashed away in a bunch of
destructors. It's as transparent as return or goto or break.

Not sure I can completely agree with that. Fundamentally, defer means that
something happens at a point where there is no associated code. If you read
the line "return 5;", you don't know if that triggers one (or multiple)
function calls. Yes, you only have to look for those in function scope (which
could be 5000+ lines in some cases) (you only have to look for destructors in
function scope too btw), but it is still a "hidden code path". For me
personally it doesn't really matter at that point if I have to first search in
the function body (and then potentially in the cleanup function), or first in
the function body and then potentially in destructors. It removes the ability
to reason about code line by line either way. Which might be a price that is
worth to pay for easier cleanup logic, but the way I'm seeing it I'm only
getting like a quarter of the benefit of destructors but I'm paying
essentially the same price.

~~~
clarry
> This is about composed objects. So an allocated array of objects, which in
> turn also hold allocated objects, which might in turn also hold allocated
> objects.

The way this is solved by calling the cleanup routine for the first object in
the hierarchy. If that object is responsible for the lifetime of other
objects, then it will also call their cleanup routine. From the user's
perspective, those are invisible, just as any calls to free() or close() or
fflush() inside an fclose() are.

> If you read the line "return 5;", you don't know if that triggers one (or
> multiple) function calls.

If you read the line "continue;" or "break;", you don't know if that triggers
one (or multiple) function calls until you read the surrounding code.

I still find it much easier to follow code inside a function than to jump
through classes.

But the bigger picture is that it makes lifetime management explicit and thus
transparent, and you needn't complicate the language with copy constructors
and move semantics.

~~~
pingyong
>If you read the line "continue;" or "break;", you don't know if that triggers
one (or multiple) function calls until you read the surrounding code.

Not sure what you mean exactly - you have to read the surrounding code to know
where the control flow continues, but there's no hidden function call between
that point and and the break. That's like arguing you need to understand the
entire code base to know what return does - since it returns control flow to
potentially a completely different file. But that's not really the point, the
point is that it doesn't do anything else in between - if you know where
control flow is going to, you know everything that is happening. More so with
break and continue even, since for break and continue where control flow
continues doesn't even depend on runtime state, whereas return could return to
different places depending on where the function was called.

~~~
clarry
With break, control flow goes out of the loop or out of the switch. You may
find function calls there. With continue, control flow goes to the start of
the loop. You may find function calls there. With return, control goes through
the defer blocks and then out of the function. Similar reasoning for goto. In
every case, you know where the control flow goes by looking at the context
inside the function.

I don't agree with the assessment that one of them is more hidden than the
other.

~~~
pingyong
>With break, control flow goes out of the loop or out of the switch. You may
find function calls there.

Yeah but you don't have to look for anything in between. You look for where it
goes, and that's it. That there could be a function call _after_ the continue
has executed isn't relevant at all, there could also be function call after a
return has executed. Stepping through the code in your head is trivial because
the execution never jumps anywhere without an explicit statement to jump.

With defers, you need to make sure you find every single defer that was
executed up to the return (which might not be trivial if defers are executed
conditionally). With a break there is exactly one, unconditional, position at
which execution continues - and if you have found it, there is no reason to
keep looking anywhere else.

I mean wanting to make the defer trade-off but not the destructor trade-off,
fine, that is ultimately a matter of opinion. But defer being hidden control
flow is just objectively true, execution jumps somewhere without a
corresponding explicit jump in the code (return only explicitly jumps out of
the function).

------
jefftime
This is so cool! I'm really impressed with what I've seen out of Zig so far. I
need to come up with a project to try it out with. It seems like a great
successor to C

~~~
pron
Yep. Zig is shaping up to be the low-level programming language I've been
waiting for to replace C++ for me. It just needs to mature and stabilize a bit
before it's production-quality.

~~~
3fe9a03ccd14ca5
Why not rust?

~~~
csande17
Rust is great for when you're writing a serious, security-critical program
that can't have any memory-corruption bugs or data races. It makes writing
programs a little more challenging, and sometimes you sacrifice a bit of
runtime performance compared to C, but it's often worth it.

But Game Boy Advance games don't really fit that description. GBA games don't
accept untrusted input, and nothing bad happens if they're "compromised".
(Like, when people discovered arbitrary code execution in Super Mario World,
no one was worried about the security implications.) So languages like C or
Zig that let you cowboy values directly into specific memory locations can be
a better choice.

I'm excited about Zig in particular because the mission statement seems to be
"C but nicer" \-- you get the same basic programming model, but with things
like instance methods, generic types, better macros, arbitrary-bit-integer
types and a "crash when hitting undefined behavior" compile mode.

~~~
swiley
Arbitrary code execution isn’t that big a deal when one of the features of the
device is that it accepts a ram image over its synchronous serial port which
is jumped into after being received.

------
cmrdporcupine
Zig looks neat; a nice middle ground, not as preachy as Rust -- I took a
stroll through the documentation but didn't get a sense from it how suitable
it would be for baremetal or OS programming... Specifically about its standard
library..

    
    
      * How dependent on libc is Zig, if at all?
      * What kind of runtime expectations does Zig have? Does it need a malloc, for example? 
      * How big is its standard library?
      * Is it possible to write code without its standard library?

~~~
Hoppetosse
Zig was started with that exact use case at the forefront of its design.

\- Zig has no hard dependency on libc, it's the user's choice whether he wants
to link it or not. It ships musl libc and can build/link it for most (all?)
supported targets.

\- Most of Zig's standard library works on freestanding targets. No runtime
needed. There are no implicit allocators, so the user must use an allocator
shipped with the standard library or implement their own.

\- Standard library is still in its early days but already provides a lot of
functionality out of the box, from containers to io.

\- Using the standard library is optional. When it is used, only what is used
gets included. The standard library is built from source every time (though
there is caching) and its semantic analysis basically does LTO in the codegen
stage so you end up with pretty slim binaries.

Here's a couple of examples:

\- [https://github.com/andrewrk/clashos](https://github.com/andrewrk/clashos)

\- [https://github.com/AndreaOrru/zen](https://github.com/AndreaOrru/zen)

~~~
cmrdporcupine
I don't get the first statement: no hard dependency on libc and ships with
musl libc. Does Zig need a libc, so they ship musl libc? Or is it fully self
hosting?

~~~
fengb
The Zig language doesn't need libc, but because it also includes a fully
functional C compiler, it ships with musl source and glibc headers for better
crossplatform support.

------
aquova
What is the state of GBA homebrew? I've been curious about learning how to
make games with it, but I haven't had much luck finding information about the
toolchains. I know for original Game Boy/Color, there is a C compiler, but
it's not recommended over assembly. Is the GBA the same way?

~~~
ant6n
GBA is very well documented (gbatek). The DS seems to be as well, but it's
much harder to work with. GBA is much easier, but still powerful enough to do
non trivial things without having to do them all in assembly. Emulator support
is superb. Hardware adapters that allow you to load ROMs have been around
forever.

I'd say it's one of the best systems to get into on-the-metal Homebrew.

~~~
swiley
Much harder is a bit of a stretch IMO.

There’s more potential complexity if you want to do complex things and maybe
3D is hard (I’ve never done it) but the basics are about as straightforward.

~~~
mikepurvis
I was intrigued about the 3D thing— a cool YouTube top 5 with some light
commentary:
[https://www.youtube.com/watch?v=Y6QtoZcYhi4](https://www.youtube.com/watch?v=Y6QtoZcYhi4)

~~~
WoodenChair
Cool video—how was such fluid 3D achieved on the GBA hardware? Anyone know of
any war story articles about developing 3D games on the GBA?

~~~
DagAgren
Low resolution combine with a decently fast processor means software rendering
is entirely doable. It's a comparable situation to very early PC 3D gaming.

~~~
WoodenChair
Just for the record, "decently fast" in this case is a 16 Mhz ARM 7.

~~~
mikepurvis
It's especially crazy that a bunch of them look to be rendering legit polygon
scenes— this isn't early nineties 2.5D titles like Doom and Wolf3d running on
486 PCs.

------
xvilka
I am waiting for the milestone[1] for Zig to be able to bootstrap itself.
Usually, this means the language is mature enough.

[1]
[https://github.com/ziglang/zig/projects/2](https://github.com/ziglang/zig/projects/2)

~~~
PezzaDev
What are the advantages of this other than showing the language is competent
enough that you can write a compiler for itself? Would it be better to keep a
compiler written in a highly portable language such as c? Genuinely curious.

~~~
jswny
I think it is usually considered a right of passage of a new programming
language to prove that it is mature enough to self-host.

Personally, I value that aspect highly because it means if I choose to work
with a language that I can understand, read, and possibly contribute to the
compiler if needed.

~~~
tom_mellior
> Personally, I value that aspect highly because it means if I choose to work
> with a language that I can understand, read, and possibly contribute to the
> compiler if needed.

You would have about the same if the compiler were written in a lingua franca.
For whatever it's worth, I have in the past had to work inside the gfortran
compiler, and I wouldn't have been able to do that if it were written in
Fortran.

Self-hosting (which nowadays often isn't "full" self-hosting, it's
implementing a frontend and possibly parts of a mid-end only and letting a
framework like GCC or LLVM do the rest) is useful because it proves that it
has reached a certain level of maturity, as you say. Another advantage is that
this gives you a non-trivial piece of software written in the language which
can be used for testing and benchmarking. If you introduce a compiler bug,
there is a certain likelihood that you will notice it immediately. This might
not be the case if you only had small toy programs to test with.

------
ngcc_hk
Keep on thinking about lisp mix of compiler time and run time construct when
watching the video.

------
mucholove
Question: would I be able to do this on say a Kindle?

Really pining for a non backlit display I control.

Never thought this would be possible but given the readable code...maybe I
should try?! Anybody know where I could start?

~~~
pjmlp
Classical Kindle uses some variation of Java, so probably only if rooted
somehow.

Other ones are Android based, so there you can use it via NDK.

------
benatkin
Interesting. First I've heard of Zig and it sounds like a cool language.

I suggest putting the README images in a doc directory so visitors know at
first glance that they aren't part of the app.

------
leggomylibro
I love stuff like this. It's fun and educational. The code and linker script
look very clean. Nice project!

------
zerr
How does it compare to JAI?

~~~
Bekwnn
Zig is a lot more focused on being a better C that focuses on systems stuff
which winds up being good for games. Jai is focused on being a good language
for games specifically and so certain highly desirable features for games
(reflection, SoA, a different sort of standard library).

But all in all from my time following both for several months they have a lot
of similarities. They're only slightly more different than one another than C#
and Java.

