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

feat: implement wasm inline functions #1808

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"@vscode/dwarf-debugging": "file:../vscode-dwarf-debugging/vscode-dwarf-debugging-0.0.2.tgz",
"@vscode/dwarf-debugging": "^0.0.2",
"@vscode/test-electron": "^2.2.3",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
Expand Down
131 changes: 115 additions & 16 deletions src/adapter/dwarf/wasmSymbolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
*--------------------------------------------------------*/

import type { IWasmWorker, MethodReturn } from '@vscode/dwarf-debugging';
import { Chrome } from '@vscode/dwarf-debugging/chrome-cxx/mnt/extension-api';
import { randomUUID } from 'crypto';
import { inject, injectable } from 'inversify';
import Cdp from '../../cdp/api';
import { ICdpApi } from '../../cdp/connection';
import { binarySearch } from '../../common/arrayUtils';
import { IDisposable } from '../../common/disposable';
import { ILogger, LogTag } from '../../common/logging';
import { once } from '../../common/objUtils';
import { Base0Position, IPosition } from '../../common/positions';
import { flatten, once } from '../../common/objUtils';
import { Base0Position, IPosition, Range } from '../../common/positions';
import { StepDirection } from '../pause';
import { getSourceSuffix } from '../templates';
import { IDwarfModuleProvider } from './dwarfModuleProvider';

