Skip to content
Merged
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
16 changes: 4 additions & 12 deletions src/ChibiRuby.Compiler/CompilationResult.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,36 @@
using System;
using System.Collections.Generic;

namespace ChibiRuby.Compiler
{
namespace ChibiRuby.Compiler;

public class CompilationResult : IDisposable
{
public IReadOnlyList<DiagnosticsDescriptor> Diagnostics { get; }

public bool HasError => contextHandle.HasError;

readonly MRubyState mrb;
readonly MrbStateHandle stateHandle;
readonly MrcCContextHandle contextHandle;
readonly IntPtr bytecodeDataPtr;
readonly int bytecodeLength;
bool disposed;

internal CompilationResult(
MRubyState mrb,
MrbStateHandle stateHandle,
MrcCContextHandle contextHandle,
IntPtr bytecodeDataPtr,
int bytecodeLength)
{
this.mrb = mrb;
this.stateHandle = stateHandle;
this.contextHandle = contextHandle;
this.bytecodeDataPtr = bytecodeDataPtr;
this.bytecodeLength = bytecodeLength;
Diagnostics = contextHandle.GetDiagnostics();
}

internal CompilationResult(
MRubyState mrb,
MrbStateHandle stateHandle,
MrcCContextHandle contextHandle)
internal CompilationResult(MRubyState mrb, MrcCContextHandle contextHandle)
{
this.mrb = mrb;
this.stateHandle = stateHandle;
this.contextHandle = contextHandle;
Diagnostics = contextHandle.GetDiagnostics();
}
Expand Down Expand Up @@ -89,5 +82,4 @@ unsafe void Dispose(bool disposing)
contextHandle.Dispose();
disposed = true;
}
}
}
}
71 changes: 34 additions & 37 deletions src/ChibiRuby.Compiler/MRubyCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class MRubyCompileException(string message) : Exception(message);
public record MRubyCompileOptions
{
public static MRubyCompileOptions Default { get; set; } = new();

public bool EnableDebugInfo { get; set; } = true;
}

public class MRubyCompiler : IDisposable
Expand All @@ -21,19 +23,19 @@ public static MRubyCompiler Create(MRubyState mrb, MRubyCompileOptions? options
return new MRubyCompiler(mrb, compilerStateHandle, options);
}

public MRubyState State => mruby;
public MRubyState State => mrubyState;

readonly MRubyState mruby;
readonly MRubyState mrubyState;
readonly MrbStateHandle compileStateHandle;
readonly MRubyCompileOptions options;
bool disposed;

MRubyCompiler(
MRubyState mruby,
MRubyState mrubyState,
MrbStateHandle compileStateHandle,
MRubyCompileOptions? options = null)
{
this.mruby = mruby;
this.mrubyState = mrubyState;
this.compileStateHandle = compileStateHandle;
this.options = options ?? MRubyCompileOptions.Default;
}
Expand All @@ -46,19 +48,22 @@ public static MRubyCompiler Create(MRubyState mrb, MRubyCompileOptions? options
public MRubyValue LoadSourceCodeFile(string path)
{
using var compilation = CompileFile(path);
return mruby.LoadBytecode(compilation.AsBytecode());
return mrubyState.LoadBytecode(compilation.AsBytecode());
}

public async Task<MRubyValue> LoadSourceCodeFileAsync(string path, CancellationToken cancellationToken = default)
public async Task<MRubyValue> LoadSourceCodeFileAsync(
string path,
MRubyCompileOptions? options = null,
CancellationToken? cancellationToken = default)
{
using var compilation = await CompileFileAsync(path, cancellationToken);
return mruby.LoadBytecode(compilation.AsBytecode());
using var compilation = await CompileFileAsync(path, options, cancellationToken.GetValueOrDefault());
return mrubyState.LoadBytecode(compilation.AsBytecode());
}

public MRubyValue LoadSourceCode(ReadOnlySpan<byte> utf8Source)
{
using var compilation = Compile(utf8Source);
return mruby.LoadBytecode(compilation.AsBytecode());
return mrubyState.LoadBytecode(compilation.AsBytecode());
}

public MRubyValue LoadSourceCode(string source)
Expand All @@ -70,8 +75,8 @@ public MRubyValue LoadSourceCode(string source)
public RFiber LoadSourceCodeAsFiber(ReadOnlySpan<byte> utf8Source)
{
using var compilation = Compile(utf8Source);
var proc = mruby.CreateProc(compilation.ToIrep());
return mruby.CreateFiber(proc);
var proc = mrubyState.CreateProc(compilation.ToIrep());
return mrubyState.CreateFiber(proc);
}

public RFiber LoadSourceCodeAsFiber(string source)
Expand All @@ -80,50 +85,41 @@ public RFiber LoadSourceCodeAsFiber(string source)
return LoadSourceCodeAsFiber(utf8Source);
}

