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.S4on the vector forms, substitutes correctly. - Redefinition is an error;
#undeffirst, or pick a new name. - The
-D name[=value]flag predefines an object macro from the command line, repeatable, exactly as#definewould; a-Dwithout a value defines the name as1. - 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.