Skip to content

Commit be37568

Browse files
committed
calculate method sets at runtime = smaller JS output (#136)
1 parent 67627ac commit be37568

File tree

4 files changed

+145
-92
lines changed

4 files changed

+145
-92
lines changed

compiler/compiler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter) error {
123123
}
124124
}
125125

126-
if _, err := w.Write([]byte("$initAnonTypes();\n$packages[\"runtime\"].$init()();\n$go($packages[\"" + string(mainPkg.ImportPath) + "\"].$init, [], true);\n$flushConsole();\n\n}).call(this);\n")); err != nil {
126+
if _, err := w.Write([]byte("$synthesizeMethods();\n$packages[\"runtime\"].$init()();\n$go($packages[\"" + string(mainPkg.ImportPath) + "\"].$init, [], true);\n$flushConsole();\n\n}).call(this);\n")); err != nil {
127127
return err
128128
}
129129

compiler/natives/reflect/reflect.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ func reflectType(typ js.Object) *rtype {
4646
js.InternalObject(rt).Set("jsType", typ)
4747
typ.Set("reflectType", js.InternalObject(rt))
4848

49-
methods := typ.Get("methods")
50-
if typ.Get("typeName").String() != "" || methods.Length() != 0 {
51-
reflectMethods := make([]method, methods.Length())
49+
methodSet := js.Global.Call("$methodSet", typ)
50+
if typ.Get("typeName").String() != "" || methodSet.Length() != 0 {
51+
reflectMethods := make([]method, methodSet.Length())
5252
for i := range reflectMethods {
53-
m := methods.Index(i)
53+
m := methodSet.Index(i)
5454
t := m.Get("typ")
5555
reflectMethods[i] = method{
5656
name: newStringPtr(m.Get("name")),
@@ -510,7 +510,7 @@ func methodReceiver(op string, v Value, i int) (rcvrtype, t *rtype, fn unsafe.Po
510510
panic("reflect: " + op + " of unexported method")
511511
}
512512
t = m.mtyp
513-
prop = jsType(v.typ).Get("methods").Index(i).Get("prop").String()
513+
prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String()
514514
}
515515
rcvr := v.object()
516516
if isWrapped(v.typ) {
@@ -601,7 +601,7 @@ func (t *uncommonType) Method(i int) (m Method) {
601601
}
602602
mt := p.typ
603603
m.Type = mt
604-
prop := js.InternalObject(t).Get("jsType").Get("methods").Index(i).Get("prop").String()
604+
prop := js.Global.Call("$methodSet", js.InternalObject(t).Get("jsType")).Index(i).Get("prop").String()
605605
fn := func(rcvr js.Object) js.Object {
606606
return rcvr.Get(prop).Call("apply", rcvr, js.Arguments[1:])
607607
}

compiler/package.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -380,29 +380,29 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
380380
})
381381
d.MethodListCode = c.CatchOutput(0, func() {
382382
if _, isInterface := o.Type().Underlying().(*types.Interface); !isInterface {
383-
writeMethodSet := func(t types.Type) {
384-
methodSet := types.NewMethodSet(t)
385-
if methodSet.Len() == 0 {
386-
return
383+
named := o.Type().(*types.Named)
384+
var methods []string
385+
var ptrMethods []string
386+
for i := 0; i < named.NumMethods(); i++ {
387+
method := named.Method(i)
388+
name := method.Name()
389+
if reservedKeywords[name] {
390+
name += "$"
387391
}
388-
methods := make([]string, methodSet.Len())
389-
for i := range methods {
390-
method := methodSet.At(i)
391-
pkgPath := ""
392-
if !method.Obj().Exported() {
393-
pkgPath = method.Obj().Pkg().Path()
394-
}
395-
t := method.Type().(*types.Signature)
396-
name := method.Obj().Name()
397-
if reservedKeywords[name] {
398-
name += "$"
399-
}
400-
methods[i] = fmt.Sprintf(`{prop: "%s", name: "%s", pkg: "%s", typ: $funcType(%s)}`, name, method.Obj().Name(), pkgPath, c.initArgs(t))
392+
pkgPath := ""
393+
if !method.Exported() {
394+
pkgPath = method.Pkg().Path()
395+
}
396+
t := method.Type().(*types.Signature)
397+
entry := fmt.Sprintf(`{prop: "%s", name: "%s", pkg: "%s", typ: $funcType(%s)}`, name, method.Name(), pkgPath, c.initArgs(t))
398+
if _, isPtr := t.Recv().Type().(*types.Pointer); isPtr {
399+
ptrMethods = append(ptrMethods, entry)
400+
continue
401401
}
402-
c.Printf("%s.methods = [%s];", c.typeName(t), strings.Join(methods, ", "))
402+
methods = append(methods, entry)
403403
}
404-
writeMethodSet(o.Type())
405-
writeMethodSet(types.NewPointer(o.Type()))
404+
c.Printf("%s.methods = [%s];", c.typeName(o.Type()), strings.Join(methods, ", "))
405+
c.Printf("%s.methods = [%s];", c.typeName(types.NewPointer(o.Type())), strings.Join(ptrMethods, ", "))
406406
}
407407
})
408408
switch t := o.Type().Underlying().(type) {

compiler/prelude/types.go

Lines changed: 118 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ var $kindString = 24;
2828
var $kindStruct = 25;
2929
var $kindUnsafePointer = 26;
3030
31+
var $methodSynthesizers = [];
32+
var $addMethodSynthesizer = function(f) {
33+
if ($methodSynthesizers === null) {
34+
f();
35+
return;
36+
}
37+
$methodSynthesizers.push(f);
38+
};
39+
var $synthesizeMethods = function() {
40+
$methodSynthesizers.forEach(function(f) { f(); });
41+
$methodSynthesizers = null;
42+
};
43+
3144
var $newType = function(size, kind, string, name, pkg, constructor) {
3245
var typ;
3346
switch(kind) {
@@ -225,29 +238,31 @@ var $newType = function(size, kind, string, name, pkg, constructor) {
225238
typ.ptr.nil = Object.create(constructor.prototype, properties);
226239
typ.ptr.nil.$val = typ.ptr.nil;
227240
/* methods for embedded fields */
228-
var forwardMethod = function(target, m, f) {
229-
if (target.prototype[m.prop] !== undefined) { return; }
230-
target.prototype[m.prop] = function() {
231-
var v = this.$val[f.prop];
232-
if (f.typ === $js.Object) {
233-
v = new $js.container.ptr(v);
234-
}
235-
if (v.$val === undefined) {
236-
v = new f.typ(v);
237-
}
238-
return v[m.prop].apply(v, arguments);
241+
$addMethodSynthesizer(function() {
242+
var synthesizeMethod = function(target, m, f) {
243+
if (target.prototype[m.prop] !== undefined) { return; }
244+
target.prototype[m.prop] = function() {
245+
var v = this.$val[f.prop];
246+
if (f.typ === $js.Object) {
247+
v = new $js.container.ptr(v);
248+
}
249+
if (v.$val === undefined) {
250+
v = new f.typ(v);
251+
}
252+
return v[m.prop].apply(v, arguments);
253+
};
239254
};
240-
};
241-
fields.forEach(function(f) {
242-
if (f.name === "") {
243-
f.typ.methods.forEach(function(m) {
244-
forwardMethod(typ, m, f);
245-
forwardMethod(typ.ptr, m, f);
246-
});
247-
$ptrType(f.typ).methods.forEach(function(m) {
248-
forwardMethod(typ.ptr, m, f);
249-
});
250-
}
255+
fields.forEach(function(f) {
256+
if (f.name === "") {
257+
$methodSet(f.typ).forEach(function(m) {
258+
synthesizeMethod(typ, m, f);
259+
synthesizeMethod(typ.ptr, m, f);
260+
});
261+
$methodSet($ptrType(f.typ)).forEach(function(m) {
262+
synthesizeMethod(typ.ptr, m, f);
263+
});
264+
}
265+
});
251266
});
252267
};
253268
break;
@@ -331,11 +346,77 @@ var $newType = function(size, kind, string, name, pkg, constructor) {
331346
typ.typeName = name;
332347
typ.pkg = pkg;
333348
typ.methods = [];
349+
typ.methodSetCache = null;
334350
typ.comparable = true;
335-
var rt = null;
336351
return typ;
337352
};
338353
354+
var $methodSet = function(typ) {
355+
if (typ.methodSetCache !== null) {
356+
return typ.methodSetCache;
357+
}
358+
var base = {};
359+
360+
var isPtr = (typ.kind === $kindPtr);
361+
if (isPtr && typ.elem.kind === $kindInterface) {
362+
typ.methodSetCache = [];
363+
return [];
364+
}
365+
366+
var current = [{typ: isPtr ? typ.elem : typ, indirect: isPtr}];
367+
368+
var seen = {};
369+
370+
while (current.length > 0) {
371+
var next = [];
372+
var mset = [];
373+
374+
current.forEach(function(e) {
375+
if (seen[e.typ.string]) {
376+
return;
377+
}
378+
seen[e.typ.string] = true;
379+
380+
if(e.typ.typeName !== "") {
381+
mset = mset.concat(e.typ.methods);
382+
if (e.indirect) {
383+
mset = mset.concat($ptrType(e.typ).methods);
384+
}
385+
}
386+
387+
switch (e.typ.kind) {
388+
case $kindStruct:
389+
e.typ.fields.forEach(function(f) {
390+
if (f.name === "") {
391+
var fTyp = f.typ;
392+
var fIsPtr = (fTyp.kind === $kindPtr);
393+
next.push({typ: fIsPtr ? fTyp.elem : fTyp, indirect: e.indirect || fIsPtr});
394+
}
395+
});
396+
break;
397+
398+
case $kindInterface:
399+
mset = mset.concat(e.typ.methods);
400+
break;
401+
}
402+
});
403+
404+
mset.forEach(function(m) {
405+
if (base[m.name] === undefined) {
406+
base[m.name] = m;
407+
}
408+
});
409+
410+
current = next;
411+
}
412+
413+
typ.methodSetCache = [];
414+
Object.keys(base).sort().forEach(function(name) {
415+
typ.methodSetCache.push(base[name]);
416+
});
417+
return typ.methodSetCache;
418+
};
419+
339420
var $Bool = $newType( 1, $kindBool, "bool", "bool", "", null);
340421
var $Int = $newType( 4, $kindInt, "int", "int", "", null);
341422
var $Int8 = $newType( 1, $kindInt8, "int8", "int8", "", null);
@@ -355,19 +436,6 @@ var $Complex128 = $newType(16, $kindComplex128, "complex128", "complex
355436
var $String = $newType( 8, $kindString, "string", "string", "", null);
356437
var $UnsafePointer = $newType( 4, $kindUnsafePointer, "unsafe.Pointer", "Pointer", "", null);
357438
358-
var $anonTypeInits = [];
359-
var $addAnonTypeInit = function(f) {
360-
if ($anonTypeInits === null) {
361-
f();
362-
return;
363-
}
364-
$anonTypeInits.push(f);
365-
};
366-
var $initAnonTypes = function() {
367-
$anonTypeInits.forEach(function(f) { f(); });
368-
$anonTypeInits = null;
369-
};
370-
371439
var $nativeArray = function(elemKind) {
372440
switch (elemKind) {
373441
case $kindInt:
@@ -410,7 +478,7 @@ var $arrayType = function(elem, len) {
410478
if (typ === undefined) {
411479
typ = $newType(12, $kindArray, string, "", "", null);
412480
$arrayTypes[string] = typ;
413-
$addAnonTypeInit(function() { typ.init(elem, len); });
481+
typ.init(elem, len);
414482
}
415483
return typ;
416484
};
@@ -422,7 +490,7 @@ var $chanType = function(elem, sendOnly, recvOnly) {
422490
if (typ === undefined) {
423491
typ = $newType(4, $kindChan, string, "", "", null);
424492
elem[field] = typ;
425-
$addAnonTypeInit(function() { typ.init(elem, sendOnly, recvOnly); });
493+
typ.init(elem, sendOnly, recvOnly);
426494
}
427495
return typ;
428496
};
@@ -443,7 +511,7 @@ var $funcType = function(params, results, variadic) {
443511
if (typ === undefined) {
444512
typ = $newType(4, $kindFunc, string, "", "", null);
445513
$funcTypes[string] = typ;
446-
$addAnonTypeInit(function() { typ.init(params, results, variadic); });
514+
typ.init(params, results, variadic);
447515
}
448516
return typ;
449517
};
@@ -460,7 +528,7 @@ var $interfaceType = function(methods) {
460528
if (typ === undefined) {
461529
typ = $newType(8, $kindInterface, string, "", "", null);
462530
$interfaceTypes[string] = typ;
463-
$addAnonTypeInit(function() { typ.init(methods); });
531+
typ.init(methods);
464532
}
465533
return typ;
466534
};
@@ -483,7 +551,7 @@ var $mapType = function(key, elem) {
483551
if (typ === undefined) {
484552
typ = $newType(4, $kindMap, string, "", "", null);
485553
$mapTypes[string] = typ;
486-
$addAnonTypeInit(function() { typ.init(key, elem); });
554+
typ.init(key, elem);
487555
}
488556
return typ;
489557
};
@@ -493,7 +561,7 @@ var $ptrType = function(elem) {
493561
if (typ === undefined) {
494562
typ = $newType(4, $kindPtr, "*" + elem.string, "", "", null);
495563
elem.ptr = typ;
496-
$addAnonTypeInit(function() { typ.init(elem); });
564+
typ.init(elem);
497565
}
498566
return typ;
499567
};
@@ -510,7 +578,7 @@ var $sliceType = function(elem) {
510578
if (typ === undefined) {
511579
typ = $newType(12, $kindSlice, "[]" + elem.string, "", "", null);
512580
elem.Slice = typ;
513-
$addAnonTypeInit(function() { typ.init(elem); });
581+
typ.init(elem);
514582
}
515583
return typ;
516584
};
@@ -546,22 +614,7 @@ var $structType = function(fields) {
546614
}
547615
});
548616
$structTypes[string] = typ;
549-
$anonTypeInits.push(function() {
550-
/* collect methods for anonymous fields */
551-
for (var i = 0; i < fields.length; i++) {
552-
var f = fields[i];
553-
if (f.name === "") {
554-
f.typ.methods.forEach(function(m) {
555-
typ.methods.push(m);
556-
typ.ptr.methods.push(m);
557-
});
558-
$ptrType(f.typ).methods.forEach(function(m) {
559-
typ.ptr.methods.push(m);
560-
});
561-
}
562-
};
563-
typ.init(fields);
564-
});
617+
typ.init(fields);
565618
}
566619
return typ;
567620
};
@@ -577,13 +630,13 @@ var $assertType = function(value, type, returnTuple) {
577630
ok = type.implementedBy[valueTypeString];
578631
if (ok === undefined) {
579632
ok = true;
580-
var valueMethods = value.constructor.methods;
581-
var typeMethods = type.methods;
582-
for (var i = 0; i < typeMethods.length; i++) {
583-
var tm = typeMethods[i];
633+
var valueMethodSet = $methodSet(value.constructor);
634+
var interfaceMethods = type.methods;
635+
for (var i = 0; i < interfaceMethods.length; i++) {
636+
var tm = interfaceMethods[i];
584637
var found = false;
585-
for (var j = 0; j < valueMethods.length; j++) {
586-
var vm = valueMethods[j];
638+
for (var j = 0; j < valueMethodSet.length; j++) {
639+
var vm = valueMethodSet[j];
587640
if (vm.name === tm.name && vm.pkg === tm.pkg && vm.typ === tm.typ) {
588641
found = true;
589642
break;

0 commit comments

Comments
 (0)