diff --git a/css/ui.css b/css/ui.css index 621f3818..69b8f39f 100644 --- a/css/ui.css +++ b/css/ui.css @@ -376,10 +376,9 @@ div.replaydiv { } div.markdown { background-color: #fff; - width:94%; - margin:3%; - padding:1em; - user-select: auto; + width:100%; + padding:2em; + user-select: text; } div.markdown table { margin:1em; @@ -612,6 +611,11 @@ div.asset_toolbar { color: #6666ff; background-color: #eeeeff; margin:1%; + width:100%; + max-width:25em; +} +.transcript-input-char { + max-width:5em; } .tree-header { border: 2px solid #555; diff --git a/doc/notes.txt b/doc/notes.txt index 15a96465..af429fdd 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -177,12 +177,9 @@ TODO: - Safari: doesn't send good exception reasons ("undefined") - probably XMLHttpRequest.timeout (https://github.com/getsentry/sentry-javascript/issues/2210) - C64 - - debugging at reset - - gotoxy goes to $EA24 (KIL) from PLOT (kplot.s) missing UPDCRAMPTR - - emu doesn't reset properly (after gotoxy() BIOS call) - disk image support - https://github.com/cc65/cc65/issues/946 - - sample buffer skips (need to sync w/ vsync) + - need to sync advanceFrame() w/ vsync - upgrade to ES2020? - https://github.com/microsoft/TypeScript/issues/16577 @@ -194,8 +191,7 @@ TODO: - ca65 line numbers are not aligned with source code - segments not read properly - Debug Browser - - hex viewer - - show 16/32 typed arrays + - more stuff like 7800 display lists WEB WORKER FORMAT diff --git a/index.html b/index.html index d205ee93..074b6138 100644 --- a/index.html +++ b/index.html @@ -223,7 +223,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) { diff --git a/src/common/recorder.ts b/src/common/recorder.ts index 62669a49..4b4badfe 100644 --- a/src/common/recorder.ts +++ b/src/common/recorder.ts @@ -224,13 +224,13 @@ export class ProbeRecorder implements ProbeAll { if (this.singleFrame) this.reset(); } logExecute(address:number, SP:number) { - // TODO? record stack pushes (not all platforms use logExecute) + // record stack pushes/pops (from last instruction) if (this.cur_sp !== SP) { if (SP < this.cur_sp) { - this.log(ProbeFlags.SP_PUSH | (this.cur_sp - SP)); + this.log(ProbeFlags.SP_PUSH | SP); } if (SP > this.cur_sp) { - this.log(ProbeFlags.SP_POP | (SP - this.cur_sp)); + this.log(ProbeFlags.SP_POP | SP); } this.cur_sp = SP; } diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 44ef7aa9..9736c485 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -298,8 +298,8 @@ function refreshWindowList() { return new Views.ProbeSymbolView(); }); /* - addWindowItem("#spheatmap", "Stack Probe", () => { - return new Views.RasterStackMapView(); + addWindowItem("#callstack", "Call Stack", () => { + return new Views.CallStackView(); }); */ } @@ -1125,6 +1125,8 @@ function checkRunReady() { function openRelevantListing(state: EmuState) { // if we clicked on another window, retain it if (lastViewClicked != null) return; + // has to support disassembly, at least + if (!platform.disassemble) return; // search through listings var listings = current_project.getListings(); var bestid = "#disasm"; @@ -1144,7 +1146,7 @@ function openRelevantListing(state: EmuState) { bestid = wndid; bestscore = pc-res.offset; } - console.log(hex(pc,4), wndid, lstfn, bestid, bestscore); + //console.log(hex(pc,4), wndid, lstfn, bestid, bestscore); } } } @@ -1568,9 +1570,6 @@ function setupDebugControls() { uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id','dbg_stepback'); } uitoolbar.newGroup(); - if (platform.newCodeAnalyzer) { - uitoolbar.add(null, 'Analyze CPU Timing', 'glyphicon-time', traceTiming); - } // add menu clicks $(".dropdown-menu").collapse({toggle: false}); $("#item_new_file").click(_createNewFile); @@ -1613,6 +1612,9 @@ function setupDebugControls() { if (platform.showHelp) { uitoolbar.add('ctrl+alt+?', 'Show Help', 'glyphicon-question-sign', _lookupHelp); } + if (platform.newCodeAnalyzer) { + uitoolbar.add(null, 'Analyze CPU Timing', 'glyphicon-time', traceTiming); + } // setup replay slider if (platform.setRecorder && platform.advance) { setupReplaySlider(); diff --git a/src/ide/views.ts b/src/ide/views.ts index a9e61615..3a083aca 100644 --- a/src/ide/views.ts +++ b/src/ide/views.ts @@ -919,6 +919,8 @@ abstract class ProbeViewBaseBase { probe : ProbeRecorder; tooldiv : HTMLElement; + abstract tick() : void; + addr2str(addr : number) : string { var _addr2sym = (platform.debugSymbols && platform.debugSymbols.addr2symbol) || {}; var sym = _addr2sym[addr]; @@ -951,8 +953,6 @@ abstract class ProbeViewBaseBase { } } - abstract tick() : void; - redraw( eventfn:(op,addr,col,row,clk,value) => void ) { var p = this.probe; if (!p || !p.idx) return; // if no probe, or if empty @@ -987,12 +987,15 @@ abstract class ProbeViewBaseBase { case ProbeFlags.VRAM_WRITE: s = "VRAM Write"; break; case ProbeFlags.INTERRUPT: s = "Interrupt"; break; case ProbeFlags.ILLEGAL: s = "Error"; break; + //case ProbeFlags.SP_PUSH: s = "Stack Push"; break; + //case ProbeFlags.SP_POP: s = "Stack Pop"; break; default: return ""; } if (typeof addr == 'number') s += " " + this.addr2str(addr); if ((op & ProbeFlags.HAS_VALUE) && typeof value == 'number') s += " = $" + hex(value,2); return s; } + getOpRGB(op:number) : number { switch (op) { case ProbeFlags.EXECUTE: return 0x018001; @@ -1015,7 +1018,9 @@ abstract class ProbeViewBase extends ProbeViewBaseBase { canvas : HTMLCanvasElement; ctx : CanvasRenderingContext2D; recreateOnResize = true; - + + abstract drawEvent(op, addr, col, row); + createCanvas(parent:HTMLElement, width:number, height:number) { var div = document.createElement('div'); var canvas = document.createElement('canvas'); @@ -1056,8 +1061,6 @@ abstract class ProbeViewBase extends ProbeViewBaseBase { this.clear(); this.redraw(this.drawEvent.bind(this)); } - - abstract drawEvent(op, addr, col, row); } abstract class ProbeBitmapViewBase extends ProbeViewBase { @@ -1161,44 +1164,6 @@ export class RasterPCHeatMapView extends ProbeBitmapViewBase implements ProjectV } } -// TODO? -export class RasterStackMapView extends ProbeBitmapViewBase implements ProjectView { - pcstack = []; - pushed = false; - color = 0; - root = {}; - - drawEvent(op, addr, col, row) { - var iofs = col + row * this.canvas.width; - if (op == ProbeFlags.SP_PUSH) { - this.pcstack.push({}); - this.pushed = true; - } else if (op == ProbeFlags.SP_POP) { - var entry = this.pcstack.pop(); - if (entry && entry.addr !== undefined) { - var node = this.root; - for (var e of this.pcstack) { - if (node[e.addr]) { - node = node[e.addr]; - } else { - node = node[e.addr] = {}; - } - } - } - //if (this.pcstack.length == 1) console.log(this.root); - this.pushed = false; - } else if (op == ProbeFlags.EXECUTE) { - if (this.pushed) { - this.pcstack.pop(); - this.pcstack.push({addr:addr, scol:col, srow:row}); - this.pushed = false; - } - } - var data = 0xff224488 << this.pcstack.length; - this.datau32[iofs] = data; - } -} - export class ProbeLogView extends ProbeViewBaseBase { vlist : VirtualTextScroller; maindiv : HTMLElement; @@ -1218,6 +1183,7 @@ export class ProbeLogView extends ProbeViewBaseBase { var xtra : string = line.info.join(", "); s = "(" + lpad(line.row,3) + ", " + lpad(line.col,3) + ") " + rpad(line.asm||"",20) + xtra; if (xtra.indexOf("Write ") >= 0) c = "seg_io"; + // if (xtra.indexOf("Stack ") >= 0) c = "seg_code"; } return {text:s, clas:c}; } @@ -1336,7 +1302,7 @@ class TreeNode { children : Map; expanded = false; level : number; - view : TreeViewBase; + view : ProjectView; constructor(parent : TreeNode, name : string) { this.parent = parent; @@ -1394,7 +1360,7 @@ class TreeNode { text = obj+""; // primitive types } else if (typeof obj == 'number') { - text = obj.toString(); + text = obj + "\t($" + hex(obj) + ")"; } else if (typeof obj == 'boolean') { text = obj.toString(); } else if (typeof obj == 'string') { @@ -1470,18 +1436,23 @@ class TreeNode { } } +function createTreeRootNode(parent : HTMLElement, view : ProjectView) : TreeNode { + var mainnode = new TreeNode(null, null); + mainnode.view = view; + mainnode._content = parent; + var root = new TreeNode(mainnode, "/"); + root.expanded = true; + root.getDiv(); // create it + root._div.style.padding = '0px'; + return root; // should be cached +} + export abstract class TreeViewBase implements ProjectView { root : TreeNode; createDiv(parent : HTMLElement) : HTMLElement { - var mainnode = new TreeNode(null, null); - mainnode.view = this; - mainnode._content = parent; - this.root = new TreeNode(mainnode, "/"); - this.root.expanded = true; - this.root.getDiv(); // create it - this.root._div.style.padding = '0px'; - return this.root.getDiv(); // should be cached + this.root = createTreeRootNode(parent, this); + return this.root.getDiv(); } refresh() { @@ -1503,6 +1474,77 @@ export class DebugBrowserView extends TreeViewBase implements ProjectView { getRootObject() { return platform.getDebugTree(); } } +// TODO? +interface CallGraphNode { + count : number; + calls : {[id:string] : CallGraphNode}; +} + +export class CallStackView extends ProbeViewBaseBase implements ProjectView { + treeroot : TreeNode; + graph : CallGraphNode; + stack : CallGraphNode[]; + lastsp : number; + jsr : boolean; + + createDiv(parent : HTMLElement) : HTMLElement { + this.clear(); + this.treeroot = createTreeRootNode(parent, this); + return this.treeroot.getDiv(); + } + + refresh() { + this.tick(); + } + + tick() { + this.treeroot.update(this.getRootObject()); + } + + clear() { + this.graph = {count:0, calls:{}}; + this.reset(); + } + + reset() { + this.stack = [this.graph]; // TODO??? should continue across frames + this.lastsp = -1; + this.jsr = false; + } + + getRootObject() : Object { + this.reset(); + this.redraw((op,addr,col,row,clk,value) => { + switch (op) { + case ProbeFlags.SP_PUSH: + case ProbeFlags.SP_POP: + if ((this.lastsp - addr) == 2) { + this.jsr = true; + } + if ((this.lastsp - addr) == -2 && this.stack.length > 1) { + this.stack.pop(); + } + this.lastsp = addr; + break; + case ProbeFlags.EXECUTE: + if (this.jsr) { + var top = this.stack[this.stack.length-1]; + var sym = this.addr2str(addr); + var child = top.calls[sym]; + if (child == null) { child = top.calls[sym] = {count:0, calls:{}}; } + //this.stack.forEach((node) => node.count++); + this.stack.push(child); + child.count++; + this.jsr = false; + } + break; + } + }); + return this.graph; + } +} + + /// diff --git a/src/platform/x86.ts b/src/platform/x86.ts index d741857e..a7213b3f 100644 --- a/src/platform/x86.ts +++ b/src/platform/x86.ts @@ -70,7 +70,7 @@ class X86PCPlatform implements Platform { return "yasm"; } getDefaultExtension(): string { - return "asm"; + return ".asm"; } getPresets() { return PC_PRESETS; @@ -133,6 +133,9 @@ class X86PCPlatform implements Platform { }); } + getDebugTree() { + return this.v86; + } readAddress(addr:number) { return this.v86.cpu.mem8[addr]; } diff --git a/src/platform/zmachine.ts b/src/platform/zmachine.ts index 45f28be4..bdfe977a 100644 --- a/src/platform/zmachine.ts +++ b/src/platform/zmachine.ts @@ -110,6 +110,11 @@ class GlkImpl { if (this.focused) { $(this.input).focus(); } + // change size + if (this.waitingfor == 'char') + $(this.input).addClass('transcript-input-char') + else + $(this.input).removeClass('transcript-input-char') } hideinput() { $(this.input).appendTo($(this.page).parent()).hide();