Expand Down Expand Up @@ -169,8 +171,8 @@ export interface IWasmSymbols extends IDisposable {
/**
* Gets the source position for the given position in compiled code.
*
* Following CDP semantics, it returns a position on line 0 with the column
* offset being the byte offset in webassembly.
* Following CDP semantics, it assumes the column is being the byte offset
* in webassembly. However, we encode the inline frame index in the line.
*/
originalPositionFor(
compiledPosition: IPosition,
Expand All @@ -182,16 +184,40 @@ export interface IWasmSymbols extends IDisposable {
* Following CDP semantics, it assumes the position is line 0 with the column
* offset being the byte offset in webassembly.
*/
compiledPositionFor(sourceUrl: string, sourcePosition: IPosition): Promise<IPosition | undefined>;
compiledPositionFor(sourceUrl: string, sourcePosition: IPosition): Promise<IPosition[]>;

/**
* Gets variables in the program scope at the given position. If not
* implemented, the variable store should use its default behavior.
*
* Following CDP semantics, it assumes the position is line 0 with the column
* offset being the byte offset in webassembly.
* Following CDP semantics, it assumes the column is being the byte offset
* in webassembly. However, we encode the inline frame index in the line.
*/
getVariablesInScope?(callFrameId: string, position: IPosition): Promise<IWasmVariable[]>;

/**
* Gets the stack of WASM functions at the given position. Generally this will
* return an element with a single item containing the function name. However,
* inlined functions may return multiple functions for a position.
*
* It may return an empty array if function information is not available.
*
* @see https://github.com/ChromeDevTools/devtools-frontend/blob/c9f204731633fd2e2b6999a2543e99b7cc489b4b/docs/language_extension_api.md#dealing-with-inlined-functions
*/
getFunctionStack?(position: IPosition): Promise<{ name: string }[]>;

/**
* Gets ranges that should be stepped for the given step kind and location.
*
* Following CDP semantics, it assumes the column is being the byte offset
* in webassembly. However, we encode the inline frame index in the line.
*/
getStepSkipList?(
direction: StepDirection,
position: IPosition,
sourceUrl?: string,
mappedPosition?: IPosition,
): Promise<Range[]>;
}

class DecompiledWasmSymbols implements IWasmSymbols {
Expand Down Expand Up @@ -245,19 +271,19 @@ class DecompiledWasmSymbols implements IWasmSymbols {
public async compiledPositionFor(
sourceUrl: string,
sourcePosition: IPosition,
): Promise<IPosition | undefined> {
): Promise<IPosition[]> {
if (sourceUrl !== this.decompiledUrl) {
return undefined;
return [];
}

const { byteOffsetsOfLines } = await this.doDisassemble();
const { lineNumber } = sourcePosition.base0;
if (lineNumber >= byteOffsetsOfLines.length) {
return undefined;
return [];
}

const columnNumber = byteOffsetsOfLines[sourcePosition.base0.lineNumber];
return new Base0Position(0, columnNumber);
return [new Base0Position(0, columnNumber)];
}

public dispose(): void {
Expand Down Expand Up @@ -340,7 +366,7 @@ class WasmSymbols extends DecompiledWasmSymbols {
): Promise<{ url: string; position: IPosition } | undefined> {
const locations = await this.rpc.sendMessage('rawLocationToSourceLocation', {
codeOffset: compiledPosition.base0.columnNumber - this.codeOffset,
inlineFrameIndex: 0,
inlineFrameIndex: compiledPosition.base0.lineNumber,
rawModuleId: this.moduleId,
});

Expand All @@ -358,7 +384,7 @@ class WasmSymbols extends DecompiledWasmSymbols {
public override async compiledPositionFor(
sourceUrl: string,
sourcePosition: IPosition,
): Promise<IPosition | undefined> {
): Promise<IPosition[]> {
const { lineNumber, columnNumber } = sourcePosition.base0;
const locations = await this.rpc.sendMessage('sourceLocationToRawLocation', {
lineNumber,
Expand All @@ -380,8 +406,9 @@ class WasmSymbols extends DecompiledWasmSymbols {
}

// todo@connor4312: will there ever be a location in another module?
const location = locations.find(l => l.rawModuleId === this.moduleId);
return location && new Base0Position(0, this.codeOffset + locations[0].startOffset);
return locations
.filter(l => l.rawModuleId === this.moduleId)
.map(l => new Base0Position(0, this.codeOffset + l.startOffset));
}

/** @inheritdoc */
Expand All @@ -396,7 +423,7 @@ class WasmSymbols extends DecompiledWasmSymbols {
): Promise<IWasmVariable[]> {
const location = {
codeOffset: position.base0.columnNumber - this.codeOffset,
inlineFrameIndex: 0,
inlineFrameIndex: position.base0.lineNumber,
rawModuleId: this.moduleId,
};

Expand All @@ -415,6 +442,78 @@ class WasmSymbols extends DecompiledWasmSymbols {
);
}

/** @inheritdoc */
public async getFunctionStack(position: IPosition): Promise<{ name: string }[]> {
const info = await this.rpc.sendMessage('getFunctionInfo', {
codeOffset: position.base0.columnNumber - this.codeOffset,
inlineFrameIndex: position.base0.lineNumber,
rawModuleId: this.moduleId,
});

return 'frames' in info ? info.frames : [];
}

/** @inheritdoc */
public async getStepSkipList(
direction: StepDirection,
position: IPosition,
sourceUrl?: string,
mappedPosition?: IPosition,
): Promise<Range[]> {
const thisLocation = {
codeOffset: position.base0.columnNumber - this.codeOffset,
inlineFrameIndex: position.base0.lineNumber,
rawModuleId: this.moduleId,
};

const getOwnLineRanges = () => {
if (!(mappedPosition && sourceUrl)) {
return [];
}
return this.rpc.sendMessage('sourceLocationToRawLocation', {
lineNumber: mappedPosition.base0.lineNumber,
columnNumber: -1,
rawModuleId: this.moduleId,
sourceFileURL: sourceUrl,
});
};

let rawRanges: Chrome.DevTools.RawLocationRange[];
switch (direction) {
case StepDirection.Out: {
// Step out should step out of inline functions.
rawRanges = await this.rpc.sendMessage('getInlinedFunctionRanges', thisLocation);
break;
}
case StepDirection.Over: {
// step over should both step over inline functions and any
// intermediary statements on this line, which may exist
// in WAT assembly but not in source code.
const ranges = await Promise.all([
this.rpc.sendMessage('getInlinedCalleesRanges', thisLocation),
getOwnLineRanges(),
]);
rawRanges = flatten(ranges);
break;
}
case StepDirection.In:
// Step in should skip over any intermediary statements on this line
rawRanges = await getOwnLineRanges();
break;
default:
rawRanges = [];
break;
}

return rawRanges.map(
r =>
new Range(
new Base0Position(0, r.startOffset + this.codeOffset),
new Base0Position(0, r.endOffset + this.codeOffset),
),
);
}

private getMappedLines(sourceURL: string) {
const prev = this.mappedLines.get(sourceURL);
if (prev) {
Expand Down
41 changes: 41 additions & 0 deletions src/adapter/pause.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import Cdp from '../cdp/api';
import { IPossibleBreakLocation } from './breakpoints';
import { StackTrace } from './stackTrace';
import { Thread } from './threads';

export type PausedReason =
| 'step'
| 'breakpoint'
| 'exception'
| 'pause'
| 'entry'
| 'goto'
| 'function breakpoint'
| 'data breakpoint'
| 'frame_entry';

export const enum StepDirection {
In,
Over,
Out,
}

export type ExpectedPauseReason =
| { reason: Exclude<PausedReason, 'step'>; description?: string }
| { reason: 'step'; description?: string; direction: StepDirection };

export interface IPausedDetails {
thread: Thread;
reason: PausedReason;
event: Cdp.Debugger.PausedEvent;
description: string;
stackTrace: StackTrace;
stepInTargets?: IPossibleBreakLocation[];
hitBreakpoints?: string[];
text?: string;
exception?: Cdp.Runtime.RemoteObject;
}
2 changes: 1 addition & 1 deletion src/adapter/smartStepping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { inject, injectable } from 'inversify';
import { ILogger, LogTag } from '../common/logging';
import { isInstanceOf } from '../common/objUtils';
import { AnyLaunchConfiguration } from '../configuration';
import { ExpectedPauseReason, IPausedDetails, PausedReason, StepDirection } from './pause';
import { isSourceWithMap } from './source';
import { UnmappedReason } from './sourceContainer';
import { StackFrame } from './stackTrace';
import { ExpectedPauseReason, IPausedDetails, PausedReason, StepDirection } from './threads';

export const enum StackFrameStepOverReason {
NotStepped,
Expand Down
15 changes: 10 additions & 5 deletions src/adapter/sourceContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ export class SourceContainer {
continue;
}

let location: IUiLocation;
let locations: IUiLocation[];
if ('decompiledUrl' in value) {
const entry = await value.compiledPositionFor(
sourceUrl,
Expand All @@ -487,8 +487,11 @@ export class SourceContainer {
if (!entry) {
continue;
}
const { lineNumber, columnNumber } = entry.base1;
location = { lineNumber, columnNumber, source: compiled };
locations = entry.map(l => ({
lineNumber: l.base1.lineNumber,
columnNumber: l.base1.columnNumber,
source: compiled,
}));
} else {
const entry = this.sourceMapFactory.guardSourceMapFn(
value,
Expand All @@ -508,10 +511,12 @@ export class SourceContainer {
compiled.inlineScriptOffset,
);

location = { lineNumber, columnNumber, source: compiled };
// recurse for nested sourcemaps:
const location = { lineNumber, columnNumber, source: compiled };
locations = [location, ...(await this.getCompiledLocations(location))];
}

output = output.concat(location, await this.getCompiledLocations(location));
output = output.concat(locations);
}

return output;
Expand Down
Loading
Loading