Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions sandbox/ChibiRuby.Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,33 @@
// return;
// }

// Lightweight in-process A/B timing (no BenchmarkDotNet cross-process overhead):
// --quick-bench <script.rb> [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)
Expand Down
252 changes: 144 additions & 108 deletions src/ChibiRuby/Internals/Operands.cs
Original file line number Diff line number Diff line change
@@ -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<OperandB>(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<OperandBB>(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<OperandS>(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<OperandBS>(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<OperandBBB>(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<OperandBSS>(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<OperandW>(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;
}
}
}
}
Loading
Loading