UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

226 lines 9.08 kB
"use strict"; /* * Data structures for recording execution traces. * * Igor Konnov, 2023 * * Copyright 2022-2023 Informal Systems * Licensed under the Apache License, Version 2.0. * See LICENSE in the project root for license information. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.newTraceRecorder = exports.noExecutionListener = void 0; const assert_1 = require("assert"); const verbosity_1 = require("./../verbosity"); const either_1 = require("@sweet-monads/either"); const runtimeValue_1 = require("./impl/runtimeValue"); const util_1 = require("./../util"); const emptyFrameError = { code: 'QNT501', message: 'empty frame' }; /** * An implementation of ExecutionListener that does nothing. */ exports.noExecutionListener = { onUserOperatorCall: (_app) => { }, onUserOperatorReturn: (_app, _args, _result) => { }, onAnyOptionCall: (_anyExpr, _position) => { }, onAnyOptionReturn: (_anyExpr, _position) => { }, onAnyReturn: (_noptions, _choice) => { }, onNextState: (_oldState, _newState) => { }, onRunCall: () => { }, onRunReturn: (_outcome, _trace) => { }, }; // a trace recording listener const newTraceRecorder = (verbosityLevel, rng, tracesToRecord = 1) => { return new TraceRecorderImpl(verbosityLevel, rng, tracesToRecord); }; exports.newTraceRecorder = newTraceRecorder; // a private implementation of a trace recorder class TraceRecorderImpl { constructor(verbosityLevel, rng, tracesToStore) { this.verbosityLevel = verbosityLevel; this.rng = rng; const bottom = this.newBottomFrame(); this.tracesToStore = tracesToStore; this.bestTraces = []; this.runSeed = this.rng.getState(); this.currentFrame = bottom; this.frameStack = [bottom]; } clear() { this.bestTraces = []; const bottom = this.newBottomFrame(); this.currentFrame = bottom; this.frameStack = [bottom]; } onUserOperatorCall(app) { // For now, we cannot tell apart actions from other user definitions. // https://github.com/informalsystems/quint/issues/747 if (verbosity_1.verbosity.hasUserOpTracking(this.verbosityLevel)) { const newFrame = { app: app, args: [], result: (0, either_1.left)(emptyFrameError), subframes: [], }; if (this.frameStack.length == 0) { // this should not happen, as there is always bottomFrame, // but we do not throw here, as trace collection is not the primary // function of the simulator. this.frameStack = [newFrame]; } else if (this.frameStack.length === 2 && this.frameStack[1].app.opcode === '_') { // A placeholder frame created from `q::input` or `then`. Modify in place. const frame = this.frameStack[1]; frame.app = app; frame.args = []; frame.result = (0, either_1.left)(emptyFrameError); frame.subframes = []; } else { // connect the new frame to the previous frame this.frameStack[this.frameStack.length - 1].subframes.push(newFrame); // and push the new frame to be on top of the stack this.frameStack.push(newFrame); } } } onUserOperatorReturn(_app, args, result) { if (verbosity_1.verbosity.hasUserOpTracking(this.verbosityLevel)) { const top = this.frameStack.pop(); if (top) { // since this frame is connected via the parent frame, // the result will not disappear top.args = args; top.result = result; } } } onAnyOptionCall(anyExpr, _position) { if (verbosity_1.verbosity.hasActionTracking(this.verbosityLevel)) { // the option has to be hidden under its own frame, // so it can be popped as a single frame later const newFrame = { app: anyExpr, args: [], result: (0, either_1.left)(emptyFrameError), subframes: [], }; if (this.frameStack.length > 0) { // add the option directly to the stack of the current expression // that contains `any { ... }`. this.frameStack[this.frameStack.length - 1].subframes.push(newFrame); this.frameStack.push(newFrame); } else { this.frameStack = [newFrame]; } } } onAnyOptionReturn(_anyExpr, _position) { if (verbosity_1.verbosity.hasActionTracking(this.verbosityLevel)) { this.frameStack.pop(); } } onAnyReturn(noptions, choice) { if (verbosity_1.verbosity.hasActionTracking(this.verbosityLevel)) { (0, assert_1.strict)(this.frameStack.length > 0); const top = this.frameStack[this.frameStack.length - 1]; const start = top.subframes.length - noptions; // leave only the chosen frame as well as the older ones top.subframes = top.subframes.filter((_, i) => i < start || i == start + choice); if (choice >= 0) { // The top frame contains the frames of the chosen option that are // wrapped in anyExpr.args[position], see onAnyOptionCall. // Unwrap the option, as we do not need it any longer. const optionFrame = top.subframes.pop(); if (optionFrame) { top.subframes = top.subframes.concat(optionFrame.subframes); } } } } onNextState(_oldState, _newState) { // introduce a new frame that is labelled with a dummy operator if (verbosity_1.verbosity.hasUserOpTracking(this.verbosityLevel)) { const dummy = { id: 0n, kind: 'app', opcode: '_', args: [] }; const newFrame = { app: dummy, args: [], result: (0, either_1.left)(emptyFrameError), subframes: [] }; // forget the frames, except the bottom one, and push the new one this.frameStack = [this.frameStack[0], newFrame]; // connect the new frame to the topmost frame, which effects in a new step this.frameStack[0].subframes.push(newFrame); } } onRunCall() { // reset the stack this.frameStack = [this.newBottomFrame()]; this.runSeed = this.rng.getState(); } onRunReturn(outcome, trace) { (0, assert_1.strict)(this.frameStack.length > 0); const traceToSave = this.frameStack[0]; traceToSave.result = outcome; traceToSave.args = trace; const traceWithSeed = { frame: traceToSave, seed: this.runSeed }; // Insert the trace into the list of best traces, // keeping the list sorted by descending quality. this.insertTraceSortedByQuality(traceWithSeed); // If there are more traces than needed, remove the worst trace, // ie. the last one, since the traces are sorted by descending quality. if (this.bestTraces.length > this.tracesToStore) { this.bestTraces.pop(); } } insertTraceSortedByQuality(trace) { (0, util_1.insertSorted)(this.bestTraces, trace, compareTracesByQuality); } // create a bottom frame, which encodes the whole trace newBottomFrame() { return { // this is just a dummy operator application app: { id: 0n, kind: 'app', opcode: 'Rec', args: [] }, // we will store the sequence of states here args: [], // the result of the trace evaluation result: (0, either_1.right)(runtimeValue_1.rv.mkBool(true)), // and here we store the subframes for the top-level actions subframes: [], }; } } // Compare two traces by quality. // // Prefer short traces for error, and longer traces for non error. // Therefore, trace a is better than trace b iff // - when a has an error: a is shorter or b has no error; // - when a has no error: a is longer and b has no error. function compareTracesByQuality(a, b) { const fromResult = (r) => { if (r.isLeft()) { return true; } else { const rex = r.value.toQuintEx({ nextId: () => 0n }); return rex.kind === 'bool' && !rex.value; } }; const aNotOk = fromResult(a.frame.result); const bNotOk = fromResult(b.frame.result); if (aNotOk) { if (bNotOk) { return a.frame.args.length - b.frame.args.length; } else { return -1; } } else { // a is ok if (bNotOk) { return 1; } else { return b.frame.args.length - a.frame.args.length; } } } //# sourceMappingURL=trace.js.map