rubycc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +56 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +52 -0
- data/README.md +208 -0
- data/data/README.md +117 -0
- data/data/r10_corpus_scan.json +4082 -0
- data/data/r10_manual_classification.json +5342 -0
- data/data/r10_verification_m4a.json +1531 -0
- data/data/r10_verification_m4b.json +1818 -0
- data/data/r10_verification_m4c.json +1489 -0
- data/data/r10_verification_m4d.json +318 -0
- data/data/r10_verification_rbs.json +192 -0
- data/data/verified_gems.json +397 -0
- data/exe/rmake +16 -0
- data/exe/rubycc +9 -0
- data/exe/rubycc-ar +114 -0
- data/exe/rubycc-doctor +14 -0
- data/exe/rubycc-pkgconf +8 -0
- data/include/float.h +87 -0
- data/include/iso646.h +18 -0
- data/include/libc/alloca.h +18 -0
- data/include/libc/arpa/inet.h +61 -0
- data/include/libc/assert.h +43 -0
- data/include/libc/dirent.h +72 -0
- data/include/libc/dlfcn.h +58 -0
- data/include/libc/features.h +205 -0
- data/include/libc/glibc/aarch64/ctype.h +108 -0
- data/include/libc/glibc/aarch64/endian.h +54 -0
- data/include/libc/glibc/aarch64/errno.h +154 -0
- data/include/libc/glibc/aarch64/fcntl.h +144 -0
- data/include/libc/glibc/aarch64/inttypes.h +182 -0
- data/include/libc/glibc/aarch64/limits.h +63 -0
- data/include/libc/glibc/aarch64/pthread.h +131 -0
- data/include/libc/glibc/aarch64/setjmp.h +84 -0
- data/include/libc/glibc/aarch64/stdint.h +174 -0
- data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
- data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
- data/include/libc/glibc/aarch64/sys/select.h +75 -0
- data/include/libc/glibc/aarch64/sys/stat.h +136 -0
- data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
- data/include/libc/glibc/aarch64/sys/time.h +74 -0
- data/include/libc/glibc/aarch64/sys/types.h +127 -0
- data/include/libc/glibc/aarch64/time.h +113 -0
- data/include/libc/glibc/x86_64/ctype.h +108 -0
- data/include/libc/glibc/x86_64/endian.h +54 -0
- data/include/libc/glibc/x86_64/errno.h +154 -0
- data/include/libc/glibc/x86_64/fcntl.h +134 -0
- data/include/libc/glibc/x86_64/inttypes.h +182 -0
- data/include/libc/glibc/x86_64/limits.h +61 -0
- data/include/libc/glibc/x86_64/pthread.h +111 -0
- data/include/libc/glibc/x86_64/setjmp.h +82 -0
- data/include/libc/glibc/x86_64/stdint.h +163 -0
- data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
- data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
- data/include/libc/glibc/x86_64/sys/select.h +75 -0
- data/include/libc/glibc/x86_64/sys/stat.h +130 -0
- data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
- data/include/libc/glibc/x86_64/sys/time.h +74 -0
- data/include/libc/glibc/x86_64/sys/types.h +123 -0
- data/include/libc/glibc/x86_64/time.h +113 -0
- data/include/libc/grp.h +50 -0
- data/include/libc/langinfo.h +126 -0
- data/include/libc/link.h +18 -0
- data/include/libc/locale.h +83 -0
- data/include/libc/math.h +191 -0
- data/include/libc/netinet/in.h +126 -0
- data/include/libc/netinet/tcp.h +44 -0
- data/include/libc/poll.h +46 -0
- data/include/libc/pwd.h +58 -0
- data/include/libc/regex.h +53 -0
- data/include/libc/sched.h +35 -0
- data/include/libc/signal.h +204 -0
- data/include/libc/stdio.h +157 -0
- data/include/libc/stdlib.h +92 -0
- data/include/libc/string.h +74 -0
- data/include/libc/strings.h +25 -0
- data/include/libc/sys/cdefs.h +126 -0
- data/include/libc/sys/inotify.h +111 -0
- data/include/libc/sys/ioctl.h +36 -0
- data/include/libc/sys/mman.h +65 -0
- data/include/libc/sys/param.h +41 -0
- data/include/libc/sys/resource.h +109 -0
- data/include/libc/sys/socket.h +167 -0
- data/include/libc/sys/statfs.h +82 -0
- data/include/libc/sys/timerfd.h +55 -0
- data/include/libc/sys/uio.h +40 -0
- data/include/libc/sys/un.h +25 -0
- data/include/libc/sys/utsname.h +35 -0
- data/include/libc/sys/wait.h +135 -0
- data/include/libc/termios.h +179 -0
- data/include/libc/unistd.h +194 -0
- data/include/stdalign.h +16 -0
- data/include/stdarg.h +31 -0
- data/include/stdatomic.h +158 -0
- data/include/stdbool.h +15 -0
- data/include/stdckdint.h +28 -0
- data/include/stddef.h +60 -0
- data/include/stdnoreturn.h +18 -0
- data/include/x86intrin.h +16 -0
- data/lib/rubycc/backend/aarch64.rb +1724 -0
- data/lib/rubycc/backend/x86_64.rb +1369 -0
- data/lib/rubycc/compile_error.rb +36 -0
- data/lib/rubycc/compiler.rb +305 -0
- data/lib/rubycc/doctor/builder.rb +151 -0
- data/lib/rubycc/doctor/cli.rb +190 -0
- data/lib/rubycc/doctor/fetcher.rb +84 -0
- data/lib/rubycc/doctor/gemfile.rb +115 -0
- data/lib/rubycc/doctor/verified_gems.rb +114 -0
- data/lib/rubycc/doctor.rb +16 -0
- data/lib/rubycc/driver.rb +463 -0
- data/lib/rubycc/front/ast.rb +528 -0
- data/lib/rubycc/front/constant_evaluator.rb +631 -0
- data/lib/rubycc/front/initializer_resolver.rb +592 -0
- data/lib/rubycc/front/lexeme_reader.rb +460 -0
- data/lib/rubycc/front/lexer.rb +232 -0
- data/lib/rubycc/front/parser.rb +4122 -0
- data/lib/rubycc/front/token.rb +49 -0
- data/lib/rubycc/ir/call_convention.rb +486 -0
- data/lib/rubycc/ir/generator.rb +6036 -0
- data/lib/rubycc/ir/ir.rb +417 -0
- data/lib/rubycc/link/compat_runtime.rb +89 -0
- data/lib/rubycc/link/errors.rb +13 -0
- data/lib/rubycc/link/executable_linker.rb +405 -0
- data/lib/rubycc/link/library_resolver.rb +437 -0
- data/lib/rubycc/link/partial_linker.rb +546 -0
- data/lib/rubycc/link/shared_linker.rb +1732 -0
- data/lib/rubycc/mkmf_shim.rb +83 -0
- data/lib/rubycc/objfile/ar_archive.rb +437 -0
- data/lib/rubycc/objfile/elf_reader.rb +646 -0
- data/lib/rubycc/objfile/elf_writer.rb +891 -0
- data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
- data/lib/rubycc/pkgconf/cli.rb +139 -0
- data/lib/rubycc/pkgconf/errors.rb +39 -0
- data/lib/rubycc/pkgconf/model.rb +28 -0
- data/lib/rubycc/pkgconf/parser.rb +109 -0
- data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
- data/lib/rubycc/pkgconf/resolver.rb +70 -0
- data/lib/rubycc/pkgconf/search_path.rb +38 -0
- data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
- data/lib/rubycc/preprocess/constant_expression.rb +219 -0
- data/lib/rubycc/preprocess/glibc_version.rb +124 -0
- data/lib/rubycc/preprocess/pp_token.rb +62 -0
- data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
- data/lib/rubycc/preprocess/scanner.rb +290 -0
- data/lib/rubycc/preprocess/token_converter.rb +157 -0
- data/lib/rubycc/rmake/cli.rb +143 -0
- data/lib/rubycc/rmake/errors.rb +71 -0
- data/lib/rubycc/rmake/executor.rb +818 -0
- data/lib/rubycc/rmake/expander.rb +251 -0
- data/lib/rubycc/rmake/makefile.rb +352 -0
- data/lib/rubycc/rmake/model.rb +137 -0
- data/lib/rubycc/rmake/parser.rb +226 -0
- data/lib/rubycc/rmake/rmake.rb +15 -0
- data/lib/rubycc/type.rb +1236 -0
- data/lib/rubycc/version.rb +5 -0
- data/lib/rubycc.rb +36 -0
- data/lib/rubygems_plugin.rb +102 -0
- metadata +219 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
require_relative "compile_error"
|
|
5
|
+
require_relative "compiler"
|
|
6
|
+
require_relative "preprocess/preprocessor"
|
|
7
|
+
require_relative "link/errors"
|
|
8
|
+
require_relative "link/partial_linker"
|
|
9
|
+
require_relative "link/shared_linker"
|
|
10
|
+
require_relative "link/executable_linker"
|
|
11
|
+
require_relative "link/library_resolver"
|
|
12
|
+
require_relative "link/compat_runtime"
|
|
13
|
+
|
|
14
|
+
module Rubycc
|
|
15
|
+
# The gcc-compatible command-line driver: it reads the same option and input
|
|
16
|
+
# vocabulary a compiler driver is invoked with (`rubycc -c -fPIC -Iinc -o x.o
|
|
17
|
+
# x.c`, `rubycc -shared -o x.so a.o b.o -Llib -lz`, `rubycc -o prog main.c
|
|
18
|
+
# util.c`) and drives the existing compiler and linker components to satisfy
|
|
19
|
+
# it. It owns no compilation or linking logic of its own — it classifies the
|
|
20
|
+
# inputs, picks the output mode gcc's flag precedence dictates, and hands the
|
|
21
|
+
# work to Compiler, SharedLinker, ExecutableLinker and LibraryResolver.
|
|
22
|
+
#
|
|
23
|
+
# Three modes are selected exactly as gcc selects them: `-c` compiles each
|
|
24
|
+
# source to an object without linking; `-shared` links every input into a
|
|
25
|
+
# shared object; the absence of a mode flag links into an executable. A
|
|
26
|
+
# one-shot invocation (`-c` absent, a `.c` given) compiles each source in
|
|
27
|
+
# memory and feeds the resulting object bytes straight to the linker, so no
|
|
28
|
+
# intermediate `.o` is ever written to disk. `-E` runs the preprocessor only.
|
|
29
|
+
#
|
|
30
|
+
# Unknown options are handled the tolerant way a build driver must (R6): a
|
|
31
|
+
# documented-but-unmodelled flag family (`-O*`, `-g*`, `-W*`, most `-f*`, `-m*`,
|
|
32
|
+
# `-std=…`, and friends) is accepted and ignored silently, and any other
|
|
33
|
+
# `-`-prefixed token draws a warning and is ignored, so an environment-specific
|
|
34
|
+
# flag mkmf passes never derails a build. A genuine mistake with a definite
|
|
35
|
+
# meaning — a missing `-o` operand — is still an error.
|
|
36
|
+
class Driver
|
|
37
|
+
PROG = "rubycc"
|
|
38
|
+
# C++ is outside rubycc's supported language scope (DESIGN §3.3). Keep the
|
|
39
|
+
# list in step with the corpus census gate so a C++ file passed directly to
|
|
40
|
+
# the driver is diagnosed instead of falling through as an object input.
|
|
41
|
+
CPP_INPUT_EXTENSIONS = %w[.cpp .cc .cxx .c++ .hpp .hxx .hh].freeze
|
|
42
|
+
|
|
43
|
+
# Options that take a separate operand this driver does not act on. They are
|
|
44
|
+
# recognized only so the operand is not mistaken for an input file; both the
|
|
45
|
+
# option and its argument are skipped. (`-isystem` and friends are handled
|
|
46
|
+
# separately, as header search directories.)
|
|
47
|
+
ARG_CONSUMING_IGNORED = %w[-Xlinker -z -u -T -MF -MT -MQ -include -isysroot
|
|
48
|
+
-aux-info -Xassembler -Xpreprocessor].freeze
|
|
49
|
+
|
|
50
|
+
# Bare flags accepted and ignored: linking/codegen switches whose effect this
|
|
51
|
+
# minimal toolchain does not model but whose presence must not be an error.
|
|
52
|
+
SILENT_IGNORE_EXACT = %w[-pipe -pthread -no-pie -pie -rdynamic -static -s
|
|
53
|
+
-nostdlib -nostartfiles -shared-libgcc -static-libgcc
|
|
54
|
+
-symbolic -fsyntax-only].freeze
|
|
55
|
+
|
|
56
|
+
# A command-line usage error (a missing operand, an unsupported combination):
|
|
57
|
+
# reported with the driver's "error:" prefix and a non-zero exit.
|
|
58
|
+
class UsageError < StandardError; end
|
|
59
|
+
|
|
60
|
+
class << self
|
|
61
|
+
# Runs the driver for `argv` and returns the process exit status (0 on
|
|
62
|
+
# success, 1 on any diagnosed failure). `stdout`/`stderr` are injectable so
|
|
63
|
+
# a test can drive the driver in-process and capture its streams.
|
|
64
|
+
def run(argv, stdout: $stdout, stderr: $stderr)
|
|
65
|
+
new(argv, stdout: stdout, stderr: stderr).run
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def initialize(argv, stdout: $stdout, stderr: $stderr)
|
|
70
|
+
@argv = argv
|
|
71
|
+
@out = stdout
|
|
72
|
+
@err = stderr
|
|
73
|
+
@inputs = [] # [{ path:, kind: }] in command-line order
|
|
74
|
+
@output = nil
|
|
75
|
+
@mode_flag = nil # :compile / :shared / :preprocess (nil => executable)
|
|
76
|
+
@include_paths = []
|
|
77
|
+
@defines = [] # ordered [:define, "NAME[=VAL]"] / [:undef, "NAME"]
|
|
78
|
+
@libraries = [] # -l request strings, in order
|
|
79
|
+
@lib_dirs = [] # -L directories, in order
|
|
80
|
+
@pic = false
|
|
81
|
+
@default_visibility = :default
|
|
82
|
+
@soname = nil
|
|
83
|
+
@system_includes = true # -nostdinc clears this
|
|
84
|
+
@default_libs = true # -nodefaultlibs clears this
|
|
85
|
+
@target = nil # -target/--target; defaults to the host CPU
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def run
|
|
89
|
+
return print_version if version_requested?
|
|
90
|
+
|
|
91
|
+
parse
|
|
92
|
+
dispatch
|
|
93
|
+
0
|
|
94
|
+
rescue UsageError => e
|
|
95
|
+
@err.puts "#{PROG}: error: #{e.message}"
|
|
96
|
+
1
|
|
97
|
+
rescue CompileError => e
|
|
98
|
+
# A compiler diagnostic already carries its gcc-style file:line:col header
|
|
99
|
+
# and caret, so it is printed verbatim.
|
|
100
|
+
@err.puts e.message
|
|
101
|
+
1
|
|
102
|
+
rescue Backend::UnsupportedError => e
|
|
103
|
+
# A construct the selected backend cannot lower yet: valid C the target is
|
|
104
|
+
# not ready for, reported as a diagnostic rather than a Ruby crash.
|
|
105
|
+
@err.puts "#{PROG}: error: #{e.message}"
|
|
106
|
+
1
|
|
107
|
+
rescue Link::LinkError => e
|
|
108
|
+
@err.puts "#{PROG}: error: #{e.message}"
|
|
109
|
+
1
|
|
110
|
+
rescue Errno::ENOENT => e
|
|
111
|
+
@err.puts "#{PROG}: error: #{e.message}"
|
|
112
|
+
1
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# --version / -v keep their existing meaning (print the version and exit),
|
|
118
|
+
# taking precedence over any other argument.
|
|
119
|
+
def version_requested?
|
|
120
|
+
@argv.include?("--version") || @argv.include?("-v")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def print_version
|
|
124
|
+
@out.puts "rubycc #{Rubycc::VERSION}"
|
|
125
|
+
0
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# --- argument parsing --------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def parse
|
|
131
|
+
i = 0
|
|
132
|
+
i = handle_arg(@argv[i], i) while i < @argv.length
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Dispatches one argument and returns the index of the next unconsumed one.
|
|
136
|
+
# The separated ("-I dir") and joined ("-Idir") spellings of the path/macro/
|
|
137
|
+
# library options are both accepted, matching gcc.
|
|
138
|
+
def handle_arg(arg, i)
|
|
139
|
+
case arg
|
|
140
|
+
when "-c" then @mode_flag = :compile; i + 1
|
|
141
|
+
when "-shared" then @mode_flag = :shared; i + 1
|
|
142
|
+
when "-E" then @mode_flag = :preprocess; i + 1
|
|
143
|
+
when "-fPIC", "-fpic", "-fPIE", "-fpie" then @pic = true; i + 1
|
|
144
|
+
when "-fvisibility=hidden" then @default_visibility = :hidden; i + 1
|
|
145
|
+
when "-fvisibility=default" then @default_visibility = :default; i + 1
|
|
146
|
+
when "-fvisibility=internal" then @default_visibility = :internal; i + 1
|
|
147
|
+
when "-fvisibility=protected" then @default_visibility = :protected; i + 1
|
|
148
|
+
when "-nostdinc" then @system_includes = false; i + 1
|
|
149
|
+
when "-nodefaultlibs" then @default_libs = false; i + 1
|
|
150
|
+
when "-target", "--target" then @target = normalize_target(value(arg, i)); i + 2
|
|
151
|
+
when /\A--?target=(.+)\z/m then @target = normalize_target(Regexp.last_match(1)); i + 1
|
|
152
|
+
when "-o" then @output = value(arg, i); i + 2
|
|
153
|
+
when "-I" then @include_paths << value(arg, i); i + 2
|
|
154
|
+
when /\A-I(.+)\z/m then @include_paths << Regexp.last_match(1); i + 1
|
|
155
|
+
when "-isystem", "-iquote", "-idirafter"
|
|
156
|
+
@include_paths << value(arg, i); i + 2
|
|
157
|
+
when "-D" then @defines << [:define, value(arg, i)]; i + 2
|
|
158
|
+
when /\A-D(.+)\z/m then @defines << [:define, Regexp.last_match(1)]; i + 1
|
|
159
|
+
when "-U" then @defines << [:undef, value(arg, i)]; i + 2
|
|
160
|
+
when /\A-U(.+)\z/m then @defines << [:undef, Regexp.last_match(1)]; i + 1
|
|
161
|
+
when "-l" then @libraries << value(arg, i); i + 2
|
|
162
|
+
when /\A-l(.+)\z/m then @libraries << Regexp.last_match(1); i + 1
|
|
163
|
+
when "-L" then @lib_dirs << value(arg, i); i + 2
|
|
164
|
+
when /\A-L(.+)\z/m then @lib_dirs << Regexp.last_match(1); i + 1
|
|
165
|
+
when /\A-Wl,(.+)\z/m then parse_linker_options(Regexp.last_match(1)); i + 1
|
|
166
|
+
when *ARG_CONSUMING_IGNORED
|
|
167
|
+
@argv[i + 1].nil? ? i + 1 : i + 2
|
|
168
|
+
else
|
|
169
|
+
handle_other(arg, i)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# A token matching none of the explicit options: an input file, a silently
|
|
174
|
+
# accepted codegen/warning flag, or an unknown option (warned and dropped).
|
|
175
|
+
def handle_other(arg, i)
|
|
176
|
+
if !arg.start_with?("-")
|
|
177
|
+
classify_input(arg)
|
|
178
|
+
elsif silently_ignored?(arg)
|
|
179
|
+
# Accepted with no effect (an optimization/debug/warning switch).
|
|
180
|
+
else
|
|
181
|
+
warning("unknown option '#{arg}' ignored")
|
|
182
|
+
end
|
|
183
|
+
i + 1
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Whether `arg` is a documented gcc flag family this toolchain does not model
|
|
187
|
+
# but accepts without complaint: the optimization (`-O*`), debug (`-g*`),
|
|
188
|
+
# warning (`-W*`) and remaining code-generation (`-f*`) switches, the language-standard
|
|
189
|
+
# selector (`-std=…`), and the fixed bare set above. A machine switch (`-m*`)
|
|
190
|
+
# is deliberately excluded — it names a target capability this toolchain does
|
|
191
|
+
# not honor, so it is warned about like any other unmodelled option.
|
|
192
|
+
def silently_ignored?(arg)
|
|
193
|
+
SILENT_IGNORE_EXACT.include?(arg) ||
|
|
194
|
+
arg.match?(/\A-(?:O|g|W|f)/) || arg.start_with?("-std=")
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The operand of an option that takes a separate value, erroring with a
|
|
198
|
+
# gcc-style message when it is missing.
|
|
199
|
+
def value(name, i)
|
|
200
|
+
operand = @argv[i + 1]
|
|
201
|
+
raise UsageError, "missing #{value_kind(name)} after '#{name}'" if operand.nil?
|
|
202
|
+
|
|
203
|
+
operand
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def value_kind(name)
|
|
207
|
+
case name
|
|
208
|
+
when "-o" then "filename"
|
|
209
|
+
when "-I", "-L", "-isystem", "-iquote", "-idirafter" then "directory"
|
|
210
|
+
else "argument"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Interprets the comma-separated linker options in a `-Wl,` passthrough. Only
|
|
215
|
+
# the ones with a driver-level effect are acted on: `-soname NAME` (in the
|
|
216
|
+
# `-soname NAME`, `-soname=NAME` and `-h NAME` spellings) sets the shared
|
|
217
|
+
# object's DT_SONAME. Options that carry a value we ignore (`-rpath` and kin)
|
|
218
|
+
# skip that value so it is not read as a bare option; every other linker
|
|
219
|
+
# option is silently ignored.
|
|
220
|
+
def parse_linker_options(joined)
|
|
221
|
+
tokens = joined.split(",")
|
|
222
|
+
j = 0
|
|
223
|
+
while j < tokens.length
|
|
224
|
+
token = tokens[j]
|
|
225
|
+
case token
|
|
226
|
+
when "-soname", "--soname", "-h" then @soname = tokens[j + 1]; j += 2
|
|
227
|
+
when /\A(?:-soname|--soname|-h)=(.+)\z/ then @soname = Regexp.last_match(1); j += 1
|
|
228
|
+
when "-rpath", "-rpath-link", "--dynamic-linker", "--version-script" then j += 2
|
|
229
|
+
else j += 1
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Classifies an input by extension into the role it plays: a `.c` source to
|
|
235
|
+
# compile, a `.o`/`.obj` object or `.a` archive to link, a `.so[.N]` shared
|
|
236
|
+
# library to depend on. C++ source/header suffixes are rejected explicitly;
|
|
237
|
+
# anything else is treated as a linker input object, as gcc defaults an
|
|
238
|
+
# unrecognized suffix to.
|
|
239
|
+
def classify_input(path)
|
|
240
|
+
extension = File.extname(path).downcase
|
|
241
|
+
if CPP_INPUT_EXTENSIONS.include?(extension)
|
|
242
|
+
raise UsageError, "C++ input '#{path}' is not supported"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
kind =
|
|
246
|
+
case extension
|
|
247
|
+
when ".c" then :source
|
|
248
|
+
when ".o", ".obj" then :object
|
|
249
|
+
when ".a" then :archive
|
|
250
|
+
else path.match?(/\.so(?:\.\d+)*\z/) ? :shared : :object
|
|
251
|
+
end
|
|
252
|
+
@inputs << { path: path, kind: kind }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# --- dispatch ----------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
def dispatch
|
|
258
|
+
raise UsageError, "no input file" if @inputs.empty?
|
|
259
|
+
|
|
260
|
+
case mode
|
|
261
|
+
when :preprocess then preprocess_only
|
|
262
|
+
when :compile then ensure_supported_target && compile_only
|
|
263
|
+
when :shared, :executable then ensure_supported_target && link
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# The code-generation target: the value of -target/--target if given,
|
|
268
|
+
# otherwise the host CPU (RbConfig::CONFIG["host_cpu"], normalized). Both
|
|
269
|
+
# paths run through #normalize_target so a triple like "x86_64-linux-gnu"
|
|
270
|
+
# collapses to its architecture and aliases (amd64) canonicalize.
|
|
271
|
+
def target
|
|
272
|
+
@target ||= normalize_target(RbConfig::CONFIG["host_cpu"])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# The C library whose ABI the bundled headers are read under: the host's
|
|
276
|
+
# own (see Preprocessor.host_libc), which is what makes a plain `rubycc`
|
|
277
|
+
# invocation on an Alpine image compile against the musl branches of the
|
|
278
|
+
# bundled headers and the same invocation on a Debian image against the
|
|
279
|
+
# glibc ones. It is not derived from #target: the machine and the libc are
|
|
280
|
+
# independent axes (either arch runs either libc), so it is threaded to the
|
|
281
|
+
# compiler separately rather than read out of Compiler::TARGETS.
|
|
282
|
+
def libc
|
|
283
|
+
@libc ||= Preprocess::Preprocessor.host_libc
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Reduces a target spelling — a bare CPU name, a gcc/clang triple, or a
|
|
287
|
+
# host_cpu string — to a canonical architecture name: the part before the
|
|
288
|
+
# first "-" of a triple, with common aliases folded (amd64/x64 -> x86_64,
|
|
289
|
+
# arm64 -> aarch64).
|
|
290
|
+
def normalize_target(spec)
|
|
291
|
+
cpu = spec.to_s.split("-", 2).first.to_s
|
|
292
|
+
case cpu
|
|
293
|
+
when "amd64", "x64" then "x86_64"
|
|
294
|
+
when "arm64" then "aarch64"
|
|
295
|
+
else cpu
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Verifies the selected target has an implemented backend, raising a
|
|
300
|
+
# gcc-style usage error for an entirely unknown architecture. x86_64 and
|
|
301
|
+
# aarch64 are both built; anything else is a plain "unsupported target".
|
|
302
|
+
# Returns true so it can gate #dispatch.
|
|
303
|
+
def ensure_supported_target
|
|
304
|
+
return true if Compiler::TARGETS.key?(target)
|
|
305
|
+
|
|
306
|
+
raise UsageError, "unsupported target '#{target}'"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The output mode gcc's precedence selects: -E over -c over -shared, and an
|
|
310
|
+
# executable when no mode flag is present.
|
|
311
|
+
def mode
|
|
312
|
+
@mode_flag || :executable
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# --- compile-only (-c) -------------------------------------------------
|
|
316
|
+
|
|
317
|
+
# Compiles each source to its own object. gcc rejects `-o` with `-c` when
|
|
318
|
+
# more than one source is compiled (one `-o` cannot name several outputs);
|
|
319
|
+
# with a single source `-o` names its object, otherwise the object is the
|
|
320
|
+
# source's basename with a `.o` suffix, dropped into the current directory.
|
|
321
|
+
def compile_only
|
|
322
|
+
sources = @inputs.select { |input| input[:kind] == :source }
|
|
323
|
+
warn_unused_link_inputs
|
|
324
|
+
if @output && sources.length > 1
|
|
325
|
+
raise UsageError, "cannot specify '-o' with '-c' and multiple compilations"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
sources.each do |input|
|
|
329
|
+
output = @output || default_object_name(input[:path])
|
|
330
|
+
Compiler.compile_file(input[:path], output, include_paths: @include_paths,
|
|
331
|
+
pic: @pic, defines: @defines,
|
|
332
|
+
system_includes: @system_includes,
|
|
333
|
+
target: target, libc: libc,
|
|
334
|
+
default_visibility: @default_visibility)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# A `.o`/`.a`/`.so` passed alongside `-c` cannot contribute to a
|
|
339
|
+
# compile-only run; gcc keeps it a warning rather than an error.
|
|
340
|
+
def warn_unused_link_inputs
|
|
341
|
+
@inputs.each do |input|
|
|
342
|
+
next if input[:kind] == :source
|
|
343
|
+
|
|
344
|
+
warning("#{input[:path]}: linker input file unused because linking not done")
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def default_object_name(path)
|
|
349
|
+
base = File.basename(path)
|
|
350
|
+
ext = File.extname(base)
|
|
351
|
+
"#{ext.empty? ? base : base[0...-ext.length]}.o"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# --- link (-shared / executable) ---------------------------------------
|
|
355
|
+
|
|
356
|
+
# Links every input into the requested output. Sources are compiled to object
|
|
357
|
+
# bytes in memory and passed straight to the linker; objects and archives are
|
|
358
|
+
# passed by path; a `.so` input becomes a dependency. The `-l`/`-L` requests
|
|
359
|
+
# are resolved into further archive inputs (appended after the objects so the
|
|
360
|
+
# lazy archive pull-in sees the objects' undefined symbols first) and the
|
|
361
|
+
# dependency shared objects imports bind against. rubycc's compiler-support
|
|
362
|
+
# runtime is appended last, after the user inputs and the resolved libraries,
|
|
363
|
+
# so its members are pulled in lazily only when something ahead still leaves
|
|
364
|
+
# one of their symbols undefined (the libgcc-style default link). The output
|
|
365
|
+
# is made executable so it can be run in place, as gcc leaves its own output.
|
|
366
|
+
def link
|
|
367
|
+
output = @output || "a.out"
|
|
368
|
+
link_inputs = []
|
|
369
|
+
needed = []
|
|
370
|
+
@inputs.each do |input|
|
|
371
|
+
case input[:kind]
|
|
372
|
+
when :source then link_inputs << compile_source(input)
|
|
373
|
+
when :object, :archive then link_inputs << input[:path]
|
|
374
|
+
when :shared then needed << input[:path]
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
resolution = Link::LibraryResolver.resolve(@libraries, search_dirs: @lib_dirs, target: target)
|
|
379
|
+
link_inputs.concat(resolution.inputs)
|
|
380
|
+
needed.concat(resolution.needed)
|
|
381
|
+
link_inputs << Link::CompatRuntime.archive_bytes(target: target) if @default_libs
|
|
382
|
+
|
|
383
|
+
if mode == :shared
|
|
384
|
+
Link::SharedLinker.link_to(link_inputs, output, needed: needed, soname: @soname)
|
|
385
|
+
else
|
|
386
|
+
Link::ExecutableLinker.link_to(link_inputs, output, needed: needed)
|
|
387
|
+
end
|
|
388
|
+
File.chmod(0o755, output)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def compile_source(input)
|
|
392
|
+
Compiler.new.compile(File.read(input[:path]), filename: input[:path],
|
|
393
|
+
include_paths: @include_paths, pic: @pic, defines: @defines,
|
|
394
|
+
system_includes: @system_includes, target: target, libc: libc,
|
|
395
|
+
default_visibility: @default_visibility)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# --- preprocess-only (-E) ----------------------------------------------
|
|
399
|
+
|
|
400
|
+
# Runs the preprocessor over each source and writes the token stream to `-o`
|
|
401
|
+
# (or standard output). The reconstruction is a best-effort re-spelling of the
|
|
402
|
+
# preprocessing tokens — token spellings joined with a space where whitespace
|
|
403
|
+
# separated them and a newline where the line advanced — rather than a
|
|
404
|
+
# byte-faithful copy of gcc's `-E` output; it is enough for a probe that only
|
|
405
|
+
# needs macros expanded and headers included. Non-source inputs are ignored.
|
|
406
|
+
def preprocess_only
|
|
407
|
+
text = +""
|
|
408
|
+
@inputs.each do |input|
|
|
409
|
+
next unless input[:kind] == :source
|
|
410
|
+
|
|
411
|
+
tokens = preprocessor_for_target.preprocess(
|
|
412
|
+
File.read(input[:path]), filename: input[:path],
|
|
413
|
+
include_paths: @include_paths, defines: @defines,
|
|
414
|
+
system_includes: @system_includes
|
|
415
|
+
)
|
|
416
|
+
text << render_preprocessed(tokens.reject(&:eof?))
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
if @output
|
|
420
|
+
File.write(@output, text)
|
|
421
|
+
else
|
|
422
|
+
@out.print(text)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# A preprocessor configured for the selected target and libc, so `-E`
|
|
427
|
+
# predefines the same macros a full compile of the same command line would.
|
|
428
|
+
# An unknown target falls back to the x86-64 entry: `-E` never reaches
|
|
429
|
+
# #ensure_supported_target, and reporting a bad -target as a preprocessing
|
|
430
|
+
# crash would be worse than preprocessing under the default identity.
|
|
431
|
+
def preprocessor_for_target
|
|
432
|
+
entry = Compiler::TARGETS.fetch(target) { Compiler::TARGETS.fetch("x86_64") }
|
|
433
|
+
Preprocess::Preprocessor.new(char_unsigned: !entry[:char_signed],
|
|
434
|
+
arch_macros: entry[:arch_macros],
|
|
435
|
+
libc_arch: entry[:libc_arch],
|
|
436
|
+
libc: libc)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def render_preprocessed(tokens)
|
|
440
|
+
out = +""
|
|
441
|
+
previous = nil
|
|
442
|
+
tokens.each do |token|
|
|
443
|
+
if previous.nil?
|
|
444
|
+
# first token: no leading separator
|
|
445
|
+
elsif token.filename != previous.filename || token.line != previous.line
|
|
446
|
+
out << "\n"
|
|
447
|
+
elsif token.space_before
|
|
448
|
+
out << " "
|
|
449
|
+
end
|
|
450
|
+
out << token.text
|
|
451
|
+
previous = token
|
|
452
|
+
end
|
|
453
|
+
out << "\n" unless out.empty?
|
|
454
|
+
out
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# --- diagnostics -------------------------------------------------------
|
|
458
|
+
|
|
459
|
+
def warning(message)
|
|
460
|
+
@err.puts "#{PROG}: warning: #{message}"
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
end
|