Files
2026-09-21 19:49:04 +02:00

3.2 KiB

Preprocessing: include, define and selection

Layer 1, the common language. Verified against the preprocessor inside go tool asm of Go 1.27.1 (cmd/asm/internal/lex), whose directives are #define, #undef, #include, #ifdef, #ifndef, #else, #endif and #line, and against gasm's implementation, which is differentially tested against the toolchain's.

Input runs through a simplified C preprocessor before the parser sees it. The set is deliberately small: there is no #if with constant expressions and no token pasting with ##. #line is honoured, so it changes the positions the assembler reports and records.

#include

#include "textflag.h"
#include "go_asm.h"
#include "defs_linux_amd64.h"

The search path, in order: the directory of the including file, then the directories given by repeatable -I flags. The assembler seeds no default of its own: a bare go tool asm invocation finds none of the standard headers, and it is the go build system that passes $GOROOT/pkg/include among the -I directories when it drives the build. That directory ships textflag.h, funcdata.h and the per architecture register headers. Includes nest; a file included twice through different paths is processed twice, which is why headers guard their defines.

#define and #undef

#define bufSize 1024
#define MOVD(d, s) MOVQ s, d
#undef bufSize
  • An object macro replaces its name with its token sequence at the point of use.
  • A parameterised macro takes its arguments in parentheses and substitutes them into the body. Macro parameters compose with the rest of the language: an argument used with an element suffix, as in A.S4 on the vector forms, substitutes correctly.
  • Redefinition is an error; #undef first, or pick a new name.
  • The -D name[=value] flag predefines an object macro from the command line, repeatable, exactly as #define would; a -D without a value defines the name as 1.
  • Expansion happens when the name is used, so a macro may expand to instructions, operands or fragments of either, and a macro body may use macros defined before it.

textflag.h and funcdata.h are themselves ordinary #define files: the flag names and the runtime macros are preprocessor definitions, not language keywords. That is why a missing include produces a parser error at the first use of NOSPLIT rather than a complaint about the name.

#ifdef, #ifndef, #else, #endif

#ifdef GOOS_windows
	#define SYSCALL_INT 0x2b
#endif

Selection is by defined-name only: #ifdef, #ifndef, #else, #endif, nesting freely. There is no #if defined(x) && y, because the preprocessor evaluates no expressions; reach that with a build-tag Go file generating a header, which is exactly how the runtime's own go_asm.h and defs headers are produced.

What preprocessing does not cover

The preprocessor is textual and runs first, so it knows nothing of assembly semantics: it does not check that a macro expansion is a legal instruction, and it does not participate in the constant expression evaluator, which runs later, in the parser. A constant folded with #define and a constant folded in an operand expression end at the same value through different doors; GOOBJ.md records both in the object identically.