rubycc 1.0.0 → 1.1.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +26 -14
  4. data/data/verified_gems.json +85 -63
  5. data/exe/rubycc-ar +11 -3
  6. data/include/libc/sys/cdefs.h +12 -0
  7. data/lib/rubycc/backend/aarch64.rb +705 -117
  8. data/lib/rubycc/backend/slot_residency.rb +169 -0
  9. data/lib/rubycc/backend/x86_64.rb +924 -137
  10. data/lib/rubycc/command_line.rb +339 -0
  11. data/lib/rubycc/compile_error.rb +6 -3
  12. data/lib/rubycc/compiler.rb +17 -2
  13. data/lib/rubycc/diagnostics.rb +105 -0
  14. data/lib/rubycc/doctor/gemfile.rb +12 -3
  15. data/lib/rubycc/doctor/verified_gems.rb +5 -1
  16. data/lib/rubycc/driver.rb +66 -10
  17. data/lib/rubycc/front/ast.rb +18 -7
  18. data/lib/rubycc/front/constant_evaluator.rb +12 -0
  19. data/lib/rubycc/front/lexeme_reader.rb +3 -1
  20. data/lib/rubycc/front/parser.rb +51 -18
  21. data/lib/rubycc/ir/analysis.rb +82 -0
  22. data/lib/rubycc/ir/call_convention.rb +74 -7
  23. data/lib/rubycc/ir/generator.rb +319 -9
  24. data/lib/rubycc/ir/ir.rb +39 -1
  25. data/lib/rubycc/ir/promotion.rb +255 -0
  26. data/lib/rubycc/ir/simplify.rb +570 -0
  27. data/lib/rubycc/link/library_resolver.rb +17 -5
  28. data/lib/rubycc/link/partial_linker.rb +8 -1
  29. data/lib/rubycc/link/shared_linker.rb +2 -2
  30. data/lib/rubycc/mkmf_shim.rb +178 -12
  31. data/lib/rubycc/objfile/ar_archive.rb +13 -2
  32. data/lib/rubycc/objfile/elf_reader.rb +13 -2
  33. data/lib/rubycc/pkgconf/parser.rb +4 -0
  34. data/lib/rubycc/pkgconf/resolver.rb +3 -1
  35. data/lib/rubycc/pkgconf/system_path_filter.rb +8 -2
  36. data/lib/rubycc/preprocess/preprocessor.rb +161 -37
  37. data/lib/rubycc/preprocess/scanner.rb +69 -14
  38. data/lib/rubycc/preprocess/token_converter.rb +11 -1
  39. data/lib/rubycc/rmake/cli.rb +32 -4
  40. data/lib/rubycc/rmake/executor.rb +157 -226
  41. data/lib/rubycc/rmake/makefile.rb +35 -13
  42. data/lib/rubycc/rmake/parser.rb +8 -2
  43. data/lib/rubycc/rmake/rmake.rb +1 -0
  44. data/lib/rubycc/rmake/tool_command.rb +69 -0
  45. data/lib/rubycc/shell.rb +510 -0
  46. data/lib/rubycc/type.rb +23 -7
  47. data/lib/rubycc/version.rb +1 -1
  48. data/lib/rubycc.rb +12 -0
  49. data/lib/rubygems_plugin.rb +31 -3
  50. metadata +14 -3
data/lib/rubycc/driver.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rbconfig"
4
+ require_relative "diagnostics"
4
5
  require_relative "compile_error"
5
6
  require_relative "compiler"
6
7
  require_relative "preprocess/preprocessor"
@@ -67,7 +68,12 @@ module Rubycc
67
68
  end
68
69
 
69
70
  def initialize(argv, stdout: $stdout, stderr: $stderr)
70
- @argv = argv
71
+ # The command line crosses into rubycc here, so it is re-tagged as bytes
72
+ # (lib/rubycc.rb): every option is spelled in ASCII and every operand is a
73
+ # path or a macro spelling, none of which this driver reads as text. The
74
+ # array is rebuilt rather than mutated, since an in-process caller (rmake's
75
+ # executor) owns the one it passes.
76
+ @argv = argv.map { |arg| arg.encoding == Encoding::BINARY ? arg : arg.b }
71
77
  @out = stdout
72
78
  @err = stderr
73
79
  @inputs = [] # [{ path:, kind: }] in command-line order
@@ -83,13 +89,18 @@ module Rubycc
83
89
  @system_includes = true # -nostdinc clears this
