10

So I am really just starting out in my assembly coding adventure. I have a little bit of knowledge of C/C++ but nothing too advanced yet.

But I also used to do a tiny bit of Z80 assembly, and I wanted to make some original Game Boy games (kind of a life-long ambition which is now just a fun hobby instead).

I have a working program which shows sprites and background etc. However I am trying to separate it into two or more assembly source files. And I ran into a problem of "constants.inc is already included". I managed to overcome it by simply not including "constant.inc" in my main.asm and then moving the "player.asm" include to the top. But this probably isn't the best way to handle it, it could get very complicated with multiple files.

Until now I have taken for granted the magic #pragma once line in C++.

How would you handle circular dependencies in ASM?

(Currently I am using RGBASM)

(I.e. I have files "constants.inc", "hardware.inc", "player.asm", "main.asm"; both asm files need access to both of the inc files. Main needs access to Player.asm but not the other way around)

7
  • 1
    Well the main.asm routine starts the game and in turn initializes my player. But the hardware.inc and constants.inc values are needed for other things than just the player, however the player script also does need them. If I had included Main.asm inside player then I guess I couldn't have include Player in main, and I need to call routines such as update player or moveleft etc on my player from inside main (for example because my input handler is in main) Jun 14, 2022 at 15:40
  • 1
    Little Nitpick, the Gameboy CPU is NOT a Z80. it's an enhanced 8080 using Z80 alike mnemonics.
    – Raffzahn
    Jun 14, 2022 at 16:02
  • 1
    I realise that, but there is no tag for sm83 and it is very similar to Z80 Jun 14, 2022 at 16:03
  • 1
    "it is very similar to Z80" No, it is not. it misses everything that distinguishes the Z80 from an 8080. It only ses Z80 like mnemonics. That's like reading Hamlet in German. It doesn't become Goethe, just because written in German, it stays Shakespeare.
    – Raffzahn
    Jun 14, 2022 at 16:14
  • 1
    Many of the assemblers I have used allow me to write a file with extension .z80 and assemble it in as a Game Boy rom. Almost all the instructions are the same as when I coded ZX Spectrum. In the context of include guards they are very similar Jun 14, 2022 at 16:29

3 Answers 3

18

Assembly language is typically not providing any safety nets or harnesses - so, in general, the answer would be "no". Assembly language is also not in any way standardized, especially in the area of assembler directives - so, even if there were some specific assemblers that supported such constructs, it certainly would not be generally true.

There might be workarounds that use conditional assembly in some specific assemblers that allowed you to mimic the

 #ifndef INCLUDE_GUARD
 #define INCLUDE_GUARD

 ......

 #endif

construct you might know from C, however. My assembler suports something like

 GENIF    include-guard = 0
 SETNUM   include-guard 1

 .......

 ENDGEN

That does exactly the same thing - YMMV.

From comments:

rgbasm apparently allows conditional assembly with

IF !DEF(guard)
  DEF guard 
  / … / 
ENDC

which more or less does the same.

