diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml
index 0120f1213082c3..c0fc75ad7d9b2f 100644
--- a/.github/actions/setup/directories/action.yml
+++ b/.github/actions/setup/directories/action.yml
@@ -175,7 +175,7 @@ runs:
echo final='rmdir ${{ inputs.builddir }}' >> $GITHUB_OUTPUT
- name: clean
- uses: gacts/run-and-post-run@d803f6920adc9a47eeac4cb6c93dbc2e2890c684 # v1.4.2
+ uses: gacts/run-and-post-run@81b6ce503cde93862cec047c54652e45c5dca991 # v1.4.3
with:
working-directory:
post: |
diff --git a/doc/stringio/getbyte.rdoc b/doc/stringio/getbyte.rdoc
new file mode 100644
index 00000000000000..48c334b5252a58
--- /dev/null
+++ b/doc/stringio/getbyte.rdoc
@@ -0,0 +1,29 @@
+Reads and returns the next integer byte (not character) from the stream:
+
+ s = 'foo'
+ s.bytes # => [102, 111, 111]
+ strio = StringIO.new(s)
+ strio.getbyte # => 102
+ strio.getbyte # => 111
+ strio.getbyte # => 111
+
+Returns +nil+ if at end-of-stream:
+
+ strio.eof? # => true
+ strio.getbyte # => nil
+
+Returns a byte, not a character:
+
+ s = 'тест'
+ s.bytes # => [209, 130, 208, 181, 209, 129, 209, 130]
+ strio = StringIO.new(s)
+ strio.getbyte # => 209
+ strio.getbyte # => 130
+
+ s = 'こんにちは'
+ s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175]
+ strio = StringIO.new(s)
+ strio.getbyte # => 227
+ strio.getbyte # => 129
+
+Related: StringIO.getc.
diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc
new file mode 100644
index 00000000000000..892c3feb53a9bf
--- /dev/null
+++ b/doc/stringio/gets.rdoc
@@ -0,0 +1,98 @@
+Reads and returns a line from the stream;
+returns +nil+ if at end-of-stream.
+
+Side effects:
+
+- Increments stream position by the number of bytes read.
+- Assigns the return value to global variable $_.
+
+With no arguments given, reads a line using the default record separator
+(global variable $/,* whose initial value is "\n"):
+
+ strio = StringIO.new(TEXT)
+ strio.pos # => 0
+ strio.gets # => "First line\n"
+ strio.pos # => 11
+ $_ # => "First line\n"
+ strio.gets # => "Second line\n"
+ strio.read # => "\nFourth line\nFifth line\n"
+ strio.eof? # => true
+ strio.gets # => nil
+
+ strio = StringIO.new('тест') # Four 2-byte characters.
+ strio.pos # => 0
+ strio.gets # => "тест"
+ strio.pos # => 8
+
+Argument +sep+
+
+With only string argument +sep+ given, reads a line using that string as the record separator:
+
+ strio = StringIO.new(TEXT)
+ strio.gets(' ') # => "First "
+ strio.gets(' ') # => "line\nSecond "
+ strio.gets(' ') # => "line\n\nFourth "
+
+Argument +limit+
+
+With only integer argument +limit+ given,
+reads a line using the default record separator;
+limits the size (in characters) of each line to the given limit:
+
+ strio = StringIO.new(TEXT)
+ strio.gets(10) # => "First line"
+ strio.gets(10) # => "\n"
+ strio.gets(10) # => "Second lin"
+ strio.gets(10) # => "e\n"
+
+Arguments +sep+ and +limit+
+
+With arguments +sep+ and +limit+ both given, honors both:
+
+ strio = StringIO.new(TEXT)
+ strio.gets(' ', 10) # => "First "
+ strio.gets(' ', 10) # => "line\nSecon"
+ strio.gets(' ', 10) # => "d "
+
+Position
+
+As stated above, method +gets+ reads and returns the next line in the stream.
+
+In the examples above each +strio+ object starts with its position at beginning-of-stream;
+but in other cases the position may be anywhere:
+
+ strio = StringIO.new(TEXT)
+ strio.pos = 12
+ strio.gets # => "econd line\n"
+
+The position need not be at a character boundary:
+
+ strio = StringIO.new('тест') # Four 2-byte characters.
+ strio.pos = 2 # At beginning of second character.
+ strio.gets # => "ест"
+ strio.pos = 3 # In middle of second character.
+ strio.gets # => "\xB5ст"
+
+Special Record Separators
+
+Like some methods in class IO, method +gets+ honors two special record separators;
+see {Special Line Separators}[https://docs.ruby-lang.org/en/master/IO.html#class-IO-label-Special+Line+Separator+Values]:
+
+ strio = StringIO.new(TEXT)
+ strio.gets('') # Read "paragraph" (up to empty line).
+ # => "First line\nSecond line\n\n"
+
+ strio = StringIO.new(TEXT)
+ strio.gets(nil) # "Slurp": read all.
+ # => "First line\nSecond line\n\nFourth line\nFifth line\n"
+
+Keyword Argument +chomp+
+
+With keyword argument +chomp+ given as +true+ (the default is +false+),
+removes the trailing newline (if any) from the returned line:
+
+ strio = StringIO.new(TEXT)
+ strio.gets # => "First line\n"
+ strio.gets(chomp: true) # => "Second line"
+
+Related: StringIO.each_line.
diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec
index 1554dcdb304bef..44e6b65142e2d3 100644
--- a/ext/io/wait/io-wait.gemspec
+++ b/ext/io/wait/io-wait.gemspec
@@ -15,20 +15,20 @@ Gem::Specification.new do |spec|
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z`.split("\x0").reject do |f|
- File.identical?(f, __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)})
- end
- end
+ jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby'
+ dir, gemspec = File.split(__FILE__)
+ excludes = [
+ *%w[:^/.git* :^/Gemfile* :^/Rakefile* :^/bin/ :^/test/ :^/rakelib/ :^*.java],
+ *(jruby ? %w[:^/ext/io] : %w[:^/ext/java]),
+ ":(exclude,literal,top)#{gemspec}"
+ ]
+ files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0")
+
+ spec.files = files
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]
- jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby'
- spec.files.delete_if do |f|
- f.end_with?(".java") or
- f.start_with?("ext/") && (jruby ^ f.start_with?("ext/java/"))
- end
if jruby
spec.platform = 'java'
spec.files << "lib/io/wait.jar"
diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c
index cf3e06a71f130e..d66768a2c50279 100644
--- a/ext/stringio/stringio.c
+++ b/ext/stringio/stringio.c
@@ -990,10 +990,10 @@ strio_getc(VALUE self)
/*
* call-seq:
- * getbyte -> byte or nil
+ * getbyte -> integer or nil
+ *
+ * :include: stringio/getbyte.rdoc
*
- * Reads and returns the next 8-bit byte from the stream;
- * see {Byte IO}[rdoc-ref:IO@Byte+IO].
*/
static VALUE
strio_getbyte(VALUE self)
@@ -1428,9 +1428,8 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr)
* gets(limit, chomp: false) -> string or nil
* gets(sep, limit, chomp: false) -> string or nil
*
- * Reads and returns a line from the stream;
- * assigns the return value to $_;
- * see {Line IO}[rdoc-ref:IO@Line+IO].
+ * :include: stringio/gets.rdoc
+ *
*/
static VALUE
strio_gets(int argc, VALUE *argv, VALUE self)
diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb
index dd20d65080ff00..0ebcbd789d4038 100644
--- a/lib/rubygems/package/tar_header.rb
+++ b/lib/rubygems/package/tar_header.rb
@@ -56,7 +56,7 @@ class Gem::Package::TarHeader
##
# Pack format for a tar header
- PACK_FORMAT = ("a100" + # name
+ PACK_FORMAT = "a100" + # name
"a8" + # mode
"a8" + # uid
"a8" + # gid
@@ -71,12 +71,12 @@ class Gem::Package::TarHeader
"a32" + # gname
"a8" + # devmajor
"a8" + # devminor
- "a155").freeze # prefix
+ "a155" # prefix
##
# Unpack format for a tar header
- UNPACK_FORMAT = ("A100" + # name
+ UNPACK_FORMAT = "A100" + # name
"A8" + # mode
"A8" + # uid
"A8" + # gid
@@ -91,7 +91,7 @@ class Gem::Package::TarHeader
"A32" + # gname
"A8" + # devmajor
"A8" + # devminor
- "A155").freeze # prefix
+ "A155" # prefix
attr_reader(*FIELDS)
diff --git a/lib/uri/mailto.rb b/lib/uri/mailto.rb
index f747b79ec753b3..cb8024f301fc47 100644
--- a/lib/uri/mailto.rb
+++ b/lib/uri/mailto.rb
@@ -52,11 +52,7 @@ class MailTo < Generic
HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/
# practical regexp for email address
# https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
- EMAIL_REGEXP = %r[\A#{
- atext = %q[(?:[a-zA-Z0-9!\#$%&'*+\/=?^_`{|}~-]+)]
- }(?:\.#{atext})*@#{
- label = %q[(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)]
- }(?:\.#{label})*\z]
+ EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/
# :startdoc:
#
diff --git a/test/rubygems/test_gem_package_tar_header.rb b/test/rubygems/test_gem_package_tar_header.rb
index 34f92967e9905b..a3f95bb7704f91 100644
--- a/test/rubygems/test_gem_package_tar_header.rb
+++ b/test/rubygems/test_gem_package_tar_header.rb
@@ -26,25 +26,6 @@ def setup
@tar_header = Gem::Package::TarHeader.new header
end
- def test_decode_in_ractor
- new_header = Ractor.new(@tar_header.to_s) do |str|
- Gem::Package::TarHeader.from StringIO.new str
- end.value
-
- assert_headers_equal @tar_header, new_header
- end if defined?(Ractor) && Ractor.instance_methods.include?(:value)
-
- def test_encode_in_ractor
- header_bytes = @tar_header.to_s
-
- new_header = Ractor.new(header_bytes) do |str|
- header = Gem::Package::TarHeader.from StringIO.new str
- header.to_s
- end.value
-
- assert_headers_equal header_bytes, new_header
- end if defined?(Ractor) && Ractor.instance_methods.include?(:value)
-
def test_self_from
io = TempIO.new @tar_header.to_s
diff --git a/test/uri/test_mailto.rb b/test/uri/test_mailto.rb
index 59bb5ded09b9a9..6cd33529784796 100644
--- a/test/uri/test_mailto.rb
+++ b/test/uri/test_mailto.rb
@@ -145,29 +145,23 @@ def test_check_to
u.to = 'a@valid.com'
assert_equal(u.to, 'a@valid.com')
- # Invalid emails
- assert_raise(URI::InvalidComponentError) do
- u.to = '#1@mail.com'
- end
+ # Intentionally allowed violations of RFC 5322
+ u.to = 'a..a@valid.com'
+ assert_equal(u.to, 'a..a@valid.com')
- assert_raise(URI::InvalidComponentError) do
- u.to = '@invalid.email'
- end
+ u.to = 'hello.@valid.com'
+ assert_equal(u.to, 'hello.@valid.com')
- assert_raise(URI::InvalidComponentError) do
- u.to = '.hello@invalid.email'
- end
-
- assert_raise(URI::InvalidComponentError) do
- u.to = 'hello.@invalid.email'
- end
+ u.to = '.hello@valid.com'
+ assert_equal(u.to, '.hello@valid.com')
+ # Invalid emails
assert_raise(URI::InvalidComponentError) do
- u.to = 'n.@invalid.email'
+ u.to = '#1@mail.com'
end
assert_raise(URI::InvalidComponentError) do
- u.to = 'n..t@invalid.email'
+ u.to = '@invalid.email'
end
# Invalid host emails
diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs
index 9d3bf18dcdab41..cfedca4540361d 100644
--- a/zjit/src/asm/x86_64/mod.rs
+++ b/zjit/src/asm/x86_64/mod.rs
@@ -779,13 +779,6 @@ pub fn imul(cb: &mut CodeBlock, opnd0: X86Opnd, opnd1: X86Opnd) {
write_rm(cb, false, true, opnd0, opnd1, None, &[0x0F, 0xAF]);
}
- // Flip the operands to handle this case. This instruction has weird encoding restrictions.
- (X86Opnd::Mem(_), X86Opnd::Reg(_)) => {
- //REX.W + 0F AF /rIMUL r64, r/m64
- // Quadword register := Quadword register * r/m64.
- write_rm(cb, false, true, opnd1, opnd0, None, &[0x0F, 0xAF]);
- }
-
_ => unreachable!()
}
}
diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs
index 0f867259466de9..d574bdb0341066 100644
--- a/zjit/src/asm/x86_64/tests.rs
+++ b/zjit/src/asm/x86_64/tests.rs
@@ -228,22 +228,28 @@ fn test_cqo() {
fn test_imul() {
let cb1 = compile(|cb| imul(cb, RAX, RBX));
let cb2 = compile(|cb| imul(cb, RDX, mem_opnd(64, RAX, 0)));
- // Operands flipped for encoding since multiplication is commutative
- let cb3 = compile(|cb| imul(cb, mem_opnd(64, RAX, 0), RDX));
- assert_disasm_snapshot!(disasms!(cb1, cb2, cb3), @r"
+ assert_disasm_snapshot!(disasms!(cb1, cb2), @r"
0x0: imul rax, rbx
0x0: imul rdx, qword ptr [rax]
- 0x0: imul rdx, qword ptr [rax]
");
- assert_snapshot!(hexdumps!(cb1, cb2, cb3), @r"
+ assert_snapshot!(hexdumps!(cb1, cb2), @r"
480fafc3
480faf10
- 480faf10
");
}
+#[test]
+#[should_panic]
+fn test_imul_mem_reg() {
+ // imul doesn't have (Mem, Reg) encoding. Since multiplication is communicative, imul() could
+ // swap operands. However, x86_scratch_split may need to move the result to the output operand,
+ // which can be complicated if the assembler may sometimes change the result operand.
+ // So x86_scratch_split should be responsible for that swap, not the assembler.
+ compile(|cb| imul(cb, mem_opnd(64, RAX, 0), RDX));
+}
+
#[test]
fn test_jge_label() {
let cb = compile(|cb| {
diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs
index d762b14c911503..acf0576f9c80be 100644
--- a/zjit/src/backend/arm64/mod.rs
+++ b/zjit/src/backend/arm64/mod.rs
@@ -79,6 +79,9 @@ impl From for A64Opnd {
Opnd::Mem(Mem { base: MemBase::VReg(_), .. }) => {
panic!("attempted to lower an Opnd::Mem with a MemBase::VReg base")
},
+ Opnd::Mem(Mem { base: MemBase::Stack { .. }, .. }) => {
+ panic!("attempted to lower an Opnd::Mem with a MemBase::Stack base")
+ },
Opnd::VReg { .. } => panic!("attempted to lower an Opnd::VReg"),
Opnd::Value(_) => panic!("attempted to lower an Opnd::Value"),
Opnd::None => panic!(
@@ -203,6 +206,7 @@ pub const ALLOC_REGS: &[Reg] = &[
/// [`Assembler::arm64_scratch_split`] or [`Assembler::new_with_scratch_reg`].
const SCRATCH0_OPND: Opnd = Opnd::Reg(X15_REG);
const SCRATCH1_OPND: Opnd = Opnd::Reg(X17_REG);
+const SCRATCH2_OPND: Opnd = Opnd::Reg(X14_REG);
impl Assembler {
/// Special register for intermediate processing in arm64_emit. It should be used only by arm64_emit.
@@ -690,22 +694,129 @@ impl Assembler {
/// need to be split with registers after `alloc_regs`, e.g. for `compile_exits`, so this
/// splits them and uses scratch registers for it.
fn arm64_scratch_split(self) -> Assembler {
- let mut asm = Assembler::new_with_asm(&self);
+ /// If opnd is Opnd::Mem with a too large disp, make the disp smaller using lea.
+ fn split_large_disp(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd) -> Opnd {
+ match opnd {
+ Opnd::Mem(Mem { num_bits, disp, .. }) if !mem_disp_fits_bits(disp) => {
+ asm.lea_into(scratch_opnd, opnd);
+ Opnd::mem(num_bits, scratch_opnd, 0)
+ }
+ _ => opnd,
+ }
+ }
+
+ /// If opnd is Opnd::Mem with MemBase::Stack, lower it to Opnd::Mem with MemBase::Reg, and split a large disp.
+ fn split_stack_membase(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd, stack_state: &StackState) -> Opnd {
+ let opnd = split_only_stack_membase(asm, opnd, scratch_opnd, stack_state);
+ split_large_disp(asm, opnd, scratch_opnd)
+ }
+
+ /// split_stack_membase but without split_large_disp. This should be used only by lea.
+ fn split_only_stack_membase(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd, stack_state: &StackState) -> Opnd {
+ if let Opnd::Mem(Mem { base: stack_membase @ MemBase::Stack { .. }, disp: opnd_disp, num_bits: opnd_num_bits }) = opnd {
+ let base = Opnd::Mem(stack_state.stack_membase_to_mem(stack_membase));
+ let base = split_large_disp(asm, base, scratch_opnd);
+ asm.load_into(scratch_opnd, base);
+ Opnd::Mem(Mem { base: MemBase::Reg(scratch_opnd.unwrap_reg().reg_no), disp: opnd_disp, num_bits: opnd_num_bits })
+ } else {
+ opnd
+ }
+ }
+
+ /// If opnd is Opnd::Mem, lower it to scratch_opnd. You should use this when `opnd` is read by the instruction, not written.
+ fn split_memory_read(asm: &mut Assembler, opnd: Opnd, scratch_opnd: Opnd) -> Opnd {
+ if let Opnd::Mem(_) = opnd {
+ let opnd = split_large_disp(asm, opnd, scratch_opnd);
+ let scratch_opnd = opnd.num_bits().map(|num_bits| scratch_opnd.with_num_bits(num_bits)).unwrap_or(scratch_opnd);
+ asm.load_into(scratch_opnd, opnd);
+ scratch_opnd
+ } else {
+ opnd
+ }
+ }
+
+ /// If opnd is Opnd::Mem, set scratch_reg to *opnd. Return Some(Opnd::Mem) if it needs to be written back from scratch_reg.
+ fn split_memory_write(opnd: &mut Opnd, scratch_opnd: Opnd) -> Option {
+ if let Opnd::Mem(_) = opnd {
+ let mem_opnd = opnd.clone();
+ *opnd = opnd.num_bits().map(|num_bits| scratch_opnd.with_num_bits(num_bits)).unwrap_or(scratch_opnd);
+ Some(mem_opnd)
+ } else {
+ None
+ }
+ }
+
+ // Prepare StackState to lower MemBase::Stack
+ let stack_state = StackState::new(self.stack_base_idx);
+
+ let mut asm_local = Assembler::new_with_asm(&self);
+ let asm = &mut asm_local;
asm.accept_scratch_reg = true;
let mut iterator = self.insns.into_iter().enumerate().peekable();
while let Some((_, mut insn)) = iterator.next() {
match &mut insn {
- &mut Insn::Mul { out, .. } => {
+ Insn::Add { left, right, out } |
+ Insn::Sub { left, right, out } |
+ Insn::And { left, right, out } |
+ Insn::Or { left, right, out } |
+ Insn::Xor { left, right, out } |
+ Insn::CSelZ { truthy: left, falsy: right, out } |
+ Insn::CSelNZ { truthy: left, falsy: right, out } |
+ Insn::CSelE { truthy: left, falsy: right, out } |
+ Insn::CSelNE { truthy: left, falsy: right, out } |
+ Insn::CSelL { truthy: left, falsy: right, out } |
+ Insn::CSelLE { truthy: left, falsy: right, out } |
+ Insn::CSelG { truthy: left, falsy: right, out } |
+ Insn::CSelGE { truthy: left, falsy: right, out } => {
+ *left = split_memory_read(asm, *left, SCRATCH0_OPND);
+ *right = split_memory_read(asm, *right, SCRATCH1_OPND);
+ let mem_out = split_memory_write(out, SCRATCH0_OPND);
+
+ asm.push_insn(insn);
+
+ if let Some(mem_out) = mem_out {
+ let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND);
+ asm.store(mem_out, SCRATCH0_OPND);
+ }
+ }
+ Insn::Mul { left, right, out } => {
+ *left = split_memory_read(asm, *left, SCRATCH0_OPND);
+ *right = split_memory_read(asm, *right, SCRATCH1_OPND);
+ let mem_out = split_memory_write(out, SCRATCH0_OPND);
+ let reg_out = out.clone();
+
asm.push_insn(insn);
+ if let Some(mem_out) = mem_out {
+ let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND);
+ asm.store(mem_out, SCRATCH0_OPND);
+ };
+
// If the next instruction is JoMul
if matches!(iterator.peek(), Some((_, Insn::JoMul(_)))) {
// Produce a register that is all zeros or all ones
// Based on the sign bit of the 64-bit mul result
- asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: out, shift: Opnd::UImm(63) });
+ asm.push_insn(Insn::RShift { out: SCRATCH0_OPND, opnd: reg_out, shift: Opnd::UImm(63) });
+ }
+ }
+ Insn::RShift { opnd, out, .. } => {
+ *opnd = split_memory_read(asm, *opnd, SCRATCH0_OPND);
+ let mem_out = split_memory_write(out, SCRATCH0_OPND);
+
+ asm.push_insn(insn);
+
+ if let Some(mem_out) = mem_out {
+ let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND);
+ asm.store(mem_out, SCRATCH0_OPND);
}
}
+ Insn::Cmp { left, right } |
+ Insn::Test { left, right } => {
+ *left = split_memory_read(asm, *left, SCRATCH0_OPND);
+ *right = split_memory_read(asm, *right, SCRATCH1_OPND);
+ asm.push_insn(insn);
+ }
// For compile_exits, support splitting simple C arguments here
Insn::CCall { opnds, .. } if !opnds.is_empty() => {
for (i, opnd) in opnds.iter().enumerate() {
@@ -714,16 +825,32 @@ impl Assembler {
*opnds = vec![];
asm.push_insn(insn);
}
- &mut Insn::Lea { opnd, out } => {
- match (opnd, out) {
- // Split here for compile_exits
- (Opnd::Mem(_), Opnd::Mem(_)) => {
- asm.lea_into(SCRATCH0_OPND, opnd);
- asm.store(out, SCRATCH0_OPND);
- }
- _ => {
- asm.push_insn(insn);
+ Insn::Lea { opnd, out } => {
+ *opnd = split_only_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state);
+ let mem_out = split_memory_write(out, SCRATCH0_OPND);
+
+ asm.push_insn(insn);
+
+ if let Some(mem_out) = mem_out {
+ let mem_out = split_large_disp(asm, mem_out, SCRATCH1_OPND);
+ asm.store(mem_out, SCRATCH0_OPND);
+ }
+ }
+ Insn::Load { opnd, out } |
+ Insn::LoadInto { opnd, dest: out } => {
+ *opnd = split_stack_membase(asm, *opnd, SCRATCH0_OPND, &stack_state);
+ *out = split_stack_membase(asm, *out, SCRATCH1_OPND, &stack_state);
+
+ if let Opnd::Mem(_) = out {
+ // If NATIVE_STACK_PTR is used as a source for Store, it's handled as xzr, storeing zero.
+ // To save the content of NATIVE_STACK_PTR, we need to load it into another register first.
+ if *opnd == NATIVE_STACK_PTR {
+ asm.load_into(SCRATCH0_OPND, NATIVE_STACK_PTR);
+ *opnd = SCRATCH0_OPND;
}
+ asm.store(*out, *opnd);
+ } else {
+ asm.push_insn(insn);
}
}
&mut Insn::IncrCounter { mem, value } => {
@@ -741,31 +868,24 @@ impl Assembler {
asm.cmp(SCRATCH1_OPND, 0.into());
asm.jne(label);
}
- &mut Insn::Store { dest, src } => {
- let Opnd::Mem(Mem { num_bits: dest_num_bits, disp: dest_disp, .. }) = dest else {
- panic!("Insn::Store destination must be Opnd::Mem: {dest:?}, {src:?}");
- };
-
- // Split dest using a scratch register if necessary.
- let dest = if mem_disp_fits_bits(dest_disp) {
- dest
- } else {
- asm.lea_into(SCRATCH0_OPND, dest);
- Opnd::mem(dest_num_bits, SCRATCH0_OPND, 0)
- };
-
- asm.store(dest, src);
+ Insn::Store { dest, .. } => {
+ *dest = split_stack_membase(asm, *dest, SCRATCH0_OPND, &stack_state);
+ asm.push_insn(insn);
}
- &mut Insn::Mov { dest, src } => {
+ Insn::Mov { dest, src } => {
+ *src = split_stack_membase(asm, *src, SCRATCH0_OPND, &stack_state);
+ *dest = split_large_disp(asm, *dest, SCRATCH1_OPND);
match dest {
- Opnd::Reg(_) => asm.load_into(dest, src),
- Opnd::Mem(_) => asm.store(dest, src),
+ Opnd::Reg(_) => asm.load_into(*dest, *src),
+ Opnd::Mem(_) => asm.store(*dest, *src),
_ => asm.push_insn(insn),
}
}
// Resolve ParallelMov that couldn't be handled without a scratch register.
Insn::ParallelMov { moves } => {
for (dst, src) in Self::resolve_parallel_moves(moves, Some(SCRATCH0_OPND)).unwrap() {
+ let src = split_stack_membase(asm, src, SCRATCH1_OPND, &stack_state);
+ let dst = split_large_disp(asm, dst, SCRATCH2_OPND);
match dst {
Opnd::Reg(_) => asm.load_into(dst, src),
Opnd::Mem(_) => asm.store(dst, src),
@@ -779,7 +899,7 @@ impl Assembler {
}
}
- asm
+ asm_local
}
/// Emit platform-specific machine code
@@ -1157,10 +1277,11 @@ impl Assembler {
load_effective_address(cb, Self::EMIT_OPND, src_base_reg_no, src_disp);
A64Opnd::new_mem(dest.rm_num_bits(), Self::EMIT_OPND, 0)
};
+ let dst = A64Opnd::Reg(Self::EMIT_REG.with_num_bits(src_num_bits));
match src_num_bits {
- 64 | 32 => ldur(cb, Self::EMIT_OPND, src_mem),
- 16 => ldurh(cb, Self::EMIT_OPND, src_mem),
- 8 => ldurb(cb, Self::EMIT_OPND, src_mem),
+ 64 | 32 => ldur(cb, dst, src_mem),
+ 16 => ldurh(cb, dst, src_mem),
+ 8 => ldurb(cb, dst, src_mem),
num_bits => panic!("unexpected num_bits: {num_bits}")
};
Self::EMIT_REG
diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs
index 584251de802bf2..66e89a1304d715 100644
--- a/zjit/src/backend/lir.rs
+++ b/zjit/src/backend/lir.rs
@@ -28,8 +28,12 @@ pub static JIT_PRESERVED_REGS: &[Opnd] = &[CFP, SP, EC];
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum MemBase
{
+ /// Register: Every Opnd::Mem should have MemBase::Reg as of emit.
Reg(u8),
+ /// Virtual register: Lowered to MemBase::Reg or MemBase::Stack in alloc_regs.
VReg(usize),
+ /// Stack slot: Lowered to MemBase::Reg in scratch_split.
+ Stack { stack_idx: usize, num_bits: u8 },
}
// Memory location
@@ -55,6 +59,8 @@ impl fmt::Display for Mem {
match self.base {
MemBase::Reg(reg_no) => write!(f, "{}", mem_base_reg(reg_no))?,
MemBase::VReg(idx) => write!(f, "v{idx}")?,
+ MemBase::Stack { stack_idx, num_bits } if num_bits == 64 => write!(f, "Stack[{stack_idx}]")?,
+ MemBase::Stack { stack_idx, num_bits } => write!(f, "Stack{num_bits}[{stack_idx}]")?,
}
if self.disp != 0 {
let sign = if self.disp > 0 { '+' } else { '-' };
@@ -1143,6 +1149,81 @@ impl LiveRange {
}
}
+/// StackState manages which stack slots are used by which VReg
+pub struct StackState {
+ /// The maximum number of spilled VRegs at a time
+ stack_size: usize,
+ /// Map from index at the C stack for spilled VRegs to Some(vreg_idx) if allocated
+ stack_slots: Vec