Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(vm): use dynamic dispatch to enable/disable tracing of instructions #35

Merged
merged 9 commits into from
Oct 26, 2023
6 changes: 6 additions & 0 deletions src/cmd/cmd.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const vm_core = @import("../vm/core.zig");
const RunContext = @import("../vm/run_context.zig").RunContext;
const relocatable = @import("../vm/memory/relocatable.zig");
const Config = @import("../vm/config.zig").Config;
const build_options = @import("../main.zig").build_options;
nils-mathieu marked this conversation as resolved.
Show resolved Hide resolved

// ************************************************************
// * GLOBAL VARIABLES *
Expand Down Expand Up @@ -89,6 +90,11 @@ fn execute(_: []const []const u8) !void {
.{},
);

if (build_options.disable_tracing and config.enable_trace) {
std.log.err("Tracing is disabled in this build.\n", .{});
return;
nils-mathieu marked this conversation as resolved.
Show resolved Hide resolved
}

// Create a new VM instance.
var vm = try vm_core.CairoVM.init(&gpa_allocator, config);
defer vm.deinit(); // <-- This ensures that resources are freed when exiting the scope
Expand Down
8 changes: 8 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ pub const std_options = struct {
pub const logFn = customlogFn;
};

/// Global compilation flags for the project.
pub const build_options = struct {
/// Whether tracing should be disabled globally. This prevents the
/// user from enabling tracing via the command line but it might
/// improve performance slightly.
pub const disable_tracing = true;
};

// *****************************************************************************
// * MAIN ENTRY POINT *
// *****************************************************************************
Expand Down
38 changes: 12 additions & 26 deletions src/vm/core.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const instructions = @import("instructions.zig");
const RunContext = @import("run_context.zig").RunContext;
const CairoVMError = @import("error.zig").CairoVMError;
const Config = @import("config.zig").Config;
const TraceContext = @import("trace_context.zig").TraceContext;
const build_options = @import("../main.zig").build_options;

