Skip to content

Commit

Permalink
feat: memoization of constructors + member accesses
Browse files Browse the repository at this point in the history
test: updated all tests to use memoization, when possible
  • Loading branch information
WinPlay02 committed Jan 22, 2024
1 parent 6849744 commit 5b906b8
Show file tree
Hide file tree
Showing 41 changed files with 202 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
isSdsBlockLambda,
isSdsBlockLambdaResult,
isSdsCall,
isSdsCallable,
isSdsCallable, isSdsClass,
isSdsEnumVariant,
isSdsExpressionLambda,
isSdsExpressionStatement,
Expand All @@ -38,6 +38,7 @@ import {
isSdsMap,
isSdsMemberAccess,
isSdsModule,
isSdsParameter,
isSdsParenthesizedExpression,
isSdsPipeline,
isSdsPlaceholder,
Expand Down Expand Up @@ -637,17 +638,25 @@ export class SafeDsPythonGenerator {
} else if (isSdsCall(expression)) {
const callable = this.nodeMapper.callToCallable(expression);
const sortedArgs = this.sortArguments(expression.argumentList.arguments);
if (isSdsFunction(callable)) {
const pythonCall = this.builtinAnnotations.getPythonCall(callable);
if (pythonCall) {
// Memoize constructor or function call
if (isSdsFunction(callable) || isSdsClass(callable)) {
if (isSdsFunction(callable)) {
const pythonCall = this.builtinAnnotations.getPythonCall(callable);
if (pythonCall) {
let thisParam: CompositeGeneratorNode | undefined = undefined;
if (isSdsMemberAccess(expression.receiver)) {
thisParam = this.generateExpression(expression.receiver.receiver, frame);
}
const argumentsMap = this.getArgumentsMap(expression.argumentList.arguments, frame);
return this.generatePythonCall(expression, pythonCall, argumentsMap, frame, thisParam);
}
}
if (this.isCallMemoizable(expression)) {
let thisParam: CompositeGeneratorNode | undefined = undefined;
if (isSdsMemberAccess(expression.receiver)) {
thisParam = this.generateExpression(expression.receiver.receiver, frame);
}
const argumentsMap = this.getArgumentsMap(expression.argumentList.arguments, frame);
return this.generatePythonCall(expression, pythonCall, argumentsMap, frame, thisParam);
} else if (this.isCallMemoizable(expression)) {
return this.generateMemoizedCall(expression, sortedArgs, frame);
return this.generateMemoizedCall(expression, sortedArgs, frame, thisParam);
}
}
return this.generatePlainCall(expression, sortedArgs, frame);
Expand Down Expand Up @@ -783,12 +792,12 @@ export class SafeDsPythonGenerator {
},
{ separator: '' },
)!;
// Member Accesses are not memoized
if (!this.isCallMemoizable(expression) || thisParam) {
// Non-memoizable calls can be directly generated
if (!this.isCallMemoizable(expression)) {
return generatedPythonCall;
}
frame.addImport({ importPath: RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE });
const hiddenParameters = this.getMemoizedCallHiddenParameters(expression);
const hiddenParameters = this.getMemoizedCallHiddenParameters(expression, frame);
const callable = this.nodeMapper.callToCallable(expression);
const memoizedArgs = getParameters(callable).map(
(parameter) => this.nodeMapper.callToParameterValue(expression, parameter)!,
Expand All @@ -802,14 +811,15 @@ export class SafeDsPythonGenerator {
memoizedArgs,
(arg) => this.generateExpression(arg, frame),
{ separator: ', ' },
)}], [${hiddenParameters.join(', ')}])`;
)}], [${joinToNode(hiddenParameters, (param => param), { separator: ', ' })}])`;
}

private isCallMemoizable(expression: SdsCall): boolean {
const impurityReasons = this.purityComputer.getImpurityReasonsForExpression(expression);
let memoizable = true;
for (const reason of impurityReasons) {
if (!(reason instanceof FileRead)) {
// If the file is not known, the call is not memoizable
if (!(reason instanceof FileRead) || reason.path === undefined) {
memoizable &&= false;
}
}
Expand All @@ -820,9 +830,10 @@ export class SafeDsPythonGenerator {
expression: SdsCall,
sortedArgs: SdsArgument[],
frame: GenerationInfoFrame,
thisParam: CompositeGeneratorNode | undefined = undefined
): CompositeGeneratorNode {
frame.addImport({ importPath: RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE });
const hiddenParameters = this.getMemoizedCallHiddenParameters(expression);
const hiddenParameters = this.getMemoizedCallHiddenParameters(expression, frame);
const memoizedArgs = getParameters(this.nodeMapper.callToCallable(expression)).map(
(parameter) => this.nodeMapper.callToParameterValue(expression, parameter)!,
);
Expand All @@ -831,30 +842,48 @@ export class SafeDsPythonGenerator {
.map((arg) => this.nodeMapper.argumentToParameter(arg))
.filter((param) => param)
.find((param) => !Parameter.isRequired(param)) !== undefined;
return expandTracedToNode(
expression,
)`${RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE}.runner_memoized_function_call("${this.generateFullyQualifiedFunctionName(
const fullyQualifiedTargetName = this.generateFullyQualifiedFunctionName(
expression,
frame,
)}", ${containsOptionalArgs ? 'lambda *_ : ' : ''}${
);
return expandTracedToNode(
expression,
)`${RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE}.runner_memoized_function_call("${fullyQualifiedTargetName}", ${containsOptionalArgs ? 'lambda *_ : ' : ''}${
containsOptionalArgs
? this.generatePlainCall(expression, sortedArgs, frame)
: this.generateExpression(expression.receiver, frame)
}, [${joinTracedToNode(expression.argumentList, 'arguments')(
: (isSdsMemberAccess(expression.receiver) && isSdsCall(expression.receiver.receiver) ? expandTracedToNode(expression.receiver)`${this.generateExpression(expression.receiver.receiver.receiver, frame)}.${this.generateExpression(expression.receiver.member!, frame)}` : this.generateExpression(expression.receiver, frame))
}, [${thisParam ? thisParam.append(", ") : ""}${joinTracedToNode(expression.argumentList, 'arguments')(
memoizedArgs,
(arg) => this.generateExpression(arg, frame),
{
separator: ', ',
},
)}], [${hiddenParameters.join(', ')}])`;
)}], [${joinToNode(hiddenParameters, (param => param), { separator: ', ' })}])`;
}

private getMemoizedCallHiddenParameters(expression: SdsCall): string[] {
private getMemoizedCallHiddenParameters(expression: SdsCall, frame: GenerationInfoFrame): CompositeGeneratorNode[] {
const impurityReasons = this.purityComputer.getImpurityReasonsForExpression(expression);
const hiddenParameters: string[] = [];
const hiddenParameters: CompositeGeneratorNode[] = [];
for (const reason of impurityReasons) {
if (reason instanceof FileRead) {
hiddenParameters.push(`${RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE}.runner_filemtime("${reason.path}")`);
if (typeof reason.path === 'string') {
hiddenParameters.push(
expandTracedToNode(expression)`${RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE}.runner_filemtime('${reason.path}')`,
);
} else if (isSdsParameter(reason.path)) {
const argument = this.nodeMapper
.parametersToArguments([reason.path], expression.argumentList.arguments)
.get(reason.path);
if (!argument) {
/* c8 ignore next 4 */
throw new Error(
'File Read impurity with dependency on parameter is present on call, but no argument has been provided.',
);
}
hiddenParameters.push(
expandTracedToNode(argument)`${RUNNER_SERVER_PIPELINE_MANAGER_PACKAGE}.runner_filemtime(${this.generateArgument(argument, frame)})`,
);
}
}
}
return hiddenParameters;
Expand Down Expand Up @@ -890,6 +919,7 @@ export class SafeDsPythonGenerator {
}.${this.getPythonNameOrDefault(declaration)}`;
}
}
/* c8 ignore next */
return undefined;
}

Expand All @@ -898,13 +928,17 @@ export class SafeDsPythonGenerator {
frame: GenerationInfoFrame,
): CompositeGeneratorNode {
const reference = expression.receiver;
if (isSdsMemberAccess(reference) && isSdsCall(reference.receiver)) {
return expandTracedToNode(reference)`${this.generateFullyQualifiedFunctionName(reference.receiver, frame)}.${this.getPythonNameOrDefault(reference.member?.target.ref!)}`;
}
if (isSdsReference(reference)) {
const declaration = reference.target.ref!;
const fullyQualifiedReferenceName = this.getFullyQualifiedReferenceName(reference, declaration);
if (fullyQualifiedReferenceName) {
return expandTracedToNode(reference)`${fullyQualifiedReferenceName}`;
}
}
/* c8 ignore next */
return this.generateExpression(reference, frame);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ describe('generation', async () => {

// File contents must match
for (const expectedOutputFile of test.expectedOutputFiles) {
if (expectedOutputFile.uri.toString().endsWith('.map')) {
continue;
}
const actualCode = actualOutputs.get(expectedOutputFile.uri.toString());
expect(actualCode).toBe(expectedOutputFile.code);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Imports ----------------------------------------------------------------------

import safeds_runner.server.pipeline_manager

# Segments ---------------------------------------------------------------------

def f1(l):
Expand All @@ -11,11 +15,11 @@ def f2(l):

def test():
def __gen_block_lambda_0(a, b):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_d = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambdaResult.g", g, [], [])
return __gen_block_lambda_result_d
f1(__gen_block_lambda_0)
def __gen_block_lambda_1(a, b):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_e = g()
__gen_block_lambda_result_d = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambdaResult.g", g, [], [])
__gen_block_lambda_result_e = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambdaResult.g", g, [], [])
return __gen_block_lambda_result_d, __gen_block_lambda_result_e
f2(__gen_block_lambda_1)

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

Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@

def test():
def __gen_block_lambda_0(a, b=2):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_d = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambda.g", g, [], [])
return __gen_block_lambda_result_d
f1(__gen_block_lambda_0)
def __gen_block_lambda_1(a, b):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_d = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambda.g", g, [], [])
return __gen_block_lambda_result_d
f1(__gen_block_lambda_1)
def __gen_block_lambda_2():
pass
f2(__gen_block_lambda_2)
def __gen_block_lambda_3(a, b=2):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_d = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambda.g", g, [], [])
return __gen_block_lambda_result_d
g2(f3(__gen_block_lambda_3))
g2(safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambda.f3", f3, [__gen_block_lambda_3], []))
def __gen_block_lambda_4(a, b=2):
__gen_block_lambda_result_d = g()
__gen_block_lambda_result_d = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambda.g", g, [], [])
return __gen_block_lambda_result_d
c = f3(__gen_block_lambda_4)
c = safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.blockLambda.f3", f3, [__gen_block_lambda_4], [])
safeds_runner.server.pipeline_manager.runner_save_placeholder('c', c)
g2(c)

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

Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Imports ----------------------------------------------------------------------

import safeds_runner.server.pipeline_manager

# Pipelines --------------------------------------------------------------------

def test():
f(g(1, param2=2))
f(g(2, param2=1))
f(h(1, param_2=2))
f(h(2, param_2=1))
f(safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.call.g", lambda *_ : g(1, param2=2), [1, 2], []))
f(safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.call.g", lambda *_ : g(2, param2=1), [2, 1], []))
f(safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.call.h", lambda *_ : h(1, param_2=2), [1, 2], []))
f(safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.call.h", lambda *_ : h(2, param_2=1), [2, 1], []))
f(safeds_runner.server.pipeline_manager.runner_memoized_function_call("tests.generator.call.h", h, [2, 0], []))
'abc'.i()
'abc'.j(123)
k(456, 1.23)

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

Loading

0 comments on commit 5b906b8

Please sign in to comment.