An alternative could be to run assembly files through a C preprocessor (cpp actually doesn't normally care a lot what it pre-processes and will only look at preprocessor directives). That way you could even use #pragma oncein your assembly files.

6
  • 3
    thank you! I am unable to upvote because of my low rank. But could you expand a little on the GENIF. Specifically , what is include-guard in those lines? Is that just the filename of the included source file? Jun 14, 2022 at 15:44
  • 1
    In the example, include-guard is simply a symbol. The first time the include file is pulled into assembly, it will be undefined (thus evaluated to 0), then everything between GENIF and ENDGEN will actually be evaluated - which then sets include-guard to 1, so any following inclusions of this file will no longer be evaluated and not generate any code. Note again this is true for my specific assembler - maybe yours has something similar.
    – tofro
    Jun 14, 2022 at 15:48
  • 1
    Superb! I cannot claim to fully understand yet. But I think I almost understand. I will keep trying to implement this and I will make a full example if I run into problem trying it I will make the minimal example and make a new question. Thanks again for your help! Jun 14, 2022 at 15:51
  • 7
    In RGBDS syntax, the classic include guard would be IF !DEF(guard) / DEF guard / … / ENDC, see rgbasm(5). That said, I don’t see it being too popular, at least in GB assembly projects that I know of (which aren’t that many). Jun 14, 2022 at 16:22
  • 3
    Re: cpp. Linux kernel does it that way - they not only run assembly files through the C preprocessor, they even run device tree files through it (which are not programs at all, but rather configuration files).
    – jaskij
    Jun 15, 2022 at 13:00
4

Hard to give a definite answer without seeing the real structure. Even more as 'assemblers' isn't a singular language. each is different. A link to it's documentation would be very helpful and in what configuration (command line?) compilation is done.

From your write up it's possible to assume the whole project is not split in modules, assembled separate and linked later, but assembled as one source file into one object file. This structure is often the case with simple/beginner projects. Here every source file, no matter how you name it, is included into the assembled source at the point its 'include' statement is placed. Thus including the same 'header' in different files is producing an error.

Most C projects in contrast are build with each source file compiled separate and linked later on. Thus includes in one source file do not collide with the same include in any other.

The same can be done of course with assembler (*1). Assemble each of your source files into s distinct object module and link them at the end. That way each is handled at its own and no duplicates arise.


In your case this would look mean

  • Compile MAIN.ASM and PLAYER.ASM separate using the RGBASM tool
    • Each including all definition files needed.
  • Link them into an executable using RGBLINK

Since each is a distinct compilation unit, no error arise and no hacks are needed to avoid multiple inclusion.

Of course, since these are now separate compilation units, access between them must be granted, as by default an Assembler treats everything in a separate unit as private to this unit. So if there's for example an init function named SetupPlayer within PLAYER.ASM, then access to this must be allowed to external by exporting that entry point via an EXPORT directrive

     EXPORT SetupPlayer

...

SetupPlayer: ...

Usually all exports get placed on a single line and near the start of a source file to improve readability.

Now that function can be used from MAIN.ASM without further action (*3)

     CALL SetupPlayer

The whole point is to support separate compilation and linking while providing private name spaces.


That, or take into account that your source is a single item spread over different files and structure it accordingly - like include everything into a main file - only there and only once (*3).


*1 - C's capability of separate compilation is based on the object and link structure provided by its assembler base, so no difference if used right.

*2 - This is specific to RGBASM, which treats all unknown symbols as external. Other Assemblers may need some IMPORT or EXTERN directive, essentially creating a system of contracts, further enhancing encapsulation.

*3 - Naa, don't do there. Use the tools as intended, don't build 'hacks' to emulate separate compilation by some hack around includes. That is not only bad style, but a great way to introduce future issues - hard to solve issues.

9
  • Thanks for your help. I do understand that each assembler can vary a lot and likewise for each of the target machines. I did in the end specify RGBASM and Game Boy. I did also try reading the RGBASM docs but I got overwhelmed. As far as I can tell it doesn't address 'duplicate symbols' (as you correctly point out is a better term for this problem). Thanks again for the help here. I understand I didn't provide any code for your context. This was my mistake (I felt it might bloat the question without any use) Jun 14, 2022 at 15:56
  • But for now at least I can continue with my simple game. And I am going to try to implement Tofro's solution when I can. Jun 14, 2022 at 15:57
  • @DaveGold Context is the most important. Didn't say you didn't read. Documentation of some new tool can be overwhelming. All I suggested was to add a link to the tools you're using (as there are millions around) plus information how they are used. Also, going "Tofro's Way" might be a direct way to doom. Hacking to force a certain structure against your tools is usually no good idea.
    – Raffzahn
    Jun 14, 2022 at 15:58
  • (I wasn't accusing you of complaining at me for not reading the docs -just to clarify that :P ) ...But hopefully this won't be oneway ticket to doom, because really it is just a way to guard against including a file twice. Rather than doing the work yourself and figuring whether a file is already included, the computer should be able to do that far easier than me :D Jun 14, 2022 at 16:01
  • @DaveGold THe assembler IS already doing this - with the message given. The normal workflow would be to assemble each ASM source (MAIN.ASM and PLAYER.ASM) with a separate run of RGBASM, so the error would not arise. After that RGBLINK is used to combine them into an executable
    – Raffzahn
    Jun 14, 2022 at 16:09
2

Assemblers generally read all of the source files in a project two or three times. The first time will be used to define symbols. When targeting platforms where the size of an instruction will depend upon operand values, a second pass may be used to recompute symbols in case code got smaller. Then a final pass will be used to actually generate the output, using the symbols computed in the earlier pass or passes. As a result, circular dependencies would rarely pose a problem unless the shrinking of code in the middle pass would allow what would have been a long branch to become a short one; such situations may be rectified either by explicitly telling the assembler to use a short jump in a place that will end up being a short branch (the assembler would squawk if it wasn't a short branch on the final pass), or else telling the assembler to use more than three passes. At least one assembler I used allowed programmers to request an arbitrary number of passes, since could contrive a program which would take dozens of passes, each with one more branch getting shrunk, thus allowing a branch just before it to also shrink, but in practice I've never needed more than three.

4
  • 3
    Circular dependencies are in fact not much of a problem in assembly - duplicate symbols are, however, and multiple passes don't help much here.
    – tofro
    Jun 14, 2022 at 15:52
  • Thank you for the insightful info. I may have used the wrong term "Circular Dependency" but in nutshell the assembler failed to finish because of the "already included" error. But I have got passed that problem now but the way I fixed it won't help much if this was a complex project with dozens of src files. Jun 14, 2022 at 15:59
  • 1
    @tofro: Duplicate symbols are a problem, but there's generally no need to include a file multiple times unless one wants a chunk of code repeated multiple times in a project, in which case one would generally need to exploit features to allow symbol reuse (local symbols, redefinable symbols with a reference syntax to distinguish whether to use the next or previous definition, etc.).
    – supercat
    Jun 14, 2022 at 16:51
  • @tofro: In Borland's TASM, for example (IIRC), if one attempts to define a symbol that starts with @@ (e.g. @@FOO) and no such symbol has yet been created, the assembler will define "00FOO". The next time, "01FOO", then "02FOO", etc. If one refers to symbol "@@FOO.p" and the last definition was 04FOO" the assembler would substitute "04FOO". If one had instead refered to "04FOO.n", the assembler would substitute "05FOO" in the expectation that such a symbol would be defined later.
    – supercat
    Jun 14, 2022 at 16:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .