diff --git a/CHANGELOG.md b/CHANGELOG.md index 0985ad96..4bfa6037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ See PR links for more information. - CHG: changed shrinkler output name from `a.exe.config.shrinkler` to `a.shrinkler_config.exe` so you can use `EXE2ADF` on the resulting shrinkled file - CHG: `Makefile`: don't strip symbol names (`-s`) in `elf2hunk` so Shrinkler can display nicer `shrinklerstats` - FIX: fixed/optimized `memclr` +- NEW: ELF disassembly: move cursor with `Shift` to show accumulated cycle counts +- FIX: flashing document when `Show Source` has been disabled in Profiler `Assembly` view ## 1.7.6 - NEW: modified template project to include `-ffunction-sections`, `-fdata-sections`, `--gc-sections` flags and changed assembly files to use unique sections so linker can better strip unused code/data diff --git a/src/client/client.tsx b/src/client/client.tsx index 713d4c72..8a5e3430 100644 --- a/src/client/client.tsx +++ b/src/client/client.tsx @@ -195,5 +195,12 @@ window.onerror = (event: Event | string, source?: string, lineno?: number, colno }); }; +// disable text selection with shift+click. disturbs multi-select in objdump. https://stackoverflow.com/a/54101500 +["keyup", "keydown"].forEach((event) => { + window.addEventListener(event, (e: KeyboardEvent) => { + document.onselectstart = () => !(e.key === "Shift" && e.shiftKey); + }); +}); + // eslint-disable-next-line no-unused-expressions, @typescript-eslint/no-unused-expressions TryProfiler() || TryObjdump() || TrySavestate(); diff --git a/src/client/objdump.tsx b/src/client/objdump.tsx index 6413697a..394948ba 100644 --- a/src/client/objdump.tsx +++ b/src/client/objdump.tsx @@ -222,7 +222,6 @@ export const ObjdumpView: FunctionComponent<{ const [model, setModel] = useState(() => { if(frame === -1) return new ObjdumpModel(OBJDUMP); - return new ObjdumpModel(MODELS[0].base.objdump, MODELS[0].base.cpuCycleUnit === 256 ? true : false, MODELS[frame].amiga.pcTrace); // theoretical cycles only for 7MHz (68000) }); const [opacity, setOpacity] = useState(1.0); @@ -236,7 +235,6 @@ export const ObjdumpView: FunctionComponent<{ pcMap.set(line.pc, index); }); - const jumps: JumpAbsolute[] = model.jumps.filter((jump) => [...jump.start, jump.end].every((pc) => pcMap.has(pc)) ).map((jump) => { @@ -255,6 +253,28 @@ export const ObjdumpView: FunctionComponent<{ }, [model, rowHeight]); const [curRow, setCurRow] = useState(0); + const [selRow, setSelRow] = useState(0); + + const cycleSums = useMemo(() : number[][] => { + if(curRow === selRow) + return []; + const ret: number[][] = []; + const sum = [0, 0]; + for(let i = Math.min(curRow, selRow); i <= Math.max(curRow, selRow); i++) { + if(model.content[i].theoreticalCycles) { + const c = model.content[i].theoreticalCycles.values.map((c) => c[0]).sort((a, b) => a - b); + if(c.length > 1) { + sum[0] += c[0]; + sum[1] += c[1]; + } else { + sum[0] += c[0]; + sum[1] += c[0]; + } + } + ret.push([...sum]); + } + return ret; + }, [model, curRow, selRow]); // Find const [find, setFind] = useState<{ text: string; internal: boolean }>({ text: '', internal: true }); @@ -283,6 +303,7 @@ export const ObjdumpView: FunctionComponent<{ if(result.length > 0) { setCurFind(0); setCurRow(result[0]); + setSelRow(result[0]); } console.timeEnd("findResult"); return result; @@ -292,6 +313,7 @@ export const ObjdumpView: FunctionComponent<{ const [row, regs] = (() => { if(frame === -1 || findResult.length) return [curRow, []]; + // get PC and regs from traces from time const pcTrace = MODELS[frame].amiga.pcTrace; let t = 0; let pc = 0; @@ -329,10 +351,11 @@ export const ObjdumpView: FunctionComponent<{ }); }, []); - const [hovered, setHovered] = useState<{ markdown: string; x: number; y: number; justify: string; width?: number; }>({ markdown: '', x: -1, y: -1, justify: '' }); - const [hoveredTimings, setHoveredTimings] = useState<{ markdown: string; x: number; y: number; justify: string; }>({ markdown: '', x: -1, y: -1, justify: '' }); + const [hovered, setHovered] = useState<{ markdown: string; x: number; y: number; justify: string; width?: number }>({ markdown: '', x: -1, y: -1, justify: '' }); + const [hoveredTimings, setHoveredTimings] = useState<{ markdown: string; x: number; y: number; justify: string }>({ markdown: '', x: -1, y: -1, justify: '' }); const tooltipRef = useRef(); + // opcode tooltip const onMouseEnterOpcode = useCallback((evt: JSX.TargetedMouseEvent) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const opcode = evt.currentTarget.attributes['data'].nodeValue as string; @@ -359,6 +382,7 @@ export const ObjdumpView: FunctionComponent<{ tooltipRef.current.scroller.setScrollPositionSmooth(tooltipRef.current.scroller.getFutureScrollPosition() + evt.deltaY); }, [tooltipRef.current]); + // timing tooltip const onMouseEnterTiming = useCallback((evt: JSX.TargetedMouseEvent, timings: InstructionTiming) => { const rect = evt.currentTarget.getBoundingClientRect(); const markdown = formatTimingTable(timings); @@ -379,7 +403,7 @@ export const ObjdumpView: FunctionComponent<{ }, []); const renderRow = useCallback((c: Line, index: number) => { - const extra: string[] = []; + const extraStyles: string[] = []; function getLoc(row: number) { for(let i = row; i > 0; i--) { @@ -388,42 +412,52 @@ export const ObjdumpView: FunctionComponent<{ } } + let cycleSum = ''; + if(cycleSums.length > 0 && index >= Math.min(row, selRow) && index < Math.min(row, selRow) + cycleSums.length) { + const s = cycleSums[index - Math.min(row, selRow)]; + if(s[0] < 900) + cycleSum = s.join('-').padStart(8, ' ') + 'Σ'; + else + cycleSum = Math.max(...s).toString().padStart(8, ' ') + 'Σ'; // not enough space for both min and max, just show maximum + console.log(cycleSum); + } + if(index === row) { - extra.push(styles.cur); + extraStyles.push(styles.cur); } else if(row !== -1 && content[row].pc !== undefined && content[index].pc !== undefined) { // highlight rows with the same source location as the current row const curLoc = getLoc(row); if(curLoc) { const loc = getLoc(index); if(loc === curLoc) - extra.push(styles.cur_same_loc); + extraStyles.push(styles.cur_same_loc); } } const text = (find.internal && findResult.length > 0) ? - : (c.opcode - ? <> + : (c.opcode ? <> {c.pc.toString(16).padStart(8, ' ')}: {c.opcode} {' '.repeat(Math.max(0, 7 - c.opcode.length))} {c.rest} - - : c.text); + : c.text); return (c.pc === undefined - ?
{text}{'\n'}
- :
+ ?
{text}{'\n'}
+ :
{frame !== -1 ? <> {c.traceHits > 0 ? (integerFormat.format(c.traceCycles).padStart(7, ' ') + 'cy') : ''.padStart(9, ' ')} {c.traceHits > 0 ? (integerFormat.format(c.traceHits).padStart(6) + 'x ' + integerFormat.format(c.traceCycles / c.traceHits).padStart(3, ' ') + '⌀') : ''.padStart(8 + 4, ' ')} - : ''} - onMouseEnterTiming(e, c.theoreticalCycles)} onMouseLeave={onMouseLeaveTiming}>{c.theoreticalCycles ? c.theoreticalCycles.values.map((c) => c[0]).sort((a, b) => a - b).join('-').padStart(8, ' ') + 'T' : ''.padStart(9)} + : '' } + {cycleSum.length + ? {cycleSum} + : onMouseEnterTiming(e, c.theoreticalCycles)} onMouseLeave={onMouseLeaveTiming}>{c.theoreticalCycles ? c.theoreticalCycles.values.map((c) => c[0]).sort((a, b) => a - b).join('-').padStart(8, ' ') + 'T' : ''.padStart(9)}}
{text} {(c.loc !== undefined && frame !== -1) ? : ''} {'\n'}
); - }, [onClickLoc, row, content, frame, findResult, find, curFind, onMouseEnterOpcode, onMouseLeaveOpcode, onWheelOpcode]); + }, [onClickLoc, row, selRow, content, frame, findResult, find, curFind, cycleSums, onMouseEnterOpcode, onMouseLeaveOpcode, onWheelOpcode]); const renderJump = useCallback((jump: JumpAbsolute) => { const right = 70; // needs to match CSS @@ -555,18 +589,10 @@ export const ObjdumpView: FunctionComponent<{ // Open source on row change useEffect(() => { - if (frame >= 0 && showSource) { + if (frame >= 0 && showSource) openSource(row); - } }, [row, showSource, openSource]); - // Close doc when source disabled - useEffect(() => { - if (frame >= 0 && !showSource) { - VsCodeApi.postMessage({ type: 'closeDocument' }); - } - }, [showSource]); - // cursor navigation useEffect(() => { const navStack: number[] = []; @@ -581,40 +607,60 @@ export const ObjdumpView: FunctionComponent<{ evt.preventDefault(); } if(frame === -1) { - if(evt.key === 'ArrowDown') - setCurRow((curRow) => Math.min(content.length - 1, curRow + 1)); - else if(evt.key === 'ArrowUp') - setCurRow((curRow) => Math.max(0, curRow - 1)); - else if(evt.key === 'PageDown') - setCurRow((curRow) => Math.min(content.length - 1, Math.floor(curRow + (window.innerHeight / rowHeight * 0.9)))); - else if(evt.key === 'PageUp') - setCurRow((curRow) => Math.max(0, Math.floor(curRow - (window.innerHeight / rowHeight * 0.9)))); - else if(evt.key === 'Home') + // wraps curRow update callback to also update selRow depending on shift key + const shiftWrap = (func: (row: number) => number) => (row: number) => { + const newRow = func(row); + if(!evt.shiftKey) + setSelRow(newRow); + return newRow; + }; + + switch(evt.key) { + case 'ArrowDown': + setCurRow(shiftWrap((curRow) => Math.min(content.length - 1, curRow + 1))); + break; + case 'ArrowUp': + setCurRow(shiftWrap((curRow) => Math.max(0, curRow - 1))); + break; + case 'PageDown': + setCurRow(shiftWrap((curRow) => Math.min(content.length - 1, Math.floor(curRow + (window.innerHeight / rowHeight * 0.9))))); + break; + case 'PageUp': + setCurRow(shiftWrap((curRow) => Math.max(0, Math.floor(curRow - (window.innerHeight / rowHeight * 0.9))))); + break; + case 'Home': setCurRow(0); - else if(evt.key === 'End') + setSelRow(0); + break; + case 'End': setCurRow(content.length - 1); - else if(evt.key === 'ArrowRight') { - setCurRow((curRow) => { + setSelRow(content.length - 1); + break; + case 'ArrowRight': + setCurRow(shiftWrap((curRow) => { const jump = jumps.find((j) => j.start.includes(curRow)); if(jump) { navStack.push(curRow); return jump.end; } return curRow; - }); - } else if(evt.key === 'ArrowLeft') { - setCurRow((curRow) => { + })); + break; + case 'ArrowLeft': + setCurRow(shiftWrap((curRow) => { if(navStack.length) return navStack.pop(); return curRow; - }); - } else + })); + break; + default: return; + } evt.preventDefault(); } }; // make list accept keyboard events - (listRef.current.base as HTMLElement).tabIndex = -1; + (listRef.current?.base as HTMLElement).tabIndex = -1; (listRef.current?.base as HTMLElement)?.focus(); listRef.current?.base?.addEventListener('keydown', listener); return () => listRef.current?.base?.removeEventListener('keydown', listener); @@ -653,6 +699,7 @@ export const ObjdumpView: FunctionComponent<{ const sel = content.findIndex((c: Line) => c.pc === selected.pc); if(frame === -1) { setCurRow(sel); + setSelRow(sel); } else { const scrollTo = (sel - 2) * rowHeight; // -2: function line scroller.setScrollPositionSmooth(scrollTo); @@ -666,6 +713,12 @@ export const ObjdumpView: FunctionComponent<{ if(elem.getAttribute('data-row')) { const row = parseInt(elem.getAttribute('data-row')); setCurRow(row); + if(frame === -1) { + if(!evt.shiftKey) + setSelRow(row); + else + document.getSelection().removeAllRanges(); + } return; } } @@ -678,12 +731,14 @@ export const ObjdumpView: FunctionComponent<{ const n = (curFind + findResult.length - 1) % findResult.length; setCurFind(n); setCurRow(findResult[n]); + setSelRow(findResult[n]); } } else if(action === 'next') { if(findResult.length) { const n = (curFind + 1) % findResult.length; setCurFind(n); setCurRow(findResult[n]); + setSelRow(findResult[n]); } } else { setFind({ text: text ?? '', internal: true }); @@ -723,7 +778,7 @@ export const ObjdumpView: FunctionComponent<{ )} {frame >= 0 && ( - + { setShowSource(checked); if(!checked) VsCodeApi.postMessage({ type: 'closeDocument' }); }} icon="Source" label="Show Source" /> )}