84
90
  @default_libs = true # -nodefaultlibs clears this
85
91
  @target = nil # -target/--target; defaults to the host CPU
92
+ @warnings = true # -w clears this
86
93
  end
87
94
 
88
95
  def run
89
96
  return print_version if version_requested?
90
97
 
91
98
  parse
92
- dispatch
99
+ # Compiler warnings (Diagnostics.warn, today only #warning) follow this
100
+ # invocation's error stream, so an in-process caller that injected one —
101
+ # rmake running the Driver inside a step worker — captures them with
102
+ # everything else this run reports. `-w` discards them instead.
103
+ Diagnostics.to(@warnings ? @err : nil) { dispatch }
93
104
  0
94
105
  rescue UsageError => e
95
106
  @err.puts "#{PROG}: error: #{e.message}"
@@ -145,6 +156,7 @@ module Rubycc
145
156
  when "-fvisibility=default" then @default_visibility = :default; i + 1
146
157
  when "-fvisibility=internal" then @default_visibility = :internal; i + 1
147
158
  when "-fvisibility=protected" then @default_visibility = :protected; i + 1
159
+ when "-w" then @warnings = false; i + 1
148
160
  when "-nostdinc" then @system_includes = false; i + 1
149
161
  when "-nodefaultlibs" then @default_libs = false; i + 1
150
162
  when "-target", "--target" then @target = normalize_target(value(arg, i)); i + 2
@@ -186,9 +198,35 @@ module Rubycc
186
198
  # Whether `arg` is a documented gcc flag family this toolchain does not model
187
199
  # but accepts without complaint: the optimization (`-O*`), debug (`-g*`),
188
200
  # 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.
201
+ # selector (`-std=…`), and the fixed bare set above.
202
+ #
203
+ # The warning switches deserve their decision spelled out, now that there is
204
+ # a warning to switch (Diagnostics.warn, reached today only by `#warning`):
205
+ #
206
+ # * `-w` (suppress all warnings) **is honored** — see #handle_arg. It costs
207
+ # one flag and cannot change what compiles, only what is printed, and it
208
+ # was not even in the ignore list before (lowercase, so the `-W*` family
209
+ # regex misses it): a build passing `-w` drew "unknown option '-w'
210
+ # ignored", which is itself noise of the kind `-w` asks to be spared.
211
+ # It silences the compiler's diagnostics only, not this driver's own
212
+ # "PROG: warning: …" lines, which is where gcc draws the same line
213
+ # (measured 2026-08-19, gcc 13.3.0: `gcc -w -c a.c b.o` prints no #warning
214
+ # but still prints "gcc: warning: b.o: linker input file unused …").
215
+ # * `-Werror` (and `-Werror=…`) **is not honored**: it stays accepted and
216
+ # ignored. Honoring it would mean failing a build over the one warning
217
+ # rubycc happens to implement while staying silent about the hundreds gcc
218
+ # would have raised on the same source — a promotion of an arbitrary
219
+ # subset, which is worse than not promoting at all. It would also aim
220
+ # straight at what this channel was built for: a portability header's
221
+ # `#warning "unrecognized compiler"` would go back to killing the build,
222
+ # the very failure #warning support exists to end. Revisit if rubycc ever
223
+ # grows a warning set worth calling a set.
224
+ # * The individual `-Wfoo` / `-Wno-foo` selectors are likewise accepted and
225
+ # ignored: rubycc has no warning categories to select between yet.
226
+ #
227
+ # A machine switch (`-m*`) is deliberately excluded — it names a target
228
+ # capability this toolchain does not honor, so it is warned about like any
229
+ # other unmodelled option.
192
230
  def silently_ignored?(arg)
193
231
  SILENT_IGNORE_EXACT.include?(arg) ||
194
232
  arg.match?(/\A-(?:O|g|W|f)/) || arg.start_with?("-std=")
@@ -389,7 +427,8 @@ module Rubycc
389
427
  end
390
428
 
391
429
  def compile_source(input)
