From 85dd51463143c9451ba6ffea6d7bc51ab91f262f Mon Sep 17 00:00:00 2001 From: hadashiA Date: Sun, 5 Jul 2026 18:16:17 +0900 Subject: [PATCH 1/2] Support EXT1/EXT2/EXT3 operand extension opcodes --- src/ChibiRuby/Internals/Operands.cs | 252 ++++++++++++++++------------ src/ChibiRuby/MRubyState.Dump.cs | 193 +++++++++++---------- src/ChibiRuby/MRubyState.Vm.cs | 227 ++++++++++++++----------- src/ChibiRuby/StdLib/ProcMembers.cs | 2 +- tests/ChibiRuby.Tests/VmTest.cs | 42 +++++ 5 files changed, 415 insertions(+), 301 deletions(-) diff --git a/src/ChibiRuby/Internals/Operands.cs b/src/ChibiRuby/Internals/Operands.cs index 191a50a2..32052744 100644 --- a/src/ChibiRuby/Internals/Operands.cs +++ b/src/ChibiRuby/Internals/Operands.cs @@ -1,180 +1,216 @@ using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace ChibiRuby.Internals; -enum OperandType -{ - Z, - B, - S, - BB, - BS, - BBB, - BSS, - W -} - -[StructLayout(LayoutKind.Explicit)] -struct Operand -{ - [FieldOffset(0)] - public OperandType Type; - - [FieldOffset(1)] - public OperandB B; - - [FieldOffset(1)] - public OperandS S; - - [FieldOffset(1)] - public OperandBB BB; - - [FieldOffset(1)] - public OperandBS BS; - - [FieldOffset(1)] - public OperandBBB BBB; - - [FieldOffset(1)] - public OperandBSS BSS; - - [FieldOffset(1)] - public OperandW W; -} +// Operand decoding for the VM bytecode. +// +// mruby widens operands with the EXT1/EXT2/EXT3 prefix opcodes when an operand +// does not fit in a single byte (e.g. a literal-pool or symbol index over 255): +// EXT1 -> the 1st operand is widened from B (1 byte) to S (2 bytes) +// EXT2 -> the 2nd operand is widened +// EXT3 -> both the 1st and 2nd operands are widened +// Only the first two operands are ever widened; a trailing B (the 3rd operand of +// a BBB, the C of a BSS) always stays one byte. Operands that are already S/W are +// never prefixed, so those readers ignore `ext`. +// +// Fields are `ushort` (a widened operand is at most 2 bytes = 65535), which keeps +// these structs small and register-friendly. `ext` is 0 on the overwhelmingly +// common path, so each Read keeps a single-shot fast path and pushes the widened +// decode out of line. -[StructLayout(LayoutKind.Explicit)] struct OperandZ { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Read(ref byte sequence, ref int pc) { pc += 1; } } -[StructLayout(LayoutKind.Explicit)] struct OperandB { - [FieldOffset(0)] - public byte A; + public ushort A; - public static OperandB Read(ref byte sequence, ref int pc) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static OperandB Read(ref byte sequence, ref int pc, int ext = 0) { + if (ext != 0) return ReadExtended(ref sequence, ref pc, ext); pc += 2; - var result = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, (pc - 1))); + return new OperandB { A = Unsafe.Add(ref sequence, pc - 1) }; + } - return result; + [MethodImpl(MethodImplOptions.NoInlining)] + static OperandB ReadExtended(ref byte sequence, ref int pc, int ext) + { + var p = pc + 1; + var a = Operands.ReadOperand(ref sequence, ref p, Operands.Widen1(ext)); + pc = p; + return new OperandB { A = (ushort)a }; } } -[StructLayout(LayoutKind.Explicit)] struct OperandBB { - [FieldOffset(0)] - public byte A; - - [FieldOffset(1)] - public byte B; + public ushort A; + public ushort B; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OperandBB Read(ref byte sequence, ref int pc) + public static OperandBB Read(ref byte sequence, ref int pc, int ext = 0) { + if (ext != 0) return ReadExtended(ref sequence, ref pc, ext); + ref var p = ref Unsafe.Add(ref sequence, pc + 1); pc += 3; - var result = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, (pc - 2))); + return new OperandBB { A = p, B = Unsafe.Add(ref p, 1) }; + } - return result; + [MethodImpl(MethodImplOptions.NoInlining)] + static OperandBB ReadExtended(ref byte sequence, ref int pc, int ext) + { + var p = pc + 1; + var a = Operands.ReadOperand(ref sequence, ref p, Operands.Widen1(ext)); + var b = Operands.ReadOperand(ref sequence, ref p, Operands.Widen2(ext)); + pc = p; + return new OperandBB { A = (ushort)a, B = (ushort)b }; } } -[StructLayout(LayoutKind.Explicit)] -unsafe struct OperandS +struct OperandS { - [FieldOffset(0)] - fixed byte bytesA[2]; - - public int A => (bytesA[0] << 8) | bytesA[1]; + public int A; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static OperandS Read(ref byte sequence, ref int pc) { + ref var p = ref Unsafe.Add(ref sequence, pc + 1); pc += 3; - return Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, pc - 2)); + return new OperandS { A = (p << 8) | Unsafe.Add(ref p, 1) }; } } -[StructLayout(LayoutKind.Explicit)] -unsafe struct OperandBS +struct OperandBS { - [FieldOffset(0)] - public byte A; - - [FieldOffset(1)] - fixed byte bytesB[2]; - - public int B => (bytesB[0] << 8) | bytesB[1]; + public ushort A; + public ushort B; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OperandBS Read(ref byte sequence, ref int pc) + public static OperandBS Read(ref byte sequence, ref int pc, int ext = 0) { + if (ext != 0) return ReadExtended(ref sequence, ref pc, ext); + ref var p = ref Unsafe.Add(ref sequence, pc + 1); pc += 4; - return Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, (pc - 3))); + return new OperandBS { A = p, B = (ushort)((Unsafe.Add(ref p, 1) << 8) | Unsafe.Add(ref p, 2)) }; + } + + // Only the leading B can widen (the trailing operand is already S). + [MethodImpl(MethodImplOptions.NoInlining)] + static OperandBS ReadExtended(ref byte sequence, ref int pc, int ext) + { + var p = pc + 1; + var a = Operands.ReadOperand(ref sequence, ref p, Operands.Widen1(ext)); + var b = Operands.ReadOperand(ref sequence, ref p, widen: true); + pc = p; + return new OperandBS { A = (ushort)a, B = (ushort)b }; } } -[StructLayout(LayoutKind.Explicit)] struct OperandBBB { - [FieldOffset(0)] - public byte A; - - [FieldOffset(1)] - public byte B; - - [FieldOffset(2)] - public byte C; + public ushort A; + public ushort B; + public ushort C; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OperandBBB Read(ref byte sequence, ref int pc) + public static OperandBBB Read(ref byte sequence, ref int pc, int ext = 0) { + if (ext != 0) return ReadExtended(ref sequence, ref pc, ext); + ref var p = ref Unsafe.Add(ref sequence, pc + 1); pc += 4; - return Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, (pc - 3))); + return new OperandBBB { A = p, B = Unsafe.Add(ref p, 1), C = Unsafe.Add(ref p, 2) }; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static OperandBBB ReadExtended(ref byte sequence, ref int pc, int ext) + { + var p = pc + 1; + var a = Operands.ReadOperand(ref sequence, ref p, Operands.Widen1(ext)); + var b = Operands.ReadOperand(ref sequence, ref p, Operands.Widen2(ext)); + var c = Operands.ReadOperand(ref sequence, ref p, widen: false); + pc = p; + return new OperandBBB { A = (ushort)a, B = (ushort)b, C = (ushort)c }; } } -[StructLayout(LayoutKind.Explicit)] -unsafe struct OperandBSS +struct OperandBSS { - [FieldOffset(0)] - public byte A; - - [FieldOffset(1)] - fixed byte bytesB[2]; - - [FieldOffset(3)] - fixed byte bytesC[2]; - - public int B => (bytesB[0] << 8) | bytesB[1]; - public int C => (bytesC[0] << 8) | bytesC[1]; + public ushort A; + public int B; + public int C; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OperandBSS Read(ref byte sequence, ref int pc) + public static OperandBSS Read(ref byte sequence, ref int pc, int ext = 0) { + if (ext != 0) return ReadExtended(ref sequence, ref pc, ext); + ref var p = ref Unsafe.Add(ref sequence, pc + 1); pc += 6; - return Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, pc - 5)); + return new OperandBSS + { + A = p, + B = (Unsafe.Add(ref p, 1) << 8) | Unsafe.Add(ref p, 2), + C = (Unsafe.Add(ref p, 3) << 8) | Unsafe.Add(ref p, 4), + }; + } + + // Only the leading B can widen (the two trailing operands are already S). + [MethodImpl(MethodImplOptions.NoInlining)] + static OperandBSS ReadExtended(ref byte sequence, ref int pc, int ext) + { + var p = pc + 1; + var a = Operands.ReadOperand(ref sequence, ref p, Operands.Widen1(ext)); + var b = Operands.ReadOperand(ref sequence, ref p, widen: true); + var c = Operands.ReadOperand(ref sequence, ref p, widen: true); + pc = p; + return new OperandBSS { A = (ushort)a, B = b, C = c }; } } -[StructLayout(LayoutKind.Explicit)] -unsafe struct OperandW +struct OperandW { - // ReSharper disable once UnassignedField.Local - [FieldOffset(0)] - public fixed byte Bytes[3]; - public int A => (Bytes[0] << 16) | (Bytes[1] << 8) | Bytes[2]; + public int A; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static OperandW Read(ref byte sequence, ref int pc) { + ref var p = ref Unsafe.Add(ref sequence, pc + 1); pc += 4; - return Unsafe.ReadUnaligned(ref Unsafe.Add(ref sequence, pc - 3)); + return new OperandW { A = (p << 16) | (Unsafe.Add(ref p, 1) << 8) | Unsafe.Add(ref p, 2) }; + } +} + +static class Operands +{ + // EXT1 widens operand 1, EXT3 widens both. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Widen1(int ext) => ext == 1 || ext == 3; + + // EXT2 widens operand 2, EXT3 widens both. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Widen2(int ext) => ext == 2 || ext == 3; + + // Read one operand, advancing `pc`. Widened operands are 2 bytes big-endian. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadOperand(ref byte sequence, ref int pc, bool widen) + { + if (widen) + { + var value = (Unsafe.Add(ref sequence, pc) << 8) | Unsafe.Add(ref sequence, pc + 1); + pc += 2; + return value; + } + else + { + var value = Unsafe.Add(ref sequence, pc); + pc += 1; + return value; + } } -} \ No newline at end of file +} diff --git a/src/ChibiRuby/MRubyState.Dump.cs b/src/ChibiRuby/MRubyState.Dump.cs index 9b8ff71e..1b627b5e 100644 --- a/src/ChibiRuby/MRubyState.Dump.cs +++ b/src/ChibiRuby/MRubyState.Dump.cs @@ -164,7 +164,7 @@ void WriteRegister(int n) } } - void WriteLocalVariableA(short a) + void WriteLocalVariableA(int a) { if (a == 0 || a >= irep.LocalVariables.Length) { @@ -177,7 +177,7 @@ void WriteLocalVariableA(short a) writer.Write("\n"u8); } - void WriteLocalVariableAB(short a, short b) + void WriteLocalVariableAB(int a, int b) { if (a + b == 0 || (a >= irep.LocalVariables.Length && b >= irep.LocalVariables.Length)) { @@ -237,8 +237,21 @@ void WriteLocalVariableAB(short a, short b) //TODO: Irep debug info var opcode = (OpCode)irep.Sequence[pc]; + + // Consume any operand-widening prefix (mruby EXT1/EXT2/EXT3) so the + // real opcode's operands are read at the widened width and pc stays + // aligned for the following instructions. + int ext = 0; + while (opcode is OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3) + { + ext = opcode - OpCode.EXT1 + 1; + writer.Write(GetOpCodeName(opcode)); + writer.Write(" "u8); + pc++; + opcode = (OpCode)irep.Sequence[pc]; + } + if (opcode is OpCode.Nop or OpCode.Call or OpCode.KeyEnd or OpCode.Stop - or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 or OpCode.RetSelf or OpCode.RetNil or OpCode.RetTrue or OpCode.RetFalse) { writer.Write(GetOpCodeName(opcode)); @@ -258,12 +271,12 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 break; case OpCode.Move: - var bb = OperandBB.Read(ref sequence, ref pc); + var bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tR{bb.B}\n"); break; case OpCode.LoadL: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}"); var value = irep.PoolValues[bb.B]; switch (value.VType) @@ -283,27 +296,27 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 break; } case OpCode.LoadI8: - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\t"); WriteLocalVariableA(bb.A); break; case OpCode.LoadINeg: - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t-{bb.B}\t"); WriteLocalVariableA(bb.A); break; case OpCode.LoadI16: - var bs = OperandBS.Read(ref sequence, ref pc); + var bs = OperandBS.Read(ref sequence, ref pc, ext); Format(writer, $"R{bs.A}\t{unchecked((short)bs.B)}\t"); WriteLocalVariableA(bs.A); break; case OpCode.LoadI32: - var bss = OperandBSS.Read(ref sequence, ref pc); + var bss = OperandBSS.Read(ref sequence, ref pc, ext); Format(writer, $"R{bss.A}\t{((ushort)bss.B << 16) | (ushort)bss.C}\t"); WriteLocalVariableA(bss.A); break; case OpCode.LoadI__1: - var b = OperandB.Read(ref sequence, ref pc); + var b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"tR{b.A}\t(-1)\t"); WriteLocalVariableA(b.A); break; @@ -316,7 +329,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 case OpCode.LoadI_6: case OpCode.LoadI_7: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); var value = (int)opcode - (int)OpCode.LoadI_0; Format(writer, $"R{b.A}\t{value}\t"); WriteLocalVariableA(b.A); @@ -324,111 +337,111 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.LoadSym: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.LoadNil: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t(nil)\t"); WriteLocalVariableA(b.A); break; } case OpCode.LoadSelf: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t(R0)\t"); WriteLocalVariableA(b.A); break; } case OpCode.LoadT: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t(true)\t"); WriteLocalVariableA(b.A); break; } case OpCode.LoadF: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t(false)\t"); WriteLocalVariableA(b.A); break; } case OpCode.GetGV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.SetGV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"{symbolTable.NameOf(irep.Symbols[bb.B])}\tR{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.GetSV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.SetSV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"{symbolTable.NameOf(irep.Symbols[bb.B])}\tR{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.GetConst: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.SetConst: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"{symbolTable.NameOf(irep.Symbols[bb.B])}\tR{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.GetMCnst: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tR{bb.A}::{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.SetMCnst: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}::{symbolTable.NameOf(irep.Symbols[bb.B])}\tR{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.GetIV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.SetIV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"{symbolTable.NameOf(irep.Symbols[bb.B])}\tR{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.GetUpVar: - var bbb = OperandBBB.Read(ref sequence, ref pc); + var bbb = OperandBBB.Read(ref sequence, ref pc, ext); { Format(writer, $"R{bbb.A}\t{bbb.B}\t{bbb.C}\t"); WriteLocalVariableA(bbb.A); @@ -436,40 +449,40 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.SetUpVar: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t{bbb.B}\t{bbb.C}\t"); WriteLocalVariableA(bbb.A); break; } case OpCode.GetCV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.SetCV: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"{symbolTable.NameOf(irep.Symbols[bb.B])}\tR{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.GetIdx: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\tR{b.A + 1}\n"); break; } case OpCode.GetIdx0: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tR{bb.B}\n"); break; } case OpCode.SetIdx: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\tR{b.A + 1}\tR{b.A + 2}\n"); break; } @@ -489,7 +502,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.JmpIf: { - bs = OperandBS.Read(ref sequence, ref pc); + bs = OperandBS.Read(ref sequence, ref pc, ext); var i = pc; Format(writer, $"R{bs.A}\t{i + bs.B}\t"); WriteLocalVariableA(bs.A); @@ -497,7 +510,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.JmpNot: { - bs = OperandBS.Read(ref sequence, ref pc); + bs = OperandBS.Read(ref sequence, ref pc, ext); var i = pc; Format(writer, $"R{bs.A}\t{i + bs.B}\t"); WriteLocalVariableA(bs.A); @@ -505,7 +518,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.JmpNil: { - bs = OperandBS.Read(ref sequence, ref pc); + bs = OperandBS.Read(ref sequence, ref pc, ext); var i = pc; Format(writer, $"R{bs.A}\t{i + bs.B}\t"); WriteLocalVariableA(bs.A); @@ -513,28 +526,28 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.SSend: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t:{symbolTable.NameOf(irep.Symbols[bbb.B])}\t"); WriteLocalVariableA(bbb.A); break; } case OpCode.SSendB: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t:{symbolTable.NameOf(irep.Symbols[bbb.B])}\t"); WriteLocalVariableA(bbb.A); break; } case OpCode.Send: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t:{symbolTable.NameOf(irep.Symbols[bbb.B])}\t"); WriteLocalVariableA(bbb.A); break; } case OpCode.SendB: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t:{symbolTable.NameOf(irep.Symbols[bbb.B])}\t"); WriteLocalVariableA(bbb.A); break; @@ -542,14 +555,14 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 case OpCode.SSend0: case OpCode.Send0: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.BlkCall: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\n"); break; } @@ -559,14 +572,14 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.Super: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.ArgAry: { - bs = OperandBS.Read(ref sequence, ref pc); + bs = OperandBS.Read(ref sequence, ref pc, ext); Format(writer, $"R{bs.A}\t{(bs.B >> 11) & 0x3f}:{(bs.B >> 10) & 0x1}:{(bs.B >> 5) & 0x1f}:{(bs.B >> 4) & 0x1f} ({bs.B & 0xf})\t"); WriteLocalVariableA(bs.A); break; @@ -579,7 +592,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.KeyP: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; @@ -591,91 +604,91 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.KArg: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.Return: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.ReturnBlk: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.Break: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.BlkPush: { - bs = OperandBS.Read(ref sequence, ref pc); + bs = OperandBS.Read(ref sequence, ref pc, ext); Format(writer, $"R{bs.A}\t{(bs.B >> 11) & 0x3f}:{(bs.B >> 10) & 0x1}:{(bs.B >> 5) & 0x1f}:{(bs.B >> 4) & 0x1} ({(bs.B >> 0) & 0xf})\t"); WriteLocalVariableA(bs.A); break; } case OpCode.Lambda: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tI[{bb.B}]\n"); break; } case OpCode.Block: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tI[{bb.B}]\n"); break; } case OpCode.Method: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tI[{bb.B}]\n"); break; } case OpCode.RangeInc: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\n"); break; } case OpCode.RangeExc: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\n"); break; } case OpCode.Def: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}\t(R{bb.A + 1})\n"); break; } case OpCode.TDef: case OpCode.SDef: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t:{symbolTable.NameOf(irep.Symbols[bbb.B])}\tI[{bbb.C}]\n"); break; } case OpCode.Undef: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $":{symbolTable.NameOf(irep.Symbols[b.A])}\n"); break; } case OpCode.Alias: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $":{symbolTable.NameOf(irep.Symbols[bb.A])}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}\n"); break; } @@ -685,7 +698,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.AddI: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\t"); WriteLocalVariableA(bb.A); break; @@ -696,7 +709,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.SubI: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\t"); WriteLocalVariableA(bb.A); break; @@ -704,7 +717,7 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 case OpCode.AddILV: case OpCode.SubILV: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\tR{bbb.B}\t{bbb.C}\t"); WriteLocalVariableA(bbb.A); break; @@ -717,160 +730,160 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 case OpCode.GE: case OpCode.EQ: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\tR{b.A + 1}\n"); break; } case OpCode.Array: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tR{bb.A}\t{bb.B}"); WriteLocalVariableA(bb.A); break; } case OpCode.Array2: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\tR{bbb.B}\t{bbb.C}"); WriteLocalVariableA(bbb.A); break; } case OpCode.AryCat: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\tR{b.A + 1}\t"); WriteLocalVariableA(b.A); break; } case OpCode.AryPush: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.ArySplat: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t"); WriteLocalVariableA(b.A); break; } case OpCode.ARef: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\tR{bbb.B}\t{bbb.C}"); WriteLocalVariableA(bbb.A); break; } case OpCode.ASet: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\tR{bbb.B}\t{bbb.C}"); WriteLocalVariableAB(bbb.A, bbb.B); break; } case OpCode.APost: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bbb.A}\t{bbb.B}\t{bbb.C}"); WriteLocalVariableA(bbb.A); break; } case OpCode.Intern: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t"); WriteLocalVariableA(b.A); break; } case OpCode.Symbol: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tL[{bb.B}]\t; {irep.PoolValues[bb.B].As().AsSpan()}"); WriteLocalVariableA(bb.A); break; } case OpCode.String: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tL[{bb.B}]\t; {irep.PoolValues[bb.B].As().AsSpan()}"); WriteLocalVariableA(bb.A); break; } case OpCode.StrCat: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\tR{b.A + 1}\t"); WriteLocalVariableA(b.A); break; } case OpCode.Hash: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.HashAdd: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t{bb.B}\t"); WriteLocalVariableA(bb.A); break; } case OpCode.HashCat: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\tR{b.A + 1}\t"); WriteLocalVariableA(b.A); break; } case OpCode.OClass: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t"); WriteLocalVariableA(b.A); break; } case OpCode.Class: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}"); WriteLocalVariableA(bb.A); break; } case OpCode.Module: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\t:{symbolTable.NameOf(irep.Symbols[bb.B])}"); WriteLocalVariableA(bb.A); break; } case OpCode.Exec: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tI[{bb.B}]"); WriteLocalVariableA(bb.A); break; } case OpCode.SClass: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t"); WriteLocalVariableA(b.A); break; } case OpCode.TClass: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.Err: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); var message = irep.PoolValues[b.A]; if (message.Object is RString) Format(writer, $"{message.As().AsSpan()}\n"); @@ -879,35 +892,35 @@ or OpCode.EXT1 or OpCode.EXT2 or OpCode.EXT3 } case OpCode.Except: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.Rescue: { - bb = OperandBB.Read(ref sequence, ref pc); + bb = OperandBB.Read(ref sequence, ref pc, ext); Format(writer, $"R{bb.A}\tR{bb.B}"); WriteLocalVariableAB(bb.A, bb.B); break; } case OpCode.RaiseIf: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.MatchErr: { - b = OperandB.Read(ref sequence, ref pc); + b = OperandB.Read(ref sequence, ref pc, ext); Format(writer, $"R{b.A}\t\t"); WriteLocalVariableA(b.A); break; } case OpCode.Debug: { - bbb = OperandBBB.Read(ref sequence, ref pc); + bbb = OperandBBB.Read(ref sequence, ref pc, ext); Format(writer, $"{bbb.A}\t{bbb.B}\t{bbb.C}\n"); break; } diff --git a/src/ChibiRuby/MRubyState.Vm.cs b/src/ChibiRuby/MRubyState.Vm.cs index 6ef48577..18ab7525 100644 --- a/src/ChibiRuby/MRubyState.Vm.cs +++ b/src/ChibiRuby/MRubyState.Vm.cs @@ -551,9 +551,26 @@ internal MRubyValue Execute(Irep irep, int pc, int stackKeep, RException? inject // breakpoint, step trap, etc.) actually applies to this pc. DebuggerHook?.OnInstruction(this, irep, callInfo.ProgramCounter); + // Operand-widening prefix (mruby EXT1/EXT2/EXT3). `ext` is 0 on the + // common path; the EXT cases below set it and re-decode the real + // opcode so its operand reads pick up the widened width. + int ext = 0; + DecodeOpcode: var opcode = (OpCode)Unsafe.Add(ref sequence, callInfo.ProgramCounter); switch (opcode) { + case OpCode.EXT1: + ext = 1; + callInfo.ProgramCounter++; + goto DecodeOpcode; + case OpCode.EXT2: + ext = 2; + callInfo.ProgramCounter++; + goto DecodeOpcode; + case OpCode.EXT3: + ext = 3; + callInfo.ProgramCounter++; + goto DecodeOpcode; case OpCode.Nop: Markers.Nop(); { @@ -562,22 +579,22 @@ internal MRubyValue Execute(Irep irep, int pc, int stackKeep, RException? inject } case OpCode.Move: Markers.Move(); - var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = Unsafe.Add(ref registers, bb.B); goto Next; case OpCode.LoadL: Markers.LoadL(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = irep.PoolValues[bb.B]; goto Next; case OpCode.LoadI8: Markers.LoadI8(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = bb.B; goto Next; case OpCode.LoadINeg: Markers.LoadINeg(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = -bb.B; goto Next; case OpCode.LoadI__1: @@ -590,72 +607,72 @@ internal MRubyValue Execute(Irep irep, int pc, int stackKeep, RException? inject case OpCode.LoadI_6: case OpCode.LoadI_7: Markers.LoadI__1(); - int a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + int a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = (int)opcode - (int)OpCode.LoadI_0; goto Next; case OpCode.LoadI16: Markers.LoadI16(); - var bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter); + var bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bs.A) = unchecked((short)bs.B); goto Next; case OpCode.LoadI32: Markers.LoadI32(); - var bss = OperandBSS.Read(ref sequence, ref callInfo.ProgramCounter); + var bss = OperandBSS.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bss.A) = NewIntegerFlex((bss.B << 16) + bss.C); goto Next; case OpCode.LoadSym: Markers.LoadSym(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = Unsafe.Add(ref symbols, bb.B); goto Next; case OpCode.LoadNil: Markers.LoadNil(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = default; goto Next; case OpCode.LoadSelf: Markers.LoadSelf(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = Unsafe.Add(ref registers, 0); goto Next; case OpCode.LoadT: Markers.LoadT(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = MRubyValue.True; goto Next; case OpCode.LoadF: Markers.LoadF(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = MRubyValue.False; goto Next; case OpCode.GetGV: Markers.GetGV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = globalVariables.Get(Unsafe.Add(ref symbols, bb.B)); goto Next; case OpCode.SetGV: Markers.SetGV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); globalVariables.Set(Unsafe.Add(ref symbols, bb.B), Unsafe.Add(ref registers, bb.A)); goto Next; case OpCode.GetSV: Markers.GetSV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = globalVariables.Get(Unsafe.Add(ref symbols, bb.B)); goto Next; case OpCode.SetSV: Markers.SetSV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); globalVariables.Set(Unsafe.Add(ref symbols, bb.B), Unsafe.Add(ref registers, bb.A)); goto Next; case OpCode.GetIV: Markers.GetIV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = Unsafe.Add(ref registers, 0).As().InstanceVariables.Get(Unsafe.Add(ref symbols, bb.B)); goto Next; case OpCode.SetIV: Markers.SetIV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, 0) .As() .InstanceVariables.Set(Unsafe.Add(ref symbols, bb.B), @@ -663,18 +680,18 @@ internal MRubyValue Execute(Irep irep, int pc, int stackKeep, RException? inject goto Next; case OpCode.GetCV: Markers.GetCV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, bb.A) = GetClassVariable(Unsafe.Add(ref symbols, bb.B)); goto Next; case OpCode.SetCV: Markers.SetCV(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); SetClassVariable(Unsafe.Add(ref symbols, bb.B), Unsafe.Add(ref registers, bb.A)); goto Next; case OpCode.GetConst: Markers.GetConst(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); ref var registerA = ref Unsafe.Add(ref registers, bb.A); { var id = Unsafe.Add(ref symbols, bb.B); @@ -725,7 +742,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu case OpCode.SetConst: { Markers.SetConst(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); //var id = Unsafe.Add(ref symbols, bb.B); var c = callInfo.Proc?.Scope?.TargetClass ?? ObjectClass; SetConst(Unsafe.Add(ref symbols, bb.B), c, Unsafe.Add(ref registers, bb.A)); @@ -733,7 +750,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu } case OpCode.GetMCnst: Markers.GetMCnst(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, bb.A); { //var mod = Unsafe.Add(ref registers, bb.A); @@ -744,7 +761,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu case OpCode.SetMCnst: { Markers.SetMCnst(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, bb.A); //var mod = Unsafe.Add(ref registers, bb.A + 1); var name = Unsafe.Add(ref symbols, bb.B); @@ -754,7 +771,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu case OpCode.GetIdx: { Markers.GetIdx(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); var valueB = Unsafe.Add(ref registerA, 1); switch (registerA.Object) @@ -788,7 +805,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu case OpCode.GetIdx0: { Markers.GetIdx0(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var recv = Unsafe.Add(ref registers, bb.B); switch (recv.Object) { @@ -812,7 +829,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu case OpCode.SetIdx: { Markers.SetIdx(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); var keyVal = Unsafe.Add(ref registerA, 1); var setVal = Unsafe.Add(ref registerA, 2); @@ -845,7 +862,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu Markers.GetUpVar(); OperandBBB bbb; { - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var env = callInfo.Proc?.FindUpperEnvTo(bbb.C); if (env != null && bbb.B < env.Stack.Length) { @@ -860,7 +877,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu case OpCode.SetUpVar: { Markers.SetUpVar(); - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var env = callInfo.Proc?.FindUpperEnvTo(bbb.C); if (env != null && bbb.B < env.Stack.Length) { @@ -876,7 +893,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu goto Next; case OpCode.JmpIf: Markers.JmpIf(); - bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter); + bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter, ext); if (Unsafe.Add(ref registers, bs.A).Truthy) { callInfo.ProgramCounter += bs.B; @@ -884,7 +901,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu goto Next; case OpCode.JmpNot: Markers.JmpNot(); - bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter); + bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter, ext); if (Unsafe.Add(ref registers, bs.A).Falsy) { callInfo.ProgramCounter += bs.B; @@ -892,7 +909,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu goto Next; case OpCode.JmpNil: Markers.JmpNil(); - bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter); + bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter, ext); if (Unsafe.Add(ref registers, bs.A).IsNil) { callInfo.ProgramCounter += bs.B; @@ -908,7 +925,7 @@ static void GetConstSlowPath(MRubyState state, ref MRubyValue registerA, ref MRu Markers.Sub(); Markers.Mul(); Markers.Div(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); var rhs = Unsafe.Add(ref registerA, 1); @@ -1097,7 +1114,7 @@ static bool ArithmeticSlowPath(MRubyState state, ref MRubyValue registerA, int a case OpCode.AddILV: case OpCode.SubILV: Markers.AddILV(); - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, bbb.A); { var rV = opcode == OpCode.AddILV ? bbb.C : -bbb.C; @@ -1148,7 +1165,7 @@ static bool ArithmeticSlowPath(MRubyState state, ref MRubyValue registerA, int a case OpCode.AddI: case OpCode.SubI: Markers.AddI(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, bb.A); { @@ -1212,7 +1229,7 @@ static bool AddISubISlowPath(MRubyState state, ref MRubyValue registerA, MRubyVa Markers.LE(); Markers.GT(); Markers.GE(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); rhs = Unsafe.Add(ref registerA, 1); @@ -1305,7 +1322,7 @@ static bool ComparisonSlowPath(ref MRubyValue registerA, MRubyValue lhs, MRubyVa case OpCode.Return: { Markers.Return(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); var returnValue = Unsafe.Add(ref registers, a); if (TryReturnJump(ref callInfo, Context.CallDepth, returnValue)) { @@ -1387,7 +1404,7 @@ static void JmpUw(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequenc } case OpCode.Except: Markers.Except(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = Exception switch { MRubyRaiseException x => x.ExceptionObject, @@ -1399,14 +1416,14 @@ static void JmpUw(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequenc case OpCode.Rescue: { Markers.Rescue(); - if (TryRescue(this, ref callInfo, ref registers, ref sequence)) + if (TryRescue(this, ref callInfo, ref registers, ref sequence, ext)) goto JumpAndNext; goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static bool TryRescue(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence) + static bool TryRescue(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, int ext) { - var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var exceptionObjectValue = Unsafe.Add(ref registers, bb.A); var exceptionClassValue = Unsafe.Add(ref registers, bb.B); switch (exceptionClassValue.VType) @@ -1433,7 +1450,7 @@ static bool TryRescue(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyVal case OpCode.RaiseIf: { Markers.RaiseIf(); - var signal = RaiseIf(this, ref callInfo, ref registers, ref sequence, irep, out var retVal); + var signal = RaiseIf(this, ref callInfo, ref registers, ref sequence, irep, out var retVal, ext); switch (signal) { case VmSignal.JumpAndNext: goto JumpAndNext; @@ -1442,10 +1459,10 @@ static bool TryRescue(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyVal goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static VmSignal RaiseIf(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, Irep irep, out MRubyValue retVal) + static VmSignal RaiseIf(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, Irep irep, out MRubyValue retVal, int ext) { retVal = default; - var a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + var a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); var exceptionValue = Unsafe.Add(ref registers, a); switch (exceptionValue.Object) { @@ -1508,7 +1525,7 @@ static VmSignal RaiseIf(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyV case OpCode.MatchErr: { Markers.MatchErr(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); if (Unsafe.Add(ref registers, a).Falsy) { Raise(Names.NoMatchingPatternError, "no matching pattern"u8); @@ -1526,7 +1543,7 @@ static VmSignal RaiseIf(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyV // Send0/SSend0 use BB operand (no argc byte); synthesize C=0. if (opcode is OpCode.Send0 or OpCode.SSend0) { - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); bbb = default; bbb.A = bb.A; bbb.B = bb.B; @@ -1534,7 +1551,7 @@ static VmSignal RaiseIf(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyV } else { - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); } // Trivial getter fast path — skip full dispatch for no-arg sends @@ -1764,14 +1781,14 @@ static void CallProc(MRubyState state, out Irep irep, ref MRubyCallInfo callInfo { Markers.Super(); - Super(this, ref callInfo, Context.Stack.AsSpan(callInfo.StackPointer), ref sequence); + Super(this, ref callInfo, Context.Stack.AsSpan(callInfo.StackPointer), ref sequence, ext); callInfo = ref Context.CurrentCallInfo; goto case OpCode.SendInternal; [MethodImpl(MethodImplOptions.NoInlining)] - static void Super(MRubyState state, ref MRubyCallInfo callInfo, Span registers, ref byte sequence) + static void Super(MRubyState state, ref MRubyCallInfo callInfo, Span registers, ref byte sequence, int ext) { - var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var targetClass = callInfo.Scope.TargetClass; var methodId = callInfo.MethodId; if (methodId == default || targetClass == null!) @@ -1804,7 +1821,7 @@ static void Super(MRubyState state, ref MRubyCallInfo callInfo, Span { Markers.ArgAry(); - bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter); + bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter, ext); var bits = (ushort)bs.B; var m1 = (bits >> 11) & 0x3f; var r = (bits >> 10) & 0x1; @@ -1868,8 +1885,8 @@ RArray NewArrayFromArgumentArray(Span sourceStack, int m1, int m2) { Markers.Enter(); - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); - var bits = (uint)bbb.A << 16 | (uint)bbb.B << 8 | bbb.C; + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); + var bits = (uint)bbb.A << 16 | (uint)bbb.B << 8 | (uint)bbb.C; var aspec = new ArgumentSpec(bits); var argc = callInfo.ArgumentCount; @@ -2069,7 +2086,7 @@ void EnterSlowPath(ref MRubyCallInfo callInfo, Span argv, Spansyms[b]); var key = Unsafe.Add(ref symbols, bb.B); var kargOffset = callInfo.KeywordArgumentOffset; @@ -2098,7 +2115,7 @@ void RaiseMissingKeywordError(MRubyValue keyValue) case OpCode.KeyP: { Markers.KeyP(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var key = Unsafe.Add(ref symbols, bb.B); var kdict = Unsafe.Add(ref registers, callInfo.KeywordArgumentOffset); Unsafe.Add(ref registers, bb.A) = kdict.As().TryGetValue(key, out _); @@ -2131,7 +2148,7 @@ void RaiseUnknownKeyword(MRubyValue keyValue) { goto case OpCode.Return; } - var signal = ReturnBlk(this, ref callInfo, ref registers, ref sequence, out var retVal); + var signal = ReturnBlk(this, ref callInfo, ref registers, ref sequence, out var retVal, ext); switch (signal) { case VmSignal.JumpAndNext: goto JumpAndNext; @@ -2140,10 +2157,10 @@ void RaiseUnknownKeyword(MRubyValue keyValue) goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static VmSignal ReturnBlk(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, out MRubyValue retVal) + static VmSignal ReturnBlk(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, out MRubyValue retVal, int ext) { retVal = default; - var a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + var a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); var dest = callInfo.Proc!.FindReturningDestination(out var env); if (dest.Scope is not REnv destEnv || destEnv.Context == state.Context) { @@ -2174,7 +2191,7 @@ static VmSignal ReturnBlk(MRubyState state, ref MRubyCallInfo callInfo, ref MRub { goto case OpCode.Return; } - var signal = Break(this, ref callInfo, ref registers, ref sequence, out var retVal); + var signal = Break(this, ref callInfo, ref registers, ref sequence, out var retVal, ext); switch (signal) { case VmSignal.JumpAndNext: goto JumpAndNext; @@ -2183,10 +2200,10 @@ static VmSignal ReturnBlk(MRubyState state, ref MRubyCallInfo callInfo, ref MRub goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static VmSignal Break(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, out MRubyValue retVal) + static VmSignal Break(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, out MRubyValue retVal, int ext) { retVal = default; - var a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + var a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); if (callInfo.Proc is { } proc && !proc.HasFlag(MRubyObjectFlags.ProcOrphan) && proc.Scope is REnv env && env.Context == state.Context) @@ -2213,12 +2230,12 @@ static VmSignal Break(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyVal case OpCode.BlkPush: { Markers.BlkPush(); - BlkPush(this, ref callInfo, Context.Stack.AsSpan(callInfo.StackPointer), ref sequence); + BlkPush(this, ref callInfo, Context.Stack.AsSpan(callInfo.StackPointer), ref sequence, ext); [MethodImpl(MethodImplOptions.NoInlining)] - static void BlkPush(MRubyState state, ref MRubyCallInfo callInfo, Span registers, ref byte sequence) + static void BlkPush(MRubyState state, ref MRubyCallInfo callInfo, Span registers, ref byte sequence, int ext) { - var bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter); + var bs = OperandBS.Read(ref sequence, ref callInfo.ProgramCounter, ext); var b = bs.B; var m1 = (b >> 11) & 0x3f; var r = (b >> 10) & 0x1; @@ -2257,7 +2274,7 @@ static void BlkPush(MRubyState state, ref MRubyCallInfo callInfo, Span(); array.Set(bbb.C, Unsafe.Add(ref registers, bbb.A)); goto Next; @@ -2370,7 +2387,7 @@ static void BlkPush(MRubyState state, ref MRubyCallInfo callInfo, Span()); goto Next; case OpCode.Symbol: { Markers.Symbol(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); //var name = irep.PoolValues[bb.B].As(); Unsafe.Add(ref registers, bb.A) = Intern(irep.PoolValues[bb.B].As()); goto Next; @@ -2451,21 +2468,21 @@ static void APostLong(MRubyState state, RArray array, int pre, int post, ref MRu case OpCode.String: { Markers.String(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var str = irep.PoolValues[bb.B].As(); Unsafe.Add(ref registers, bb.A) = str.Dup(); goto Next; } case OpCode.StrCat: Markers.StrCat(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); registerA.As().Concat(Stringify(Unsafe.Add(ref registerA, 1))); goto Next; case OpCode.Hash: { Markers.Hash(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, bb.A); var hash = NewHash(bb.B); var lastIndex = bb.B * 2; @@ -2480,7 +2497,7 @@ static void APostLong(MRubyState state, RArray array, int pre, int post, ref MRu case OpCode.HashAdd: { Markers.HashAdd(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, bb.A); var hashValue = registerA; var lastIndex = bb.B * 2 + 1; @@ -2506,7 +2523,7 @@ static void APostLong(MRubyState state, RArray array, int pre, int post, ref MRu case OpCode.Method: { Markers.Lambda(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); RProc proc; if (opcode == OpCode.Method) { @@ -2527,7 +2544,7 @@ static void APostLong(MRubyState state, RArray array, int pre, int post, ref MRu case OpCode.RangeInc: case OpCode.RangeExc: Markers.RangeInc(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); { var begin = registerA; @@ -2539,20 +2556,20 @@ static void APostLong(MRubyState state, RArray array, int pre, int post, ref MRu } case OpCode.OClass: Markers.OClass(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = ObjectClass; goto Next; case OpCode.Class: { Markers.Class(); - Class(this, irep, Context.Stack.AsSpan(callInfo.StackPointer), ref sequence, ref callInfo); + Class(this, irep, Context.Stack.AsSpan(callInfo.StackPointer), ref sequence, ref callInfo, ext); goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static void Class(MRubyState state, Irep irep, Span registers, ref byte sequence, ref MRubyCallInfo callInfo) + static void Class(MRubyState state, Irep irep, Span registers, ref byte sequence, ref MRubyCallInfo callInfo, int ext) { - var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var id = irep.Symbols[bb.B]; var outer = registers[bb.A]; var super = registers[bb.A + 1]; @@ -2632,13 +2649,13 @@ void RaiseSuperClassMismatch(MRubyValue oldValue) case OpCode.Module: { Markers.Module(); - Module(this, ref callInfo, ref registers, ref sequence, ref symbols); + Module(this, ref callInfo, ref registers, ref sequence, ref symbols, ext); goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static void Module(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, ref Symbol symbols) + static void Module(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue registers, ref byte sequence, ref Symbol symbols, int ext) { - var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); ref var registerA = ref Unsafe.Add(ref registers, bb.A); var id = Unsafe.Add(ref symbols, bb.B); RClass outerClass; @@ -2672,7 +2689,7 @@ static void Module(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue case OpCode.Exec: { Markers.Exec(); - Exec(this, ref callInfo, ref sequence, ref registers); + Exec(this, ref callInfo, ref sequence, ref registers, ext); callInfo = ref Context.CurrentCallInfo; irep = callInfo.Proc!.Irep; sequence = ref GetArrayDataReference(irep.Sequence); @@ -2681,9 +2698,9 @@ static void Module(MRubyState state, ref MRubyCallInfo callInfo, ref MRubyValue goto Next; [MethodImpl(MethodImplOptions.NoInlining)] - static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence, ref MRubyValue registers) + static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence, ref MRubyValue registers, int ext) { - var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + var bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var receiver = Unsafe.Add(ref registers, bb.A); var irep = callInfo.Proc!.Irep; var targetIrep = irep.Children[bb.B]; @@ -2711,7 +2728,7 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.Def: { Markers.Def(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var target = Unsafe.Add(ref registers, bb.A).As(); var proc = Unsafe.Add(ref registers, bb.A + 1).As(); var methodId = Unsafe.Add(ref symbols, bb.B); @@ -2724,7 +2741,7 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.TDef: { Markers.TDef(); - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var target = callInfo.Scope.TargetClass; var methodId = Unsafe.Add(ref symbols, bbb.B); var proc = NewProc(irep.Children[bbb.C]); @@ -2737,7 +2754,7 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.SDef: { Markers.SDef(); - bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter); + bbb = OperandBBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var target = SingletonClassOf(Unsafe.Add(ref registers, bbb.A)); var methodId = Unsafe.Add(ref symbols, bbb.B); var proc = NewProc(irep.Children[bbb.C]); @@ -2750,7 +2767,7 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.Alias: { Markers.Alias(); - bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter); + bb = OperandBB.Read(ref sequence, ref callInfo.ProgramCounter, ext); var c = callInfo.Scope.TargetClass; var newMethodId = Unsafe.Add(ref symbols, bb.A); var oldMethodId = Unsafe.Add(ref symbols, bb.B); @@ -2761,7 +2778,7 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.Undef: { Markers.Undef(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); var c = callInfo.Scope.TargetClass; var methodId = Unsafe.Add(ref symbols, a); UndefMethod(c, methodId); @@ -2770,7 +2787,7 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.SClass: { Markers.SClass(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); registerA = ref Unsafe.Add(ref registers, a); registerA = SingletonClassOf(registerA); goto Next; @@ -2778,14 +2795,14 @@ static void Exec(MRubyState state, ref MRubyCallInfo callInfo, ref byte sequence case OpCode.TClass: { Markers.TClass(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); Unsafe.Add(ref registers, a) = callInfo.Scope.TargetClass; goto Next; } case OpCode.Err: { Markers.Err(); - a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter); + a = ReadOperandB(ref sequence, ref callInfo.ProgramCounter, ext); var message = irep.PoolValues[a]; Raise(Names.LocalJumpError, message.As()); goto Next; @@ -2859,11 +2876,17 @@ ref MRubyCallInfo GetNextCallInfo(int nextStackPointer, OpCode code, byte argCou } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static byte ReadOperandB(ref byte sequence, ref int pc) + static int ReadOperandB(ref byte sequence, ref int pc, int ext = 0) { + if (ext != 0) + { + var p = pc + 1; + var widened = Operands.ReadOperand(ref sequence, ref p, Operands.Widen1(ext)); + pc = p; + return widened; + } pc += 2; - var result = Unsafe.Add(ref sequence, pc - 1); - return result; + return Unsafe.Add(ref sequence, pc - 1); } /// I don't know why, but introducing this method makes the code faster. diff --git a/src/ChibiRuby/StdLib/ProcMembers.cs b/src/ChibiRuby/StdLib/ProcMembers.cs index 6276ecdb..77bd0df8 100644 --- a/src/ChibiRuby/StdLib/ProcMembers.cs +++ b/src/ChibiRuby/StdLib/ProcMembers.cs @@ -84,7 +84,7 @@ public static MRubyValue Arity(MRubyState state, MRubyValue self) var pc = 0; var bbb = OperandBBB.Read(ref GetArrayDataReference(sequence), ref pc); - var bits = (uint)bbb.A << 16 | (uint)bbb.B << 8 | bbb.C; + var bits = (uint)bbb.A << 16 | (uint)bbb.B << 8 | (uint)bbb.C; var aspec = new ArgumentSpec(bits); // arity = ra || (MRB_PROC_STRICT_P(p) && op) ? -(ma + pa + 1) : ma + pa; var arity = aspec.TakeRestArguments || (proc.HasFlag(MRubyObjectFlags.ProcStrict) && aspec.OptionalArgumentsCount > 0) diff --git a/tests/ChibiRuby.Tests/VmTest.cs b/tests/ChibiRuby.Tests/VmTest.cs index f0a729bc..0b40b2f0 100644 --- a/tests/ChibiRuby.Tests/VmTest.cs +++ b/tests/ChibiRuby.Tests/VmTest.cs @@ -631,6 +631,48 @@ public void ArrayRotateBangAndInclude() Assert.That(result, Is.EqualTo(MRubyValue.True)); } + // https://github.com/hadashiA/ChibiRuby/issues/188 + // A single method holding more than 255 distinct string literals overflows + // the irep pool index into two bytes, so the compiler prefixes the widened + // `String` operand with EXT2. The VM must decode the prefix instead of + // throwing "Invalid opcode EXT2". + [Test] + public void ExtendedOperand_StringPoolOverflow() + { + var source = new System.Text.StringBuilder(); + source.AppendLine("def big"); + source.AppendLine(" arr = []"); + for (var i = 0; i < 300; i++) + { + source.AppendLine($" arr << \"str_{i}\""); + } + source.AppendLine(" arr.length"); + source.AppendLine("end"); + source.AppendLine("big"); + + var result = Exec(System.Text.Encoding.UTF8.GetBytes(source.ToString())); + Assert.That(result, Is.EqualTo(new MRubyValue(300))); + } + + // A single method calling more than 255 distinct method names overflows the + // symbol pool, widening the `Send` symbol operand (EXT2). Also exercises the + // last widened literal actually round-trips to the right value. + [Test] + public void ExtendedOperand_SymbolPoolOverflow() + { + var source = new System.Text.StringBuilder(); + source.AppendLine("s = \"\""); + for (var i = 0; i < 300; i++) + { + source.AppendLine($"def m{i}; {i}; end"); + source.AppendLine($"s = m{i}.to_s"); + } + source.AppendLine("s"); + + var result = Exec(System.Text.Encoding.UTF8.GetBytes(source.ToString())); + Assert.That(result.As().ToString(), Is.EqualTo("299")); + } + MRubyValue Exec(ReadOnlySpan code) { using var compilation = compiler.Compile(code); From 08cded3cbec325641019f7d487a3ae8f3602308b Mon Sep 17 00:00:00 2001 From: hadashiA Date: Sun, 5 Jul 2026 18:16:17 +0900 Subject: [PATCH 2/2] Add --quick-bench mode to benchmark runner --- sandbox/ChibiRuby.Benchmark/Program.cs | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/sandbox/ChibiRuby.Benchmark/Program.cs b/sandbox/ChibiRuby.Benchmark/Program.cs index dd2b711d..4aa2506a 100644 --- a/sandbox/ChibiRuby.Benchmark/Program.cs +++ b/sandbox/ChibiRuby.Benchmark/Program.cs @@ -73,6 +73,33 @@ // return; // } +// Lightweight in-process A/B timing (no BenchmarkDotNet cross-process overhead): +// --quick-bench [warmup] [iters] +// Prints median + min ms/iter over `iters` measured runs after `warmup` runs. +if (args is ["--quick-bench", ..]) +{ + var file = args.Length >= 2 ? args[1] : "bm_ao_render.rb"; + var warmup = args.Length >= 3 && int.TryParse(args[2], out var w) ? w : 5; + var iters = args.Length >= 4 && int.TryParse(args[3], out var it) ? it : 15; + + using var loader = new RubyScriptLoader(); + loader.PreloadScriptFromFile(file); + for (var i = 0; i < warmup; i++) loader.RunChibiRuby(); + + var samples = new double[iters]; + for (var i = 0; i < iters; i++) + { + var sw = Stopwatch.StartNew(); + loader.RunChibiRuby(); + sw.Stop(); + samples[i] = sw.Elapsed.TotalMilliseconds; + } + Array.Sort(samples); + var median = samples[iters / 2]; + Console.WriteLine($"[quick-bench] {file}: median={median:F1}ms min={samples[0]:F1}ms max={samples[iters - 1]:F1}ms (n={iters})"); + return; +} + if (args is ["--quick-optcarrot", ..]) { var frames = args.Length >= 2 && int.TryParse(args[1], out var parsedFrames)