From 1b174641489b1dd5948c85c42123dc31fb0f5d17 Mon Sep 17 00:00:00 2001 From: CountBleck Date: Thu, 1 Dec 2022 13:55:27 -0800 Subject: [PATCH 1/2] Revert "Remove __asonNameof for shorter compilation times and smaller binaries" This reverts commit 38f8aa77ee02f304cdf6f9ce2c0edf8b1c6946dc. --- packages/assembly/index.ts | 11 +- .../transform/src/createAsonNameofMethod.ts | 145 ++++++++++++++++++ packages/transform/src/index.ts | 11 +- 3 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 packages/transform/src/createAsonNameofMethod.ts diff --git a/packages/assembly/index.ts b/packages/assembly/index.ts index 9e39480..7aab340 100644 --- a/packages/assembly/index.ts +++ b/packages/assembly/index.ts @@ -102,7 +102,7 @@ export namespace ASON { // @ts-ignore inside isDefined() if (isDefined(ASON_TRACE)) { // @ts-ignore interface added at runtime - // trace(`serializing ${(value as InternalTransformInterface).__asonNameof()}`); + trace(`serializing ${(value as InternalTransformInterface).__asonNameof()}`); } } } @@ -148,7 +148,7 @@ export namespace ASON { if (isManaged(value) && !isFunction(value)) { if (isDefined(ASON_TRACE)) { // @ts-ignore interface added at runtime - // trace(`putting ${(value as InternalTransformInterface).__asonNameof()}`); + trace(`putting ${(value as InternalTransformInterface).__asonNameof()}`); } } } @@ -878,7 +878,7 @@ export namespace ASON { if (!success) { // @ts-ignore inside isDefined() if (isDefined(ASON_TRACE)) { - assert(false, `Deserialize: expected ${nameof()} (${idof()}), received ${getObjectType(entry0)}`); + assert(false, `Deserialize: expected ${nameof()}, received ${changetype(entry0).__asonNameof()}`); } else { assert(false, `Deserialize: received invalid type`); } @@ -1175,8 +1175,13 @@ export namespace ASON { } interface InternalTransformInterface { + __asonNameof(): string __asonPut(ser: U, entryId: u32): void __asonAlignofValueofParameter(): usize __asonLength(): i32 } + + function __asonNameofID(id: u32): string { + return unreachable(); + } } diff --git a/packages/transform/src/createAsonNameofMethod.ts b/packages/transform/src/createAsonNameofMethod.ts new file mode 100644 index 0000000..989e692 --- /dev/null +++ b/packages/transform/src/createAsonNameofMethod.ts @@ -0,0 +1,145 @@ +import {CommonFlags, ClassDeclaration, Node} from "assemblyscript/dist/assemblyscript.js"; +import binaryen from "assemblyscript/lib/binaryen.js"; + +export function createAsonNameofMethod({range, isGeneric, members}: ClassDeclaration): void { + members.push( + Node.createMethodDeclaration( + Node.createIdentifierExpression("__asonNameof", range), + null, + ( + CommonFlags.Public | + CommonFlags.Instance | + (isGeneric ? CommonFlags.GenericContext : 0) + ), + null, + Node.createFunctionType( + [], + Node.createNamedType( + Node.createSimpleTypeName("string", range), + null, + false, + range + ), + null, + false, + range + ), + Node.createBlockStatement( + [ + Node.createReturnStatement( + Node.createCallExpression( + Node.createIdentifierExpression("nameof", range), + [ + Node.createNamedType( + Node.createSimpleTypeName("this", range), + null, + false, + range + ) + ], + [], + range + ), + range + ) + ], + range + ), + range + ) + ); +} + +export function createAsonNameofIDFunction(module: binaryen.Module): void { + const {ExpressionIds} = binaryen; + const internalFunctionName = "~asonInternalNameof"; + + let nameofIDRef = null; + let overrideRef = null; + let overrideBodyRef = null; + const numFunctions = module.getNumFunctions(); + for (let i = 0; i < numFunctions; i++) { + const ref = module.getFunctionByIndex(i); + const {name, body} = binaryen.getFunctionInfo(ref); + if (name.endsWith("ASON.__asonNameofID")) { + nameofIDRef = ref; + } else if (name.endsWith("ASON.InternalTransformInterface#__asonNameof@override")) { + overrideRef = ref; + overrideBodyRef = body; + } + } + + if (!nameofIDRef || !overrideRef || !overrideBodyRef) return; + + // Peel the block onion until we find the local.set, which we must remove. + let localSet: binaryen.ExpressionRef; + let currentBodyRef = overrideBodyRef; + for (;;) { + const info = binaryen.getExpressionInfo(currentBodyRef) as binaryen.BlockInfo; + const [child] = info.children; + const childId = binaryen.getExpressionId(child); + if (childId !== ExpressionIds.Block) { + if (childId !== ExpressionIds.LocalSet) { + throw new TypeError("unexpected expression ID: " + childId); + } + + localSet = child; + break; + } + currentBodyRef = child; + } + + // Surgically remove the local.set, set local $1 to the value of local $0, + // and set local $0 to (i32.const 0) in order to match our new function's + // signature. AssemblyScript uses local $1 for the ID and local $0 for the + // actual object. + // @ts-ignore binaryen.js is incomplete + binaryen.Block.setChildAt( + currentBodyRef, + 0, + module.block( + null, + [ + module.local.set(1, module.local.get(0, binaryen.i32)), + module.local.set(0, module.i32.const(0)) + ] + ) + ); + + // Extract the local.set's child. + // @ts-ignore binaryen.js is incomplete + const localSetChild = binaryen.LocalSet.getValue(localSet); + + // Modify the override function so it is now a stub for our internal + // function, plus the code that reads the object's ID. + // @ts-ignore binaryen.js is incomplete + binaryen.Function.setBody( + overrideRef, + module.call( + internalFunctionName, + [localSetChild], + binaryen.i32 + ) + ); + + // Move the override function's core logic into our internal function. + module.addFunction( + internalFunctionName, + binaryen.createType([binaryen.i32]), + binaryen.i32, + [binaryen.i32], + overrideBodyRef + ) + + // Replace the placeholder body in our own __asonNameofID function with a + // stub for our internal function. + // @ts-ignore binaryen.js is incomplete + binaryen.Function.setBody( + nameofIDRef, + module.call( + internalFunctionName, + [module.local.get(0, binaryen.i32)], + binaryen.i32 + ) + ) +} \ No newline at end of file diff --git a/packages/transform/src/index.ts b/packages/transform/src/index.ts index ac464e3..a810443 100644 --- a/packages/transform/src/index.ts +++ b/packages/transform/src/index.ts @@ -15,10 +15,13 @@ import { import { Transform, } from "assemblyscript/dist/transform.js"; +import binaryen from "assemblyscript/lib/binaryen.js"; + import { createAsonInstanceOfMethod } from "./createAsonInstanceOfMethod.js"; import { createAsonPutMethod } from "./createAsonPutMethod.js"; +import { createAsonNameofIDFunction, createAsonNameofMethod } from "./createAsonNameofMethod.js"; import { createAsonAlignofValueofMethod } from "./createAsonAlignofValueofMethod.js"; import { createAsonLengthMethod } from "./createAsonLengthMethod.js"; @@ -53,7 +56,7 @@ export default class ASONTransform extends Transform { 1 ); - const methodNames = ["__asonPut", "__asonAlignofValueofParameter", "__asonLength"]; + const methodNames = ["__asonNameof", "__asonPut", "__asonAlignofValueofParameter", "__asonLength"]; const baseMethods = new Map(); for (const name of methodNames) { baseMethods.set(name, internalInterface.instanceMembers!.get(name)! as FunctionPrototype); @@ -94,6 +97,10 @@ export default class ASONTransform extends Transform { ]; resolvedClasses.forEach(clazz => clazz.addInterface(resolvedInternalInterface)); } + + afterCompile(module: unknown): void { + createAsonNameofIDFunction(module as binaryen.Module); + } }; function traverseStatements(statements: Statement[]): void { @@ -105,6 +112,7 @@ function traverseStatements(statements: Statement[]): void { const classDeclaration = statement; createAsonPutMethod(classDeclaration); createAsonInstanceOfMethod(classDeclaration); + createAsonNameofMethod(classDeclaration); createAsonAlignofValueofMethod(classDeclaration); createAsonLengthMethod(classDeclaration); } else if (statement.kind === NodeKind.InterfaceDeclaration) { @@ -113,6 +121,7 @@ function traverseStatements(statements: Statement[]): void { // Don't declare methods on the internal interface if (interfaceDeclaration.name.text === INTERNAL_TRANSFORM_NAME) continue; + createAsonNameofMethod(interfaceDeclaration); createAsonAlignofValueofMethod(interfaceDeclaration); createAsonLengthMethod(interfaceDeclaration); } else if (statement.kind === NodeKind.NamespaceDeclaration) { From cc5005938865a58c3d1c1a77f4b48f69ba8dfc66 Mon Sep 17 00:00:00 2001 From: CountBleck Date: Fri, 2 Dec 2022 12:21:27 -0800 Subject: [PATCH 2/2] Ensure __asonNameof is compiled when __asonNameofID is used Otherwise, that nasty unreachable() will be hit at runtime. --- packages/assembly/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/assembly/index.ts b/packages/assembly/index.ts index 7aab340..65a22c2 100644 --- a/packages/assembly/index.ts +++ b/packages/assembly/index.ts @@ -1182,6 +1182,7 @@ export namespace ASON { } function __asonNameofID(id: u32): string { - return unreachable(); + // Ensure __asonNameof() is compiled if __asonNameofID is used. + return changetype