Class: Rubycc::Preprocess::Preprocessor

Inherits:
Object
  • Object
show all
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.expand_path("../../../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_arch keyword (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.expand_path("../../../include/libc/glibc/x86_64", __dir__).freeze
BUNDLED_LIBC_INCLUDE_DIR =
File.expand_path("../../../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 to libc_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 libc is "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-gnu holds the x86-64 bits/, and its AArch64 counterpart holds a different one. It therefore belongs to libc_arch exactly 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 install can 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_macros below.

%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/null on 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

Instance Method Summary collapse

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.expand_path("../../../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_libcObject

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.expand_path(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