Class: Rubycc::Preprocess::Preprocessor
- Inherits:
-
Object
- Object
- Rubycc::Preprocess::Preprocessor
- Defined in:
- lib/rubycc/preprocess/preprocessor.rb
Overview
Entry point for translation phases 1-4: source text in, a Front::Token stream out. It scans preprocessing tokens (line splicing and comment removal), executes directives, expands macros, and converts what remains into ordinary tokens.
The macro table and the #include search path are instance state so an includee shares them with its includer: a translation unit is preprocessed as a single accumulating pass, with #include recursion feeding an includee's tokens through the same line-oriented loop. Conditional groups (#if and its kin) select which lines reach that loop. Both object-like and function-like macros are expanded (6.10.3), the latter gathering their arguments across line boundaries; the "#" (stringize) and "##" (paste) operators, the compiler-supplied macros (FILE and kin), the _has* queries and "#pragma once" complete the model.
Defined Under Namespace
Classes: Frame, Invocation, Macro
Constant Summary collapse
- BUNDLED_INCLUDE_DIR =
The compiler-supplied ("freestanding") headers rubycc ships in the gem's top-level include/ directory: stdarg.h, stddef.h and kin, which glibc does not provide because they are the compiler's responsibility. Resolved relative to this file so it works from a source checkout and an installed gem alike (preprocess -> rubycc -> lib -> gem root, then include/).
File.("../../../include", __dir__).freeze
- BUNDLED_LIBC_ARCH_INCLUDE_DIR =
The bundled libc compatibility headers (R8): rubycc's own copies of the C library's headers (stdio.h, stdlib.h, string.h and kin), shipped so a hosted translation unit compiles on a host that lacks the system libc's development headers (the distroless target). They live in two layers under include/libc/: a common declaration layer (BUNDLED_LIBC_INCLUDE_DIR) and a target-specific layer that pins the type widths, struct layouts and macro values to one concrete libc-and-arch ABI. Two such layers ship today, glibc/x86-64 and glibc/aarch64; which one is on the search path is chosen per instance by the
libc_archkeyword (see #initialize), so a cross compile reads the target's ABI rather than the host's. This constant names the x86-64 default's directory -- kept so the default path and the tests that pin its order can refer to it -- while #default_system_include_paths substitutes the instance's own arch directory (@libc_arch_include_dir). The arch layer is searched before the common layer so a same-named header in it (an ABI-specific override) wins over the shared declaration. The libc the headers describe is not a directory axis: both layers carry the glibc and the musl value of everything the two disagree on, selected by #if on LIBC_MUSL_MACRO (see LIBCS), so the directory names below still say "glibc" only because that is where the files have always lived. File.("../../../include/libc/glibc/x86_64", __dir__).freeze
- BUNDLED_LIBC_INCLUDE_DIR =
File.("../../../include/libc", __dir__).freeze
- LIBC_ARCHS =
The bundled libc-and-arch layers that ship under include/libc/glibc/;
libc_arch(see #initialize) must name one of them. %w[x86_64 aarch64].freeze
- LIBCS =
The C libraries the bundled headers can be read under;
libc(see #initialize) must name one of them. This is an axis of its own, at right angles tolibc_arch: the two libcs disagree on a handful of ABI values (struct rusage's size, O_ACCMODE, the fast-integer widths, BUFSIZ and kin) on every machine alike, so the difference is expressed as #if branches inside the bundled headers rather than as another directory layer -- fifteen-odd divergences do not justify duplicating a hundred files whose remaining content is identical, and an #if keeps both measured values side by side where they can be audited (R8).The musl side is complete only for x86-64: the aarch64 arch layer's five ABI-switched headers carry glibc's values alone, because the musl figures were measured on x86-64 and an arch layer is exactly where a value may move between machines (each of those files says so in its own provenance note). An aarch64 musl target therefore reads musl's common layer and glibc's arch layer until an aarch64 musl run measures it.
%w[glibc musl].freeze
- LIBC_MUSL_MACRO =
The macro the bundled headers select their musl branch on. It is predefined (to 1) only when
libcis "musl"; on glibc it stays undefined, so#if defined(__RUBYCC_LIBC_MUSL__)reads "the musl ABI" and its #else arm is the long-standing glibc one. Unlike the platform macros it is reserved on both settings (see #reject_reserved_name): it names which ABI the bundled headers were pinned to when the compiler was configured, so a translation unit that could -D it into existence, or -U it away, would get headers describing one libc and an object laid out for the other. "__RUBYCC_LIBC_MUSL__"- LIBC_MULTIARCH_INCLUDE_DIRS =
The libc system header directories, in the order gcc reports them for angled includes. Only the C library's own directories are listed; the compiler's private include directory is deliberately absent, because BUNDLED_INCLUDE_DIR supplies those headers instead.
The first entry is Debian's multiarch directory, which is named after the target:
/usr/include/x86_64-linux-gnuholds the x86-64bits/, and its AArch64 counterpart holds a different one. It therefore belongs tolibc_archexactly like the bundled arch layer does -- see LIBC_SYSTEM_INCLUDE_PATHS_FOR, which #libc_system_include_paths uses per instance. Naming one target here unconditionally was a real defect: an AArch64 host searched a directory that does not exist and never looked in its own (GAPS V). { "x86_64" => "/usr/include/x86_64-linux-gnu", "aarch64" => "/usr/include/aarch64-linux-gnu" }.freeze
- LIBC_SYSTEM_INCLUDE_PATHS =
The x86-64 baseline, kept as a constant for the same reason DEFAULT_SYSTEM_INCLUDE_PATHS is: it is the shape every per-instance list takes, with its own arch in the multiarch slot.
libc_system_include_paths_for("x86_64")
- DEFAULT_SYSTEM_INCLUDE_PATHS =
The default system include search path: the bundled freestanding headers first (so rubycc's stdarg.h/stddef.h win over any same-named file further down), then the bundled libc compatibility headers (arch layer before common layer), then the host libc directories. A bundled libc header thus wins over the host's same-named one, yet can still reach the host copy via #include_next, which resumes the search past whichever directory the bundled header was found in. The host directories stay on the path by default (they are only dropped in the distroless mode, where they are absent anyway); the whole default path is appended after the user's -I/-isystem directories, and suppressed entirely by -nostdinc.
This is the x86-64 baseline: #default_system_include_paths builds the same list per instance, substituting the instance's own arch layer for the arch slot, so a cross compile's path is this shape with a different arch layer.
[ BUNDLED_INCLUDE_DIR, BUNDLED_LIBC_ARCH_INCLUDE_DIR, BUNDLED_LIBC_INCLUDE_DIR, *LIBC_SYSTEM_INCLUDE_PATHS ].freeze
- HERMETIC_SYSTEM_INCLUDE_PATHS =
The hermetic-headers system search path: the bundled freestanding and bundled libc layers only, with the host libc directories dropped. Selected by RUBYCC_HERMETIC_HEADERS (see #default_system_include_paths) so a full
gem installcan be driven with rubycc's own headers exclusively -- the distroless posture -- without every conftest command having to pass -nostdinc explicitly. It is the same set the distroless ruby.h build uses, so if a translation unit reaches for a declaration only the host's real headers carry, it fails here the way it would on a headerless image instead of silently borrowing it from /usr/include. Like DEFAULT_SYSTEM_INCLUDE_PATHS this is the x86-64 baseline; #default_system_include_paths builds the per-instance equivalent with the instance's own arch layer. [ BUNDLED_INCLUDE_DIR, BUNDLED_LIBC_ARCH_INCLUDE_DIR, BUNDLED_LIBC_INCLUDE_DIR ].freeze
- HERMETIC_HEADERS_ENV =
The environment variable that switches the default system search path to the hermetic (bundled-only) set. Any non-empty value other than "0" enables it; the default (unset) keeps the host libc directories on the path, so existing behaviour is unchanged.
"RUBYCC_HERMETIC_HEADERS"- INCLUDE_DEPTH_LIMIT =
A guard against unbounded #include recursion (a header that includes itself); 200 is comfortably deeper than any sane header nesting.
200- EXPANSION_TOKEN_LIMIT =
The cumulative ceiling on how many tokens macro expansion may process across a whole translation unit. Blue-painting (see #expand_tokens) stops self-reference and mutual recursion, but nothing otherwise bounds an exponentially expanding macro — the classic "#define B1 B0 B0 ... #define B40 B39 B39" doubles its output each level, so "B40" would materialize 2^40 tokens and exhaust CPU and memory long before finishing. Charging one unit per token pulled from the work queue and tripping this ceiling turns that runaway into a located CompileError. The bound is a whole-run cumulative budget (expand_tokens runs once per gathered line and once per #if condition, and recurses for each argument), so an expansion that explodes across many small calls is still caught. The real #include <ruby.h> header graph — the whole CRuby + libc header set, a worst-case legitimate input — consumes about 137k, so one million leaves a 7x margin while it never fires on real code. It is deliberately not larger: because a doubling macro is rejected only after the full budget is processed, the ceiling also caps the worst-case work a hostile input can force (about three seconds here), so raising it would trade rejection latency for headroom no real translation unit needs.
1_000_000- CONDITIONAL_NESTING_LIMIT =
The ceiling on conditional-directive nesting within a single file. A deeply nested tower of "#if"s is not a stack risk (the frames are held in a heap array, not on the Ruby stack), but capping it keeps a hostile source from building an arbitrarily large conditional stack; 256 is far beyond any real header's conditional nesting.
256- MACRO_ARGUMENT_NESTING_LIMIT =
The ceiling on parenthesis nesting inside a function-like macro's argument list. #collect_arguments balances parentheses with a plain integer depth counter (no recursion, so this is not a stack guard), but bounding it rejects a pathological "M(((((...)))))" up front rather than scanning an unbounded run. It is generous — a macro argument is a full expression, and the parser re-checks nesting downstream — so this only trips on clearly abusive input.
2000- CONDITIONAL_DIRECTIVES =
The directives that steer a conditional group (6.10.1). They are acted on whether or not the enclosing group is active, so nesting stays balanced inside a skipped region; every other directive is inert while skipping. Kept as a Hash (used only for membership) so the check is O(1).
%w[if ifdef ifndef elif else endif].to_h { |name| [name, true] }.freeze
- BUILTIN_MACROS =
The macros the preprocessor supplies itself (6.10.8): each is expanded from the use site, so its value cannot be a fixed replacement list stored at definition time. GNUC is deliberately absent (DESIGN R7), so a header cannot select a gcc-specific path. None may be redefined or undefined. Kept as a Hash (used only for membership) so the check is O(1).
%w[__FILE__ __LINE__ __STDC__ __STDC_VERSION__ __RUBYCC__].to_h { |name| [name, true] }.freeze
- KNOWN_BUILTINS =
The identifiers __has_builtin (6.10.1) answers true for: exactly the builtins rubycc's front end actually recognizes — the varargs intrinsics, the branch-prediction hint, the stack allocator, offsetof, the constant/choose folds, the count-leading/trailing-zero scans, the unreachable hint, memcpy, the three overflow-checked arithmetic forms, the nine _atomic* forms and the ten legacy _sync* forms. Every other builtin query is false, so a header that guards a fallback behind __has_builtin (e.g. json's bswap path) takes the fallback for one rubycc does not provide. Kept in sync with the parser's builtin keywords. Kept as a Hash (used only for membership) so the check is O(1).
%w[__builtin_va_start __builtin_va_arg __builtin_va_end __builtin_va_copy __builtin_expect __builtin_alloca __builtin_offsetof __builtin_constant_p __builtin_choose_expr __builtin_ctz __builtin_ctzll __builtin_clz __builtin_clzll __builtin_unreachable __builtin_memcpy __builtin_add_overflow __builtin_sub_overflow __builtin_mul_overflow __atomic_load_n __atomic_store_n __atomic_exchange_n __atomic_compare_exchange_n __atomic_fetch_add __atomic_fetch_sub __atomic_add_fetch __atomic_sub_fetch __atomic_or_fetch __atomic_thread_fence __sync_fetch_and_add __sync_fetch_and_sub __sync_add_and_fetch __sync_sub_and_fetch __sync_or_and_fetch __sync_lock_test_and_set __sync_lock_release __sync_synchronize __sync_bool_compare_and_swap __sync_val_compare_and_swap].to_h { |name| [name, true] }.freeze
- PREDEFINED_PLATFORM_MACROS =
The platform macros gcc keeps predefined even under strict ISO C (-std=c11): only the reserved forms (a leading underscore followed by another underscore or an uppercase letter, 7.1.3), so headers relying on "linux", "unix" or "i386" (the non-reserved spellings, gcc drops these under -std=c11) still see them undefined here. GNUC is deliberately excluded (DESIGN R7): rubycc targets Linux/ELF LP64 only, so this fixed set is enough for glibc's own dispatch (e.g. gnu/stubs.h) to settle on the right branch, without claiming gcc compatibility beyond that. Unlike BUILTIN_MACROS these are ordinary #define'd entries in @macros, so a translation unit may #undef or redefine them (gcc allows this too).
These are the macros every supported target shares; the CPU-identifying ones are per-target and arrive through
arch_macrosbelow. %w[__linux__ __gnu_linux__ __unix__ __ELF__ __LP64__ _LP64 __STDC_HOSTED__].freeze
- X86_64_ARCH_MACROS =
The CPU-identifying macros for each target, the subset of gcc's that glibc's own headers dispatch on. Getting these from the target rather than fixing them was forced by the aarch64 backend: a unit compiled for aarch64 that asked
#ifdef __x86_64__used to take the x86-64 branch, and the cross libc headers were being read under the wrong CPU identity. %w[__x86_64__ __amd64__].freeze
- AARCH64_ARCH_MACROS =
%w[__aarch64__ __AARCH64EL__].freeze
- PREDEFINED_NUMERIC_MACROS =
The numeric macros gcc predefines: the limit/size ones describing the target's fundamental types, and the memory-order enumerators the _atomic* builtins take as an argument (grouped here because they are the same kind of thing — a fixed integer replacement text a translation unit may #undef, not a use-site-computed BUILTIN_MACROS entry). glibc's headers reach for these directly when GNUC is absent (e.g. limits.h's LONG_MAX via ruby's special_consts.h), so they must carry gcc's exact spellings — value base (hex vs decimal) and integer suffix — for a gcc-differential #if to agree. The right-hand sides are the verbatim replacement texts of
gcc -dM -E </dev/nullon this x86-64 LP64 target; they become ordinary object macros (a translation unit may #undef or redefine them). WCHAR_MIN's value is a parenthesized expression referring to another of these, which expands recursively at the use site like any macro. Only the reserved "X" forms are provided; GNUC and version macros stay absent (DESIGN R7). The replacement text is re-scanned into pp-tokens rather than hand-built, so multi-token values need no special casing. { "__CHAR_BIT__" => "8", "__SCHAR_MAX__" => "0x7f", "__SHRT_MAX__" => "0x7fff", "__INT_MAX__" => "0x7fffffff", "__LONG_MAX__" => "0x7fffffffffffffffL", "__LONG_LONG_MAX__" => "0x7fffffffffffffffLL", "__WCHAR_MAX__" => "0x7fffffff", "__WCHAR_MIN__" => "(-__WCHAR_MAX__ - 1)", "__WINT_MAX__" => "0xffffffffU", "__WINT_MIN__" => "0U", "__PTRDIFF_MAX__" => "0x7fffffffffffffffL", "__SIZE_MAX__" => "0xffffffffffffffffUL", "__INTMAX_MAX__" => "0x7fffffffffffffffL", "__UINTMAX_MAX__" => "0xffffffffffffffffUL", "__INTPTR_MAX__" => "0x7fffffffffffffffL", "__UINTPTR_MAX__" => "0xffffffffffffffffUL", "__SIZEOF_INT__" => "4", "__SIZEOF_LONG__" => "8", "__SIZEOF_LONG_LONG__" => "8", "__SIZEOF_SHORT__" => "2", "__SIZEOF_POINTER__" => "8", "__SIZEOF_SIZE_T__" => "8", "__SIZEOF_PTRDIFF_T__" => "8", "__SIZEOF_FLOAT__" => "4", "__SIZEOF_DOUBLE__" => "8", "__SIZEOF_WCHAR_T__" => "4", "__SIZEOF_WINT_T__" => "4", # The memory-order arguments the __atomic_* builtins take (C11 7.17.3's # memory_order enumerators, which gcc predefines under these spellings). # The values are the verbatim ones `gcc -dM -E </dev/null` prints on this # target, so a header comparing them (or building one out of another) # agrees with a gcc build. rubycc implements every atomic operation at # the strongest order regardless of which of these is passed — see # IR::Generator#gen_builtin_atomic — but the constants must still carry # gcc's values, because a caller may compute with them. "__ATOMIC_RELAXED" => "0", "__ATOMIC_CONSUME" => "1", "__ATOMIC_ACQUIRE" => "2", "__ATOMIC_RELEASE" => "3", "__ATOMIC_ACQ_REL" => "4", "__ATOMIC_SEQ_CST" => "5" }.freeze
- GLIBC_MAJOR_MACRO =
The glibc version macros, predefined on a glibc target so the bundled <features.h> does not have to name a version it cannot know. GLIBC is a constant (glibc's major has been 2 since 1997); the minor is measured from the C library the compile will link against, because a single shipped header set otherwise reports one host's version on every host, and a version gate then selects a branch the local libc may not be able to back (docs/development/GAPS.md gap U).
"__GLIBC__"- GLIBC_MINOR_MACRO =
"__GLIBC_MINOR__"- GLIBC_MAJOR =
2
Class Method Summary collapse
-
.host_glibc_minor(libc_arch = "x86_64") ⇒ Object
The measured glibc minor version for the
libc_archtarget, or nil when this host offers nothing to measure (see GlibcVersion). -
.host_libc ⇒ Object
The libc this host's C library is: "musl" or "glibc" (see LIBCS).
- .libc_system_include_paths_for(libc_arch) ⇒ Object
Instance Method Summary collapse
-
#initialize(char_unsigned: false, arch_macros: X86_64_ARCH_MACROS, libc_arch: "x86_64", libc: Preprocessor.host_libc, glibc_minor: (libc == "glibc" ? Preprocessor.host_glibc_minor(libc_arch) : nil)) ⇒ Preprocessor
constructor
char_unsignedsays whether plaincharis unsigned on the target being compiled for (it is under AAPCS64, and is not under the x86-64 System V psABI, hence the default). -
#preprocess(source, filename:, include_paths: [], defines: [], system_includes: true) ⇒ Object
Runs translation phases 1-4 and returns the resulting preprocessing-token stream (terminated by the unit's :eof), before it is converted into Front tokens.
- #run(source, filename:, include_paths: [], defines: [], system_includes: true) ⇒ Object
Constructor Details
#initialize(char_unsigned: false, arch_macros: X86_64_ARCH_MACROS, libc_arch: "x86_64", libc: Preprocessor.host_libc, glibc_minor: (libc == "glibc" ? Preprocessor.host_glibc_minor(libc_arch) : nil)) ⇒ Preprocessor
char_unsigned says whether plain char is unsigned on the target being
compiled for (it is under AAPCS64, and is not under the x86-64 System V
psABI, hence the default). When it is, CHAR_UNSIGNED joins the
predefined macros with the value gcc gives it, so a header can select the
same branch it would there — the bundled <limits.h> uses exactly that to
pick CHAR_MIN/CHAR_MAX.
arch_macros names the target's CPU-identifying macros (see
X86_64_ARCH_MACROS); it defaults to x86-64's, the default target.
libc_arch selects which bundled libc-and-arch header layer sits on the
default search path ("x86_64" or "aarch64", see LIBC_ARCHS); it defaults to
x86-64 so an unconfigured host compile is byte-for-byte unchanged, and a
cross compile passes the target's own so its ABI headers (struct stat's
128-byte aarch64 layout, the narrower nlink_t/blksize_t, the unsigned
WCHAR_MIN/MAX and kin) are read instead of the host's.
libc selects which C library's ABI those bundled headers describe
("glibc" or "musl", see LIBCS); it defaults to the host's own (see
.host_libc), and on "musl" it predefines LIBC_MUSL_MACRO so the headers
take their musl branches.
glibc_minor is the glibc minor version the version macros are to
report on a glibc target; it defaults to the one measured from the C
library that target links against (see .host_glibc_minor), and nil --
which is also what an unmeasurable host yields -- leaves both macros
undefined for the bundled <features.h> to fall back on. It is a keyword
so a caller can pin a version deliberately (a cross compile against a
sysroot this host cannot search, and the tests' fallback case). The
default measures only on a glibc target: on "musl" the value is unused,
and reading a C library to answer a question nobody asks would cost
every musl translation unit a megabyte-scale read for nothing.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/rubycc/preprocess/preprocessor.rb', line 423 def initialize(char_unsigned: false, arch_macros: X86_64_ARCH_MACROS, libc_arch: "x86_64", libc: Preprocessor.host_libc, glibc_minor: (libc == "glibc" ? Preprocessor.host_glibc_minor(libc_arch) : nil)) unless LIBC_ARCHS.include?(libc_arch) raise ArgumentError, "unsupported libc arch: #{libc_arch.inspect} (expected one of #{LIBC_ARCHS.join(", ")})" end unless LIBCS.include?(libc) raise ArgumentError, "unsupported libc: #{libc.inspect} (expected one of #{LIBCS.join(", ")})" end unless glibc_minor.nil? || (glibc_minor.is_a?(Integer) && !glibc_minor.negative?) raise ArgumentError, "glibc minor version must be a non-negative Integer or nil: #{glibc_minor.inspect}" end # The bundled libc arch layer this instance searches (see # BUNDLED_LIBC_ARCH_INCLUDE_DIR). For the x86-64 default it equals that # constant, so the default search path is identical to before. @libc_arch_include_dir = File.("../../../include/libc/glibc/#{libc_arch}", __dir__) # The host libc directories this instance searches. The multiarch slot # follows the same `libc_arch` as the bundled layer above, so a compile # never looks for another target's `bits/` (GAPS V). @libc_system_include_paths = self.class.libc_system_include_paths_for(libc_arch) # name (String) => Macro. @macros = {} (arch_macros + PREDEFINED_PLATFORM_MACROS).each { |name| @macros[name] = predefined_target_macro } @macros["__CHAR_UNSIGNED__"] = predefined_target_macro if char_unsigned @macros[LIBC_MUSL_MACRO] = predefined_target_macro if libc == "musl" PREDEFINED_NUMERIC_MACROS.each { |name, text| @macros[name] = predefined_numeric_macro(text) } # The glibc version pair, defined only on a glibc target and only when # the version could be measured. They are ordinary numeric macros like # the ones above (a translation unit may #undef or redefine them), and # the bundled <features.h> defines each only when it is absent, so this # is what makes a version gate agree with the host's own headers. On # musl, and when nothing could be measured, nothing is defined here and # <features.h> keeps supplying the reference platform's pair, which is # what every compile did before the measurement existed. if libc == "glibc" && glibc_minor @macros[GLIBC_MAJOR_MACRO] = predefined_numeric_macro(GLIBC_MAJOR.to_s) @macros[GLIBC_MINOR_MACRO] = predefined_numeric_macro(glibc_minor.to_s) end @include_depth = 0 # Absolute paths of files that asked (via "#pragma once") to be read at # most once; a later #include resolving to one of them is skipped. @pragma_once = {} # Absolute path => index into @include_paths of the -I directory a file # was found in. Only files resolved along the search path get an entry # (the main source file and a quote-relative resolution beside its # includer never do), which is exactly what #include_next needs to tell # "resume the search past here" from "there is no here" (GNU extension). @include_origin = {} # Resolved include path (the exact spelling #include resolves to) => # that file's scanned pp-token array. Scanning (phases 2-3) is a pure # function of the file's bytes — no macro state reaches it — and both # PPToken and the directive walk are non-mutating (painting copies), so # one scan per header serves every re-#include verbatim. This is where # a real unit burns most of its time otherwise: ruby.h's include graph # re-includes the same headers hundreds of times, and each guard-skipped # body still had to be re-scanned to find its #endif (Step 108). @scan_cache = {} # Resolved include path => the file's include-guard macro name, or nil # when its shape rules the optimization out (gcc's multiple-include # optimization, Step 109). A header whose entire significant content is # wrapped in one "#ifndef G ... #endif" (or "#if !defined(G)") behaves, # when G is defined, exactly like an empty file: the walk would activate # nothing and change no state. So a re-#include whose recorded guard is # currently defined skips the directive walk outright. Detection is a # pure function of the scanned tokens (see #detect_include_guard); the # skip consults the live macro table, so an #undef of the guard makes # the next #include process the file again. @guard_cache = {} end |
Class Method Details
.host_glibc_minor(libc_arch = "x86_64") ⇒ Object
The measured glibc minor version for the libc_arch target, or nil when
this host offers nothing to measure (see GlibcVersion). nil is not an
error and not a substitute value: the two macros are then left
undefined, and the bundled <features.h> supplies its own fallback pair
-- the reference platform's 2.39 -- exactly as it did before this was
measured at all. Keeping the fallback in the header rather than
repeating the number here also keeps a header read outside rubycc (or
under -nostdinc with the host's own headers) on the same value.
391 392 393 |
# File 'lib/rubycc/preprocess/preprocessor.rb', line 391 def self.host_glibc_minor(libc_arch = "x86_64") GlibcVersion.minor_for(libc_arch) end |
.host_libc ⇒ Object
The libc this host's C library is: "musl" or "glibc" (see LIBCS). Read
from RbConfig's arch triplet, which is how MRI itself distinguishes a
musl build ("x86_64-linux-musl") from a glibc one ("x86_64-linux") --
the same source test/abi_harness/harness.rb's #host_libc and
tools/verify_gem_tests.rb's environment_string read, so the compiler,
the ABI harness and the verification records all agree on what "this
environment" is. It is the default for libc below, which is what makes
an unconfigured compile on a musl host read the musl branches; a cross
compile passes the target's own.
368 369 370 |
# File 'lib/rubycc/preprocess/preprocessor.rb', line 368 def self.host_libc RbConfig::CONFIG["arch"].to_s.include?("musl") ? "musl" : "glibc" end |
.libc_system_include_paths_for(libc_arch) ⇒ Object
108 109 110 111 112 113 |
# File 'lib/rubycc/preprocess/preprocessor.rb', line 108 def self.libc_system_include_paths_for(libc_arch) multiarch = LIBC_MULTIARCH_INCLUDE_DIRS.fetch(libc_arch) do raise ArgumentError, "unsupported libc arch: #{libc_arch.inspect}" end [multiarch, "/usr/include"].freeze end |
Instance Method Details
#preprocess(source, filename:, include_paths: [], defines: [], system_includes: true) ⇒ Object
Runs translation phases 1-4 and returns the resulting preprocessing-token
stream (terminated by the unit's :eof), before it is converted into
Front tokens. It is what #run builds on, and what the -E driver mode
re-spells into preprocessed text. defines is the ordered command-line
-D/-U list (see #apply_command_line_definitions).
system_includes (the default) appends the compiler-supplied and libc
directories after the caller's -I/-isystem set, so an angled #include of
<stdarg.h> or a libc header resolves with no explicit -I; the driver's
-nostdinc passes it false to search only the caller's directories.
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 |
# File 'lib/rubycc/preprocess/preprocessor.rb', line 510 def preprocess(source, filename:, include_paths: [], defines: [], system_includes: true) system_paths = system_includes ? default_system_include_paths : [] @system_include_paths = system_paths.map { |path| File.(path) } @include_paths = include_paths + system_paths # #resolve_include's cache keys a resolved path off @include_paths (and, # for quote includes, the includer's directory), so it must start empty # every run rather than survive across calls with a different search path. @resolve_cache = {} # The whole-run macro-expansion budget (see EXPANSION_TOKEN_LIMIT), reset # here so every translation unit starts with a full allowance. @expansion_tokens = 0 # The presumed-line state a #line directive sets (6.10.4): a delta added # to a token's physical line for __LINE__, and a presumed file name for # __FILE__ (nil = the token's own file). It is per-file, saved and # restored across #include (see #process_include). @presumed_line_delta = 0 @presumed_file = nil apply_command_line_definitions(defines) pp_tokens = Scanner.new(source, filename: filename).scan output = [] process_lines(pp_tokens, filename, output) # process_lines stops at the unit's end-of-file marker without emitting # it; carry it through so the converter can terminate its stream. output << pp_tokens.last output end |
#run(source, filename:, include_paths: [], defines: [], system_includes: true) ⇒ Object
494 495 496 497 498 499 |
# File 'lib/rubycc/preprocess/preprocessor.rb', line 494 def run(source, filename:, include_paths: [], defines: [], system_includes: true) TokenConverter.new.convert( preprocess(source, filename: filename, include_paths: include_paths, defines: defines, system_includes: system_includes) ) end |