diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 16a301038ddee193f5f038f0708ce4d91bff0ad8..25899d68a16c82618f6cb06d58e4d6295096cd88 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,4 +34,5 @@ include: - arkoala/loader/.gitlab-ci.yml - arkoala-arkts/.gitlab-ci.yml - arkoala-arkts/libarkts/.gitlab-ci.yml + - arkoala-arkts/memo-plugin/.gitlab-ci.yml - interop/.gitlab-ci.yml diff --git a/arkoala-arkts/libarkts/src/arkts-api/types.ts b/arkoala-arkts/libarkts/src/arkts-api/types.ts index d83d0709658f2378f9a8bf90924864e7f4977cb0..7be8fafd1a26b51638a078da5d2ad41dad492ba1 100644 --- a/arkoala-arkts/libarkts/src/arkts-api/types.ts +++ b/arkoala-arkts/libarkts/src/arkts-api/types.ts @@ -771,8 +771,15 @@ export class ReturnStatement extends AstNode { } static create( - argument: AstNode, + argument?: AstNode, ): ReturnStatement { + if (argument === undefined) { + return new ReturnStatement( + global.generatedEs2panda._CreateReturnStatement( + global.context + ) + ) + } return new ReturnStatement( global.es2panda._CreateReturnStatement1( global.context, diff --git a/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts b/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts index dbc9a8b880859f3551e96d8343845893d7a37e4b..dbea933f3c14c53834281e4863a39e766a3d4279 100644 --- a/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts +++ b/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts @@ -79,3 +79,7 @@ export function getOriginalNode(node: AstNode): AstNode { } return unpackNonNullableNode(node.originalPeer) } + +export function getFileName(): string { + return global.filePath +} diff --git a/arkoala-arkts/memo-plugin/.gitlab-ci.yml b/arkoala-arkts/memo-plugin/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..51db6fa47f4de74fcde6d68d223cb10c64710301 --- /dev/null +++ b/arkoala-arkts/memo-plugin/.gitlab-ci.yml @@ -0,0 +1,40 @@ +build memo-plugin: + stage: build + interruptible: true + extends: + - .linux-vm-shell-task + needs: + - install node modules (arkoala) + - install node modules (arkoala-arkts) + - install node modules (incremental) + - install node modules (interop) + before_script: + - !reference [.setup, script] + - npm run panda:sdk:install --prefix arkoala-arkts/libarkts + - npm run compile --prefix arkoala-arkts/libarkts + script: + - npm run compile --prefix arkoala-arkts/memo-plugin + +test memo-plugin: + stage: test + interruptible: true + extends: + - .linux-vm-shell-task + needs: + - install node modules (arkoala) + - install node modules (arkoala-arkts) + - install node modules (incremental) + - install node modules (interop) + - build incremental.abc + before_script: + - !reference [.setup, script] + - npm run compile:all --prefix incremental/compat + - npm run compile --prefix incremental/common + - npm run compile --prefix incremental/compiler-plugin + - npm run compile --prefix incremental/runtime + - npm run unmemoize --prefix incremental/runtime + - npm run panda:sdk:install --prefix arkoala-arkts/libarkts + - npm run compile --prefix arkoala-arkts/libarkts + - npm run compile --prefix arkoala-arkts/memo-plugin + script: + - npm run demo:run --prefix arkoala-arkts/memo-plugin diff --git a/arkoala-arkts/memo-plugin/demo/demo.ts b/arkoala-arkts/memo-plugin/demo/demo.ts index a5990afd4aa5eb6169fbdf225bfaed04e26f2701..989a003bf8da4c9e77cfb0b926af4f54cc2d4f10 100644 --- a/arkoala-arkts/memo-plugin/demo/demo.ts +++ b/arkoala-arkts/memo-plugin/demo/demo.ts @@ -13,6 +13,7 @@ function main() { const manager = GlobalStateManager.instance const state = manager.computableState((context: StateContext) => { memoEntry(context, 0, foo_wrapper) + return 20 }) console.log(state.value) } diff --git a/arkoala-arkts/memo-plugin/demo/package.json b/arkoala-arkts/memo-plugin/demo/package.json index 863ce63477b5cfe718a24b97b84ca656fec9db72..0c938a24b762ad5edd04d58ef28ce1f8e662321e 100644 --- a/arkoala-arkts/memo-plugin/demo/package.json +++ b/arkoala-arkts/memo-plugin/demo/package.json @@ -8,9 +8,9 @@ "ts-like:compile": "npm run unmemoize && fast-arktsc --input-files ./arktsconfig-unmemoized.json --output-dir ./build --compiler ../../../incremental/tools/panda/arkts/arktsc --link-name stub && ninja ${NINJA_OPTIONS} -f build/build.ninja", "arkts-like:compile": "../../../incremental/tools/panda/arkts/arktsc --arktsconfig ./arktsconfig.json --output ./build/demo.abc ./demo.sts", "arkts-like:compile:capi": "../../../incremental/tools/panda/arkts/arktsc-capi --arktsconfig ./arktsconfig.json --output ./build/demo.abc --file ./demo.sts --dump-plugin-ast", - "run": "../../../incremental/tools/panda/node_modules/@panda/sdk/linux_host_tools/bin/ark --load-runtimes=ets --boot-panda-files=../../../incremental/tools/panda/node_modules/@panda/sdk/ets/etsstdlib.abc:../../../incremental/runtime/build/incremental.abc:build/stub.abc ./build/demo.abc @koalaui.runtime.demo.ETSGLOBAL::main", - "run:unmemoized": "npm run unmemoize && npm run ts-like:compile && ../../../incremental/tools/panda/node_modules/@panda/sdk/linux_host_tools/bin/ark --load-runtimes=ets --boot-panda-files=../../../incremental/tools/panda/node_modules/@panda/sdk/ets/etsstdlib.abc:../../../incremental/runtime/build/incremental.abc ./build/stub.abc @koalaui.runtime.demo.ETSGLOBAL::main", - "disasm": "../../../incremental/tools/panda/node_modules/@panda/sdk/linux_host_tools/bin/ark_disasm build/stub.abc build/stub.disasm" + "run": "../../../incremental/tools/panda/arkts/ark ./build/demo.abc --ark-boot-files ../../../incremental/runtime/build/incremental.abc:build/stub.abc --ark-entry-point @koalaui.runtime.demo.ETSGLOBAL::main", + "run:unmemoized": "npm run unmemoize && npm run ts-like:compile && ../../../incremental/tools/panda/arkts/ark ./build/demo.abc --ark-boot-files ../../../incremental/runtime/build/incremental.abc --ark-entry-point @koalaui.runtime.demo.ETSGLOBAL::main", + "disasm": "../../../incremental/tools/panda/arkts/arkdisasm build/stub.abc build/stub.disasm" }, "devDependencies": { "@koalaui/ets-tsc": "4.9.5-r4", diff --git a/arkoala-arkts/memo-plugin/demo/tsconfig-unmemoize.json b/arkoala-arkts/memo-plugin/demo/tsconfig-unmemoize.json index afa892abb3b8dfe26d92c70e60440da23166f68a..c76ebb21f5a4b95cd04ae271d7a507603e0b020f 100644 --- a/arkoala-arkts/memo-plugin/demo/tsconfig-unmemoize.json +++ b/arkoala-arkts/memo-plugin/demo/tsconfig-unmemoize.json @@ -9,7 +9,7 @@ "transform": "@koalaui/compiler-plugin/build/lib/src/koala-transformer.js", "trace": false, "only_unmemoize": true, - "unmemoizeDir": "build/unmemoized" + "unmemoizeDir": "build/unmemoized", } ] }, diff --git a/arkoala-arkts/memo-plugin/package.json b/arkoala-arkts/memo-plugin/package.json index 83de11ca6bc80b882a51ae4cf7f3b9770835d74c..875b28ee72166ec3abec62486f26b8decf19fbcc 100644 --- a/arkoala-arkts/memo-plugin/package.json +++ b/arkoala-arkts/memo-plugin/package.json @@ -4,7 +4,7 @@ "scripts": { "compile": "tsc -b .", "compile:libarkts": "npm run compile --prefix ../libarkts", - "demo:run": "npm run clean --prefix demo && npm run compile && npm run ts-like:compile --prefix demo && npm run arkts-like:compile:capi --prefix demo && npm run run --prefix demo", + "demo:run": "npm run compile && npm run ts-like:compile --prefix demo && npm run arkts-like:compile:capi --prefix demo && npm run run --prefix demo", "demo:disasm": "npm run disasm --prefix demo" } } \ No newline at end of file diff --git a/arkoala-arkts/memo-plugin/runtime-api/states/State.sts b/arkoala-arkts/memo-plugin/runtime-api/states/State.sts index 361be38504d6237800663b04478815d01d446e0d..afd523fe6e250ef3e53240380e8ba5588d54dda4 100644 --- a/arkoala-arkts/memo-plugin/runtime-api/states/State.sts +++ b/arkoala-arkts/memo-plugin/runtime-api/states/State.sts @@ -7,7 +7,13 @@ export interface Disposable { disposed: boolean dispose(): void } +export interface InternalScope { + readonly unchanged: boolean + readonly cached: Value + recache(newValue?: Value): Value +} export declare interface ComputableState extends Disposable, State { } export declare interface StateContext { computableState(compute: (context: StateContext) => Value, cleanup?: (context: StateContext, value: Value | undefined) => void): ComputableState + scope(id: int): InternalScope } diff --git a/arkoala-arkts/memo-plugin/src/memo-detector.ts b/arkoala-arkts/memo-plugin/src/function-transformer.ts similarity index 40% rename from arkoala-arkts/memo-plugin/src/memo-detector.ts rename to arkoala-arkts/memo-plugin/src/function-transformer.ts index db447f07c7899ab623c433fff44a9ab8148193d8..0c86fe39ae503404e5e28b4189b89e677cf89374 100644 --- a/arkoala-arkts/memo-plugin/src/memo-detector.ts +++ b/arkoala-arkts/memo-plugin/src/function-transformer.ts @@ -15,29 +15,105 @@ import * as arkts from "@koalaui/libarkts" import { AbstractVisitor } from "./AbstractVisitor" -import { createContextParameter, createIdParameter, RuntimeNames } from "./utils" +import { createContextParameter, createIdParameter, createContextArgument, createIdArgument, RuntimeNames, PositionalIdTracker } from "./utils" -export function hasMemoAnnotation(node: arkts.ScriptFunction) { +function hasMemoAnnotation(node: arkts.ScriptFunction) { return arkts.getAnnotations(node).some((it) => arkts.isIdentifier(it.expr) && it.expr.name === RuntimeNames.ANNOTATION ) } -export function createHiddenParameters(withType: boolean): arkts.ETSParameterExpression[] { - return [createContextParameter(withType), createIdParameter(withType)] +function createHiddenParameters(): arkts.ETSParameterExpression[] { + return [createContextParameter(), createIdParameter()] } -export class MemoDetector extends AbstractVisitor { +function createHiddenArguments(hash: arkts.NumberLiteral | arkts.StringLiteral): arkts.AstNode[] { + return [createContextArgument(), createIdArgument(hash)] +} + +function updateFunctionBody(node: arkts.BlockStatement | undefined, hash: arkts.NumberLiteral | arkts.StringLiteral): arkts.BlockStatement | undefined { + if (node === undefined) + return node + const scopeDeclaraion = arkts.factory.createVariableDeclaration( + 0, + arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_CONST, + [ + arkts.factory.createVariableDeclarator( + arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_CONST, + arkts.factory.createIdentifier(RuntimeNames.SCOPE), + arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + arkts.factory.createIdentifier(RuntimeNames.CONTEXT), + arkts.factory.createIdentifier(RuntimeNames.INTERNAL_SCOPE), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + arkts.factory.createTypeParameterDeclaration( + [arkts.factory.createIdentifier("")] + ), + [ + createIdArgument(hash) + ] + ) + ) + ] + ) + const unchangedCheck = arkts.factory.createIfStatement( + arkts.factory.createMemberExpression( + arkts.factory.createIdentifier(RuntimeNames.SCOPE), + arkts.factory.createIdentifier(RuntimeNames.INTERNAL_VALUE_OK), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_NONE, + false, + false + ), + arkts.factory.createReturnStatement( + arkts.factory.createMemberExpression( + arkts.factory.createIdentifier(RuntimeNames.SCOPE), + arkts.factory.createIdentifier(RuntimeNames.INTERNAL_VALUE), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_NONE, + false, + false + ) + ) + ) + const recache = arkts.factory.createReturnStatement( + arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + arkts.factory.createIdentifier(RuntimeNames.SCOPE), + arkts.factory.createIdentifier(RuntimeNames.INTERNAL_VALUE_NEW), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + [] + ) + ) + return arkts.factory.updateBlock( + node, + [ + scopeDeclaraion, + unchangedCheck, + ...node.statements, + recache + ] + ) +} + +export class FunctionTransformer extends AbstractVisitor { + constructor(private positionalIdTracker: PositionalIdTracker) { + super() + } + visitor(beforeChildren: arkts.AstNode): arkts.AstNode { // TODO: Remove (currently annotations are lost on visitor) const methodDefinitionHasMemoAnnotation = beforeChildren instanceof arkts.MethodDefinition && hasMemoAnnotation(beforeChildren.scriptFunction) const node = this.visitEachChild(beforeChildren) - console.log(" ".repeat(this.indentation) + node.constructor.name) if (node instanceof arkts.MethodDefinition) { if (methodDefinitionHasMemoAnnotation) { - console.log("MEMO SCRIPT FUNCTION") // TODO: fix const updatedNode = arkts.factory.updateMethodDefinition( node, @@ -46,19 +122,18 @@ export class MemoDetector extends AbstractVisitor { arkts.factory.createFunctionExpression( arkts.factory.updateScriptFunction( node.scriptFunction, - node.scriptFunction.body, + updateFunctionBody(node.scriptFunction.body, this.positionalIdTracker.id(node.name.name)), undefined, node.scriptFunction.scriptFunctionFlags, node.scriptFunction.modifiers, false, node.scriptFunction.ident, - [...createHiddenParameters(true), ...node.scriptFunction.parameters] + [...createHiddenParameters(), ...node.scriptFunction.parameters] ), ), node.modifiers, false ) - console.log("END") return updatedNode } } @@ -70,7 +145,7 @@ export class MemoDetector extends AbstractVisitor { node, node.expression, undefined, - [...createHiddenParameters(false), ...node.arguments] + [...createHiddenArguments(this.positionalIdTracker.id(decl.name.name)), ...node.arguments] ) } } diff --git a/arkoala-arkts/memo-plugin/src/memo-transformer.ts b/arkoala-arkts/memo-plugin/src/memo-transformer.ts index 1975ec2be9003f2eaceb87b1068b505c9bf0a22c..02c8d3a6a3f180f0db03d055e47bad7c981cd236 100644 --- a/arkoala-arkts/memo-plugin/src/memo-transformer.ts +++ b/arkoala-arkts/memo-plugin/src/memo-transformer.ts @@ -14,8 +14,9 @@ */ import * as arkts from "@koalaui/libarkts" -import { MemoDetector } from "./memo-detector" +import { FunctionTransformer } from "./function-transformer" import { ImportTransformer } from "./import-transformer" +import { PositionalIdTracker } from "./utils" export interface TransformerOptions { trace?: boolean, @@ -25,9 +26,10 @@ export default function memoTransformer( userPluginOptions?: TransformerOptions ) { return (node: arkts.EtsScript) => { - const memoDetector = new MemoDetector() - memoDetector.visitor(node) + const positionalIdTracker = new PositionalIdTracker(arkts.getFileName(), false) const importTransformer = new ImportTransformer() - return importTransformer.visitor(node) + const functionTransformer = new FunctionTransformer(positionalIdTracker) + importTransformer.visitor(node) + return functionTransformer.visitor(node) } } diff --git a/arkoala-arkts/memo-plugin/src/utils.ts b/arkoala-arkts/memo-plugin/src/utils.ts index 0e3c4b6065f8430afde49f2e41ae556d53bb8e91..e78b22ef47b3b2286bff559f305290df2d4dc7c0 100644 --- a/arkoala-arkts/memo-plugin/src/utils.ts +++ b/arkoala-arkts/memo-plugin/src/utils.ts @@ -13,6 +13,7 @@ * limitations under the License. */ +import { parseNumber, UniqueId } from "@koalaui/common" import * as arkts from "@koalaui/libarkts" export enum RuntimeNames { @@ -24,6 +25,11 @@ export enum RuntimeNames { CONTEXT_TYPE_DEFAULT_IMPORT = "@koalaui/runtime", ID = "__memo_id", ID_TYPE = "__memo_id_type", + INTERNAL_SCOPE = "scope", + INTERNAL_VALUE = "cached", + INTERNAL_VALUE_NEW = "recache", + INTERNAL_VALUE_OK = "unchanged", + SCOPE = "__memo_scope", } export function createContextTypeImportSpecifier(): arkts.ImportSpecifier { @@ -40,20 +46,77 @@ export function createIdTypeImportSpecifier(): arkts.ImportSpecifier { ) } -export function createContextParameter(withType: boolean): arkts.ETSParameterExpression { +export function createContextParameter(): arkts.ETSParameterExpression { return arkts.factory.createParameterDeclaration( arkts.factory.createIdentifier(RuntimeNames.CONTEXT, - withType ? arkts.factory.createIdentifier(RuntimeNames.CONTEXT_TYPE) : undefined + arkts.factory.createIdentifier(RuntimeNames.CONTEXT_TYPE) ), undefined ) } -export function createIdParameter(withType: boolean): arkts.ETSParameterExpression { +export function createIdParameter(): arkts.ETSParameterExpression { return arkts.factory.createParameterDeclaration( arkts.factory.createIdentifier(RuntimeNames.ID, - withType ? arkts.factory.createIdentifier(RuntimeNames.ID_TYPE) : undefined + arkts.factory.createIdentifier(RuntimeNames.ID_TYPE) ), undefined ) } + +export function createContextArgument(): arkts.AstNode { + return arkts.factory.createIdentifier(RuntimeNames.CONTEXT) +} + +export function createIdArgument(hash: arkts.NumberLiteral | arkts.StringLiteral): arkts.AstNode { + return arkts.factory.createBinaryExpression( + arkts.factory.createIdentifier(RuntimeNames.ID), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_PLUS, + hash + ) +} + +function baseName(path: string): string { + return path.replace(/^.*\/(.*)$/, "$1") +} + +export class PositionalIdTracker { + // Global for the whole program. + static callCount: number = 0 + + // Set `stable` to true if you want to have more predictable values. + // For example for tests. + // Don't use it in production! + constructor(public filename: string, public stableForTests: boolean = false) { + if (stableForTests) PositionalIdTracker.callCount = 0 + } + + sha1Id(callName: string, fileName: string): string { + const uniqId = new UniqueId() + uniqId.addString("memo call uniqid") + uniqId.addString(fileName) + uniqId.addString(callName) + uniqId.addI32(PositionalIdTracker.callCount++) + return uniqId.compute().substring(0, 7) + } + + stringId(callName: string, fileName: string): string { + return `${PositionalIdTracker.callCount++}_${callName}_id_DIRNAME/${fileName}` + } + + id(callName: string): arkts.NumberLiteral | arkts.StringLiteral { + + const fileName = this.stableForTests ? + baseName(this.filename) : + this.filename + + const positionId = (this.stableForTests) ? + this.stringId(callName, fileName) : + this.sha1Id(callName, fileName) + + + return this.stableForTests + ? arkts.factory.createStringLiteral(positionId) + : arkts.factory.createNumericLiteral(parseInt(positionId, 16)) + } +} diff --git a/arkoala-arkts/memo-plugin/tsconfig.json b/arkoala-arkts/memo-plugin/tsconfig.json index ef0efa64d715c743770f9853c3923a6cb73e92ea..a21696d9860d17862497d0477fe4e8fbdaf0a21e 100644 --- a/arkoala-arkts/memo-plugin/tsconfig.json +++ b/arkoala-arkts/memo-plugin/tsconfig.json @@ -4,7 +4,15 @@ "rootDir": ".", "baseUrl": ".", "outDir": "./build", - "module": "CommonJS" + "module": "CommonJS", + "paths": { + "@koalaui/compat": [ + "../../../incremental/compat/src/arkts" + ], + "@koalaui/common": [ + "../../../incremental/common/src" + ] + }, }, "include": [ "./src/**/*.ts",