/// Represents the Cairo VM.
pub const CairoVM = struct {
Expand All @@ -28,7 +30,7 @@ pub const CairoVM = struct {
/// Whether the run is finished or not.
is_run_finished: bool,
/// VM trace
trace: ?ArrayList(TraceEntry),
trace_context: TraceContext,

// ************************************************************
// * MEMORY ALLOCATION AND DEALLOCATION *
Expand All @@ -47,19 +49,15 @@ pub const CairoVM = struct {
const memory_segment_manager = try segments.MemorySegmentManager.init(allocator);
// Initialize the run context.
const run_context = try RunContext.init(allocator);

var trace: ?ArrayList(TraceEntry) = null;
if (config.enable_trace) {
// FIXME: https://github.com/keep-starknet-strange/cairo-zig/issues/30
trace = try ArrayList(TraceEntry).initCapacity(allocator.*, 4096);
}
// Initialize the trace context.
const trace_context = try TraceContext.init(allocator.*, config.enable_trace);

return CairoVM{
.allocator = allocator,
.run_context = run_context,
.segments = memory_segment_manager,
.is_run_finished = false,
.trace = trace,
.trace_context = trace_context,
};
}

Expand All @@ -70,9 +68,7 @@ pub const CairoVM = struct {
// Deallocate the run context.
self.run_context.deinit();
// Deallocate trace
if (self.trace) |trace| {
trace.deinit();
}
self.trace_context.deinit();
}

// ************************************************************
Expand Down Expand Up @@ -124,14 +120,14 @@ pub const CairoVM = struct {
self: *CairoVM,
instruction: *const instructions.Instruction,
) !void {
if (self.trace) |tracer| {
var mut_tracer = tracer;
try mut_tracer.append(TraceEntry{
if (!build_options.disable_tracing) {
try self.trace_context.traceInstruction(.{
.pc = self.run_context.pc,
.ap = self.run_context.ap,
.fp = self.run_context.fp,
});
}

const operands_result = try self.computeOperands(instruction);
_ = operands_result;
}
Expand Down Expand Up @@ -412,12 +408,6 @@ const OperandsResult = struct {
}
};

const TraceEntry = struct {
pc: *relocatable.Relocatable,
ap: *relocatable.Relocatable,
fp: *relocatable.Relocatable,
};

// ************************************************************
// * TESTS *
// ************************************************************
Expand Down Expand Up @@ -966,9 +956,7 @@ test "trace is enabled" {
// * TEST CHECKS *
// ************************************************************
// Check that trace was initialized
const trace = vm.trace;

if (trace == null) {
if (!vm.trace_context.isEnabled()) {
return error.TraceShouldHaveBeenEnabled;
}
}
Expand All @@ -993,9 +981,7 @@ test "trace is disabled" {
// * TEST CHECKS *
// ************************************************************
// Check that trace was initialized
const trace = vm.trace;

if (trace != null) {
if (vm.trace_context.isEnabled()) {
return error.TraceShouldHaveBeenDisabled;
}
}
132 changes: 132 additions & 0 deletions src/vm/trace_context.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const std = @import("std");
nils-mathieu marked this conversation as resolved.
Show resolved Hide resolved
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;

const Relocatable = @import("./memory/relocatable.zig").Relocatable;

/// The inner state of the `TraceContext`.
///
/// It's either something, or nothing. But no tag is kept around to remember which one it is. This
/// "memory" comes from dynamic dispatch.
const State = union {
enabled: TraceEnabled,
disabled: TraceDisabled,

/// A function that records a new entry in the tracing context.
const TraceInstructionFn = fn (state: *State, entry: TraceContext.Entry) Allocator.Error!void;
/// A function that frees the resources owned by the tracing context.
const DeinitFn = fn (state: *State) void;
};

/// Contains the state required to trace the execution of the Cairo VM.
///
/// This includes a big array with `TraceEntry` instances.
pub const TraceContext = struct {
/// An entry recorded representing the state of the VM at a given point in time.
pub const Entry = struct {
pc: *Relocatable,
ap: *Relocatable,
fp: *Relocatable,
};

/// The current state of the tracing context.
state: State,

//
// DYNAMIC DISPATCH FUNCTIONS
//
// The following functions "remember" whether `state` is in an intialized or deinitialized
// state and properly act accordingly.
//

/// A function to call when a new entry is recorded.
traceInstructionFn: *const State.TraceInstructionFn,
/// A functio to call
deinitFn: *const State.DeinitFn,

/// Initializes a new instance of `TraceContext`.
///
/// # Arguments
///
/// - `allocator`: the allocator to use for allocating the resources used by the tracing
/// context.
///
/// - `enable`: Whether tracing should be enabled in the first place. When `false`, the
/// API exposed by `TraceContext` becomes essentially a no-op.
///
/// # Errors
///
/// Fails in case of memory allocation errors.
pub fn init(allocator: Allocator, enable: bool) !TraceContext {
var state: State = undefined;
var traceInstructionFn: *const State.TraceInstructionFn = undefined;
var deinitFn: *const State.DeinitFn = undefined;

if (enable) {
state = State{ .enabled = try TraceEnabled.init(allocator) };
traceInstructionFn = TraceEnabled.traceInstruction;
deinitFn = TraceEnabled.deinit;
} else {
state = State{ .disabled = TraceDisabled{} };
traceInstructionFn = TraceDisabled.traceInstruction;
deinitFn = TraceDisabled.deinit;
}

return TraceContext{
.state = state,
.traceInstructionFn = traceInstructionFn,
.deinitFn = deinitFn,
};
}

/// Frees the resources owned by this instance of `TraceContext`.
pub fn deinit(self: *TraceContext) void {
self.deinitFn(&self.state);
}

/// Records a new entry in the tracing context.
pub fn traceInstruction(self: *TraceContext, entry: TraceContext.Entry) !void {
try self.traceInstructionFn(&self.state, entry);
}

/// Returns whether tracing is enabled.
pub fn isEnabled(self: *const TraceContext) bool {
return self.traceInstructionFn == TraceEnabled.traceInstruction;
}
};

/// The state of the tracing system when it's enabled.
const TraceEnabled = struct {
/// The entries that have been recorded so far.
entries: ArrayList(TraceContext.Entry),

fn init(allocator: Allocator) !TraceEnabled {
return TraceEnabled{
.entries = ArrayList(TraceContext.Entry).init(allocator),
};
}

fn deinit(self: *State) void {
const this = &self.enabled;
this.entries.deinit();
}

fn traceInstruction(self: *State, entry: TraceContext.Entry) !void {
const this = &self.enabled;
try this.entries.append(entry);
}
};

/// The state of the tracing system when it's disabled.
const TraceDisabled = struct {
fn deinit(self: *State) void {
const this = &self.disabled;
_ = this;
}

fn traceInstruction(self: *State, entry: TraceContext.Entry) !void {
const this = &self.disabled;
_ = entry;
_ = this;
}
};