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,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Front
|
|
5
|
+
# A single lexical token. `type` is one of :num, :float, :ident, :keyword,
|
|
6
|
+
# :punct, :string, :eof. `value` is an Integer for :num, a Ruby Float for
|
|
7
|
+
# :float (a floating constant), an ASCII-8BIT String of the escape-resolved
|
|
8
|
+
# bytes for :string, a String for :ident/:keyword/:punct, and nil for :eof.
|
|
9
|
+
# `base` (10/8/16) and `suffix` accompany a numeric constant so the parser
|
|
10
|
+
# can fix its type: an integer :num carries a base and a normalized u/l
|
|
11
|
+
# suffix run ("", "u", "ul") per 6.4.4.1, while a :float carries no base but
|
|
12
|
+
# a normalized floating suffix ("" for double, "f" for float, "l" for long
|
|
13
|
+
# double, itself treated as double). Both are nil on every other token (a
|
|
14
|
+
# character constant is a :num with base 10 and no suffix). The remaining
|
|
15
|
+
# fields locate the token in the source for diagnostics.
|
|
16
|
+
class Token
|
|
17
|
+
TYPES = %i[num float ident keyword punct string eof].freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :type, :value, :filename, :line, :column, :source_line, :base, :suffix
|
|
20
|
+
|
|
21
|
+
def initialize(type:, value:, filename:, line:, column:, source_line:, base: nil, suffix: nil)
|
|
22
|
+
@type = type
|
|
23
|
+
@value = value
|
|
24
|
+
@filename = filename
|
|
25
|
+
@line = line
|
|
26
|
+
@column = column
|
|
27
|
+
@source_line = source_line
|
|
28
|
+
@base = base
|
|
29
|
+
@suffix = suffix
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def punct?(str)
|
|
33
|
+
type == :punct && value == str
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def keyword?(str)
|
|
37
|
+
type == :keyword && value == str
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def eof?
|
|
41
|
+
type == :eof
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def inspect
|
|
45
|
+
"#<Token #{type} #{value.inspect} @#{line}:#{column}>"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
|
|
5
|
+
module Rubycc
|
|
6
|
+
module IR
|
|
7
|
+
# One piece of a by-value aggregate as its convention moves it: the byte
|
|
8
|
+
# `offset` within the aggregate the piece is read from (and written back to
|
|
9
|
+
# at the far end), the `size` of that access, and the `kind` of place it
|
|
10
|
+
# travels in (:gp an integer register, :sse4/:sse8 a vector one, :mem a
|
|
11
|
+
# stack eightbyte).
|
|
12
|
+
#
|
|
13
|
+
# An aggregate is never moved as a whole — the generator takes it apart into
|
|
14
|
+
# these pieces, loads each into a virtual register and hands the backend one
|
|
15
|
+
# ABI slot per piece — so the piece list *is* the classification, and it is
|
|
16
|
+
# exactly where the two conventions part ways. System V AMD64 always cuts on
|
|
17
|
+
# eightbyte boundaries (offset 8*i, size 8, the eightbyte's class), while
|
|
18
|
+
# AAPCS64 cuts a homogeneous floating aggregate along its members instead
|
|
19
|
+
# (offset 4*i, size 4 for a struct of floats, each member its own vector
|
|
20
|
+
# register). That is why a piece carries an offset and a width rather than
|
|
21
|
+
# just an index: struct { float a, b; } is one eightbyte on x86-64 and two
|
|
22
|
+
# single-precision registers on aarch64.
|
|
23
|
+
AbiPiece = Data.define(:offset, :size, :kind)
|
|
24
|
+
|
|
25
|
+
# How a convention passes one aggregate by value:
|
|
26
|
+
# :registers — `pieces` names each register-borne piece;
|
|
27
|
+
# :memory — the value is laid into the caller's stack argument area
|
|
28
|
+
# whole, and a result of this shape is written through a
|
|
29
|
+
# hidden pointer the caller passes as an ordinary leading
|
|
30
|
+
# argument (System V AMD64's MEMORY class);
|
|
31
|
+
# :by_reference — the caller copies the value somewhere of its own and
|
|
32
|
+
# passes the copy's *address* instead, and a result of
|
|
33
|
+
# this shape is written through the convention's dedicated
|
|
34
|
+
# indirect result register (AAPCS64's x8).
|
|
35
|
+
# `align16` marks an aggregate of 16-byte alignment (one built around an
|
|
36
|
+
# __int128, or declared _Alignas(16)). Both conventions round its *stack*
|
|
37
|
+
# offset up to a 16-byte boundary when it spills; AAPCS64 additionally rounds
|
|
38
|
+
# NGRN up so its first integer register is even-numbered, landing it in an
|
|
39
|
+
# aligned x-register pair (System V has no such register rule — a 16-byte
|
|
40
|
+
# argument takes two consecutive registers of either parity).
|
|
41
|
+
AggregatePlan = Data.define(:mode, :pieces, :align16)
|
|
42
|
+
|
|
43
|
+
# One argument as the placement pass sees it: the candidate kind of each of
|
|
44
|
+
# its ABI slots, whether its aggregate is 16-byte aligned, and how many
|
|
45
|
+
# stack eightbytes it occupies should it spill (ceil(size/8), so the placer
|
|
46
|
+
# can track the stack offset a 16-byte-aligned aggregate must be padded up
|
|
47
|
+
# to). Placement only ever needs to count and align, never to know a C type.
|
|
48
|
+
ArgumentRequest = Data.define(:kinds, :align16, :mem_eightbytes)
|
|
49
|
+
|
|
50
|
+
# The part of a target's calling convention the IR generator has to know
|
|
51
|
+
# about, so that where every argument lands is decided once — where the
|
|
52
|
+
# argument's C type is still in hand — rather than guessed at by a backend
|
|
53
|
+
# that only sees a tag.
|
|
54
|
+
#
|
|
55
|
+
# Three things are target-specific and all three live here:
|
|
56
|
+
#
|
|
57
|
+
# * how many registers there are to hand out. System V AMD64 offers six
|
|
58
|
+
# integer registers (rdi, rsi, rdx, rcx, r8, r9) and AAPCS64 eight
|
|
59
|
+
# (x0..x7); both offer eight vector ones. Getting the counts from the
|
|
60
|
+
# target is what lets a seventh integer argument reach x6 on aarch64
|
|
61
|
+
# instead of arriving tagged for the stack.
|
|
62
|
+
#
|
|
63
|
+
# * how an aggregate is cut up and where the pieces go (#aggregate_plan).
|
|
64
|
+
# The System V eightbyte classification and the AAPCS64 HFA / 16-byte /
|
|
65
|
+
# by-reference rules disagree in ways that are silent when guessed at:
|
|
66
|
+
# struct { float a, b; } is one SSE eightbyte in xmm0 under System V and
|
|
67
|
+
# two single-precision registers, s0 and s1, under AAPCS64.
|
|
68
|
+
#
|
|
69
|
+
# * how the register files are consumed as the argument list is walked
|
|
70
|
+
# (#placer). Both conventions place an argument as a unit, but they
|
|
71
|
+
# differ on what happens when one does not fit: System V leaves the
|
|
72
|
+
# registers it did not use available to a later argument, while AAPCS64
|
|
73
|
+
# declares the file exhausted (6.4.2 stage C sets NGRN or NSRN to eight),
|
|
74
|
+
# so a trailing int after a spilled two-register struct is in a register
|
|
75
|
+
# on x86-64 and on the stack on aarch64.
|
|
76
|
+
#
|
|
77
|
+
# `hidden_result_kind` is the kind of the implicit pointer to a
|
|
78
|
+
# caller-provided result buffer: an ordinary leading integer argument under
|
|
79
|
+
# System V, and its own mechanism (:indirect_result, the x8 register) under
|
|
80
|
+
# AAPCS64.
|
|
81
|
+
#
|
|
82
|
+
# The `va_list_*` trio is the fourth target-specific thing the convention
|
|
83
|
+
# owns, and it belongs here for the same reason the others do: a `va_list`
|
|
84
|
+
# walks exactly the register/stack layout the convention laid a call's
|
|
85
|
+
# arguments out in, so the shape of the tag, the type the front end declares
|
|
86
|
+
# `__builtin_va_list` to name, and the flavour of the va_arg walk are all
|
|
87
|
+
# facets of the one ABI. `va_list_abi` is a plain tag (:system_v / :aapcs64)
|
|
88
|
+
# the generator switches its va_arg lowering on: the two walks read a
|
|
89
|
+
# different structure with the offsets running opposite ways, so they are
|
|
90
|
+
# kept as separate lowerings rather than one merged over a descriptor — and
|
|
91
|
+
# keeping the System V path untouched is what guarantees its emitted code
|
|
92
|
+
# does not shift by a byte.
|
|
93
|
+
class CallConvention
|
|
94
|
+
attr_reader :gp_registers, :fp_registers, :hidden_result_kind,
|
|
95
|
+
:va_list_tag, :va_list_type, :va_list_abi
|
|
96
|
+
|
|
97
|
+
def initialize(gp_registers:, fp_registers:, va_list_tag:, va_list_type:, va_list_abi:,
|
|
98
|
+
hidden_result_kind: :gp)
|
|
99
|
+
@gp_registers = gp_registers
|
|
100
|
+
@fp_registers = fp_registers
|
|
101
|
+
@hidden_result_kind = hidden_result_kind
|
|
102
|
+
@va_list_tag = va_list_tag
|
|
103
|
+
@va_list_type = va_list_type
|
|
104
|
+
@va_list_abi = va_list_abi
|
|
105
|
+
freeze
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The pieces a value of `size` bytes is cut into when it travels in the
|
|
109
|
+
# stack argument area. Both conventions round a stack argument up to a
|
|
110
|
+
# multiple of eight and align it to at least eight, so a spilled value is
|
|
111
|
+
# ceil(size/8) whole eightbytes whatever shape it would have taken in
|
|
112
|
+
# registers — an aarch64 HFA that runs out of vector registers is passed
|
|
113
|
+
# as packed eightbytes, not as one stack slot per member.
|
|
114
|
+
def self.memory_pieces(size)
|
|
115
|
+
Array.new((size + 7) / 8) { |i| AbiPiece.new(offset: 8 * i, size: 8, kind: :mem) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The convention's plan for passing an aggregate of `type` by value.
|
|
119
|
+
def aggregate_plan(_type)
|
|
120
|
+
raise NotImplementedError
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A fresh running placement of one argument list (see the Placer classes).
|
|
124
|
+
def placer
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# System V AMD64 (psABI 3.2.3).
|
|
130
|
+
class SystemVAMD64Convention < CallConvention
|
|
131
|
+
def initialize
|
|
132
|
+
super(gp_registers: 6, fp_registers: 8,
|
|
133
|
+
va_list_tag: Type::VaListTag, va_list_type: Type::BuiltinVaList,
|
|
134
|
+
va_list_abi: :system_v)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Classifies an aggregate by the psABI's eightbyte rules. A struct or
|
|
138
|
+
# union larger than two eightbytes — or one with any unaligned field — is
|
|
139
|
+
# passed in memory; otherwise each of its one or two eightbytes gets a
|
|
140
|
+
# class from the scalar fields that fall in it, :gp for an INTEGER
|
|
141
|
+
# eightbyte and :sse8 for an SSE one (moved as a full 8-byte double even
|
|
142
|
+
# when it holds two packed floats, since a single movsd carries the whole
|
|
143
|
+
# eightbyte).
|
|
144
|
+
#
|
|
145
|
+
# The unaligned-field test is what a GNU __attribute__((packed)) demands
|
|
146
|
+
# (Step 28): the psABI gives an aggregate "containing unaligned fields"
|
|
147
|
+
# class MEMORY, and gcc follows it — a packed struct whose field would
|
|
148
|
+
# straddle an eightbyte boundary is passed on the stack, not in registers.
|
|
149
|
+
# Every non-packed layout here is naturally aligned, so this only ever
|
|
150
|
+
# fires for a packed struct. (AAPCS64 has no such rule, which is one more
|
|
151
|
+
# reason the classification cannot be shared.)
|
|
152
|
+
def aggregate_plan(type)
|
|
153
|
+
size = type.size
|
|
154
|
+
# A 16-byte-aligned aggregate (one holding an __int128, or _Alignas(16))
|
|
155
|
+
# is placed on a 16-byte-aligned stack slot when it spills, so its plan
|
|
156
|
+
# carries the alignment for the placer to pad NSAA up to — even for a
|
|
157
|
+
# MEMORY-class aggregate, whose stack slot the psABI aligns the same way.
|
|
158
|
+
align16 = type.alignment >= 16
|
|
159
|
+
if size > 16 || unaligned_field?(type, 0)
|
|
160
|
+
return AggregatePlan.new(mode: :memory, pieces: CallConvention.memory_pieces(size), align16: align16)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
eightbytes = Array.new((size + 7) / 8, nil)
|
|
164
|
+
classify_eightbytes(eightbytes, type, 0)
|
|
165
|
+
# A NO_CLASS eightbyte (only padding fell in it) defaults to SSE, the
|
|
166
|
+
# psABI's benign choice; INTEGER otherwise wins over SSE per #merge_class.
|
|
167
|
+
pieces = eightbytes.each_with_index.map do |cls, i|
|
|
168
|
+
AbiPiece.new(offset: 8 * i, size: 8, kind: cls == :integer ? :gp : :sse8)
|
|
169
|
+
end
|
|
170
|
+
AggregatePlan.new(mode: :registers, pieces: pieces, align16: align16)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def placer
|
|
174
|
+
Placer.new(self)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
# Whether any scalar field of `type`, placed at absolute byte offset
|
|
180
|
+
# `base`, sits on an offset that does not satisfy its own alignment — the
|
|
181
|
+
# mark of a packed layout. A nested aggregate recurses at its members'
|
|
182
|
+
# offsets (a union's members all at 0) and an array at its element's; a
|
|
183
|
+
# scalar checks base against its alignment directly.
|
|
184
|
+
def unaligned_field?(type, base)
|
|
185
|
+
if type.struct?
|
|
186
|
+
# A bit-field is packed into a storage unit by design, so it is never an
|
|
187
|
+
# "unaligned field" in the psABI sense; only its plain neighbours are
|
|
188
|
+
# tested. gcc likewise passes a small bit-field struct in registers.
|
|
189
|
+
type.members.reject(&:bitfield?).any? { |m| unaligned_field?(m.type, base + m.offset) }
|
|
190
|
+
elsif type.array?
|
|
191
|
+
unaligned_field?(type.element, base)
|
|
192
|
+
else
|
|
193
|
+
(base % type.alignment) != 0
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Walks `type` at byte offset `base` and folds each scalar field's class
|
|
198
|
+
# into the eightbyte (offset / 8) it lands in. A nested struct or union
|
|
199
|
+
# recurses at its member offsets (a union overlays every member at the same
|
|
200
|
+
# offset, which the members' zero offsets already encode), and an array
|
|
201
|
+
# recurses element by element. A struct reaching here has already passed the
|
|
202
|
+
# unaligned-field test in #aggregate_plan, so no scalar straddles an
|
|
203
|
+
# eightbyte boundary and each falls wholly in the eightbyte at offset / 8.
|
|
204
|
+
def classify_eightbytes(eightbytes, type, base)
|
|
205
|
+
if type.struct?
|
|
206
|
+
type.members.each do |m|
|
|
207
|
+
if m.bitfield?
|
|
208
|
+
classify_bitfield(eightbytes, base, m)
|
|
209
|
+
else
|
|
210
|
+
classify_eightbytes(eightbytes, m.type, base + m.offset)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
elsif type.array?
|
|
214
|
+
type.length.times { |i| classify_eightbytes(eightbytes, type.element, base + i * type.element.size) }
|
|
215
|
+
else
|
|
216
|
+
# A scalar folds its class into every eightbyte it spans. All scalars but
|
|
217
|
+
# a 128-bit integer fit in one (they are naturally aligned); a 16-byte
|
|
218
|
+
# __int128 spans two, both INTEGER, so a struct wrapping one passes by
|
|
219
|
+
# value in two integer registers, as gcc does.
|
|
220
|
+
cls = type.float? ? :sse : :integer
|
|
221
|
+
(base / 8..(base + type.size - 1) / 8).each do |index|
|
|
222
|
+
eightbytes[index] = merge_class(eightbytes[index], cls)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Folds a bit-field member into the eightbytes its bits span. Every
|
|
228
|
+
# bit-field type in this subset is an integer type, so the field
|
|
229
|
+
# contributes INTEGER to each eightbyte it touches (a field wide enough, or
|
|
230
|
+
# placed so, that it straddles an eightbyte boundary marks both). `base` is
|
|
231
|
+
# the enclosing aggregate's byte offset and the member's `bit_offset` its
|
|
232
|
+
# bit position within that aggregate.
|
|
233
|
+
def classify_bitfield(eightbytes, base, member)
|
|
234
|
+
first_bit = base * 8 + member.bit_offset
|
|
235
|
+
last_bit = first_bit + member.bit_width - 1
|
|
236
|
+
(first_bit / 64..last_bit / 64).each do |index|
|
|
237
|
+
eightbytes[index] = merge_class(eightbytes[index], :integer)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Combines two field classes sharing an eightbyte: NO_CLASS (nil) yields to
|
|
242
|
+
# the other, and INTEGER dominates SSE (a mixed integer/float eightbyte is
|
|
243
|
+
# passed in an integer register), matching the psABI merge rule this subset
|
|
244
|
+
# needs.
|
|
245
|
+
def merge_class(current, incoming)
|
|
246
|
+
return incoming if current.nil?
|
|
247
|
+
return current if incoming.nil?
|
|
248
|
+
return :integer if current == :integer || incoming == :integer
|
|
249
|
+
|
|
250
|
+
:sse
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Hands out the integer and SSE registers over one argument list. Each
|
|
254
|
+
# argument is placed as a unit: its required registers of both files are
|
|
255
|
+
# counted first, and only if *both* fit in what remains does it take them;
|
|
256
|
+
# otherwise the whole argument spills and no register is consumed — the
|
|
257
|
+
# psABI rule that an argument whose parts do not all fit in registers
|
|
258
|
+
# passes wholly in memory, and the reason a later, smaller argument can
|
|
259
|
+
# still be handed a register the spilled one could not use.
|
|
260
|
+
class Placer
|
|
261
|
+
def initialize(convention)
|
|
262
|
+
@convention = convention
|
|
263
|
+
@next_gp = 0
|
|
264
|
+
@next_sse = 0
|
|
265
|
+
@nsaa = 0 # next stacked argument, counted in eightbytes
|
|
266
|
+
@pad_stack = 0
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# System V has no even-register-pair rule — a 16-byte argument takes two
|
|
270
|
+
# *consecutive* integer registers of either parity — so #place never
|
|
271
|
+
# reserves a padding register.
|
|
272
|
+
def pad_gp
|
|
273
|
+
0
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# One stack eightbyte the most recent #place reserved to 16-align a
|
|
277
|
+
# spilled 16-byte-aligned argument (the psABI aligns a stacked argument to
|
|
278
|
+
# its natural alignment, so an __int128 that overflowed the registers onto
|
|
279
|
+
# an odd stack offset starts a slot later). Zero otherwise.
|
|
280
|
+
attr_reader :pad_stack
|
|
281
|
+
|
|
282
|
+
# :registers when the argument takes the registers its request asks for,
|
|
283
|
+
# :stack when it passes in the overflow area. A request that is already
|
|
284
|
+
# all-:mem (a MEMORY-classified aggregate) never wanted a register.
|
|
285
|
+
def place(request)
|
|
286
|
+
@pad_stack = 0
|
|
287
|
+
need_gp = request.kinds.count(:gp)
|
|
288
|
+
need_sse = request.kinds.count { |kind| kind == :sse4 || kind == :sse8 }
|
|
289
|
+
spills = request.kinds.all?(:mem) ||
|
|
290
|
+
!(@next_gp + need_gp <= @convention.gp_registers &&
|
|
291
|
+
@next_sse + need_sse <= @convention.fp_registers)
|
|
292
|
+
if spills
|
|
293
|
+
@pad_stack = 1 if request.align16 && @nsaa.odd?
|
|
294
|
+
@nsaa += @pad_stack + request.mem_eightbytes
|
|
295
|
+
return :stack
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
@next_gp += need_gp
|
|
299
|
+
@next_sse += need_sse
|
|
300
|
+
:registers
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# AAPCS64 (Procedure Call Standard for the Arm 64-bit Architecture, 6.4.2).
|
|
306
|
+
class AAPCS64Convention < CallConvention
|
|
307
|
+
# The most members a homogeneous floating aggregate may have and still
|
|
308
|
+
# travel in vector registers (6.4.2 stage B: "at most four uniquely
|
|
309
|
+
# addressable members"). A fifth member sends the whole aggregate by
|
|
310
|
+
# reference, however small each member is.
|
|
311
|
+
MAX_HFA_MEMBERS = 4
|
|
312
|
+
|
|
313
|
+
# The largest aggregate that is passed in integer registers rather than by
|
|
314
|
+
# reference. An HFA is exempt: four doubles are 32 bytes and still ride
|
|
315
|
+
# d0..d3.
|
|
316
|
+
MAX_REGISTER_AGGREGATE = 16
|
|
317
|
+
|
|
318
|
+
def initialize
|
|
319
|
+
super(gp_registers: 8, fp_registers: 8, hidden_result_kind: :indirect_result,
|
|
320
|
+
va_list_tag: Type::AArch64VaListTag, va_list_type: Type::AArch64BuiltinVaList,
|
|
321
|
+
va_list_abi: :aapcs64)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Classifies an aggregate by AAPCS64 6.4.2, in the order the standard
|
|
325
|
+
# tests it:
|
|
326
|
+
#
|
|
327
|
+
# * a Homogeneous Floating-point Aggregate — every scalar in it, however
|
|
328
|
+
# deeply nested, is the same floating type, and there are at most four
|
|
329
|
+
# of them — puts each member in a vector register of its own. This is
|
|
330
|
+
# the rule with no System V counterpart at all: struct { float a, b; }
|
|
331
|
+
# is s0 and s1 here where System V packs both into one xmm0.
|
|
332
|
+
# * any other aggregate of 16 bytes or less takes one or two consecutive
|
|
333
|
+
# integer registers, whatever its members are (a packed struct
|
|
334
|
+
# included: AAPCS64 has no unaligned-field escape to memory). One whose
|
|
335
|
+
# alignment is 16 must start at an even-numbered register, which is
|
|
336
|
+
# what `align16` asks the placer for.
|
|
337
|
+
# * anything larger travels by reference: the caller copies it and passes
|
|
338
|
+
# the copy's address.
|
|
339
|
+
def aggregate_plan(type)
|
|
340
|
+
base, count = homogeneous_float(type)
|
|
341
|
+
if base && count <= MAX_HFA_MEMBERS && type.size == base * count
|
|
342
|
+
kind = base == 8 ? :sse8 : :sse4
|
|
343
|
+
pieces = Array.new(count) { |i| AbiPiece.new(offset: base * i, size: base, kind: kind) }
|
|
344
|
+
return AggregatePlan.new(mode: :registers, pieces: pieces, align16: false)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
if type.size <= MAX_REGISTER_AGGREGATE
|
|
348
|
+
pieces = Array.new((type.size + 7) / 8) { |i| AbiPiece.new(offset: 8 * i, size: 8, kind: :gp) }
|
|
349
|
+
return AggregatePlan.new(mode: :registers, pieces: pieces, align16: type.alignment >= 16)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
AggregatePlan.new(mode: :by_reference, pieces: [], align16: false)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def placer
|
|
356
|
+
Placer.new(self)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
private
|
|
360
|
+
|
|
361
|
+
# Whether `type` is built entirely out of one floating type, and of how
|
|
362
|
+
# many of them: [element_size, count], or nil for anything else. A struct
|
|
363
|
+
# sums its members' counts, an array multiplies its element's by its
|
|
364
|
+
# length, a union takes the widest member's (all of them overlay the same
|
|
365
|
+
# storage), and a scalar float or double is one member of itself. A
|
|
366
|
+
# bit-field, an integer or a pointer anywhere inside disqualifies the
|
|
367
|
+
# whole aggregate at once.
|
|
368
|
+
#
|
|
369
|
+
# The count alone does not settle it: the caller also checks that the
|
|
370
|
+
# aggregate's size is exactly count * element_size, which is what rejects
|
|
371
|
+
# a struct that has been padded out of shape — struct { float a, b; }
|
|
372
|
+
# __attribute__((aligned(16))) has two float members but occupies 16
|
|
373
|
+
# bytes, and gcc passes it in x0/x1 rather than as an HFA.
|
|
374
|
+
def homogeneous_float(type)
|
|
375
|
+
if type.struct?
|
|
376
|
+
homogeneous_members(type)
|
|
377
|
+
elsif type.array?
|
|
378
|
+
return nil if type.length.nil? || type.length.zero?
|
|
379
|
+
|
|
380
|
+
element = homogeneous_float(type.element)
|
|
381
|
+
element && [element[0], element[1] * type.length]
|
|
382
|
+
elsif type.float?
|
|
383
|
+
[type.size, 1]
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# The [element_size, count] of an aggregate's members, or nil when they
|
|
388
|
+
# disagree (or when there are none, an aggregate C cannot form anyway).
|
|
389
|
+
def homogeneous_members(type)
|
|
390
|
+
return nil if type.members.nil? || type.members.empty?
|
|
391
|
+
|
|
392
|
+
size = nil
|
|
393
|
+
count = 0
|
|
394
|
+
type.members.each do |member|
|
|
395
|
+
return nil if member.bitfield?
|
|
396
|
+
|
|
397
|
+
element = homogeneous_float(member.type)
|
|
398
|
+
return nil if element.nil? || (size && size != element[0])
|
|
399
|
+
|
|
400
|
+
size = element[0]
|
|
401
|
+
count = type.union? ? [count, element[1]].max : count + element[1]
|
|
402
|
+
end
|
|
403
|
+
[size, count]
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Hands out x0..x7 and v0..v7 over one argument list, by 6.4.2 stage C.
|
|
407
|
+
# An argument never draws on both files here (a scalar is one or the
|
|
408
|
+
# other, an HFA is all vector, every other aggregate all integer, and an
|
|
409
|
+
# aggregate passed by reference is just a pointer), so the two counters
|
|
410
|
+
# advance independently.
|
|
411
|
+
#
|
|
412
|
+
# The difference from System V that matters is what an argument that does
|
|
413
|
+
# not fit leaves behind: the standard sets NGRN (or NSRN) to eight, so the
|
|
414
|
+
# file it overflowed is *exhausted* and every later argument of that class
|
|
415
|
+
# goes to the stack as well. The other file is untouched — a spilled HFA
|
|
416
|
+
# does not stop a following int from reaching x0.
|
|
417
|
+
class Placer
|
|
418
|
+
def initialize(convention)
|
|
419
|
+
@convention = convention
|
|
420
|
+
@ngrn = 0
|
|
421
|
+
@nsrn = 0
|
|
422
|
+
@nsaa = 0 # next stacked argument, counted in eightbytes (NSAA/8)
|
|
423
|
+
@pad_gp = 0
|
|
424
|
+
@pad_stack = 0
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# The alignment padding the most recent #place inserted *before* the
|
|
428
|
+
# argument it placed: one integer register (`pad_gp`) when a 16-byte
|
|
429
|
+
# aligned aggregate rounded NGRN up over an odd register so it lands in
|
|
430
|
+
# an aligned x-register pair (6.4.2 stage C.4), or one stack eightbyte
|
|
431
|
+
# (`pad_stack`) when such an aggregate spilled onto an odd stack offset
|
|
432
|
+
# and had to start at a 16-byte boundary (stage C.13's NSAA alignment).
|
|
433
|
+
# At most one is ever nonzero. The generator reads them to emit a matching
|
|
434
|
+
# pad slot, so the backend's sequential register/stack handout skips the
|
|
435
|
+
# same place the standard reserves.
|
|
436
|
+
attr_reader :pad_gp, :pad_stack
|
|
437
|
+
|
|
438
|
+
def place(request)
|
|
439
|
+
@pad_gp = 0
|
|
440
|
+
@pad_stack = 0
|
|
441
|
+
need_fp = request.kinds.count { |kind| kind == :sse4 || kind == :sse8 }
|
|
442
|
+
return place_fp(need_fp, request.mem_eightbytes) if need_fp.positive?
|
|
443
|
+
|
|
444
|
+
need_gp = request.kinds.count(:gp)
|
|
445
|
+
return place_gp(need_gp, request.align16, request.mem_eightbytes) if need_gp.positive?
|
|
446
|
+
|
|
447
|
+
# An :indirect_result pointer rides a register of its own (x8), which
|
|
448
|
+
# is not part of either file's budget.
|
|
449
|
+
:registers
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
private
|
|
453
|
+
|
|
454
|
+
def place_fp(count, mem_eightbytes)
|
|
455
|
+
if @nsrn + count <= @convention.fp_registers
|
|
456
|
+
@nsrn += count
|
|
457
|
+
:registers
|
|
458
|
+
else
|
|
459
|
+
@nsrn = @convention.fp_registers
|
|
460
|
+
@nsaa += mem_eightbytes
|
|
461
|
+
:stack
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def place_gp(count, align16, mem_eightbytes)
|
|
466
|
+
first = align16 && @ngrn.odd? ? @ngrn + 1 : @ngrn
|
|
467
|
+
if first + count <= @convention.gp_registers
|
|
468
|
+
@pad_gp = first - @ngrn
|
|
469
|
+
@ngrn = first + count
|
|
470
|
+
:registers
|
|
471
|
+
else
|
|
472
|
+
@ngrn = @convention.gp_registers
|
|
473
|
+
@pad_stack = 1 if align16 && @nsaa.odd?
|
|
474
|
+
@nsaa += @pad_stack + mem_eightbytes
|
|
475
|
+
:stack
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
class CallConvention
|
|
482
|
+
SYSTEM_V_AMD64 = SystemVAMD64Convention.new
|
|
483
|
+
AAPCS64 = AAPCS64Convention.new
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|