Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()}`);
}
}
}
Expand Down Expand Up @@ -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()}`);
}
}
}
Expand Down Expand Up @@ -878,7 +878,7 @@ export namespace ASON {
if (!success) {
// @ts-ignore inside isDefined()
if (isDefined(ASON_TRACE)) {
assert(false, `Deserialize: expected ${nameof<T>()} (${idof<T>()}), received ${getObjectType(entry0)}`);
assert(false, `Deserialize: expected ${nameof<T>()}, received ${changetype<InternalTransformInterface>(entry0).__asonNameof()}`);
} else {
assert(false, `Deserialize: received invalid type`);
}
Expand Down Expand Up @@ -1175,8 +1175,14 @@ export namespace ASON {
}

interface InternalTransformInterface {
__asonNameof(): string
__asonPut<U>(ser: U, entryId: u32): void
__asonAlignofValueofParameter(): usize
__asonLength(): i32
}

function __asonNameofID(id: u32): string {
// Ensure __asonNameof() is compiled if __asonNameofID is used.
return changetype<InternalTransformInterface(0).__asonNameof();
}
}
145 changes: 145 additions & 0 deletions packages/transform/src/createAsonNameofMethod.ts
Original file line number Diff line number Diff line change
@@ -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
)
)
}
11 changes: 10 additions & 1 deletion packages/transform/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -105,6 +112,7 @@ function traverseStatements(statements: Statement[]): void {
const classDeclaration = <ClassDeclaration>statement;
createAsonPutMethod(classDeclaration);
createAsonInstanceOfMethod(classDeclaration);
createAsonNameofMethod(classDeclaration);
createAsonAlignofValueofMethod(classDeclaration);
createAsonLengthMethod(classDeclaration);
} else if (statement.kind === NodeKind.InterfaceDeclaration) {
Expand All @@ -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) {
Expand Down