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
data/lib/rubycc/ir/ir.rb
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module IR
|
|
5
|
+
# A single three-address instruction over virtual registers.
|
|
6
|
+
#
|
|
7
|
+
# :const dst <- a (a is an immediate Integer; size == 8 loads a
|
|
8
|
+
# full 64-bit immediate for a long/pointer
|
|
9
|
+
# constant, otherwise a 32-bit one)
|
|
10
|
+
# :copy dst <- a
|
|
11
|
+
# :add/:sub/:mul dst <- a op b
|
|
12
|
+
# :div/:mod dst <- a op b (signed division/remainder)
|
|
13
|
+
# :udiv/:umod dst <- a op b (unsigned division/remainder;
|
|
14
|
+
# the backend zeroes edx and uses `div`)
|
|
15
|
+
# :mulhi dst <- hi64(a * b) the unsigned high 64 bits of the 128-bit
|
|
16
|
+
# product of two 64-bit values (x86 `mul r64`,
|
|
17
|
+
# REX.W F7 /4: rax*b -> rdx:rax, result taken
|
|
18
|
+
# from rdx). Used only to synthesize a 128-bit
|
|
19
|
+
# (`__int128`) multiply from 64-bit halves; the
|
|
20
|
+
# generator pairs it with ordinary :mul (the low
|
|
21
|
+
# 64) and :add. size is always 8
|
|
22
|
+
# :and/:or/:xor dst <- a op b (bitwise)
|
|
23
|
+
# :shl dst <- a << b (logical left shift; b's low byte is the
|
|
24
|
+
# shift count, taken from cl by the backend)
|
|
25
|
+
# :sar dst <- a >> b (arithmetic right shift; b's low byte is the
|
|
26
|
+
# count. A signed left operand's ">>" lowers to
|
|
27
|
+
# :sar so the sign bit is replicated)
|
|
28
|
+
# :shr dst <- a >> b (logical right shift; the unsigned counterpart
|
|
29
|
+
# of :sar, chosen when the left operand is an
|
|
30
|
+
# unsigned type — the split mirrors :div/:udiv,
|
|
31
|
+
# since the machine opcodes differ by sign)
|
|
32
|
+
# :eq/:ne dst <- (a op b) ? 1 : 0 (sign-independent)
|
|
33
|
+
# :lt/:le/:gt/:ge dst <- (a op b) ? 1 : 0 (signed compare)
|
|
34
|
+
# :ult/:ule/:ugt/:uge dst <- (a op b) ? 1 : 0 (unsigned compare,
|
|
35
|
+
# setb/setbe/seta/setae; also used for pointer
|
|
36
|
+
# ordering, addresses being unsigned)
|
|
37
|
+
# :neg dst <- -a
|
|
38
|
+
# :fadd/:fsub/:fmul/:fdiv dst <- a op b (floating arithmetic; size is
|
|
39
|
+
# the operand width, 4 for float / 8 for
|
|
40
|
+
# double, selecting the ss/sd form). A floating
|
|
41
|
+
# "-a" has no op of its own: the generator
|
|
42
|
+
# flips the sign bit with an integer :xor
|
|
43
|
+
# (0x80000000 / 0x8000000000000000, size 8)
|
|
44
|
+
# :feq/:fne dst <- (a op b) ? 1 : 0 (floating equality;
|
|
45
|
+
# size 4/8. NaN-aware: :feq is false and :fne
|
|
46
|
+
# true when either operand is NaN)
|
|
47
|
+
# :flt/:fle/:fgt/:fge dst <- (a op b) ? 1 : 0 (floating ordering;
|
|
48
|
+
# size 4/8. Every one is false when either
|
|
49
|
+
# operand is NaN, the backend reversing the
|
|
50
|
+
# ucomis operands for :flt/:fle so an unordered
|
|
51
|
+
# compare clears the flag)
|
|
52
|
+
# :itof dst <- (float)a integer a converted to a floating value.
|
|
53
|
+
# size is the destination float width (4/8);
|
|
54
|
+
# b is the [width, signed?] descriptor of the
|
|
55
|
+
# integer *source* (an unsigned long source is
|
|
56
|
+
# rejected by the generator, so never reaches
|
|
57
|
+
# here)
|
|
58
|
+
# :ftoi dst <- (int)a floating a truncated toward zero to an
|
|
59
|
+
# integer. size is the float *source* width
|
|
60
|
+
# (4/8); b is the [width, signed?] descriptor
|
|
61
|
+
# of the integer destination (an unsigned long
|
|
62
|
+
# destination is likewise rejected upstream)
|
|
63
|
+
# :ftof dst <- a float<->double width change. size is the
|
|
64
|
+
# *source* float width (4 widening to double,
|
|
65
|
+
# 8 narrowing to float)
|
|
66
|
+
# :sext dst <- a (size: 1/2/4) a's low `size` bytes sign-extended to
|
|
67
|
+
# the register's full width. size 4 is a
|
|
68
|
+
# movsxd; size 1/2 a movsx of the low byte/word
|
|
69
|
+
# :zext dst <- a (size: 1/2/4) a's low `size` bytes zero-extended to
|
|
70
|
+
# the register's full width. size 4 is a plain
|
|
71
|
+
# 32-bit mov (which zeroes the upper half);
|
|
72
|
+
# size 1/2 a movzx
|
|
73
|
+
# :ret return a (a is nil for a void function's "return;"
|
|
74
|
+
# or its implicit fall-off-the-end return,
|
|
75
|
+
# which emits no value-loading code at all).
|
|
76
|
+
# `size` is nil for an integer/pointer return
|
|
77
|
+
# (the value goes in rax/eax) or 4/8 for a
|
|
78
|
+
# floating one, which the backend loads from a's
|
|
79
|
+
# slot into xmm0 with movss/movsd, the System V
|
|
80
|
+
# register a float/double result is returned in.
|
|
81
|
+
# For an in-register struct return `size` is
|
|
82
|
+
# instead an IR::AbiPiece array, one per piece
|
|
83
|
+
# the target's convention cuts the aggregate
|
|
84
|
+
# into (offset, width and kind): a is the address
|
|
85
|
+
# of the struct's buffer, and the backend gathers
|
|
86
|
+
# each piece into its return register — under
|
|
87
|
+
# System V an eightbyte at a time into rax/rdx
|
|
88
|
+
# (:gp) or xmm0/xmm1 (:sse8), under AAPCS64 an
|
|
89
|
+
# HFA member at a time into v0..v3. A struct the
|
|
90
|
+
# convention does not return in registers is not
|
|
91
|
+
# returned this way — its callee copies the
|
|
92
|
+
# result through the hidden pointer parameter and
|
|
93
|
+
# returns that pointer too (a plain size-nil :ret)
|
|
94
|
+
# :label a = label id (a jump target; emits no code itself)
|
|
95
|
+
# :jump a = label id (unconditional branch)
|
|
96
|
+
# :jump_if_zero a = condition vreg, b = label id (branch when a == 0)
|
|
97
|
+
# :call dst <- f(args) a = callee name (String),
|
|
98
|
+
# b = array of [arg_vreg, kind] pairs (left to
|
|
99
|
+
# right). `kind` gives each argument's System V
|
|
100
|
+
# AMD64 placement, which the generator has already
|
|
101
|
+
# fixed over the whole argument list (so a Phase B
|
|
102
|
+
# struct can apply the all-or-nothing overflow rule
|
|
103
|
+
# where every argument's type is known): :gp takes
|
|
104
|
+
# the next integer register (edi,esi,edx,ecx,r8d,r9d),
|
|
105
|
+
# :sse4 (float) / :sse8 (double) the next xmm
|
|
106
|
+
# (xmm0..7, loaded from the slot with movss/movsd),
|
|
107
|
+
# and :mem a stack eightbyte. The backend follows the
|
|
108
|
+
# kind verbatim — it assigns registers in order and
|
|
109
|
+
# pushes the :mem arguments in reverse so the first
|
|
110
|
+
# lands at the lowest address (an eightbyte each, the
|
|
111
|
+
# slot's low bits carrying its value). A by-value
|
|
112
|
+
# struct argument fans out into one [vreg, kind] pair
|
|
113
|
+
# per piece its convention cuts it into (a System V
|
|
114
|
+
# eightbyte's class :gp/:sse8, an AAPCS64 HFA
|
|
115
|
+
# member's :sse4/:sse8, or :mem per eightbyte when it
|
|
116
|
+
# spills whole), all placed together so the argument
|
|
117
|
+
# stays in registers or spills as a unit; an
|
|
118
|
+
# aggregate AAPCS64 passes by reference is reduced by
|
|
119
|
+
# the generator to a single :gp pointer to a
|
|
120
|
+
# caller-made copy, so no backend needs a rule of its
|
|
121
|
+
# own for it. A call whose struct result comes back
|
|
122
|
+
# through a hidden pointer also prepends that
|
|
123
|
+
# [vreg, kind] pointer as the first argument. `size` is nil, or a
|
|
124
|
+
# [fixed, ret] pair when either half is non-nil:
|
|
125
|
+
# `fixed` is the callee's fixed parameter count for a
|
|
126
|
+
# variadic call (else nil), which makes the backend
|
|
127
|
+
# set al to the count of xmm registers it used before
|
|
128
|
+
# the call, as the ABI requires; `ret` is :sse4/:sse8
|
|
129
|
+
# when the result is a float/double (loaded from xmm0
|
|
130
|
+
# with movss/movsd into dst's slot), a
|
|
131
|
+
# [buffer_vreg, pieces] pair when the result is a
|
|
132
|
+
# struct returned in registers (dst is nil and the
|
|
133
|
+
# backend scatters each IR::AbiPiece from its return
|
|
134
|
+
# register — rax/rdx or xmm0/xmm1 under System V,
|
|
135
|
+
# x0/x1 or v0..v3 under AAPCS64 — into the buffer
|
|
136
|
+
# buffer_vreg points at), else nil (the result comes
|
|
137
|
+
# back in the integer result register as usual)
|
|
138
|
+
# :call_indirect dst <- (*a)(args) a = a vreg holding the function's
|
|
139
|
+
# address (a function pointer value), b = the
|
|
140
|
+
# [arg_vreg, kind] pairs (the same generator-fixed
|
|
141
|
+
# :gp/:sse4/:sse8/:mem placement as :call); `size`
|
|
142
|
+
# carries the same [fixed, ret] pair.
|
|
143
|
+
# The backend calls through a scratch register
|
|
144
|
+
# :func_addr dst <- &func(a) dst gets the address of the function named a
|
|
145
|
+
# (a String symbol), the value a function
|
|
146
|
+
# designator decays to (and "&f" yields); resolved
|
|
147
|
+
# by a PC-relative relocation like :global_addr
|
|
148
|
+
# :addr_of dst <- &slot(a) dst gets the address of a's stack slot
|
|
149
|
+
# :object_addr dst <- &object(a) dst gets the base address of stack
|
|
150
|
+
# object a (an array's first element)
|
|
151
|
+
# :load dst <- *a dst gets `size` bytes read through pointer a,
|
|
152
|
+
# sign-extended (a signed char/short read is a
|
|
153
|
+
# movsx; size 4/8 a plain mov)
|
|
154
|
+
# :uload dst <- *a like :load but zero-extended (an unsigned
|
|
155
|
+
# char/short read is a movzx), for unsigned
|
|
156
|
+
# narrow types and _Bool
|
|
157
|
+
# :store *a <- b `size` bytes of b are written through ptr a
|
|
158
|
+
# :memcpy *a <- *b (size) `size` bytes are copied from the address in
|
|
159
|
+
# b to the address in a (a whole-struct
|
|
160
|
+
# assignment "s = t"); both are pointer vregs,
|
|
161
|
+
# `size` the struct's byte width
|
|
162
|
+
# :string_addr dst <- &string(a) dst gets the address of read-only string
|
|
163
|
+
# a (an id into the translation unit's
|
|
164
|
+
# string pool), i.e. a decayed char *
|
|
165
|
+
# :global_addr dst <- &global(a) dst gets the address of the file-scope
|
|
166
|
+
# variable named a (a String symbol name),
|
|
167
|
+
# the lvalue every global read/write and
|
|
168
|
+
# "&g"/array decay is lowered through
|
|
169
|
+
# :got_addr dst <- &symbol(a) via GOT dst gets the address of the
|
|
170
|
+
# file-scope object or function named a (a
|
|
171
|
+
# String symbol), loaded from its Global Offset
|
|
172
|
+
# Table slot rather than formed PC-relatively.
|
|
173
|
+
# The generator emits it in place of
|
|
174
|
+
# :global_addr / :func_addr only under -fPIC,
|
|
175
|
+
# and only for a symbol this translation unit
|
|
176
|
+
# does not define, so a definition in another
|
|
177
|
+
# shared object may interpose; the backend reads
|
|
178
|
+
# the slot with "mov rax, [rip+disp32]" and the
|
|
179
|
+
# linker fills the disp with an
|
|
180
|
+
# R_X86_64_REX_GOTPCRELX relocation against the
|
|
181
|
+
# symbol. A symbol defined here keeps the
|
|
182
|
+
# PC-relative :global_addr / :func_addr form,
|
|
183
|
+
# being always resolved within this DSO
|
|
184
|
+
# :va_start a = a vreg holding the address of a
|
|
185
|
+
# __va_list_tag, b = the enclosing function's
|
|
186
|
+
# fixed (named) parameter count. Initializes the
|
|
187
|
+
# target's va_list fields (the four System V ones
|
|
188
|
+
# gp_offset/fp_offset/overflow_arg_area/reg_save_area,
|
|
189
|
+
# or the five AAPCS64 ones __stack/__gr_top/
|
|
190
|
+
# __vr_top/__gr_offs/__vr_offs) so a later
|
|
191
|
+
# __builtin_va_arg reads the variable arguments;
|
|
192
|
+
# the backend fills them from the register-save
|
|
193
|
+
# area its variadic prologue set up, deriving the
|
|
194
|
+
# named GP and SSE counts (which seed the offsets
|
|
195
|
+
# and the overflow/stack start) from
|
|
196
|
+
# Function.param_kinds rather than from b.
|
|
197
|
+
# va_arg/va_end/va_copy need no IR op of their
|
|
198
|
+
# own — the generator lowers them to ordinary
|
|
199
|
+
# load/store/branch (and, for va_copy, :memcpy)
|
|
200
|
+
# instructions
|
|
201
|
+
# :alloca dst <- alloca(a) a = a vreg holding a byte count; dst gets the
|
|
202
|
+
# base address of that many bytes of automatic
|
|
203
|
+
# storage carved from the stack (__builtin_alloca).
|
|
204
|
+
# The backend rounds the count up to a 16-byte
|
|
205
|
+
# multiple and subtracts it from the stack pointer,
|
|
206
|
+
# so that stays 16-aligned and the block is 16-byte
|
|
207
|
+
# aligned; the storage is reclaimed wholesale when
|
|
208
|
+
# the function returns, not at end of scope. What
|
|
209
|
+
# makes the moving stack pointer safe differs by
|
|
210
|
+
# target: x86-64 addresses every other value from
|
|
211
|
+
# rbp already, while aarch64 is sp-relative and so
|
|
212
|
+
# anchors the frame of an alloca-using function in
|
|
213
|
+
# x29 for the duration (see backend/aarch64.rb)
|
|
214
|
+
# :bit_scan dst <- scan(a) counts the zero bits of the integer in vreg a,
|
|
215
|
+
# for __builtin_ctz/clz (and their "ll" forms).
|
|
216
|
+
# b is the direction — :forward for a trailing
|
|
217
|
+
# count (ctz), :reverse for a leading count
|
|
218
|
+
# (clz) — and `size` the operand width (4 or 8).
|
|
219
|
+
# x86-64 lowers :forward to `bsf` and :reverse
|
|
220
|
+
# to `bsr` followed by `xor` with (size*8 - 1),
|
|
221
|
+
# so clz = (width-1) - bsr; a size-8 scan takes
|
|
222
|
+
# a REX.W prefix. AArch64 lowers :reverse to a
|
|
223
|
+
# bare `clz` and :forward to `rbit` ahead of
|
|
224
|
+
# one, at the W or X width `size` names. A zero
|
|
225
|
+
# operand is undefined (as in gcc), so no zero
|
|
226
|
+
# case is emitted. The result is an int
|
|
227
|
+
#
|
|
228
|
+
# The five atomic ops below lower gcc's __atomic_* builtins. Every one is
|
|
229
|
+
# sequentially consistent — the IR carries no memory order at all, because
|
|
230
|
+
# the generator lowers every order the source asked for at the strongest one
|
|
231
|
+
# (strengthening an order is always sound; see #gen_builtin_atomic). `size`
|
|
232
|
+
# is the access width and is only ever 4 or 8: the generator diagnoses every
|
|
233
|
+
# other width, so no backend needs a narrower or wider case.
|
|
234
|
+
#
|
|
235
|
+
# :atomic_fence a sequentially-consistent memory fence
|
|
236
|
+
# :atomic_load dst <- atomic *a dst gets `size` bytes read atomically
|
|
237
|
+
# through pointer a, with sequentially
|
|
238
|
+
# consistent ordering. Distinct from :load
|
|
239
|
+
# because the two targets differ: on x86-64 an
|
|
240
|
+
# aligned mov already is a seq_cst load, while
|
|
241
|
+
# aarch64 needs the acquire form (ldar)
|
|
242
|
+
# :atomic_store *a <- b `size` bytes of b are written atomically
|
|
243
|
+
# through pointer a, sequentially consistently.
|
|
244
|
+
# x86-64 uses `xchg` (whose implicit lock
|
|
245
|
+
# supplies the trailing fence a seq_cst store
|
|
246
|
+
# needs), aarch64 `stlr`
|
|
247
|
+
# :atomic_rmw dst <- rmw(a, b) an atomic read-modify-write through pointer
|
|
248
|
+
# a. b is a [value_vreg, kind] pair; `kind` is
|
|
249
|
+
# :exchange, :fetch_add, :fetch_sub,
|
|
250
|
+
# :add_fetch, :sub_fetch or :or_fetch. dst gets
|
|
251
|
+
# the value the corresponding builtin returns —
|
|
252
|
+
# the value read for :exchange and the
|
|
253
|
+
# :fetch_* forms, the value stored for the
|
|
254
|
+
# :*_fetch ones — and may be nil when the
|
|
255
|
+
# result is discarded
|
|
256
|
+
# :atomic_cas dst <- cas(a, b) an atomic compare-and-exchange through
|
|
257
|
+
# pointer a, for __atomic_compare_exchange_n.
|
|
258
|
+
# b is an [expected_ptr_vreg, desired_vreg]
|
|
259
|
+
# pair. If *a equals *expected_ptr, *a becomes
|
|
260
|
+
# desired and dst gets 1; otherwise *a is
|
|
261
|
+
# untouched, the value actually read is stored
|
|
262
|
+
# back through expected_ptr — a side effect
|
|
263
|
+
# callers depend on — and dst gets 0. The
|
|
264
|
+
# write-back happens only on the failing path,
|
|
265
|
+
# so a caller whose expected_ptr aliases a sees
|
|
266
|
+
# the exchanged value rather than a stale one.
|
|
267
|
+
# dst is a _Bool (0/1) and is never nil
|
|
268
|
+
#
|
|
269
|
+
# `dst`, `a`, `b` are virtual register numbers (Integers) unless noted;
|
|
270
|
+
# unused fields are nil. `size` is an operand width in bytes. On :load /
|
|
271
|
+
# :uload / :store it is the memory access width (1 char, 2 short, 4 int,
|
|
272
|
+
# 8 pointer/long). On a binary op (the arithmetic, shift and comparison ops)
|
|
273
|
+
# size == 8 selects 64-bit arithmetic for `long`/`unsigned long`/pointer
|
|
274
|
+
# values and pointer-offset scaling; a nil (or 4) size means the default
|
|
275
|
+
# 32-bit arithmetic, whose natural wrap-around matches a 4-byte C type. On
|
|
276
|
+
# :sext / :zext, `size` is instead the *source* width being extended from.
|
|
277
|
+
# On the floating ops (:fadd..:fge) `size` is the floating operand width
|
|
278
|
+
# (4 float / 8 double); on :itof it is the destination float width, on :ftoi
|
|
279
|
+
# and :ftof the *source* float width, with the paired integer width carried
|
|
280
|
+
# in `b` as a [width, signed?] descriptor for :itof / :ftoi.
|
|
281
|
+
class Instruction
|
|
282
|
+
attr_reader :op, :dst, :a, :b, :size
|
|
283
|
+
|
|
284
|
+
def initialize(op, dst: nil, a: nil, b: nil, size: nil)
|
|
285
|
+
@op = op
|
|
286
|
+
@dst = dst
|
|
287
|
+
@a = a
|
|
288
|
+
@b = b
|
|
289
|
+
@size = size
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def inspect
|
|
293
|
+
"#<IR #{op} dst=#{dst.inspect} a=#{a.inspect} b=#{b.inspect} size=#{size.inspect}>"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# A function in IR form: a name, a flat list of instructions and the number
|
|
298
|
+
# of virtual registers used (so the backend can size its stack frame).
|
|
299
|
+
# `param_count` is the number of ABI argument slots, not the number of C
|
|
300
|
+
# parameters: a scalar parameter is one slot, a by-value struct parameter one
|
|
301
|
+
# per piece its convention cuts it into, and the hidden result pointer of a
|
|
302
|
+
# function whose result does not come back in registers an extra leading
|
|
303
|
+
# slot. Those slots occupy
|
|
304
|
+
# the first `param_count` virtual registers (0..param_count-1) in that order,
|
|
305
|
+
# so the backend can spill the incoming argument registers into them; the
|
|
306
|
+
# generator then reassembles a struct parameter's slots into a stack object.
|
|
307
|
+
# `param_kinds` is an array of length `param_count` giving each slot's arrival
|
|
308
|
+
# under the *target's* calling convention (IR::CallConvention), in that
|
|
309
|
+
# flattened order, which the generator has fixed by the same placement
|
|
310
|
+
# simulation a call's arguments use: :gp (integer register), :sse4 (float
|
|
311
|
+
# vector register), :sse8 (double vector register — also each eightbyte of a
|
|
312
|
+
# struct arriving in one, and each member of an AAPCS64 HFA of doubles) or
|
|
313
|
+
# :mem (the stack overflow area). How many registers there are to hand out
|
|
314
|
+
# before a scalar spills to :mem, and how an aggregate is cut into slots at
|
|
315
|
+
# all, are the target's business — which is why the tags are fixed here and
|
|
316
|
+
# not by a backend: System V AMD64 offers six integer registers and AAPCS64
|
|
317
|
+
# eight, and struct { float a, b; } is one :sse8 slot on the first and two
|
|
318
|
+
# :sse4 slots on the second, so the same C call classifies differently on the
|
|
319
|
+
# two. One further kind names a mechanism only some conventions have: an
|
|
320
|
+
# :indirect_result slot is the implicit pointer to a caller-provided result
|
|
321
|
+
# buffer when the convention reserves a register of its own for it (AAPCS64's
|
|
322
|
+
# x8). An aggregate passed by reference needs no kind of its own — the
|
|
323
|
+
# generator reduces it to an ordinary :gp pointer to a caller-made copy. The
|
|
324
|
+
# prologue
|
|
325
|
+
# spills each slot straight from its location, and a variadic function derives
|
|
326
|
+
# its gp_offset / fp_offset / overflow start for :va_start from the counts of
|
|
327
|
+
# each kind.
|
|
328
|
+
# `stack_objects` is an array indexed by object id whose entries are the
|
|
329
|
+
# byte sizes of aggregate stack objects (arrays); the backend lays these
|
|
330
|
+
# out below the virtual-register slots and resolves :object_addr against
|
|
331
|
+
# them.
|
|
332
|
+
# `linkage` is :external for an ordinary function (a global symbol the
|
|
333
|
+
# linker can resolve across translation units) or :internal for a `static`
|
|
334
|
+
# one (a file-local symbol, emitted STB_LOCAL so it stays private to this
|
|
335
|
+
# object and never collides with a same-named function elsewhere).
|
|
336
|
+
# `variadic` is true for a "..."-terminated definition, which makes the
|
|
337
|
+
# backend emit a register-save-area prologue so :va_start / __builtin_va_arg
|
|
338
|
+
# can reach the variable arguments; a fixed-arity function leaves it false.
|
|
339
|
+
class Function
|
|
340
|
+
attr_reader :name, :insts, :vreg_count, :param_count, :stack_objects, :linkage, :variadic, :param_kinds
|
|
341
|
+
|
|
342
|
+
def initialize(name, insts, vreg_count, param_count, stack_objects, linkage, variadic, param_kinds)
|
|
343
|
+
@name = name
|
|
344
|
+
@insts = insts
|
|
345
|
+
@vreg_count = vreg_count
|
|
346
|
+
@param_count = param_count
|
|
347
|
+
@stack_objects = stack_objects
|
|
348
|
+
@linkage = linkage
|
|
349
|
+
@variadic = variadic
|
|
350
|
+
@param_kinds = param_kinds
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# A file-scope variable as laid out by the compiler. `name` is its symbol
|
|
355
|
+
# name, `size` its storage width in bytes and `align` its required
|
|
356
|
+
# alignment (its type size for a scalar/pointer, its element size for an
|
|
357
|
+
# array, its widest member's for a struct). `init` is either nil — a
|
|
358
|
+
# zero-initialized global that lives in .bss — or a GlobalInit: the
|
|
359
|
+
# `size`-byte little-endian image to place in .data together with the
|
|
360
|
+
# relocations that patch pointer slots at link time. `linkage` is :external
|
|
361
|
+
# for an ordinary file-scope variable (a global symbol) or :internal for a
|
|
362
|
+
# `static` one — a file-scope `static`, or a block-scope `static` lowered to
|
|
363
|
+
# a uniquely named file-scope object — emitted STB_LOCAL so it stays private
|
|
364
|
+
# to this object.
|
|
365
|
+
Global = Data.define(:name, :size, :align, :init, :linkage)
|
|
366
|
+
|
|
367
|
+
# The materialized initializer of a .data global: `bytes` is its full
|
|
368
|
+
# `size`-byte image (a scalar packed little-endian, an aggregate laid out
|
|
369
|
+
# member/element by member/element, a pointer slot left as eight zeros for a
|
|
370
|
+
# relocation to fill), and `relocations` is the list of GlobalReloc that
|
|
371
|
+
# patch the pointer slots. An all-zero image with no relocations is still a
|
|
372
|
+
# GlobalInit (an explicitly zero-initialized global), distinct from a nil
|
|
373
|
+
# `init` that reserves .bss space.
|
|
374
|
+
GlobalInit = Data.define(:bytes, :relocations)
|
|
375
|
+
|
|
376
|
+
# One relocation inside a global's .data image. `offset` is the byte offset
|
|
377
|
+
# of the 8-byte pointer slot within that global. `kind` is :symbol for the
|
|
378
|
+
# address of another file-scope object — `symbol` names it, resolved as an
|
|
379
|
+
# absolute 64-bit address (a "&global" or a decayed global array) — or
|
|
380
|
+
# :string for a string literal, where `string_id` indexes the translation
|
|
381
|
+
# unit's string pool and the compiler resolves it to a .rodata offset. The
|
|
382
|
+
# unused field is nil for each kind. `addend` is a constant byte displacement
|
|
383
|
+
# added to the base address (0 for a bare "&global" or string, non-zero for a
|
|
384
|
+
# computed address constant such as "&arr[i]", "arr + n" or "&rec.member");
|
|
385
|
+
# it becomes the R_X86_64_64 relocation's r_addend.
|
|
386
|
+
GlobalReloc = Data.define(:offset, :kind, :symbol, :string_id, :addend) do
|
|
387
|
+
def initialize(offset:, kind:, symbol:, string_id:, addend: 0)
|
|
388
|
+
super
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# One entry this translation unit contributes to an initializer/finalizer
|
|
393
|
+
# array. `symbol` names a function *defined here* that the runtime is to
|
|
394
|
+
# call: `kind` is :init for a constructor (the loader calls it before main,
|
|
395
|
+
# or at dlopen) or :fini for a destructor (at exit, or at dlclose), and
|
|
396
|
+
# `priority` is its run-order number, the compiler's
|
|
397
|
+
# ELFWriter::DEFAULT_ARRAY_PRIORITY standing for the unnumbered form. The
|
|
398
|
+
# compiler turns each entry into an 8-byte slot in the matching
|
|
399
|
+
# SHT_INIT_ARRAY / SHT_FINI_ARRAY section plus an absolute 64-bit relocation
|
|
400
|
+
# against `symbol`.
|
|
401
|
+
ArrayEntry = Data.define(:kind, :priority, :symbol)
|
|
402
|
+
|
|
403
|
+
# A whole translation unit lowered to IR: its `functions` (an array of
|
|
404
|
+
# Function), the shared read-only string pool `strings` (an array of
|
|
405
|
+
# ASCII-8BIT byte strings, without their NUL terminators, indexed by the id
|
|
406
|
+
# a :string_addr instruction carries), its `globals` (an array of Global
|
|
407
|
+
# in source order) and its `array_entries` (an array of ArrayEntry, empty for
|
|
408
|
+
# a unit with no constructor/destructor). Identical string contents are
|
|
409
|
+
# pooled once, so the compiler can lay them out in .rodata in this order and
|
|
410
|
+
# resolve each :string_addr to an offset.
|
|
411
|
+
Program = Data.define(:functions, :strings, :globals, :array_entries, :visibility) do
|
|
412
|
+
def initialize(functions:, strings:, globals:, array_entries: [], visibility: {})
|
|
413
|
+
super
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../compiler"
|
|
4
|
+
require_relative "../objfile/ar_archive"
|
|
5
|
+
|
|
6
|
+
module Rubycc
|
|
7
|
+
module Link
|
|
8
|
+
# rubycc's compiler-support runtime: the small set of symbols a CRuby built
|
|
9
|
+
# by a compiler other than rubycc leaves for the C runtime to supply, and
|
|
10
|
+
# which rubycc must therefore provide itself the way gcc's driver quietly
|
|
11
|
+
# links libgcc. It is delivered as an `ar` archive appended to the tail of
|
|
12
|
+
# every link so the linker's lazy member extraction pulls a definition in
|
|
13
|
+
# *only* when an input actually references it; a link that never mentions one
|
|
14
|
+
# of these symbols gets none of this code, exactly as if the archive were not
|
|
15
|
+
# on the command line.
|
|
16
|
+
#
|
|
17
|
+
# The one member of record is rb_gc_guarded_ptr_val, the target of
|
|
18
|
+
# RB_GC_GUARD's non-`__GNUC__` fallback (ruby/internal/memory.h). Because
|
|
19
|
+
# rubycc deliberately does not define __GNUC__ (DESIGN R7), CRuby's macro
|
|
20
|
+
# expands to a call to this function rather than the GNU inline-asm barrier;
|
|
21
|
+
# yet a gcc-built CRuby, having taken the asm path itself, never exports the
|
|
22
|
+
# function. The whole contract is to hand the pointer back untouched — the
|
|
23
|
+
# call is itself the optimization barrier that keeps the guarded VALUE live,
|
|
24
|
+
# and rubycc performs no optimization that the barrier would need to defeat.
|
|
25
|
+
#
|
|
26
|
+
# The archive is built by compiling the runtime's own C source *with rubycc*
|
|
27
|
+
# (a self-host: rubycc compiles its own support code) into an ET_REL object,
|
|
28
|
+
# then wrapping it in a one-member ranlib-indexed archive. Each entry in the
|
|
29
|
+
# source table is one self-contained function so the set can grow a symbol at
|
|
30
|
+
# a time without any member dragging in an unrelated definition.
|
|
31
|
+
class CompatRuntime
|
|
32
|
+
# The runtime's source fragments, keyed by the member name each is archived
|
|
33
|
+
# under. One function per fragment keeps the lazy pull-in surgical: only the
|
|
34
|
+
# fragment defining a referenced symbol is extracted. Compiled PIC because
|
|
35
|
+
# the definitions land in shared objects.
|
|
36
|
+
#
|
|
37
|
+
# `volatile` is kept on the parameter and return types so the declaration
|
|
38
|
+
# matches CRuby's own prototype spelling; it qualifies the pointee, not the
|
|
39
|
+
# ABI, so it has no effect on the generated code. `val` is unused by design
|
|
40
|
+
# — the argument exists only so the call site materializes the guarded
|
|
41
|
+
# value as a real function argument, which is the entire point of the guard.
|
|
42
|
+
SOURCES = {
|
|
43
|
+
"rb_gc_guarded_ptr_val" => <<~C
|
|
44
|
+
/* rubycc compat runtime: definitions CRuby's non-GNUC fallback paths
|
|
45
|
+
reference. RB_GC_GUARD's fallback calls rb_gc_guarded_ptr_val, which a
|
|
46
|
+
gcc-built CRuby does not export; returning the pointer unchanged is the
|
|
47
|
+
whole contract (the call itself is the optimization barrier, and rubycc
|
|
48
|
+
does not optimize). Pulled in lazily, only when referenced. */
|
|
49
|
+
typedef unsigned long __rubycc_VALUE;
|
|
50
|
+
volatile __rubycc_VALUE *rb_gc_guarded_ptr_val(volatile __rubycc_VALUE *ptr, __rubycc_VALUE val) {
|
|
51
|
+
return ptr;
|
|
52
|
+
}
|
|
53
|
+
C
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
# A stable synthetic filename for each fragment's diagnostics and file
|
|
57
|
+
# symbol, so a compile error (should the source ever regress) points at the
|
|
58
|
+
# runtime rather than at a user file.
|
|
59
|
+
SOURCE_FILENAME = "rubycc_compat_runtime.c"
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
# The compiler-support runtime as an `ar` archive (an ASCII-8BIT String),
|
|
63
|
+
# ready to append to a link's input list. `target` selects the machine the
|
|
64
|
+
# fragments are compiled for, so the runtime members match the objects they
|
|
65
|
+
# are merged with — an aarch64 link must not pull in an x86_64 definition.
|
|
66
|
+
# Memoized per target: the bytes are deterministic (N4) and their
|
|
67
|
+
# construction — compiling every fragment — is done once per target per
|
|
68
|
+
# process.
|
|
69
|
+
def archive_bytes(target: "x86_64")
|
|
70
|
+
(@archive_bytes ||= {})[target] ||= build_archive(target)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Compiles each source fragment to a PIC ET_REL object for `target` and
|
|
76
|
+
# lays them out as archive members in table order, producing a
|
|
77
|
+
# symbol-indexed archive the linker can extract from lazily.
|
|
78
|
+
def build_archive(target)
|
|
79
|
+
writer = ObjFile::ArWriter.new
|
|
80
|
+
SOURCES.each do |name, source|
|
|
81
|
+
object = Compiler.new.compile(source, filename: SOURCE_FILENAME, pic: true, target: target)
|
|
82
|
+
writer.add_member("#{name}.o", object)
|
|
83
|
+
end
|
|
84
|
+
writer.to_binary
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Link
|
|
5
|
+
# Raised for a link-time defect the user must fix: a symbol defined by two
|
|
6
|
+
# inputs, or an input construct the linker does not yet support (a COMMON
|
|
7
|
+
# symbol). It is deliberately not a CompileError — nothing here parses C; the
|
|
8
|
+
# failure belongs to the link stage and names the objects involved so the
|
|
9
|
+
# user can locate the conflict. A malformed input object/archive surfaces as
|
|
10
|
+
# the reader's ELFFormatError / ArFormatError instead.
|
|
11
|
+
class LinkError < Rubycc::Error; end
|
|
12
|
+
end
|
|
13
|
+
end
|