Skip to content

Resolve Aliased and Constant Vectors #1636

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

Draft
wants to merge 9 commits into
base: vector-support-staging
Choose a base branch
from
5 changes: 3 additions & 2 deletions src/dataflow/environments/built-in-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export interface BuiltInConstantDefinition<Value> extends BaseBuiltInDefinition
export interface BuiltInFunctionDefinition<BuiltInProcessor extends BuiltInMappingName> extends BaseBuiltInDefinition {
readonly type: 'function';
readonly processor: BuiltInProcessor;
readonly config?: ConfigOfBuiltInMappingName<BuiltInProcessor>
readonly config?: ConfigOfBuiltInMappingName<BuiltInProcessor>;
readonly evalHandler?: string
}

/**
Expand All @@ -50,7 +51,7 @@ export type BuiltInDefinition = BuiltInConstantDefinition<any> | BuiltInFunction
/**
* @see DefaultBuiltinConfig
*/
export type BuiltInDefinitions = readonly BuiltInDefinition[];
export type BuiltInDefinitions = readonly BuiltInDefinition[];

function registerBuiltInConstant<T>({ names, value, assumePrimitive }: BuiltInConstantDefinition<T>): void {
for(const name of names) {
Expand Down
12 changes: 11 additions & 1 deletion src/dataflow/environments/built-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { processQuote } from '../internal/process/functions/call/built-in/built-
import { processFunctionDefinition } from '../internal/process/functions/call/built-in/built-in-function-definition';
import { processExpressionList } from '../internal/process/functions/call/built-in/built-in-expression-list';
import { processGet } from '../internal/process/functions/call/built-in/built-in-get';
import type { ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import type { AstIdMap, ParentInformation, RNodeWithParent } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import type { RFunctionArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
import { EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
import type { RSymbol } from '../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
Expand All @@ -37,6 +37,9 @@ import { processEvalCall } from '../internal/process/functions/call/built-in/bui
import { VertexType } from '../graph/vertex';
import { RType } from '../../r-bridge/lang-4.x/ast/model/type';
import { handleUnknownSideEffect } from '../graph/unknown-side-effect';
import type { REnvironmentInformation } from './environment';
import type { Value } from '../eval/values/r-value';
import { resolveAsVector } from '../eval/resolve/resolve';

export type BuiltIn = `built-in:${string}`;

Expand Down Expand Up @@ -84,6 +87,9 @@ export interface DefaultBuiltInProcessorConfiguration extends ForceArguments {
readonly treatAsFnCall?: Record<string, readonly string[]>
}


export type BuiltInEvalHandler = (a: RNodeWithParent, env: REnvironmentInformation, map?: AstIdMap) => Value;

function defaultBuiltInProcessor<OtherInfo>(
name: RSymbol<OtherInfo & ParentInformation>,
args: readonly RFunctionArgument<OtherInfo & ParentInformation>[],
Expand Down Expand Up @@ -200,6 +206,10 @@ export const BuiltInProcessorMapper = {
'builtin:vector': processVector,
} as const satisfies Record<`builtin:${string}`, BuiltInIdentifierProcessorWithConfig<never>>;

export const BuiltInEvalHandlerMapper = {
'c': resolveAsVector
} as const satisfies Record<string, BuiltInEvalHandler>;

export type BuiltInMappingName = keyof typeof BuiltInProcessorMapper;
export type ConfigOfBuiltInMappingName<N extends BuiltInMappingName> = Parameters<typeof BuiltInProcessorMapper[N]>[4];

Expand Down
2 changes: 1 addition & 1 deletion src/dataflow/environments/default-builtin-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export const DefaultBuiltinConfig: BuiltInDefinitions = [
{ type: 'function', names: ['interference'], processor: 'builtin:apply', config: { unquoteFunction: true, nameOfFunctionArgument: 'propensity_integrand' }, assumePrimitive: false },
{ type: 'function', names: ['ddply'], processor: 'builtin:apply', config: { unquoteFunction: true, indexOfFunction: 2, nameOfFunctionArgument: '.fun' }, assumePrimitive: false },
{ type: 'function', names: ['list'], processor: 'builtin:list', config: {}, assumePrimitive: true },
{ type: 'function', names: ['c'], processor: 'builtin:vector', config: {}, assumePrimitive: true },
{ type: 'function', names: ['c'], processor: 'builtin:vector', config: {}, assumePrimitive: true, evalHandler: 'builtin:c' },
{
type: 'function',
names: ['setnames', 'setNames', 'setkey', 'setkeyv', 'setindex', 'setindexv', 'setattr'],
Expand Down
293 changes: 1 addition & 292 deletions src/dataflow/environments/resolve-by-name.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
import type { IEnvironment, REnvironmentInformation } from './environment';
import { BuiltInEnvironment, initializeCleanEnvironments } from './environment';
import { BuiltInEnvironment } from './environment';
import { Ternary } from '../../util/logic';
import type { Identifier, IdentifierDefinition } from './identifier';
import { isReferenceType, ReferenceType } from './identifier';
import { happensInEveryBranch } from '../info';
import type { BuiltInIdentifierConstant } from './built-in';
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { recoverName } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { VertexType } from '../graph/vertex';
import type { DataflowGraph } from '../graph/graph';
import { getConfig, VariableResolve } from '../../config';
import { assertUnreachable, guard } from '../../util/assert';
import type { AstIdMap, RNodeWithParent } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import { RType } from '../../r-bridge/lang-4.x/ast/model/type';
import { VisitingQueue } from '../../slicing/static/visiting-queue';
import { envFingerprint } from '../../slicing/static/fingerprint';
import { EdgeType } from '../graph/edge';
import { Bottom, Top, type Lift, type Value, type ValueSet } from '../eval/values/r-value';
import { valueFromRNode, valueFromTsValue } from '../eval/values/general';
import { setFrom } from '../eval/values/sets/set-constants';
import { onUnknownSideEffect } from '../graph/unknown-side-effect';
import type { LinkTo } from '../../queries/catalog/call-context-query/call-context-query-format';

export type ResolveResult = Lift<ValueSet<Value[]>>;

const FunctionTargetTypes = ReferenceType.Function | ReferenceType.BuiltInFunction | ReferenceType.Unknown | ReferenceType.Argument | ReferenceType.Parameter;
const VariableTargetTypes = ReferenceType.Variable | ReferenceType.Parameter | ReferenceType.Argument | ReferenceType.Unknown;
Expand Down Expand Up @@ -103,275 +84,3 @@ export function resolvesToBuiltInConstant(name: Identifier | undefined, environm
return some ? Ternary.Maybe : Ternary.Never;
}
}

/** Please use {@link resolveValueOfVariable} */
export function resolveToConstants(name: Identifier | undefined, environment: REnvironmentInformation): ResolveResult {
if(name === undefined) {
return Top;
}

const definitions = resolveByName(name, environment, ReferenceType.Constant);
if(definitions === undefined) {
return Top;
}

const values: Set<Value> = new Set<Value>();
definitions.forEach(def => values.add(valueFromTsValue((def as BuiltInIdentifierConstant).value ?? Top)));
return setFrom(...values);
}

type AliasHandler = (s: NodeId, d: DataflowGraph, e: REnvironmentInformation) => NodeId[] | undefined;

const AliasHandler = {
[VertexType.Value]: (sourceId: NodeId) => [sourceId],
[VertexType.Use]: getUseAlias,
[VertexType.FunctionCall]: () => undefined,
[VertexType.FunctionDefinition]: () => undefined,
[VertexType.VariableDefinition]: () => undefined
} as const satisfies Record<VertexType, AliasHandler>;

function getUseAlias(sourceId: NodeId, dataflow: DataflowGraph, environment: REnvironmentInformation): NodeId[] | undefined {
const definitions: NodeId[] = [];

// Source is Symbol -> resolve definitions of symbol
const identifier = recoverName(sourceId, dataflow.idMap);
if(identifier === undefined) {
return undefined;
}

const defs = resolveByName(identifier, environment);
if(defs === undefined) {
return undefined;
}

for(const def of defs) {
// If one definition is not constant (or a variable aliasing a constant)
// we can't say for sure what value the source has
if(def.type === ReferenceType.Variable) {
if(def.value === undefined) {
return undefined;
}
definitions.push(...def.value);
} else if(def.type === ReferenceType.Constant || def.type === ReferenceType.BuiltInConstant) {
definitions.push(def.nodeId);
} else {
return undefined;
}
}

return definitions;
}

export function getAliases(sourceIds: readonly NodeId[], dataflow: DataflowGraph, environment: REnvironmentInformation): NodeId[] | undefined {
const definitions: Set<NodeId> = new Set<NodeId>();

for(const sourceId of sourceIds) {
const info = dataflow.getVertex(sourceId);
if(info === undefined) {
return undefined;
}

const defs = AliasHandler[info.tag](sourceId, dataflow, environment);
for(const def of defs ?? []) {
definitions.add(def);
}
}

return [...definitions];
}

/** Please use {@link resolveValueOfVariable} */
export function trackAliasInEnvironments(identifier: Identifier | undefined, use: REnvironmentInformation, idMap?: AstIdMap): ResolveResult {
if(identifier === undefined) {
return Top;
}

const defs = resolveByName(identifier, use);
if(defs === undefined) {
return Top;
}

const values: Set<Value> = new Set<Value>();
for(const def of defs) {
if(def.type === ReferenceType.BuiltInConstant) {
values.add(valueFromTsValue(def.value));
} else if(def.type === ReferenceType.BuiltInFunction) {
// Tracked in #1207
} else if(def.value !== undefined) {
/* if there is at least one location for which we have no idea, we have to give up for now! */
if(def.value.length === 0) {
return Top;
}
for(const id of def.value) {
const value = idMap?.get(id);
if(value !== undefined) {
values.add(valueFromRNode(value));
}
}
}
}

if(values.size == 0) {
return Top;
}

return setFrom(...values);
}

onUnknownSideEffect((_graph: DataflowGraph, env: REnvironmentInformation, _id: NodeId, target?: LinkTo<RegExp | string>) => {
if(target) {
return;
}

env.current.memory.forEach(mem => mem.forEach((def) => {
if(def.type === ReferenceType.BuiltInConstant) {
// what
} else if(def.type === ReferenceType.BuiltInFunction) {
// Tracked in #1207
} else if(def.value !== undefined) {
def.value.length = 0;
}
}));
});

function isNestedInLoop(node: RNodeWithParent | undefined, ast: AstIdMap): boolean {
const parent = node?.info.parent;
if(node === undefined || !parent) {
return false;
}

const parentNode = ast.get(parent);
if(parentNode === undefined) {
return false;
}

if(parentNode.type === RType.WhileLoop || parentNode.type === RType.RepeatLoop) {
return true;
}

return isNestedInLoop(parentNode, ast);
}

export function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, idMap?: AstIdMap): ResolveResult {
idMap ??= graph.idMap;
guard(idMap !== undefined, 'The ID map is required to get the lineage of a node');
const start = graph.getVertex(id);
guard(start !== undefined, 'Unable to find start for alias tracking');

const queue = new VisitingQueue(25);
const clean = initializeCleanEnvironments();
const cleanFingerprint = envFingerprint(clean);
queue.add(id, clean, cleanFingerprint, false);

let forceBot = false;

const resultIds: NodeId[] = [];
while(queue.nonEmpty()) {
const { id, baseEnvironment } = queue.next();
const res = graph.get(id);
if(!res) {
continue;
}
const [vertex, outgoingEdges] = res;
const cds = vertex.cds;
for(const cd of cds ?? []) {
const target = graph.idMap?.get(cd.id);
if(target === undefined) {
continue;
}
if(target.type === RType.WhileLoop || target.type === RType.RepeatLoop) {
forceBot = true;
break;
}
}
if(!forceBot && (cds?.length === 0 && isNestedInLoop(idMap.get(id), idMap))) {
forceBot = true;
}
if(forceBot) {
break;
}
if(vertex.tag === VertexType.Value) {
resultIds.push(id);
continue;
}

// travel all read and defined-by edges
for(const [targetId, edge] of outgoingEdges) {
// currently, they have to be exact!
if(edge.types === EdgeType.Reads || edge.types === EdgeType.DefinedBy || edge.types === EdgeType.DefinedByOnCall) {
queue.add(targetId, baseEnvironment, cleanFingerprint, false);
}
}
}
if(forceBot || resultIds.length === 0) {
return Bottom;
}
const values: Set<Value> = new Set<Value>();
for(const id of resultIds) {
const node = idMap.get(id);
if(node !== undefined) {
values.add(valueFromRNode(node));
}
}
return setFrom(...values);
}
/**
* Convenience function using the variable resolver as specified within the configuration file
* In the future we may want to have this set once at the start of the analysis
*
* @see {@link resolveIdToValue} - for a more general approach which "evaluates" a node based on value resolve
*/
export function resolveValueOfVariable(identifier: Identifier | undefined, environment: REnvironmentInformation, idMap?: AstIdMap): ResolveResult {
const resolve = getConfig().solver.variables;

switch(resolve) {
case VariableResolve.Alias: return trackAliasInEnvironments(identifier, environment, idMap);
case VariableResolve.Builtin: return resolveToConstants(identifier, environment);
case VariableResolve.Disabled: return Bottom;
default: assertUnreachable(resolve);
}
}

export interface ResolveInfo {
/** The current environment used for name resolution */
environment?: REnvironmentInformation;
/** The id map to resolve the node if given as an id */
idMap?: AstIdMap;
/** The graph to resolve in */
graph?: DataflowGraph;
/** Whether to track variables */
full?: boolean;
}

/**
* Generalized {@link resolveValueOfVariable} function which evaluates a node based on the value resolve
*
* @param id - The node id or node to resolve
* @param environment - The current environment used for name resolution
* @param graph - The graph to resolve in
* @param idMap - The id map to resolve the node if given as an id
* @param full - Whether to track variables
*/
export function resolveIdToValue(id: NodeId | RNodeWithParent, { environment, graph, idMap, full } : ResolveInfo): ResolveResult {
idMap ??= graph?.idMap;
const node = typeof id === 'object' ? id : idMap?.get(id);
if(node === undefined) {
return Top;
}
switch(node.type) {
case RType.Symbol:
if(environment) {
return full ? resolveValueOfVariable(node.lexeme, environment, idMap) : Top;
} else if(graph && getConfig().solver.variables === VariableResolve.Alias) {
return full ? trackAliasesInGraph(node.info.id, graph, idMap) : Top;
} else {
return Top;
}
case RType.String:
case RType.Number:
case RType.Logical:
return setFrom(valueFromRNode(node));
default:
return Top;
}
}
Loading
Loading