public CompilationResult CompileFile(string filePath, bool debugInfo = true)
public CompilationResult CompileFile(string filePath, MRubyCompileOptions? options = null)
{
options ??= this.options;
var bytes = File.ReadAllBytes(filePath);

return Compile(bytes,
filename: Path.GetFullPath(filePath),
debugInfo: debugInfo);
return Compile(bytes, filename: Path.GetFullPath(filePath), options);
}

public async Task<CompilationResult> CompileFileAsync(string filePath, CancellationToken cancellationToken = default, bool debugInfo = true)
public async Task<CompilationResult> CompileFileAsync(
string filePath,
MRubyCompileOptions? options = null,
CancellationToken cancellationToken = default)
{
options ??= this.options;
var bytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
return Compile(bytes,
filename: Path.GetFullPath(filePath),
debugInfo: debugInfo);
return Compile(bytes, filename: Path.GetFullPath(filePath), options);
}

public CompilationResult Compile(string sourceCode, string? filename = null, bool debugInfo = true) =>
Compile(Encoding.UTF8.GetBytes(sourceCode), filename, debugInfo);
public CompilationResult Compile(string sourceCode, string? filename = null, MRubyCompileOptions? options = null) =>
Compile(Encoding.UTF8.GetBytes(sourceCode), filename, options);

/// <summary>
/// Compile Ruby source to <c>.mrb</c> bytecode.
/// </summary>
/// <param name="utf8Source">UTF-8 encoded source bytes.</param>
/// <param name="filename">
/// Optional source file name to record in the bytecode's DBG section. When non-null
/// (and <paramref name="debugInfo"/> is true), tools like the debugger / Backtrace
/// resolve <c>pc</c> back to <c>filename:line</c>.
/// </param>
/// <param name="debugInfo">
/// When true (default), the bytecode includes a DBG section with line numbers. Set
/// to false to produce smaller bytecode for production distribution.
/// </param>
public unsafe CompilationResult Compile(
ReadOnlySpan<byte> utf8Source,
string? filename = null,
bool debugInfo = true)
MRubyCompileOptions? options = null)
{
// Workaround for the crash that occurs when passing a blank to mrc
if (utf8Source.IsEmpty)
{
Span<byte> fallback = stackalloc byte[1];
fallback[0] = (byte)' ';
return Compile(fallback, filename, debugInfo);
return Compile(fallback, filename, options);
}

if (BomHelper.TryDetectEncoding(utf8Source, out var encoding))
Expand Down Expand Up @@ -158,21 +154,22 @@ public unsafe CompilationResult Compile(
}
}

options ??= this.options;
// MRB_DUMP_DEBUG_INFO == 1; include the DBG section in the serialized .mrb so
// the C# RiteParser can recover (file, line) info for each pc.
var dumpFlags = (byte)(debugInfo ? 1 : 0);
var dumpFlags = (byte)(options.EnableDebugInfo ? 1 : 0);

fixed (byte* sourcePtr = utf8Source)
{
var irepPtr = NativeMethods.MrcLoadStringCxt(context.DangerousGetPtr(), &sourcePtr, utf8Source.Length);
if (irepPtr == null || context.HasError)
{
// error
return new CompilationResult(mruby, compileStateHandle, context);
return new CompilationResult(mrubyState, context);
}
NativeMethods.MrcDumpIrep(context.DangerousGetPtr(), irepPtr, dumpFlags, &bin, &binLength);
NativeMethods.MrcIrepFree(context.DangerousGetPtr(), irepPtr);
return new CompilationResult(mruby, compileStateHandle, context, (IntPtr)bin, (int)binLength);
return new CompilationResult(mrubyState, context, (IntPtr)bin, (int)binLength);
}
}

Expand All @@ -190,4 +187,4 @@ public void Dispose()
GC.SuppressFinalize(this);
}
}
}
}
6 changes: 6 additions & 0 deletions src/ChibiRuby.Compiler/MRubyStateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ChibiRuby.Compiler;

public static class MRubyStateExtensions
{

}
13 changes: 4 additions & 9 deletions src/ChibiRuby.Compiler/MrbStateHandle.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
using System;
using System.Runtime.InteropServices;

namespace ChibiRuby.Compiler
{
class MrbStateHandle : SafeHandle
{
public MrbStateHandle(IntPtr invalidHandleValue) : base(invalidHandleValue, true)
{
}
namespace ChibiRuby.Compiler;

class MrbStateHandle(IntPtr invalidHandleValue) : SafeHandle(invalidHandleValue, true)
{
public static unsafe MrbStateHandle Create()
{
var ptr = NativeMethods.MrbOpen();
Expand All @@ -25,5 +21,4 @@ protected override unsafe bool ReleaseHandle()
NativeMethods.MrbClose(DangerousGetPtr());
return true;
}
}
}
}
74 changes: 35 additions & 39 deletions src/ChibiRuby.Compiler/MrcCContextHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,49 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace ChibiRuby.Compiler
namespace ChibiRuby.Compiler;

