Skip to content

Commit 04289cc

Browse files
committed
perf: rearrange code for better performance
'Variable.create' is a critical path, so it is optimized for better performance: some checks rearranged, conditions splitted, etc...
1 parent 372545d commit 04289cc

File tree

5 files changed

+108
-53
lines changed

5 files changed

+108
-53
lines changed

src/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,6 @@ export function getDisplayedExprs(): string[] {
624624
'ScalarArrayOpExpr',
625625
'SubLink',
626626
'SubscriptingRef',
627-
'TargetEntry',
628627
'Var',
629628
'WindowFunc',
630629
'WindowFuncFuncCondition',

src/debugger.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -506,17 +506,18 @@ export class CppDbgDebuggerFacade extends GenericDebuggerFacade {
506506
}
507507

508508
isValidPointerType(variable: IDebugVariable | dap.EvaluateResponse) {
509-
return pointerRegex.test(this.getValue(variable)) && !this.isNull(variable);
509+
/* Check isNull first, because lots of variables can be NULL, i.e. Bitmapset */
510+
return !this.isNull(variable) && pointerRegex.test(this.getValue(variable));
510511
}
511512

512513
isValueStruct(variable: IDebugVariable, type?: string) {
513-
/* Top level variable */
514-
if (variable.value === '{...}') {
514+
/* Value struct (also check for flexible array member) */
515+
if (!variable.value.length && !(type ?? variable.type).endsWith('[]')) {
515516
return true;
516517
}
517-
518-
/* Embedded structure (also check for flexible array member) */
519-
if (variable.value === '' && !(type ?? variable.type).endsWith('[]')) {
518+
519+
/* Top level variable */
520+
if (variable.value === '{...}') {
520521
return true;
521522
}
522523

@@ -765,26 +766,39 @@ export class CodeLLDBDebuggerFacade extends GenericDebuggerFacade {
765766
}
766767

767768
isNull(variable: IDebugVariable | dap.EvaluateResponse): boolean {
768-
const value = this.getValue(variable);
769-
return value === '<null>' || nullRegex.test(value);
769+
if ('result' in variable) {
770+
return variable.result === '<null>';
771+
}
772+
773+
/*
774+
* CodeLLDB uses both 'memoryReference' and 'value', but value stored
775+
* differs when we get NULL: memoryReference contains short '0x0', while
776+
* 'value' contains long '0x000000000' - check short memoryReference first
777+
*/
778+
if (variable.memoryReference) {
779+
return variable.memoryReference === '0x0';
780+
}
781+
782+
return nullRegex.test(variable.value);
770783
}
771784

772785
isValidPointerType(variable: IDebugVariable | dap.EvaluateResponse): boolean {
773-
const value = this.getValue(variable);
786+
if ('result' in variable) {
787+
return !(variable.result === '<null>' || variable.result === '<invalid address>');
788+
}
774789

775-
/* CodeLLDB examine pointers itself, so this is handy for us */
776-
if (value === '<invalid address>' || value === '<null>') {
790+
if (variable.memoryReference === '0x0') {
777791
return false;
778792
}
779-
793+
780794
/*
781795
* CodeLLDB is smart, but it is problem for us, because it becomes hard
782796
* to detect which type of this type: pointer to struct or builtin basic
783797
* type (i.e. 'int *').
784798
* So, here I try to be as flexible as I can - this is pointer type
785799
* if it contains any pointer in type or it is raw pointer value.
786800
*/
787-
return variable.type.indexOf('*') !== -1 || pointerRegex.test(value);
801+
return variable.type.indexOf('*') !== -1 || pointerRegex.test(this.getValue(variable));
788802
}
789803

790804
isScalarType(variable: IDebugVariable, type?: string) {

src/test/suite/variables.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const execGetVariables = async () => {
126126
};
127127

128128
suite('Variables', async function () {
129+
/* TODO: split by debugger type */
129130
/*
130131
* Store predicate and query together, so we can fast reflect any
131132
* changes in tested predicate.

src/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,13 @@ export function havePointersCount(type: string, count: number) {
136136
export function isValueStructOrPointerType(type: string) {
137137
const firstPointerPos = type.indexOf('*');
138138
if (firstPointerPos === -1) {
139+
/* Value struct */
139140
return true;
140141
}
141142

142143
const secondPointerPos = type.indexOf('*', firstPointerPos + 1);
143144
if (secondPointerPos === -1) {
145+
/* Pointer type, not array */
144146
return true;
145147
}
146148

@@ -158,6 +160,10 @@ export function isEnumResult(result: string) {
158160
return isValidIdentifier(result);
159161
}
160162

163+
export function isFlexibleArrayMember(type: string) {
164+
return type.endsWith('[]');
165+
}
166+
161167
export function joinPath(base: vscode.Uri, ...paths: string[]) {
162168
return vscode.Uri.joinPath(base, ...paths);
163169
}

src/variables.ts

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -856,14 +856,54 @@ export abstract class Variable {
856856
type: effectiveType,
857857
declaredType: debugVariable.type,
858858
};
859+
860+
/* Value struct are not so interesting for us */
861+
if (context.debug.isValueStruct(debugVariable, effectiveType)) {
862+
if (parent) {
863+
if (utils.isValueStructOrPointerType(parent.type)) {
864+
const flagsMember = context.specialMemberRegistry.getFlagsMember(
865+
utils.getStructNameFromType(parent.type),
866+
debugVariable.name);
867+
if (flagsMember) {
868+
return new FlagsMemberVariable(flagsMember, args);
869+
}
870+
}
871+
}
872+
873+
if (effectiveType === 'bitmapword') {
874+
/* Show bitmapword as bitmask, not integer */
875+
return new BitmapwordVariable(args);
876+
}
877+
878+
return new RealVariable(args);
879+
}
859880

860-
if ( context.debug.isValueStruct(debugVariable, effectiveType)
861-
|| !context.debug.isValidPointerType(debugVariable)) {
881+
/*
882+
* Pointer types can be NULL or contain invalid pointers.
883+
* cppdbg do not recognize invalid pointers, but CodeLLDB - <invalid pointer>.
884+
*/
885+
if (!context.debug.isValidPointerType(debugVariable)) {
862886
/*
863887
* We are here if got scalar type or value struct (not pointer).
864888
* These types are not so interesting for us, so pass here to
865889
* quickly return usual variable.
866890
*/
891+
892+
if ( context.debug.isNull(debugVariable)
893+
&& debugVariable.type.endsWith('List *')) {
894+
/*
895+
* Empty List is NIL == NULL == '0x0' Also 'endsWith'
896+
* covers cases like 'const List *'.
897+
*
898+
* Note that even if 'Bitmapset' also falls in this
899+
* variable category (NULL is meaningful), by design
900+
* do not create 'BitmapSetSpecialMember' for it,
901+
* because some runtime checks will end up in SEGFAULT.
902+
* Currently, there is no need for that, but take this
903+
* into account if you are planning to do this.
904+
*/
905+
return new ListNodeVariable('List', args);
906+
}
867907

868908
if (context.debug.isNull(debugVariable) &&
869909
debugVariable.type.endsWith('List *')) {
@@ -881,26 +921,12 @@ export abstract class Variable {
881921
return new ListNodeVariable('List', args);
882922
}
883923

884-
if (effectiveType === 'bitmapword') {
885-
/* Show bitmapword as bitmask, not integer */
886-
return new BitmapwordVariable(args);
887-
}
888-
889924
if (parent) {
890-
if (utils.isValueStructOrPointerType(parent.type)) {
891-
const flagsMember = context.specialMemberRegistry.getFlagsMember(
892-
utils.getStructNameFromType(parent.type),
893-
debugVariable.name);
894-
if (flagsMember) {
895-
return new FlagsMemberVariable(flagsMember, args);
896-
}
897-
}
898-
899925
/*
900926
* Flexible array members for now recognized as non-valid
901927
* pointers/scalars, but we actually can handle them.
902928
*/
903-
if (debugVariable.type.endsWith('[]')) {
929+
if (utils.isFlexibleArrayMember(debugVariable.type)) {
904930
const parentType = Variable.getRealType(parent.type, context);
905931
const specialMember = context.specialMemberRegistry
906932
.getArraySpecialMember(parentType, debugVariable.name);
@@ -931,6 +957,10 @@ export abstract class Variable {
931957
/*
932958
* PostgreSQL versions prior 16 do not have Bitmapset Node.
933959
* So handle Bitmapset (with Relids) here.
960+
*
961+
* NOTE: this check must be before general 'isNodeVar', because
962+
* NULL is valid value otherwise this will break assumption
963+
* that passed 'debugVariable' is not NULL
934964
*/
935965
if (BitmapSetSpecialMember.isBitmapsetType(effectiveType)) {
936966
return new BitmapSetSpecialMember(args);
@@ -951,7 +981,7 @@ export abstract class Variable {
951981
return new HTABSpecialMember(args);
952982
}
953983

954-
/* Simple hash table (simple hash) */
984+
/* Simple hash table (lib/simplehash.h) */
955985
if (SimplehashMember.looksLikeSimpleHashTable(effectiveType)) {
956986
const entry = context.hashTableTypes.findSimpleHashTableType(effectiveType);
957987
if (entry) {
@@ -1750,9 +1780,14 @@ export class NodeVariable extends RealVariable {
17501780
static async createNode(variable: dap.DebugVariable, frameId: number,
17511781
context: ExecContext, args: RealVariableArgs) {
17521782
const getRealNodeTag = async () => {
1753-
const expr = `((Node*)(${context.debug.getPointer(variable)}))->type`;
1783+
const expr = `((Node *)(${context.debug.getPointer(variable)}))->type`;
17541784
const response = await context.debug.evaluate(expr, frameId);
1755-
const realTag = response.result.replace('T_', '');
1785+
if (!response.result.startsWith('T_')) {
1786+
return;
1787+
}
1788+
1789+
/* Do not use replace('T_', ''), because this 'T_' can be inside identifier */
1790+
const realTag = response.result.substring(2);
17561791
if (!context.nodeVarRegistry.isNodeTag(realTag)) {
17571792
return;
17581793
}
@@ -1783,23 +1818,24 @@ export class NodeVariable extends RealVariable {
17831818
return new BitmapSetSpecialMember(args);
17841819
}
17851820

1786-
/* Expressions with it's representation */
1787-
if (context.nodeVarRegistry.exprs.has(realTag)) {
1788-
if (realTag === 'TargetEntry') {
1789-
return new TargetEntryVariable(args);
1790-
}
1791-
1792-
return new ExprNodeVariable(realTag, args);
1793-
}
1794-
17951821
/* Display expressions in EquivalenceMember and RestrictInfo */
1822+
if (realTag === 'TargetEntry') {
1823+
/* TargetEntry should be checked before 'exprs' - see class comment */
1824+
return new TargetEntryVariable(args);
1825+
}
1826+
17961827
if (realTag === 'EquivalenceMember') {
17971828
return new DisplayExprReprVariable(realTag, 'em_expr', args);
17981829
}
17991830

18001831
if (realTag === 'RestrictInfo') {
18011832
return new DisplayExprReprVariable(realTag, 'clause', args);
18021833
}
1834+
1835+
/* Expressions with it's representation */
1836+
if (context.nodeVarRegistry.exprs.has(realTag)) {
1837+
return new ExprNodeVariable(realTag, args);
1838+
}
18031839

18041840
/* Check this is a tag of 'Value' */
18051841
if (realTag === 'String' ||
@@ -3533,13 +3569,11 @@ class DisplayExprReprVariable extends NodeVariable {
35333569
}
35343570

35353571
/**
3536-
* Special case for 'TargetEntry' to display it's repr
3537-
* in description.
3538-
* It can not be moved to 'DisplayExprReprVariable' because
3539-
* it is Expr and can be used in 'ExprVariable.
3540-
* Also I do not want to move such logic to 'ExprVariable',
3541-
* because repr evaluation is resource-intensive operation
3542-
* and UI just blocks.
3572+
* Special case for 'TargetEntry' to display it's repr in description.
3573+
* It can not be moved to 'DisplayExprReprVariable' because it is Expr
3574+
* and can be used in 'ExprVariable'. Also I do not want to move such
3575+
* logic to 'ExprVariable', because repr evaluation is resource-intensive
3576+
* operation and UI just blocks.
35433577
*/
35443578
class TargetEntryVariable extends ExprNodeVariable {
35453579
constructor(args: RealVariableArgs) {
@@ -4856,6 +4890,7 @@ class HTABElementsMember extends Variable {
48564890

48574891
try {
48584892
await this.evaluateVoid(expr);
4893+
return memory;
48594894
} catch (err) {
48604895
if (!(err instanceof EvaluationError)) {
48614896
throw err;
@@ -5023,7 +5058,8 @@ class SimplehashMember extends RealVariable {
50235058
/*
50245059
* Check this is last part of typename, i.e. not part of whole typename
50255060
*/
5026-
if (type.length < index + '_hash'.length) {
5061+
const endOfType = index + '_hash'.length;
5062+
if (type.length < endOfType) {
50275063
/*
50285064
* I assume, every hash table object is a pointer type,
50295065
* not allocated on stack.
@@ -5036,7 +5072,7 @@ class SimplehashMember extends RealVariable {
50365072
* so typename actually ends with '_hash'.
50375073
* In real life only available continuation is space or star.
50385074
*/
5039-
const nextChar = type[index + '_hash'.length];
5075+
const nextChar = type[endOfType];
50405076
return nextChar === ' ' || nextChar === '*';
50415077
}
50425078

@@ -5508,7 +5544,6 @@ export class PgVariablesViewProvider implements vscode.TreeDataProvider<Variable
55085544
if (config.nodetags?.length) {
55095545
logger.debug('adding %i custom NodeTags');
55105546
try {
5511-
/* TODO: add command to parse NodeTag files and find custom */
55125547
for (const tag of config.nodetags) {
55135548
nodeVars.nodeTags.add(tag);
55145549
}

0 commit comments

Comments
 (0)