From 3b1fdb46c30ea921e2debe324ecbd62e74b57e13 Mon Sep 17 00:00:00 2001 From: jjrv Date: Mon, 24 Oct 2016 16:25:17 +0300 Subject: [PATCH] Partially implement multiple inheritance on Emscripten target. --- src/em/BindClass.ts | 150 ++++++++++++++++++++++-- src/em/Binding.cc | 51 +++++--- src/em/Caller.ts | 66 +++++------ src/em/em-api.ts | 276 ++++++++++++-------------------------------- 4 files changed, 274 insertions(+), 269 deletions(-) diff --git a/src/em/BindClass.ts b/src/em/BindClass.ts index a6ea0bf..5a66027 100644 --- a/src/em/BindClass.ts +++ b/src/em/BindClass.ts @@ -10,8 +10,10 @@ import { import {_nbind as _globals} from './Globals'; import {_nbind as _type} from './BindingType'; import {_nbind as _value} from './ValueObj'; +import {_nbind as _caller} from './Caller'; import {_nbind as _resource} from './Resource'; import {_nbind as _gc} from './GC'; +import {SignatureType} from '../common'; import {StateFlags, TypeFlags, TypeSpecWithName, TypeSpecWithParam, PolicyTbl} from '../Type'; // Let decorators run eval in current scope to read function source code. @@ -25,11 +27,17 @@ export namespace _nbind { export namespace _nbind { + export var addMethod: typeof _globals.addMethod; + export var getType: typeof _globals.getType; + export var pushValue: typeof _value.pushValue; export var popValue: typeof _value.popValue; export var resources: typeof _resource.resources; + export var makeCaller: typeof _caller.makeCaller; + export var makeMethodCaller: typeof _caller.makeMethodCaller; + export var mark: typeof _gc.mark; /** Base class for wrapped instances of bound C++ classes. @@ -62,21 +70,17 @@ export namespace _nbind { interface WrapperClass { new(marker: {}, flags: number, ptr: number, shared?: number): Wrapper; + + [key: string]: any; } const ptrMarker = {}; - interface SuperClassType extends Wrapper {} - export function makeBound( policyTbl: PolicyTbl, bindClass: BindClass ) { - const SuperClass = bindClass.superList[0] ? - bindClass.superList[0].proto as { new(): Wrapper } : - Wrapper; - - class Bound extends SuperClass { + class Bound extends Wrapper { constructor(marker: {}, flags: number, ptr: number, shared?: number) { // super() never gets called here but TypeScript 1.8 requires it. if((false && super()) || !(this instanceof Bound)) { @@ -155,6 +159,19 @@ export namespace _nbind { return(Bound); } + export interface MethodSpec { + name: string; + title: string; + signatureType?: SignatureType; + boundID?: number; + policyTbl?: PolicyTbl; + typeList?: (number | string)[]; + ptr: number; + direct?: number; + num?: number; + flags?: TypeFlags; + } + // Base class for all bound C++ classes (not their instances), // also inheriting from a generic type definition. @@ -174,6 +191,116 @@ export namespace _nbind { return(Bound); } + addMethod(spec: MethodSpec) { + const overloadList = this.methodTbl[spec.name] || []; + + overloadList.push(spec); + + this.methodTbl[spec.name] = overloadList; + } + + registerMethods(src: BindClass, staticOnly: boolean) { + let setter: ((arg: any) => void) | undefined; + + for(let name of Object.keys(src.methodTbl)) { + const overloadList = src.methodTbl[name]; + + for(let spec of overloadList) { + let target: any; + let caller: any; + + target = this.proto.prototype; + + if(staticOnly && spec.signatureType != SignatureType.func) continue; + + switch(spec.signatureType) { + case SignatureType.func: + target = this.proto; + + // tslint:disable-next-line:no-switch-case-fall-through + case SignatureType.construct: + caller = makeCaller(spec); + addMethod(target, spec.name, caller, spec.typeList!.length - 1); + break; + + case SignatureType.setter: + setter = makeMethodCaller(spec) as (arg: any) => void; + break; + + case SignatureType.getter: + Object.defineProperty(target, spec.name, { + configurable: true, + enumerable: false, + get: makeMethodCaller(spec) as () => any, + set: setter + }); + break; + + case SignatureType.method: + caller = makeMethodCaller(spec); + addMethod(target, spec.name, caller, spec.typeList!.length - 1); + break; + + default: + break; + } + } + } + } + + registerSuperMethods( + src: BindClass, + firstSuper: number, + visitTbl: { [name: string]: boolean } + ) { + if(visitTbl[src.name]) return; + visitTbl[src.name] = true; + + this.registerMethods(src, firstSuper < 0); + + let superNum = 0; + let nextFirst: number; + + for(let superId of src.superIdList || []) { + const superClass = getType(superId) as BindClass; + + if(superNum++ < firstSuper || firstSuper < 0) { + nextFirst = -1; + } else { + nextFirst = 0; + } + + this.registerSuperMethods(superClass, nextFirst, visitTbl); + } + } + + finish() { + if(this.ready) return; + this.ready = true; + + let superClass: BindClass | undefined; + let firstSuper: BindClass | undefined; + + for(let superId of this.superIdList || []) { + superClass = getType(superId) as BindClass; + superClass.finish(); + + firstSuper = firstSuper || superClass; + } + + if(firstSuper) { + const Bound = this.proto; + const Proto = function(this: Wrapper) { + this.constructor = Bound; + } as any as { new(): Wrapper }; + + Proto.prototype = firstSuper.proto.prototype; + Bound.prototype = new Proto(); + } + + this.registerSuperMethods(this, 1, {}); + } + wireRead = (arg: number) => popValue(arg, this.ptrType); wireWrite = pushValue; @@ -191,10 +318,13 @@ export namespace _nbind { /** Number of super classes left to initialize. */ pendingSuperCount = 0; - superList: BindClass[] = []; - } + ready = false; - export const pendingChildTbl: { [id: number]: number[] } = {}; + superIdList: number[]; + methodTbl: { [name: string]: MethodSpec[] } = {}; + + static list: BindClass[] = []; + } export function popPointer(ptr: number, type: BindClassPtr) { return(ptr ? new type.proto(ptrMarker, type.flags, ptr) : null); diff --git a/src/em/Binding.cc b/src/em/Binding.cc index 97c3cd3..1b6fceb 100644 --- a/src/em/Binding.cc +++ b/src/em/Binding.cc @@ -12,15 +12,19 @@ using namespace nbind; extern "C" { extern void _nbind_register_pool(unsigned int pageSize, unsigned int *usedPtr, unsigned char *rootPtr, unsigned char **pagePtr); - extern void _nbind_register_primitive( TYPEID typeID, unsigned int size, unsigned char flag); - extern void _nbind_register_type( TYPEID typeID, const char *name); - extern void _nbind_register_class(const TYPEID *typeList, const char **policies, const TYPEID *superList, unsigned int superCount, const char *name, funcPtr destructor); - extern void _nbind_register_constructor(TYPEID classType, const char **policies, const TYPEID *types, unsigned int typeCount, funcPtr func, funcPtr ptrValue); - extern void _nbind_register_function( TYPEID classType, const char **policies, const TYPEID *types, unsigned int typeCount, funcPtr func, const char *name, - unsigned int num, unsigned int flags, funcPtr direct); - extern void _nbind_register_method( TYPEID classType, const char **policies, const TYPEID *types, unsigned int typeCount, funcPtr func, const char *name, - unsigned int num, unsigned int flags, unsigned int methodType + extern void _nbind_register_primitive(TYPEID typeID, unsigned int size, unsigned char flag); + extern void _nbind_register_type(TYPEID typeID, const char *name); + extern void _nbind_register_class(const TYPEID *typeList, + const char **policies, const TYPEID *superList, unsigned int superCount, + funcPtr destructor, + const char *name ); + extern void _nbind_register_function(TYPEID boundID, + const char **policies, const TYPEID *types, unsigned int typeCount, + funcPtr func, funcPtr direct, unsigned int signatureType, + const char *name, unsigned int num, unsigned int flags + ); + extern void _nbind_finish(); } unsigned int Pool::used = 0; @@ -173,8 +177,8 @@ static void initModule() { bindClass->getPolicies(), superIdList, bindClass->getSuperClassCount(), - bindClass->getName(), - bindClass->getDeleter() + bindClass->getDeleter(), + bindClass->getName() ); } @@ -189,10 +193,11 @@ static void initModule() { signature->getTypeList(), signature->getArity() + 1, signature->getCaller(), + func.getPtr(), + static_cast(signature->getType()), func.getName(), func.getNum(), - static_cast(func.getFlags()), - func.getPtr() + static_cast(func.getFlags()) ); } @@ -216,16 +221,17 @@ static void initModule() { case SignatureType :: getter: case SignatureType :: setter: - _nbind_register_method( + _nbind_register_function( id, signature->getPolicies(), signature->getTypeList(), signature->getArity() + 1, signature->getCaller(), + nullptr, + static_cast(signature->getType()), func.getName(), func.getNum(), - static_cast(func.getFlags()), - static_cast(signature->getType()) + static_cast(func.getFlags()) ); break; @@ -238,29 +244,36 @@ static void initModule() { signature->getTypeList(), signature->getArity() + 1, signature->getCaller(), + func.getPtr(), + static_cast(signature->getType()), func.getName(), func.getNum(), - static_cast(func.getFlags()), - func.getPtr() + static_cast(func.getFlags()) ); break; case SignatureType :: construct: - _nbind_register_constructor( + _nbind_register_function( id, signature->getPolicies(), signature->getTypeList(), signature->getArity() + 1, signature->getCaller(), - signature->getValueConstructor() + signature->getValueConstructor(), + static_cast(signature->getType()), + nullptr, 0, 0 ); break; } } } + + // Set up inheritance. + + _nbind_finish(); } extern "C" { diff --git a/src/em/Caller.ts b/src/em/Caller.ts index 3bb4a46..0c8cbea 100644 --- a/src/em/Caller.ts +++ b/src/em/Caller.ts @@ -262,32 +262,27 @@ export namespace _nbind { /** Dynamically create an invoker function for calling a C++ class method. */ - export function makeMethodCaller( - ptr: number, - num: number, - flags: TypeFlags, - name: string, - boundID: number, - idList: TypeIdList, - policyTbl: PolicyTbl | null - ) { - const argCount = idList.length - 1; + export function makeMethodCaller(spec: _class.MethodSpec) { + const argCount = spec.typeList!.length - 1; // The method invoker function adds two arguments to those of the method: // - Number of the method in a list of methods with identical signatures. // - Target object - idList.splice(1, 0, 'uint32_t', boundID); + const typeIdList = spec.typeList!.slice(0); + typeIdList.splice(1, 0, 'uint32_t', spec.boundID!); - const typeList = getTypes(idList, name); + const typeList = getTypes(typeIdList, spec.title); const returnType = typeList[0]; const argTypeList = typeList.slice(3); - const needsWireRead = returnType.needsWireRead(policyTbl); - const needsWireWrite = anyNeedsWireWrite(argTypeList, policyTbl); + const needsWireRead = returnType.needsWireRead(spec.policyTbl!); + const needsWireWrite = anyNeedsWireWrite(argTypeList, spec.policyTbl!); + const ptr = spec.ptr; + const num = spec.num!; - const dynCall = getDynCall(typeList, name); + const dynCall = getDynCall(typeList, spec.title); - const mask = ~flags & TypeFlags.isConst; + const mask = ~spec.flags & TypeFlags.isConst; function err() { throw(new Error('Calling a non-const method on a const object')); @@ -321,7 +316,7 @@ export namespace _nbind { dynCall, ptr, num, - policyTbl, + spec.policyTbl!, needsWireWrite, 'ptr,num,this.__nbindPtr', returnType, @@ -333,28 +328,22 @@ export namespace _nbind { /** Dynamically create an invoker function for calling a C++ function. */ - export function makeCaller( - ptr: number | null, - num: number, - flags: TypeFlags, - name: string, - direct: number, - idList: TypeIdList, - policyTbl: PolicyTbl - ) { - const argCount = idList.length - 1; + export function makeCaller(spec: _class.MethodSpec) { + const argCount = spec.typeList!.length - 1; - const typeList = getTypes(idList, name); + let typeList = getTypes(spec.typeList!, spec.title); const returnType = typeList[0]; const argTypeList = typeList.slice(1); - const needsWireRead = returnType.needsWireRead(policyTbl); - const needsWireWrite = anyNeedsWireWrite(argTypeList, policyTbl); + const needsWireRead = returnType.needsWireRead(spec.policyTbl!); + const needsWireWrite = anyNeedsWireWrite(argTypeList, spec.policyTbl!); + const direct = spec.direct!; + let ptr = spec.ptr; - if(direct && !needsWireRead && !needsWireWrite) { + if(spec.direct && !needsWireRead && !needsWireWrite) { // If there are only a few arguments not requiring type conversion, // build a simple invoker function without using eval. - const dynCall = getDynCall(typeList, name); + const dynCall = getDynCall(typeList, spec.title); switch(argCount) { case 0: return(() => @@ -372,7 +361,7 @@ export namespace _nbind { } // Input and output types don't need conversion so omit dispatcher. - ptr = null; + ptr = 0; } let prefix: string; @@ -381,7 +370,10 @@ export namespace _nbind { // The function invoker adds an argument to those of the function: // - Number of the function in a list of functions with identical signatures. - idList.splice(1, 0, 'uint32_t'); + const typeIdList = spec.typeList!.slice(0); + typeIdList.splice(1, 0, 'uint32_t'); + + typeList = getTypes(typeIdList, spec.title); prefix = 'ptr,num'; } else { ptr = direct; @@ -389,13 +381,13 @@ export namespace _nbind { } // Type ID list was changed. - const dynCall = getDynCall(getTypes(idList, name), name); + const dynCall = getDynCall(typeList, spec.title); return(buildCallerFunction( dynCall, ptr, - num, - policyTbl, + spec.num!, + spec.policyTbl!, needsWireWrite, prefix, returnType, diff --git a/src/em/em-api.ts b/src/em/em-api.ts index 664539f..f83201f 100644 --- a/src/em/em-api.ts +++ b/src/em/em-api.ts @@ -37,7 +37,6 @@ export namespace _nbind { export var Pool: typeof _globals.Pool; export var bigEndian: typeof _globals.bigEndian; - export var addMethod: typeof _globals.addMethod; export var readTypeIdList: typeof _globals.readTypeIdList; export var readAsciiString: typeof _globals.readAsciiString; export var readPolicyList: typeof _globals.readPolicyList; @@ -57,7 +56,6 @@ export namespace _nbind { export var BindClass: typeof _class.BindClass; export var BindClassPtr: typeof _class.BindClassPtr; export var SharedClassPtr: typeof _class.BindClassPtr; - export var pendingChildTbl: typeof _class.pendingChildTbl; export var ExternalType: typeof _external.ExternalType; @@ -70,7 +68,6 @@ export namespace _nbind { export var ArrayType: typeof _std.ArrayType; export var StringType: typeof _std.StringType; - export var makeCaller: typeof _caller.makeCaller; export var makeMethodCaller: typeof _caller.makeMethodCaller; export var BufferType: typeof _buffer.BufferType; @@ -125,6 +122,15 @@ class nbind { // tslint:disable-line:class-name }; Module['toggleLightGC'] = _nbind.toggleLightGC; + + const globalScope = _nbind.makeType(_nbind.constructType, { + flags: TypeFlags.isClass, + id: 0, + name: '' + }) as _class.BindClass; + + globalScope.proto = Module as any; + _nbind.BindClass.list.push(globalScope); } @dep('_nbind', '_typeModule') @@ -157,13 +163,12 @@ class nbind { // tslint:disable-line:class-name policyListPtr: number, superListPtr: number, superCount: number, - namePtr: number, - destructorPtr: number + destructorPtr: number, + namePtr: number ) { const name = _nbind.readAsciiString(namePtr); const policyTbl = _nbind.readPolicyList(policyListPtr); const idList = HEAPU32.subarray(idListPtr / 4, idListPtr / 4 + 2); - const superIdList = HEAPU32.subarray(superListPtr / 4, superListPtr / 4 + superCount); const spec: TypeSpecWithName = { flags: TypeFlags.isClass | (policyTbl['Value'] ? TypeFlags.isValueObject : 0), @@ -180,228 +185,93 @@ class nbind { // tslint:disable-line:class-name _nbind.queryType ) as _class.BindClassPtr; - bindClass.destroy = _nbind.makeMethodCaller( - destructorPtr, - 0, // num - TypeFlags.none, - bindClass.name + '.free', - spec.id, - ['void', 'uint32_t', 'uint32_t'], - null - ) as (shared: number, flags: number) => void; - - const childTbl = _nbind.pendingChildTbl; - - // Check if superclasses are defined. - Array.prototype.forEach.call(superIdList, (superId: number) => { - const superClass = _nbind.getType(superId) as _class.BindClass; - - if(superClass) { - bindClass.superList.push(superClass); - } else { - // If a superclass is undefined, attach the current class ID - // to its ID in pendingChildTbl. Current class constructor - // will be created after its superclasses are defined. - const childList = childTbl[superId] || []; - childList.push(idList[0]); - childTbl[superId] = childList; - - ++bindClass.pendingSuperCount; - } - }); - - /** When all superclasses are defined, recursively create a JavaScript - * constructor for the class and any remaining child classes. */ - - // tslint:disable-next-line:no-shadowed-variable - function register(bindClass: _class.BindClass, id: number) { - if(!bindClass.pendingSuperCount) { - // Export the class. - Module[bindClass.name] = bindClass.makeBound(policyTbl); - - // Try to export child classes. - for(let childId of childTbl[id] || []) { - const childClass = _nbind.getType(childId) as _class.BindClass; - - childClass.superList.push(bindClass); - - --childClass.pendingSuperCount; - register(childClass, childId); - } - } + bindClass.destroy = _nbind.makeMethodCaller({ + boundID: spec.id, + flags: TypeFlags.none, + name: 'destroy', + num: 0, + ptr: destructorPtr, + title: bindClass.name + '.free', + typeList: ['void', 'uint32_t', 'uint32_t'] + }) as (shared: number, flags: number) => void; + + if(superCount) { + bindClass.superIdList = Array.prototype.slice.call( + HEAPU32.subarray(superListPtr / 4, superListPtr / 4 + superCount) + ); } - register(bindClass, idList[0]); - } + Module[bindClass.name] = bindClass.makeBound(policyTbl); - @dep('_nbind') - static _nbind_register_constructor( - typeID: number, - policyListPtr: number, - typeListPtr: number, - typeCount: number, - ptr: number, - ptrValue: number - ) { - const policyTbl = _nbind.readPolicyList(policyListPtr); - const typeList = _nbind.readTypeIdList(typeListPtr, typeCount); - const bindClass = _nbind.getType(typeID) as _class.BindClass; - const proto = bindClass.proto.prototype; - - // The constructor returns a pointer to the new object. - // It fits in uint32_t. - - typeList[0] = 'uint32_t'; - - _nbind.addMethod( - proto, - '__nbindConstructor', - _nbind.makeCaller( - null, - 0, // num - TypeFlags.none, - bindClass.name + ' constructor', - ptr, - typeList, - policyTbl - ), - typeCount - 1 - ); - - // First argument is a pointer to the C++ object to construct in place. - // It fits in uint32_t. - - typeList.splice(0, 1, 'void', 'uint32_t'); - - _nbind.addMethod( - proto, - '__nbindValueConstructor', - _nbind.makeCaller( - null, - 0, // num - TypeFlags.none, - bindClass.name + ' value constructor', - ptrValue, - typeList, - policyTbl - ), - typeCount - ); + _nbind.BindClass.list.push(bindClass); } - @dep('_nbind') + @dep('_nbind', '_removeAccessorPrefix') static _nbind_register_function( - typeID: number, + boundID: number, policyListPtr: number, typeListPtr: number, typeCount: number, ptr: number, + direct: number, + signatureType: SignatureType, namePtr: number, num: number, - flags: TypeFlags, - direct: number + flags: TypeFlags ) { - const name = _nbind.readAsciiString(namePtr); + const bindClass = _nbind.getType(boundID) as _class.BindClass; const policyTbl = _nbind.readPolicyList(policyListPtr); const typeList = _nbind.readTypeIdList(typeListPtr, typeCount); - let bindClass: _class.BindClass | undefined; - let target: any; - if(typeID) { - bindClass = _nbind.getType(typeID) as _class.BindClass; - target = bindClass.proto; + let specList: _class.MethodSpec[]; + + if(signatureType == SignatureType.construct) { + specList = [{ + direct: ptr, + name: '__nbindConstructor', + ptr: 0, + title: bindClass.name + ' constructor', + typeList: ['uint32_t'].concat(typeList.slice(1)) + }, { + direct: direct, + name: '__nbindValueConstructor', + ptr: 0, + title: bindClass.name + ' value constructor', + typeList: ['void', 'uint32_t'].concat(typeList.slice(1)) + }]; } else { - target = Module; - } - - _nbind.addMethod( - target, - name, - _nbind.makeCaller( - ptr, - num, - flags, - (bindClass ? bindClass.name + '.' : '') + name, - direct, - typeList, - policyTbl - ), - typeCount - 1 - ); - } + let name = _nbind.readAsciiString(namePtr); + const title = (bindClass.name && bindClass.name + '.') + name; - @dep('_nbind', '_removeAccessorPrefix') - static _nbind_register_method( - typeID: number, - policyListPtr: number, - typeListPtr: number, - typeCount: number, - ptr: number, - namePtr: number, - num: number, - flags: TypeFlags, - signatureType: SignatureType - ) { - let name = _nbind.readAsciiString(namePtr); - const policyTbl = _nbind.readPolicyList(policyListPtr); - const typeList = _nbind.readTypeIdList(typeListPtr, typeCount); - const bindClass = _nbind.getType(typeID) as _class.BindClass; - const proto = bindClass.proto.prototype; - - if(signatureType == SignatureType.method) { - _nbind.addMethod( - proto, - name, - _nbind.makeMethodCaller( - ptr, - num, - flags, - bindClass.name + '.' + name, - typeID, - typeList, - policyTbl - ), - typeCount - 1 - ); + if(signatureType == SignatureType.getter || signatureType == SignatureType.setter) { + name = _removeAccessorPrefix(name); + } - return; + specList = [{ + boundID: boundID, + direct: direct, + name: name, + ptr: ptr, + title: title, + typeList: typeList + }]; } - name = _removeAccessorPrefix(name); - - if(signatureType == SignatureType.setter) { - - // A setter is always followed by a getter, so we can just - // temporarily store an invoker in the property. - // The getter definition then binds it properly. + for(let spec of specList) { + spec.signatureType = signatureType; + spec.policyTbl = policyTbl; + spec.num = num; + spec.flags = flags; - proto[name] = _nbind.makeMethodCaller( - ptr, - num, - flags, - bindClass.name + '.' + 'set ' + name, - typeID, - typeList, - policyTbl - ); - } else { - Object.defineProperty(proto, name, { - configurable: true, - enumerable: true, - get: _nbind.makeMethodCaller( - ptr, - num, - flags, - bindClass.name + '.' + 'get ' + name, - typeID, - typeList, - policyTbl - ) as () => any, - set: proto[name] - }); + bindClass.addMethod(spec); } } + @dep('_nbind') + static _nbind_finish() { + for(let bindClass of _nbind.BindClass.list) bindClass.finish(); + } + @dep('_nbind') static nbind_debug() { debugger; }