Skip to content

Commit

Permalink
Implement automatic upcasting on Emscripten target.
Browse files Browse the repository at this point in the history
  • Loading branch information
jjrv committed Oct 26, 2016
1 parent f1959ef commit a7dcc68
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 36 deletions.
96 changes: 69 additions & 27 deletions src/em/BindClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ setEvil((code: string) => eval(code));

export namespace _nbind {
export var BindType = _type.BindType;
export var Wrapper = _wrapper.Wrapper;
}

export namespace _nbind {
Expand Down Expand Up @@ -45,6 +46,8 @@ export namespace _nbind {

export const ptrMarker = {};

export var callUpcast: (upcast: number, ptr: number) => number;

export interface MethodSpec {
name: string;
title: string;
Expand All @@ -65,7 +68,10 @@ export namespace _nbind {
constructor(spec: TypeSpecWithName) {
super(spec);

if(spec.paramList) this.proto = (spec.paramList[0] as BindClass).proto;
if(spec.paramList) {
this.classType = (spec.paramList[0] as BindClass).classType;
this.proto = this.classType.proto;
} else this.classType = this;
}

makeBound(policyTbl: PolicyTbl) {
Expand Down Expand Up @@ -110,20 +116,20 @@ export namespace _nbind {
break;

case SignatureType.setter:
setter = makeMethodCaller(spec) as (arg: any) => void;
setter = makeMethodCaller(src.ptrType, spec) as (arg: any) => void;
break;

case SignatureType.getter:
Object.defineProperty(target, spec.name, {
configurable: true,
enumerable: false,
get: makeMethodCaller(spec) as () => any,
get: makeMethodCaller(src.ptrType, spec) as () => any,
set: setter
});
break;

case SignatureType.method:
caller = makeMethodCaller(spec);
caller = makeMethodCaller(src.ptrType, spec);
addMethod(target, spec.name, caller, spec.typeList!.length - 1);
break;

Expand All @@ -142,8 +148,6 @@ export namespace _nbind {
if(visitTbl[src.name]) return;
visitTbl[src.name] = true;

this.registerMethods(src, firstSuper < 0);

let superNum = 0;
let nextFirst: number;

Expand All @@ -158,33 +162,47 @@ export namespace _nbind {

this.registerSuperMethods(superClass, nextFirst, visitTbl);
}

this.registerMethods(src, firstSuper < 0);
}

finish() {
if(this.ready) return;
if(this.ready) return(this);
this.ready = true;

let superClass: BindClass | undefined;
let firstSuper: BindClass | undefined;

for(let superId of this.superIdList || []) {
superClass = getType(superId) as BindClass;
superClass.finish();
this.superList = (this.superIdList || []).map(
(superId: number) => (getType(superId) as BindClass).finish()
);

firstSuper = firstSuper || superClass;
}
const Bound = this.proto;

if(firstSuper) {
const Bound = this.proto;
if(this.superList.length) {
const Proto = function(this: Wrapper) {
this.constructor = Bound;
} as any as { new(): Wrapper };

Proto.prototype = firstSuper.proto.prototype;
Proto.prototype = this.superList[0].proto.prototype;
Bound.prototype = new Proto();
}

if(Bound != Module as any) Bound.prototype.__nbindType = this;

this.registerSuperMethods(this, 1, {});
return(this);
}

upcastStep(dst: BindClass, ptr: number): number {
if(dst == this) return(ptr);

for(let i = 0; i < this.superList.length; ++i) {
const superPtr = this.superList[i].upcastStep(
dst,
callUpcast(this.upcastList[i], ptr)
);
if(superPtr) return(superPtr);
}

return(0);
}

wireRead = (arg: number) => popValue(arg, this.ptrType);
Expand All @@ -196,6 +214,7 @@ export namespace _nbind {
// Reference to JavaScript class for wrapped instances
// of this C++ class.

classType: BindClass;
proto: WrapperClass;
ptrType: BindClassPtr;

Expand All @@ -207,6 +226,8 @@ export namespace _nbind {
ready = false;

superIdList: number[];
superList: BindClass[];
upcastList: number[];
methodTbl: { [name: string]: MethodSpec[] } = {};

static list: BindClass[] = [];
Expand All @@ -216,26 +237,44 @@ export namespace _nbind {
return(ptr ? new type.proto(ptrMarker, type.flags, ptr) : null);
}

function pushPointer(obj: Wrapper, type: BindClassPtr) {
if(!(obj instanceof type.proto)) throw(new Error('Type mismatch'));
return(obj.__nbindPtr);
export function pushPointer(obj: Wrapper, type: BindClassPtr) {
if(!(obj instanceof Wrapper)) throw(new Error('Type mismatch'));

let ptr = obj.__nbindPtr;
let objType = (obj.__nbindType).classType;
let classType = type.classType;

if(obj instanceof type.proto) {
// Fast path, requested type is in object's prototype chain.

while(objType != classType) {
ptr = callUpcast(objType.upcastList[0], ptr);
objType = objType.superList[0];
}
} else {
ptr = objType.upcastStep(classType, ptr);
if(!ptr) throw(new Error('Type mismatch'));
}

return(ptr);
}

function pushMutablePointer(obj: Wrapper, type: BindClassPtr) {
if(!(obj instanceof type.proto)) throw(new Error('Type mismatch'));
const ptr = pushPointer(obj, type);

if(obj.__nbindFlags & TypeFlags.isConst) {
throw(new Error('Passing a const value as a non-const argument'));
}

return(obj.__nbindPtr);
return(ptr);
}

export class BindClassPtr extends BindType {
constructor(spec: TypeSpecWithParam) {
super(spec);

this.proto = (spec.paramList[0] as BindClass).proto;
this.classType = (spec.paramList[0] as BindClass).classType;
this.proto = this.classType.proto;

const isConst = spec.flags & TypeFlags.isConst;
const isValue = (
Expand All @@ -257,17 +296,18 @@ export namespace _nbind {
this.wireWrite = (arg: any) => push(arg, this);
}

classType: BindClass;
proto: WrapperClass;
}

export function popShared(ptr: number, type: BindClassPtr) {
export function popShared(ptr: number, type: SharedClassPtr) {
const shared = HEAPU32[ptr / 4];
const unsafe = HEAPU32[ptr / 4 + 1];

return(unsafe ? new type.proto(ptrMarker, type.flags, unsafe, shared) : null);
}

function pushShared(obj: Wrapper, type: BindClassPtr) {
function pushShared(obj: Wrapper, type: SharedClassPtr) {
if(!(obj instanceof type.proto)) throw(new Error('Type mismatch'));

return(obj.__nbindShared);
Expand All @@ -287,7 +327,8 @@ export namespace _nbind {
constructor(spec: TypeSpecWithParam) {
super(spec);

this.proto = (spec.paramList[0] as BindClass).proto;
this.classType = (spec.paramList[0] as BindClass).classType;
this.proto = this.classType.proto;

const isConst = spec.flags & TypeFlags.isConst;
const push = isConst ? pushShared : pushMutableShared;
Expand All @@ -298,6 +339,7 @@ export namespace _nbind {

readResources = [ resources.pool ];

classType: BindClass;
proto: WrapperClass;
}

Expand Down
10 changes: 8 additions & 2 deletions src/em/Binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ extern "C" {
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 **policies, const TYPEID *superList, void *(**upcastList)(void *),
unsigned int superCount,
funcPtr destructor,
const char *name
);
Expand Down Expand Up @@ -165,17 +166,22 @@ static void initModule() {
bindClass->visit();
++posPrev;

TYPEID superIdList[ bindClass->getSuperClassCount()];
const unsigned int superCount = bindClass->getSuperClassCount();
TYPEID superIdList[superCount];
TYPEID *superPtr = superIdList;
void *(*upcastList[superCount])(void *);
auto *upcastPtr = upcastList;

for(auto &spec : bindClass->getSuperClassList()) {
*superPtr++ = spec.superClass.getTypes()[0];
*upcastPtr++ = spec.upcast;
}

_nbind_register_class(
bindClass->getTypes(),
bindClass->getPolicies(),
superIdList,
upcastList,
bindClass->getSuperClassCount(),
bindClass->getDeleter(),
bindClass->getName()
Expand Down
17 changes: 11 additions & 6 deletions src/em/Caller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export namespace _nbind {
export var getTypes: typeof _globals.getTypes;
export var getDynCall: typeof _globals.getDynCall;

export var pushPointer: typeof _class.pushPointer;

export var externalList: _external.External<any>[];

export var listResources: typeof _resource.listResources;
Expand Down Expand Up @@ -116,6 +118,7 @@ export namespace _nbind {

function buildCallerFunction(
dynCall: Func,
ptrType: _class.BindClassPtr | null,
ptr: number,
num: number,
policyTbl: PolicyTbl | null,
Expand Down Expand Up @@ -263,7 +266,7 @@ export namespace _nbind {

/** Dynamically create an invoker function for calling a C++ class method. */

export function makeMethodCaller(spec: _class.MethodSpec) {
export function makeMethodCaller(ptrType: _class.BindClassPtr, spec: _class.MethodSpec) {
const argCount = spec.typeList!.length - 1;

// The method invoker function adds two arguments to those of the method:
Expand Down Expand Up @@ -296,16 +299,16 @@ export namespace _nbind {
switch(argCount) {
case 0: return(function(this: Wrapper) {
return(this.__nbindFlags & mask ? err() :
dynCall(ptr, num, this.__nbindPtr)); });
dynCall(ptr, num, pushPointer(this, ptrType))); });
case 1: return(function(this: Wrapper, a1: any) {
return(this.__nbindFlags & mask ? err() :
dynCall(ptr, num, this.__nbindPtr, a1 )); });
dynCall(ptr, num, pushPointer(this, ptrType), a1 )); });
case 2: return(function(this: Wrapper, a1: any, a2: any) {
return(this.__nbindFlags & mask ? err() :
dynCall(ptr, num, this.__nbindPtr, a1, a2 )); });
dynCall(ptr, num, pushPointer(this, ptrType), a1, a2 )); });
case 3: return(function(this: Wrapper, a1: any, a2: any, a3: any) {
return(this.__nbindFlags & mask ? err() :
dynCall(ptr, num, this.__nbindPtr, a1, a2, a3 )); });
dynCall(ptr, num, pushPointer(this, ptrType), a1, a2, a3 )); });
default:
// Function takes over 3 arguments or needs type conversion.
// Let's create the invoker dynamically then.
Expand All @@ -315,11 +318,12 @@ export namespace _nbind {

return(buildCallerFunction(
dynCall,
ptrType,
ptr,
num,
spec.policyTbl!,
needsWireWrite,
'ptr,num,this.__nbindPtr',
'ptr,num,pushPointer(this,ptrType)',
returnType,
argTypeList,
mask,
Expand Down Expand Up @@ -386,6 +390,7 @@ export namespace _nbind {

return(buildCallerFunction(
dynCall,
null,
ptr,
spec.num!,
spec.policyTbl!,
Expand Down
1 change: 1 addition & 0 deletions src/em/Wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export namespace _nbind {
* Calls the C++ constructor and returns a numeric heap pointer. */
__nbindConstructor: (...args: any[]) => number;
__nbindValueConstructor: _globals.Func;
__nbindType: _class.BindClass;

__nbindPtr: number;
__nbindShared: number;
Expand Down
8 changes: 7 additions & 1 deletion src/em/em-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export namespace _nbind {
export var BindClass: typeof _class.BindClass;
export var BindClassPtr: typeof _class.BindClassPtr;
export var SharedClassPtr: typeof _class.BindClassPtr;
export var callUpcast: typeof _class.callUpcast;

export var ExternalType: typeof _external.ExternalType;

Expand Down Expand Up @@ -123,6 +124,7 @@ class nbind { // tslint:disable-line:class-name
};

Module['toggleLightGC'] = _nbind.toggleLightGC;
_nbind.callUpcast = Module['dynCall_ii'];

const globalScope = _nbind.makeType(_nbind.constructType, {
flags: TypeFlags.isClass,
Expand Down Expand Up @@ -163,6 +165,7 @@ class nbind { // tslint:disable-line:class-name
idListPtr: number,
policyListPtr: number,
superListPtr: number,
upcastListPtr: number,
superCount: number,
destructorPtr: number,
namePtr: number
Expand All @@ -186,7 +189,7 @@ class nbind { // tslint:disable-line:class-name
_nbind.queryType
) as _class.BindClassPtr;

bindClass.destroy = _nbind.makeMethodCaller({
bindClass.destroy = _nbind.makeMethodCaller(bindClass.ptrType, {
boundID: spec.id,
flags: TypeFlags.none,
name: 'destroy',
Expand All @@ -200,6 +203,9 @@ class nbind { // tslint:disable-line:class-name
bindClass.superIdList = Array.prototype.slice.call(
HEAPU32.subarray(superListPtr / 4, superListPtr / 4 + superCount)
);
bindClass.upcastList = Array.prototype.slice.call(
HEAPU32.subarray(upcastListPtr / 4, upcastListPtr / 4 + superCount)
);
}

Module[bindClass.name] = bindClass.makeBound(policyTbl);
Expand Down

0 comments on commit a7dcc68

Please sign in to comment.