class MrcCContextHandle(IntPtr invalidHandleValue) : SafeHandle(invalidHandleValue, true)
{
class MrcCContextHandle : SafeHandle
public static unsafe MrcCContextHandle Create(MrbStateHandle mrbStateHandle)
{
public static unsafe MrcCContextHandle Create(MrbStateHandle mrbStateHandle)
{
var ptr = NativeMethods.MrcCContextNew(mrbStateHandle.DangerousGetPtr());
return new MrcCContextHandle((IntPtr)ptr);
}

public override bool IsInvalid => handle == IntPtr.Zero;
var ptr = NativeMethods.MrcCContextNew(mrbStateHandle.DangerousGetPtr());
return new MrcCContextHandle((IntPtr)ptr);
}

public unsafe bool HasError => (DangerousGetPtr()->Flags & 0x01) != 0;
public override bool IsInvalid => handle == IntPtr.Zero;

public MrcCContextHandle(IntPtr invalidHandleValue) : base(invalidHandleValue, true)
{
}
public unsafe bool HasError => (DangerousGetPtr()->Flags & 0x01) != 0;

public unsafe MrcCContext* DangerousGetPtr() => (MrcCContext*)DangerousGetHandle();
public unsafe MrcCContext* DangerousGetPtr() => (MrcCContext*)DangerousGetHandle();

protected override unsafe bool ReleaseHandle()
{
if (IsClosed) return false;
NativeMethods.MrcCContextFree(DangerousGetPtr());
return true;
}
protected override unsafe bool ReleaseHandle()
{
if (IsClosed) return false;
NativeMethods.MrcCContextFree(DangerousGetPtr());
return true;
}

public unsafe IReadOnlyList<DiagnosticsDescriptor> GetDiagnostics()
public unsafe IReadOnlyList<DiagnosticsDescriptor> GetDiagnostics()
{
var list = new List<DiagnosticsDescriptor>();
var nodePtr = DangerousGetPtr()->DiagnosticList;
while (nodePtr != null)
{
var list = new List<DiagnosticsDescriptor>();
var nodePtr = DangerousGetPtr()->DiagnosticList;
while (nodePtr != null)
var severity = nodePtr->DiagnosticCode switch
{
var severity = nodePtr->DiagnosticCode switch
{
MrcDiagnosticCode.Warning => DiagnosticSeverity.Warning,
MrcDiagnosticCode.Error => DiagnosticSeverity.Error,
MrcDiagnosticCode.GeneratorWarning => DiagnosticSeverity.GeneratorWarning,
MrcDiagnosticCode.GeneratorError => DiagnosticSeverity.GeneratorError,
_ => throw new ArgumentOutOfRangeException()
};
var message = Marshal.PtrToStringUTF8((IntPtr)nodePtr->Message);
var descriptor = new DiagnosticsDescriptor(severity, nodePtr->Line, nodePtr->Column, message);
list.Add(descriptor);
nodePtr = nodePtr->Next;
}
return list;
MrcDiagnosticCode.Warning => DiagnosticSeverity.Warning,
MrcDiagnosticCode.Error => DiagnosticSeverity.Error,
MrcDiagnosticCode.GeneratorWarning => DiagnosticSeverity.GeneratorWarning,
MrcDiagnosticCode.GeneratorError => DiagnosticSeverity.GeneratorError,
_ => throw new ArgumentOutOfRangeException()
};
var message = Marshal.PtrToStringUTF8((IntPtr)nodePtr->Message);
var descriptor = new DiagnosticsDescriptor(severity, nodePtr->Line, nodePtr->Column, message);
list.Add(descriptor);
nodePtr = nodePtr->Next;
}

return list;
}
}
}
5 changes: 4 additions & 1 deletion tests/ChibiRuby.Tests/IrepDebugInfoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ public void CompileWithDebugInfo_PopulatesIrepDebugInfo()
[Test]
public void CompileWithoutDebugInfo_DoesNotProduceIrepDebugInfo()
{
using var c = compiler.Compile("x = 1\nx"u8, filename: "test.rb", debugInfo: false);
using var c = compiler.Compile("x = 1\nx"u8, filename: "test.rb", new MRubyCompileOptions
{
EnableDebugInfo = false
});
var irep = c.ToIrep();
Assert.That(irep.DebugInfo, Is.Null, "DebugInfo should be omitted when debugInfo=false");
}
Expand Down
Loading