mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-23 18:31:08 +00:00
fixed callbackGetRemote(); started on profiler
This commit is contained in:
parent
ce019b5632
commit
ab1500ccb6
@ -98,6 +98,9 @@ export interface Platform {
|
||||
showHelp?(tool:string, ident?:string) : void;
|
||||
resize?() : void;
|
||||
|
||||
startProfiling?() : ProfilerOutput;
|
||||
getRasterScanline?() : number;
|
||||
|
||||
debugSymbols? : DebugSymbols;
|
||||
}
|
||||
|
||||
@ -122,6 +125,17 @@ export interface EmuRecorder {
|
||||
recordFrame(state : EmuState);
|
||||
}
|
||||
|
||||
export interface ProfilerScanline {
|
||||
start,end : number; // start/end frameindex
|
||||
}
|
||||
export interface ProfilerFrame {
|
||||
iptab : Uint32Array; // array of IPs
|
||||
lines : ProfilerScanline[];
|
||||
}
|
||||
export interface ProfilerOutput {
|
||||
frame : ProfilerFrame;
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
export abstract class BasePlatform {
|
||||
@ -194,10 +208,31 @@ export abstract class BaseDebugPlatform extends BasePlatform {
|
||||
this.advance(novideo);
|
||||
this.postFrame();
|
||||
}
|
||||
startProfiling() : ProfilerOutput {
|
||||
var frame = null;
|
||||
var output = {frame:null};
|
||||
var i = 0;
|
||||
var lastsl = 9999;
|
||||
var start = 0;
|
||||
(this as any).runEval((c:CpuState) => {
|
||||
var sl = (this as any).getRasterScanline();
|
||||
if (sl != lastsl) {
|
||||
if (frame) frame.lines.push({start:start,end:i});
|
||||
if (sl < lastsl) {
|
||||
output.frame = frame;
|
||||
frame = {iptab:new Uint32Array(14672), lines:[]};
|
||||
i = 0;
|
||||
}
|
||||
start = i+1;
|
||||
lastsl = sl;
|
||||
}
|
||||
frame.iptab[i++] = c.EPC || c.PC;
|
||||
return false; // profile forever
|
||||
});
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
//////
|
||||
|
||||
////// 6502
|
||||
|
||||
export function getToolForFilename_6502(fn:string) : string {
|
||||
@ -933,7 +968,7 @@ export function dumpStackToString(platform:Platform, mem:Uint8Array|number[], st
|
||||
var opcode = read(addr + jsrofs); // might be out of bounds
|
||||
if (opcode == jsrop) { // JSR
|
||||
s += "\n$" + hex(sp) + ": ";
|
||||
s += hex(addr,4) + " " + lookupSymbol(platform, addr);
|
||||
s += hex(addr,4) + " " + lookupSymbol(platform, addr, true);
|
||||
sp++;
|
||||
nraw = 0;
|
||||
} else {
|
||||
@ -946,20 +981,18 @@ export function dumpStackToString(platform:Platform, mem:Uint8Array|number[], st
|
||||
return s+"\n";
|
||||
}
|
||||
|
||||
export function lookupSymbol(platform:Platform, addr:number) {
|
||||
export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
||||
var start = addr;
|
||||
var foundsym;
|
||||
var addr2symbol = platform.debugSymbols && platform.debugSymbols.addr2symbol;
|
||||
while (addr2symbol && addr >= 0) {
|
||||
var sym = addr2symbol[addr];
|
||||
if (sym && sym.startsWith('_')) { // return first C symbol we find
|
||||
return addr2symbol[addr] + " + " + (start-addr);
|
||||
} else if (sym && !foundsym) { // cache first non-C symbol found
|
||||
foundsym = sym;
|
||||
if (sym) { // return first symbol we find
|
||||
var sym = addr2symbol[addr];
|
||||
return extra ? (sym + " + " + (start-addr)) : sym;
|
||||
}
|
||||
addr--;
|
||||
}
|
||||
return foundsym || "";
|
||||
return "";
|
||||
}
|
||||
|
||||
///// Basic Platforms
|
||||
@ -975,7 +1008,7 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
cpuCyclesPerLine : number;
|
||||
currentScanline : number;
|
||||
startLineTstates : number;
|
||||
|
||||
|
||||
cpu;
|
||||
membus : MemoryBus;
|
||||
iobus : MemoryBus;
|
||||
@ -995,12 +1028,12 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
abstract getKeyboardMap();
|
||||
abstract startScanline(sl : number);
|
||||
abstract drawScanline(sl : number);
|
||||
|
||||
|
||||
constructor(mainElement : HTMLElement) {
|
||||
super();
|
||||
this.mainElement = mainElement;
|
||||
}
|
||||
|
||||
|
||||
start() {
|
||||
this.cpuCyclesPerLine = Math.round(this.cpuFrequency / 60 / this.numTotalScanlines);
|
||||
this.ram = this.newRAM();
|
||||
@ -1074,4 +1107,3 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
this.cpu.setTstates(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString } from "../baseplatform";
|
||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString, ProfilerOutput } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags } from "../emu";
|
||||
import { hex, lpad, lzgmini } from "../util";
|
||||
import { CodeAnalyzer_nes } from "../analysis";
|
||||
@ -131,9 +131,7 @@ const _JSNESPlatform = function(mainElement) {
|
||||
nes.cpu._emulate = nes.cpu.emulate;
|
||||
nes.cpu.emulate = () => {
|
||||
var cycles = nes.cpu._emulate();
|
||||
//if (self.debugCondition && !self.debugBreakState && self.debugClock < 100) console.log(self.debugClock, nes.cpu.REG_PC);
|
||||
this.evalDebugCondition();
|
||||
// TODO: doesn't stop on breakpoint
|
||||
return cycles;
|
||||
}
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
@ -227,7 +225,35 @@ const _JSNESPlatform = function(mainElement) {
|
||||
|
||||
runToVsync() {
|
||||
var frame0 = frameindex;
|
||||
this.runEval(function(c) { return frameindex>frame0; });
|
||||
this.runEval((c) => { return frameindex>frame0; });
|
||||
}
|
||||
|
||||
getRasterScanline() : number {
|
||||
return nes.ppu.scanline;
|
||||
}
|
||||
startProfiling() : ProfilerOutput {
|
||||
var frame0 = frameindex;
|
||||
var frame = null;
|
||||
var output = {frame:null};
|
||||
var i = 0;
|
||||
var lastsl = 9999;
|
||||
var start = 0;
|
||||
this.runEval((c) => {
|
||||
var sl = this.getRasterScanline();
|
||||
if (sl != lastsl) {
|
||||
if (frame) frame.lines.push({start:start,end:i});
|
||||
if (sl < lastsl) {
|
||||
output.frame = frame;
|
||||
frame = {iptab:new Uint32Array(14672), lines:[]};
|
||||
i = 0;
|
||||
}
|
||||
start = i+1;
|
||||
lastsl = sl;
|
||||
}
|
||||
frame.iptab[i++] = c.EPC || c.PC;
|
||||
return false; //frameindex>frame0; // TODO
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
getCPUState() {
|
||||
|
@ -112,6 +112,9 @@ class VCSPlatform extends BasePlatform {
|
||||
var ypos = row-39;
|
||||
return {x:xpos, y:ypos};
|
||||
}
|
||||
getRasterScanline() : number {
|
||||
return this.getRasterPosition().y;
|
||||
}
|
||||
|
||||
// TODO: Clock changes this on event, so it may not be current
|
||||
isRunning() {
|
||||
@ -286,14 +289,14 @@ class VCSPlatform extends BasePlatform {
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
||||
}
|
||||
|
||||
|
||||
showHelp(tool:string, ident:string) {
|
||||
if (tool == 'bataribasic')
|
||||
window.open("help/bataribasic/manual.html", "_help");
|
||||
else
|
||||
window.open("https://alienbill.com/2600/101/docs/stella.html", "_help"); // TODO
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
// TODO: mixin for Base6502Platform?
|
||||
|
@ -209,20 +209,18 @@ export class CodeProject {
|
||||
webpath += ".a"; // legacy stuff
|
||||
// try to GET file, use file ext to determine text/binary
|
||||
this.callbackGetRemote( webpath, (data:FileData) => {
|
||||
if (data instanceof ArrayBuffer)
|
||||
data = new Uint8Array(data); // convert to typed array
|
||||
console.log("GET",webpath,data.length,'bytes');
|
||||
this.filedata[path] = data; // do not update store, just cache
|
||||
addResult(path, data);
|
||||
loadNext();
|
||||
}, isProbablyBinary(path) ? 'arraybuffer' : 'text')
|
||||
.fail( (err:XMLHttpRequest) => {
|
||||
console.log("Could not load preset file", path, err.status);
|
||||
// only cache result if status is 404 (not found)
|
||||
if (err.status && err.status == 404)
|
||||
if (data == null) {
|
||||
console.log("Could not load preset file", path);
|
||||
this.filedata[path] = null; // mark cache entry as invalid
|
||||
} else {
|
||||
if (data instanceof ArrayBuffer)
|
||||
data = new Uint8Array(data); // convert to typed array
|
||||
console.log("GET",webpath,data.length,'bytes');
|
||||
this.filedata[path] = data; // do not update store, just cache
|
||||
addResult(path, data);
|
||||
}
|
||||
loadNext();
|
||||
});
|
||||
}, isProbablyBinary(path) ? 'arraybuffer' : 'text');
|
||||
} else {
|
||||
// not gonna find it, keep going
|
||||
loadNext();
|
||||
|
12
src/ui.ts
12
src/ui.ts
@ -100,7 +100,12 @@ function getWithBinary(url:string, success:(text:FileData)=>void, datatype:'text
|
||||
oReq.open("GET", url, true);
|
||||
oReq.responseType = datatype;
|
||||
oReq.onload = function (oEvent) {
|
||||
success(oReq.response);
|
||||
if (oReq.status == 200)
|
||||
success(oReq.response);
|
||||
else if (oReq.status == 404)
|
||||
success(null);
|
||||
else
|
||||
throw "Error " + oReq.status + " loading " + url;
|
||||
}
|
||||
oReq.send(null);
|
||||
}
|
||||
@ -210,6 +215,11 @@ function refreshWindowList() {
|
||||
return new Views.MemoryMapView();
|
||||
});
|
||||
}
|
||||
if (platform.startProfiling && platform.runEval && platform.getRasterScanline) {
|
||||
addWindowItem("#profiler", "Profiler", function() {
|
||||
return new Views.ProfileView();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// can pass integer or string id
|
||||
|
89
src/views.ts
89
src/views.ts
@ -4,7 +4,7 @@ import $ = require("jquery");
|
||||
//import CodeMirror = require("codemirror");
|
||||
import { CodeProject } from "./project";
|
||||
import { SourceFile, WorkerError, Segment } from "./workertypes";
|
||||
import { Platform, EmuState } from "./baseplatform";
|
||||
import { Platform, EmuState, ProfilerOutput, lookupSymbol } from "./baseplatform";
|
||||
import { hex, lpad, rpad } from "./util";
|
||||
import { CodeAnalyzer } from "./analysis";
|
||||
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
|
||||
@ -610,7 +610,7 @@ export class MemoryView implements ProjectView {
|
||||
if (compparams && this.dumplines)
|
||||
this.scrollToAddress(compparams.data_start);
|
||||
}
|
||||
|
||||
|
||||
scrollToAddress(addr : number) {
|
||||
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr));
|
||||
}
|
||||
@ -774,7 +774,7 @@ export class BinaryFileView implements ProjectView {
|
||||
|
||||
refresh() {
|
||||
}
|
||||
|
||||
|
||||
getPath() { return this.path; }
|
||||
}
|
||||
|
||||
@ -789,7 +789,7 @@ export class MemoryMapView implements ProjectView {
|
||||
this.refresh();
|
||||
return this.maindiv[0];
|
||||
}
|
||||
|
||||
|
||||
// TODO: overlapping segments (e.g. ROM + LC)
|
||||
addSegment(seg : Segment) {
|
||||
var offset = $('<div class="col-md-1 segment-offset"/>');
|
||||
@ -831,5 +831,84 @@ export class MemoryMapView implements ProjectView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class ProfileView implements ProjectView {
|
||||
profilelist;
|
||||
prof : ProfilerOutput;
|
||||
maindiv : HTMLElement;
|
||||
symcache : {};
|
||||
|
||||
createDiv(parent : HTMLElement) {
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute("class", "memdump");
|
||||
parent.appendChild(div);
|
||||
this.showMemoryWindow(parent, div);
|
||||
return this.maindiv = div;
|
||||
}
|
||||
|
||||
showMemoryWindow(workspace:HTMLElement, parent:HTMLElement) {
|
||||
this.profilelist = new VirtualList({
|
||||
w: $(workspace).width(),
|
||||
h: $(workspace).height(),
|
||||
itemHeight: getVisibleEditorLineHeight(),
|
||||
totalRows: 262,
|
||||
generatorFn: (row : number) => {
|
||||
var s = this.getProfileLineAt(row);
|
||||
var linediv = document.createElement("div");
|
||||
linediv.appendChild(document.createTextNode(s));
|
||||
return linediv;
|
||||
}
|
||||
});
|
||||
$(parent).append(this.profilelist.container);
|
||||
this.symcache = {};
|
||||
this.tick();
|
||||
}
|
||||
|
||||
getProfileLineAt(row : number) : string {
|
||||
var s = lpad(row+': ',5);
|
||||
if (!this.prof) return s;
|
||||
var f = this.prof.frame;
|
||||
if (!f) return s;
|
||||
var l = f.lines[row];
|
||||
if (!l) return s;
|
||||
var lastsym = '';
|
||||
for (var i=l.start; i<=l.end; i++) {
|
||||
var pc = f.iptab[i];
|
||||
var sym = this.symcache[pc];
|
||||
if (!sym) {
|
||||
sym = lookupSymbol(platform, pc, false);
|
||||
this.symcache[pc] = sym;
|
||||
}
|
||||
if (sym != lastsym) {
|
||||
s += sym + ' ';
|
||||
lastsym = sym;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.tick();
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (this.profilelist) {
|
||||
$(this.maindiv).find('[data-index]').each( (i,e) => {
|
||||
var div = $(e);
|
||||
var row = parseInt(div.attr('data-index'));
|
||||
var oldtext = div.text();
|
||||
var newtext = this.getProfileLineAt(row);
|
||||
if (oldtext != newtext)
|
||||
div.text(newtext);
|
||||
});
|
||||
}
|
||||
// TODO: better way to keep it profiling? also, it clears the buffer
|
||||
if (platform.isRunning()) {
|
||||
this.prof = platform.startProfiling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -934,8 +934,10 @@ function linkLD65(step:BuildStep) {
|
||||
var toks = s.split(" ");
|
||||
if (toks[0] == 'al') {
|
||||
let ident = toks[2].substr(1);
|
||||
let ofs = parseInt(toks[1], 16);
|
||||
symbolmap[ident] = ofs;
|
||||
if (ident.length != 5 || !ident.startsWith('L')) { // no line numbers
|
||||
let ofs = parseInt(toks[1], 16);
|
||||
symbolmap[ident] = ofs;
|
||||
}
|
||||
}
|
||||
}
|
||||
// build segment map
|
||||
@ -1226,7 +1228,7 @@ function linkSDLDZ80(step:BuildStep)
|
||||
var symbolmap = {};
|
||||
for (var s of noiout.split("\n")) {
|
||||
var toks = s.split(" ");
|
||||
if (toks[0] == 'DEF' && !toks[1].startsWith("A$main$")) {
|
||||
if (toks[0] == 'DEF' && !toks[1].startsWith("A$")) {
|
||||
symbolmap[toks[1]] = parseInt(toks[2], 16);
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,10 @@ describe('Store', function() {
|
||||
var platform = {};
|
||||
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
|
||||
var remote = [];
|
||||
project.callbackGetRemote = function(path) { remote.push(path); return {fail:function(failfn){failfn({status:404})}} };
|
||||
project.callbackGetRemote = function(path, success, datatype) {
|
||||
remote.push(path);
|
||||
success();
|
||||
};
|
||||
project.loadFiles(['local/test','test'], function(err, result) {
|
||||
assert.equal(null, err);
|
||||
assert.deepEqual(["presets/_TEST/test"], remote);
|
||||
@ -85,7 +88,7 @@ describe('Store', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Should build linked project', function(done) {
|
||||
localStorage.clear();
|
||||
localItems['__migrated__TEST'] = 'true';
|
||||
@ -93,7 +96,7 @@ describe('Store', function() {
|
||||
var expectmsgs = [
|
||||
true,
|
||||
{ preload: 'dasm', platform: '_TEST' },
|
||||
{
|
||||
{
|
||||
buildsteps: [
|
||||
{ path: "test.a", platform: "_TEST", tool: "dasm", mainfile:true, files:["test.a"] },
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user