392
- Compiler.new.compile(File.read(input[:path]), filename: input[:path],
430
+ # Bytes, not locale-encoded text: see Preprocess::Scanner's class comment.
431
+ Compiler.new.compile(File.binread(input[:path]), filename: input[:path],
393
432
  include_paths: @include_paths, pic: @pic, defines: @defines,
394
433
  system_includes: @system_includes, target: target, libc: libc,
395
434
  default_visibility: @default_visibility)
@@ -404,12 +443,12 @@ module Rubycc
404
443
  # byte-faithful copy of gcc's `-E` output; it is enough for a probe that only
405
444
  # needs macros expanded and headers included. Non-source inputs are ignored.
406
445
  def preprocess_only
407
- text = +""
446
+ text = +"".b
408
447
  @inputs.each do |input|
409
448
  next unless input[:kind] == :source
410
449
 
411
450
  tokens = preprocessor_for_target.preprocess(
412
- File.read(input[:path]), filename: input[:path],
451
+ File.binread(input[:path]), filename: input[:path],
413
452
  include_paths: @include_paths, defines: @defines,
414
453
  system_includes: @system_includes
415
454
  )
@@ -437,7 +476,7 @@ module Rubycc
437
476
  end
438
477
 
439
478
  def render_preprocessed(tokens)
440
- out = +""
479
+ out = +"".b
441
480
  previous = nil
442
481
  tokens.each do |token|
443
482
  if previous.nil?
@@ -447,13 +486,30 @@ module Rubycc
447
486
  elsif token.space_before
448
487
  out << " "
449
488
  end
450
- out << token.text
489
+ out << token_bytes(token.text)
451
490
  previous = token
452
491
  end
453
492
  out << "\n" unless out.empty?
454
493
  out
455
494
  end
456
495
 
496
+ # A token's spelling as bytes. Almost every token comes from the scanner and
497
+ # is ASCII-8BIT already (see Preprocess::Scanner); the exceptions are the
498
+ # ones the preprocessor synthesizes rather than scans, and __FILE__ is the
499
+ # one that matters — it expands to a string literal spelled with the file
500
+ # name this driver was handed, which carries the locale's encoding. Appending
501
+ # such a string to a buffer that already holds raw bytes (a UTF-8 string
502
+ # literal out of the source) is an Encoding::CompatibilityError: -E would
503
+ # abort with a Ruby backtrace instead of printing the text it was asked for.
504
+ # Re-tagging is not transcoding — the bytes are the same either way — and it
505
+ # costs an allocation only for the strings that are actually mixed, since a
506
+ # spelling that is already bytes, or is pure ASCII, is compatible as it is.
507
+ def token_bytes(text)
508
+ return text if text.encoding == Encoding::BINARY || text.ascii_only?
509
+
510
+ text.b
511
+ end
512
+
457
513
  # --- diagnostics -------------------------------------------------------
458
514
 
459
515
  def warning(message)
@@ -12,9 +12,11 @@ module Rubycc
12
12
  IntLit = Data.define(:value, :token, :type)
13
13
 
14
14
  # Floating-point literal. `value` is a Ruby Float and `type` its
15
- # Rubycc::Type — Type::Float for an f/F-suffixed constant, Type::Double
16
- # otherwise (a plain or l/L-suffixed one) fixed by the parser from the
17
- # constant's suffix (6.4.4.2).
15
+ # Rubycc::Type — Type::Float for an f/F-suffixed constant,
16
+ # Type::LongDouble for an l/L-suffixed one and Type::Double for an
17
+ # unsuffixed one — fixed by the parser from the constant's suffix
18
+ # (6.4.4.2). `value` is a double in every case: Type::LongDouble names a
19
+ # double's representation under a different type name (see Type).
18
20
  FloatLit = Data.define(:value, :type, :token)
19
21
 
20
22
  # String literal. `value` is the escape-resolved bytes as an ASCII-8BIT
@@ -394,14 +396,23 @@ module Rubycc
394
396
  # produces no code and no side effects.
395
397
  BuiltinConstantP = Data.define(:expr, :token)
396
398
 
397
- # "__builtin_ctz/ctzll/clz/clzll ( x )": counts the trailing (ctz) or
398
- # leading (clz) zero bits of an integer, typed int. `operand` is the value
399
- # scanned, `direction` is :forward for ctz or :reverse for clz, and `width`
400
- # is the operand's byte width (4 for the plain form, 8 for the "ll" form).
399
+ # "__builtin_ctz/ctzl/ctzll/clz/clzl/clzll ( x )": counts the trailing
400
+ # (ctz) or leading (clz) zero bits of an integer, typed int. `operand` is
401
+ # the value scanned, `direction` is :forward for ctz or :reverse for clz,
402
+ # and `width` is the operand's byte width (4 for the plain form, 8 for the
403
+ # "l" and "ll" ones — `long` and `long long` are both 8 bytes here).
401
404
  # `x == 0` is undefined behavior (gcc), so no zero handling is implied.
402
405
  # `token` is the builtin keyword.
403
406
  BuiltinBitScan = Data.define(:operand, :direction, :width, :token)
404
407
 
408
+ # "__builtin_popcount/popcountl/popcountll ( x )": counts the set bits of
409
+ # an integer, typed int. `operand` is the value counted and `width` its
410
+ # byte width (4 for the plain form, 8 for the "l" and "ll" ones, `long`
411
+ # and `long long` being the same width on both targets). Unlike a bit
412
+ # scan this is total: a zero operand counts zero bits, as in gcc. `token`
413
+ # is the builtin keyword.
414
+ BuiltinPopcount = Data.define(:operand, :width, :token)
415
+
405
416
  # "__builtin_add/sub/mul_overflow ( a , b , res )": computes "a op b" with
406
417
  # infinite precision, stores the result converted to the type `res` points
407
418
  # at (wrapping or truncating like any integer conversion), and yields int 1
@@ -151,6 +151,8 @@ module Rubycc
151
151
  evaluate_builtin_constant_p(node)
152
152
  when AST::BuiltinBitScan
153
153
  evaluate_builtin_bit_scan(node)
154
+ when AST::BuiltinPopcount
155
+ evaluate_builtin_popcount(node)
154
156
  else
155
157
  # Every other node — VariableRef, Call, Assignment,
156
158
  # CompoundAssignment, IncDec, MemberAccess, Subscript, StringLit,
@@ -621,6 +623,16 @@ module Rubycc
621
623
  end
622
624
  end
623
625
 
626
+ # __builtin_popcount(x) folds to the number of set bits in a constant
627
+ # operand, over its `width`-byte value — the same mask a bit scan applies,
628
+ # which is what makes __builtin_popcount(-1) the 32 gcc gives (the operand
629
+ # is counted as an unsigned int) rather than an infinite run of sign bits.
630
+ # There is no undefined case to leave unfolded: zero counts zero bits.
631
+ def evaluate_builtin_popcount(node)
632
+ value = evaluate(node.operand) & ((1 << (node.width * 8)) - 1)
633
+ value.to_s(2).count("1")
634
+ end
635
+
624
636
  # Whether `type` is an incomplete tagged type with no size or alignment: a
625
637
  # struct/union never completed, or an incomplete (forward-referenced) enum.
626
638
  def incomplete_aggregate?(type)
@@ -39,7 +39,9 @@ module Rubycc
39
39
  __builtin_va_start __builtin_va_arg __builtin_va_end __builtin_va_copy
40
40
  __builtin_expect __builtin_alloca __builtin_offsetof
41
41
  __builtin_constant_p __builtin_choose_expr
42
- __builtin_ctz __builtin_ctzll __builtin_clz __builtin_clzll
42
+ __builtin_ctz __builtin_ctzl __builtin_ctzll
43
+ __builtin_clz __builtin_clzl __builtin_clzll
44
+ __builtin_popcount __builtin_popcountl __builtin_popcountll
43
45
  __builtin_unreachable __builtin_memcpy
44
46
  __builtin_add_overflow __builtin_sub_overflow __builtin_mul_overflow
45
47
  __atomic_load_n __atomic_store_n __atomic_exchange_n
@@ -739,8 +739,9 @@ module Rubycc
739
739
  # left out — type `int`, located at the identifier itself (6.9.1p6). C90
740
740
  # spelled that default out; C11 instead requires every identifier to be
741
741
  # declared, but gcc keeps accepting the omission with a -Wimplicit-int
742
- # warning (measured), and a warning is not a channel this compiler has, so
743
- # the older, permissive reading is the one implemented.
742
+ # warning (measured), and this compiler warns about nothing in the front
743
+ # end (Diagnostics.warn carries only #warning so far), so the older,
744
+ # permissive reading is the one implemented.
744
745
  def old_style_parameters(identifier_list, declared)
745
746
  identifier_list.names.map do |name_tok|
746
747
  type, const, decl_tok = declared[name_tok.value]
@@ -865,9 +866,8 @@ module Rubycc
865
866
  else
866
867
  # An init attribute on an object has nothing to register — there is no
867
868
  # function to call — so it is refused here. gcc only warns ("attribute
868
- # ignored"), but a warning this compiler has no channel for would be a
869
- # silent drop, and a dropped initializer is invisible until the program
870
- # misbehaves at run time.
869
+ # ignored"), but this parser emits no warnings, and a silently dropped
870
+ # initializer is invisible until the program misbehaves at run time.
871
871
  reject_init_attributes(attributes)
872
872
  register_visibility_attributes(name_tok.value, attributes)
873
873
  parse_global_declarator(type, name_tok, pointer_quals, spec_info)
@@ -1282,15 +1282,19 @@ module Rubycc
1282
1282
  return normalize_standalone(Type::Void, "void", specs, tok) if counts["void"].positive?
1283
1283
  return normalize_standalone(Type::Bool, "_Bool", specs, tok) if counts["_Bool"].positive?
1284
1284
  return normalize_standalone(Type::Float, "float", specs, tok) if counts["float"].positive?
1285
- # `double` stands alone or pairs with a single `long` ("long double",
1286
- # treated as `double` here); any other keyword, a second `double` or a
1287
- # second `long` is an ill-formed combination.
1285
+ # `double` stands alone or pairs with a single `long` ("long double");
1286
+ # any other keyword, a second `double` or a second `long` is an
1287
+ # ill-formed combination. The two are separate types here even though
1288
+ # they share a representation: a `long double` handed to a variadic
1289
+ # function must be converted to the target's own long-double format, and
1290
+ # the only way the generator can know to do that is for the name to
1291
+ # survive the declaration (see Type::LongDouble).
1288
1292
  if counts["double"].positive?
1289
1293
  non_long = specs.reject { |s| s == "long" }
1290
1294
  unless non_long == ["double"] && counts["long"] <= 1
1291
1295
  error_at(tok, "cannot combine 'double' with other type specifiers")
1292
1296
  end
1293
- return Type::Double
1297
+ return counts["long"].positive? ? Type::LongDouble : Type::Double
1294
1298
  end
1295
1299
 
1296
1300
  # `__int128` (a GNU keyword) stands alone or pairs with a single
@@ -1552,7 +1556,7 @@ module Rubycc
1552
1556
  # The run-order number of a constructor/destructor. The window and its
1553
1557
  # wording follow gcc's measured behavior (see MAX_INIT_PRIORITY); the
1554
1558
  # 0..100 range gcc reserves for the implementation is accepted here, since
1555
- # gcc only warns about it and this compiler has no warning channel.
1559
+ # gcc only warns about it and this parser emits no warnings.
1556
1560
  def parse_init_priority_argument(name)
1557
1561
  expr = parse_parenthesized_constant
1558
1562
  value = evaluate_constant_expression(expr, "'#{name}' attribute argument is not an integer constant",
@@ -3407,12 +3411,16 @@ module Rubycc
3407
3411
  parse_builtin_choose_expr
3408
3412
  elsif peek.keyword?("__builtin_ctz")
3409
3413
  parse_builtin_bit_scan(:forward, 4)
3410
- elsif peek.keyword?("__builtin_ctzll")
3414
+ elsif peek.keyword?("__builtin_ctzl") || peek.keyword?("__builtin_ctzll")
3411
3415
  parse_builtin_bit_scan(:forward, 8)
3412
3416
  elsif peek.keyword?("__builtin_clz")
3413
3417
  parse_builtin_bit_scan(:reverse, 4)
3414
- elsif peek.keyword?("__builtin_clzll")
3418
+ elsif peek.keyword?("__builtin_clzl") || peek.keyword?("__builtin_clzll")
3415
3419
  parse_builtin_bit_scan(:reverse, 8)
3420
+ elsif peek.keyword?("__builtin_popcount")
3421
+ parse_builtin_popcount(4)
3422
+ elsif peek.keyword?("__builtin_popcountl") || peek.keyword?("__builtin_popcountll")
3423
+ parse_builtin_popcount(8)
3416
3424
  elsif peek.keyword?("__builtin_unreachable")
3417
3425
  parse_builtin_unreachable
3418
3426
  elsif peek.keyword?("__builtin_memcpy")
@@ -3631,10 +3639,15 @@ module Rubycc
3631
3639
  selector.zero? ? when_false : when_true
3632
3640
  end
3633
3641
 
3634
- # "__builtin_ctz/ctzll/clz/clzll ( assignment-expression )": one integer
3635
- # operand whose trailing (`:forward`) or leading (`:reverse`) zero bits are
3636
- # counted over `width` bytes. The generator settles the operand's integer
3637
- # type check and lowers the bit scan; the result is int.
3642
+ # "__builtin_ctz/clz ( assignment-expression )", in any of their three
3643
+ # spellings: one integer operand whose trailing (`:forward`) or leading
3644
+ # (`:reverse`) zero bits are counted over `width` bytes. The generator
3645
+ # settles the operand's integer type check and lowers the bit scan; the
3646
+ # result is int.
3647
+ #
3648
+ # The "l" spelling shares the "ll" one's width because `long` and `long
3649
+ # long` are both 8 bytes on every target rubycc emits for; a target where
3650
+ # they differed would have to split them here.
3638
3651
  def parse_builtin_bit_scan(direction, width)
3639
3652
  keyword_tok = advance # the "__builtin_ctz"/... keyword
3640
3653
  expect_punct("(")
@@ -3643,6 +3656,18 @@ module Rubycc
3643
3656
  AST::BuiltinBitScan.new(operand, direction, width, keyword_tok)
3644
3657
  end
3645
3658
 
3659
+ # "__builtin_popcount/popcountl/popcountll ( assignment-expression )": one
3660
+ # integer operand whose set bits are counted over `width` bytes, the width
3661
+ # coming from the spelling exactly as it does for a bit scan. The generator
3662
+ # checks the operand's type and lowers the count; the result is int.
3663
+ def parse_builtin_popcount(width)
3664
+ keyword_tok = advance # the "__builtin_popcount"/... keyword
3665
+ expect_punct("(")
3666
+ operand = parse_assignment_expression
3667
+ expect_punct(")")
3668
+ AST::BuiltinPopcount.new(operand, width, keyword_tok)
3669
+ end
3670
+
3646
3671
  # "__builtin_add/sub/mul_overflow ( a , b , res )": exactly three arguments,
3647
3672
  # parsed as an ordinary argument list so a wrong count is an arity
3648
3673
  # diagnostic. The keyword decides only the operator; the operand types are
@@ -3837,8 +3862,16 @@ module Rubycc
3837
3862
  elsif tok.type == :float
3838
3863
  advance
3839
3864
  # The suffix (from the lexer) fixes the constant's type: "f"/"F" is
3840
- # float, everything else (plain, or "l"/"L" long double) is double.
3841
- type = tok.suffix == "f" ? Type::Float : Type::Double
3865
+ # float, "l"/"L" long double, and no suffix double. A long-double
3866
+ # constant carries only a double's value and precision (that is what
3867
+ # Type::LongDouble models), but it must keep the name so that
3868
+ # "printf(\"%Lg\", 1.5L)" passes the argument in the long-double form
3869
+ # the callee reads.
3870
+ type = case tok.suffix
3871
+ when "f" then Type::Float
3872
+ when "l" then Type::LongDouble
3873
+ else Type::Double
3874
+ end
3842
3875
  AST::FloatLit.new(tok.value, type, tok)
3843
3876
  elsif tok.type == :string
3844
3877
  advance
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ir"
4
+ require_relative "simplify"
5
+
6
+ module Rubycc
7
+ module IR
8
+ # One function's instruction list, counted once and handed to everyone who
9
+ # needs the count.
10
+ #
11
+ # Three consumers ask the same questions of the same flat list: IR::Simplify
12
+ # decides its rewrites from how often each virtual register is read and
13
+ # written, IR::Promotion decides which registers are worth a machine
14
+ # register from the same counts plus the transient set, and a backend needs
15
+ # that transient set again on every store it emits. Each of them used to
16
+ # walk the list for itself — the census three times over, the transient set
17
+ # twice, the "is every op one we recognize?" test four times — so a function
18
+ # was scanned a dozen times to answer four questions.
19
+ #
20
+ # This object is that answer, computed once on the way through and passed
21
+ # along: IR::Analysis.simplified runs the rewrites and keeps the census they
22
+ # maintained, and Compiler hands the result straight to the backend. Nothing
23
+ # here decides anything — every rule still lives in Simplify and Promotion —
24
+ # and nothing is cached that the list could invalidate, because a rewritten
25
+ # list gets an object of its own.
26
+ class Analysis
27
+ # `function` is the list this census describes (the *rewritten* one, where
28
+ # a rewrite happened), and `known` says whether every op in it is one
29
+ # IR::Simplify can enumerate the reads of. When it is false the counts are
30
+ # not to be trusted and every consumer refuses the function outright,
31
+ # which is the same fail-safe #run applies.
32
+ attr_reader :function, :insts, :param_count, :vreg_count, :reads, :writes
33
+
34
+ # The census of a function nothing has rewritten — what a backend handed a
35
+ # function directly (a test, or any caller that skips the pass) has to
36
+ # fall back on.
37
+ def self.of(function)
38
+ reads, writes = Simplify.census(function.insts, function.vreg_count)
39
+ new(function, reads, writes)
40
+ end
41
+
42
+ # IR::Simplify.run, plus the census the rewrites kept true as they went.
43
+ def self.simplified(function)
44
+ rewritten, reads, writes = Simplify.run_counted(function)
45
+ new(rewritten, reads, writes)
46
+ end
47
+
48
+ def initialize(function, reads, writes)
49
+ @function = function
50
+ @insts = function.insts
51
+ @param_count = function.param_count
52
+ @vreg_count = function.vreg_count
53
+ @reads = reads
54
+ @writes = writes
55
+ end
56
+
57
+ # Whether every op in the list is one whose reads IR::Simplify can
58
+ # enumerate. A function containing anything else is refused whole, by the
59
+ # pass and by every consumer of this object.
60
+ def known?
61
+ !@reads.nil?
62
+ end
63
+
64
+ # The transient virtual registers (IR::Simplify#transient_flags), as an
65
+ # array indexed by register number. Computed on first ask and kept, which
66
+ # is what stops IR::Promotion and the backend from computing it twice for
67
+ # the same list.
68
+ def transient
69
+ @transient ||=
70
+ if known?
71
+ Simplify.transient_flags(@insts, @param_count, @reads, @writes, @vreg_count)
72
+ else
73
+ EMPTY_FLAGS
74
+ end
75
+ end
76
+
77
+ # The answer for a function with no transient at all. Frozen and shared:
78
+ # every read of it is an index that finds nothing.
79
+ EMPTY_FLAGS = [].freeze
80
+ end
81
+ end
82
+ end
@@ -7,8 +7,8 @@ module Rubycc
7
7
  # One piece of a by-value aggregate as its convention moves it: the byte
8
8
  # `offset` within the aggregate the piece is read from (and written back to
9
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).
10
+ # travels in (:gp an integer register, :sse4/:sse8/:sse16 a vector one, :mem
11
+ # a stack eightbyte).
12
12
  #
13
13
  # An aggregate is never moved as a whole — the generator takes it apart into
14
14
  # these pieces, loads each into a virtual register and hands the backend one
@@ -22,6 +22,14 @@ module Rubycc
22
22
  # single-precision registers on aarch64.
23
23
  AbiPiece = Data.define(:offset, :size, :kind)
24
24
 
25
+ # The piece kinds that consume a vector register, so that a placer counting
26
+ # a request's demand on the vector file names them in one place. :sse4 and
27
+ # :sse8 carry a single and a double; :sse16 carries a whole 16-byte value in
28
+ # one register (AAPCS64's quad-precision `long double`) and, unlike the
29
+ # other two, names the *address* of that value rather than the value itself,
30
+ # since no 8-byte virtual-register slot could hold it.
31
+ FP_KINDS = %i[sse4 sse8 sse16].freeze
32
+
25
33
  # How a convention passes one aggregate by value:
26
34
  # :registers — `pieces` names each register-borne piece;
27
35
  # :memory — the value is laid into the caller's stack argument area
@@ -120,6 +128,28 @@ module Rubycc
120
128
  raise NotImplementedError
121
129
  end
122
130
 
131
+ # The target's `long double` format, which decides the 16-byte image the
132
+ # generator builds for a variadic `long double` argument:
133
+ # :x87_extended80 (an explicit-integer-bit 64-bit significand plus a
134
+ # sign/15-bit-exponent halfword, occupying the low ten of sixteen bytes)
135
+ # or :binary128 (IEEE 754 quadruple precision). Both are supersets of
136
+ # binary64, so the conversion from the double rubycc actually holds is
137
+ # exact; see IR::Generator#emit_long_double_image.
138
+ def long_double_format
139
+ raise NotImplementedError
140
+ end
141
+
142
+ # How that 16-byte image travels in a variadic call's variable part, as an
143
+ # AggregatePlan over the image (not over Type::LongDouble, whose #size is
144
+ # the 8 bytes rubycc computes in). The two targets disagree completely:
145
+ # System V gives `long double` the X87/X87UP classes, which no register
146
+ # can carry, so the image goes in the stack argument area; AAPCS64 makes
147
+ # it an ordinary quad-precision value that rides one whole vector
148
+ # register. Both align its stack slot to 16 bytes, hence align16.
149
+ def long_double_plan
150
+ raise NotImplementedError
151
+ end
152
+
123
153
  # A fresh running placement of one argument list (see the Placer classes).
124
154
  def placer
125
155
  raise NotImplementedError
@@ -174,6 +204,20 @@ module Rubycc
174
204
  Placer.new(self)
175
205
  end
176
206
 
207
+ # psABI 3.2.3: `long double` is the 80-bit x87 extended format, classified
208
+ # X87 (its low eightbyte) and X87UP (its high one). Neither class has a
209
+ # register to be handed out in an argument list, so the value always
210
+ # passes in memory, in a 16-byte slot the psABI aligns to 16 (measured:
211
+ # gcc 13 reserves a pad eightbyte ahead of it when the stack argument
212
+ # area has reached an odd offset).
213
+ def long_double_format
214
+ :x87_extended80
215
+ end
216
+
217
+ def long_double_plan
218
+ AggregatePlan.new(mode: :memory, pieces: CallConvention.memory_pieces(16), align16: true)
219
+ end
220
+
177
221
  private
178
222
 
179
223
  # Whether any scalar field of `type`, placed at absolute byte offset
@@ -285,7 +329,7 @@ module Rubycc
285
329
  def place(request)
286
330
  @pad_stack = 0
287
331
  need_gp = request.kinds.count(:gp)
288
- need_sse = request.kinds.count { |kind| kind == :sse4 || kind == :sse8 }
332
+ need_sse = request.kinds.count { |kind| FP_KINDS.include?(kind) }
289
333
  spills = request.kinds.all?(:mem) ||
290
334
  !(@next_gp + need_gp <= @convention.gp_registers &&
291
335
  @next_sse + need_sse <= @convention.fp_registers)
@@ -356,6 +400,24 @@ module Rubycc
356
400
  Placer.new(self)
357
401
  end
358
402
 
403
+ # AAPCS64 6.4.2: `long double` is IEEE 754 binary128, a Quad-precision
404
+ # Floating-point type, and stage C.1 gives one to the next free SIMD&FP
405
+ # register whole — v[NSRN], read as a 16-byte q register. The rule is the
406
+ # same in the variable part of a variadic call on this ABI (measured:
407
+ # gcc 13 puts the ninth argument of "printf(fmt, 1.0..7.0, ld, 9)" in q7),
408
+ # unlike the Apple variant, which stacks every anonymous argument.
409
+ # An overflowing quad spills to an NSAA the standard rounds up to the
410
+ # type's 16-byte natural alignment (stage C.13), hence align16.
411
+ def long_double_format
412
+ :binary128
413
+ end
414
+
415
+ def long_double_plan
416
+ AggregatePlan.new(mode: :registers,
417
+ pieces: [AbiPiece.new(offset: 0, size: 16, kind: :sse16)],
418
+ align16: true)
419
+ end
420
+
359
421
  private
360
422
 
361
423
  # Whether `type` is built entirely out of one floating type, and of how
@@ -438,8 +500,8 @@ module Rubycc
438
500
  def place(request)
439
501
  @pad_gp = 0
440
502
  @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?
503
+ need_fp = request.kinds.count { |kind| FP_KINDS.include?(kind) }
504
+ return place_fp(need_fp, request.align16, request.mem_eightbytes) if need_fp.positive?
443
505
 
444
506
  need_gp = request.kinds.count(:gp)
445
507
  return place_gp(need_gp, request.align16, request.mem_eightbytes) if need_gp.positive?
@@ -451,13 +513,18 @@ module Rubycc
451
513
 
452
514
  private
453
515
 
454
- def place_fp(count, mem_eightbytes)
516
+ # A spilled vector argument aligns NSAA to its own natural alignment the
517
+ # way an integer-register aggregate does; only a quad ever asks for 16
518
+ # (align16), since a single or a double is 8-aligned and a stack slot
519
+ # already is.
520
+ def place_fp(count, align16, mem_eightbytes)
455
521
  if @nsrn + count <= @convention.fp_registers
456
522
  @nsrn += count
457
523
  :registers
458
524
  else
459
525
  @nsrn = @convention.fp_registers
460
- @nsaa += mem_eightbytes
526
+ @pad_stack = 1 if align16 && @nsaa.odd?
527
+ @nsaa += @pad_stack + mem_eightbytes
461
528
  :stack
462
529
  end
463
530
  end