Index: tool/input_sdk/private/ddc_runtime/types.dart |
diff --git a/tool/input_sdk/private/ddc_runtime/types.dart b/tool/input_sdk/private/ddc_runtime/types.dart |
index 4370dd09714a19cce007cd322a913e68d922327d..9db41dd7904ed4a5a3fe073bc5944618db6d8d76 100644 |
--- a/tool/input_sdk/private/ddc_runtime/types.dart |
+++ b/tool/input_sdk/private/ddc_runtime/types.dart |
@@ -124,8 +124,104 @@ final AbstractFunctionType = JS('', ''' |
} |
'''); |
+/// Memo table for named argument groups. A named argument packet |
+/// {name1 : type1, ..., namen : typen} corresponds to the path |
+/// n, name1, type1, ...., namen, typen. The element of the map |
+/// reached via this path (if any) is the canonical representative |
+/// for this packet. |
+final _fnTypeNamedArgMap = JS('', 'new Map()'); |
+/// Memo table for positional argument groups. A positional argument |
+/// packet [type1, ..., typen] (required or optional) corresponds to |
+/// the path n, type1, ...., typen. The element reached via |
+/// this path (if any) is the canonical representative for this |
+/// packet. Note that required and optional parameters packages |
+/// may have the same canonical representation. |
+final _fnTypeArrayArgMap = JS('', 'new Map()'); |
+/// Memo table for function types. The index path consists of the |
+/// path length - 1, the returnType, the canonical positional argument |
+/// packet, and if present, the canonical optional or named argument |
+/// packet. A level of indirection could be avoided here if desired. |
+final _fnTypeTypeMap = JS('', 'new Map()'); |
+/// Memo table for small function types with no optional or named |
+/// arguments and less than a fixed n (currently 3) number of |
+/// required arguments. Indexing into this table by the number |
+/// of required arguments yields a map which is indexed by the |
+/// argument types themselves. The element reached via this |
+/// index path (if present) is the canonical function type. |
+final _fnTypeSmallMap = JS('', '[new Map(), new Map(), new Map()]'); |
+ |
final FunctionType = JS('', ''' |
class FunctionType extends $AbstractFunctionType { |
+ static _memoizeArray(map, arr, create) { |
+ let len = arr.length; |
+ map = FunctionType._lookupNonTerminal(map, len); |
+ for (var i = 0; i < len-1; ++i) { |
+ map = FunctionType._lookupNonTerminal(map, arr[i]); |
+ } |
+ let result = map.get(arr[len-1]); |
+ if (result !== void 0) return result; |
+ map.set(arr[len-1], result = create()); |
+ return result; |
+ } |
+ |
+ // Map dynamic to bottom. If meta-data is present, |
+ // we slice off the remaining meta-data and make |
+ // it the second element of a packet for processing |
+ // later on in the constructor. |
+ static _normalizeParameter(a) { |
+ if (a instanceof Array) { |
+ let result = []; |
+ result.push((a[0] == $dynamic) ? $bottom : a[0]); |
+ result.push(a.slice(1)); |
+ return result; |
+ } |
+ return (a == $dynamic) ? $bottom : a; |
+ } |
+ |
+ static _canonicalizeArray(definite, arr, map) { |
+ if (!definite) arr = arr.map(FunctionType._normalizeParameter); |
+ return FunctionType._memoizeArray(map, arr, () => arr); |
+ } |
+ |
+ // TODO(leafp): This only canonicalizes of the names are |
+ // emitted in a consistent order. |
+ static _canonicalizeNamed(definite, named, map) { |
+ let key = []; |
+ let names = $getOwnPropertyNames(named); |
+ let r = {}; |
+ for (var i = 0; i < names.length; ++i) { |
+ let name = names[i]; |
+ let type = named[name]; |
+ if (!definite) r[name] = type = FunctionType._normalizeParameter(type); |
+ key.push(name); |
+ key.push(type); |
+ } |
+ if (!definite) named = r; |
+ return FunctionType._memoizeArray(map, key, () => named); |
+ } |
+ |
+ static _lookupNonTerminal(map, key) { |
+ let result = map.get(key); |
+ if (result !== void 0) return result; |
+ map.set(key, result = new Map()); |
+ return result; |
+ } |
+ |
+ // TODO(leafp): This handles some low hanging fruit, but |
+ // really we should make all of this faster, and also |
+ // handle more cases here. |
+ static _createSmall(count, definite, returnType, args) { |
+ let map = $_fnTypeSmallMap[count]; |
+ if (!definite) args = args.map(FunctionType._normalizeParameter); |
+ for (var i = 0; i < count; ++i) { |
+ map = FunctionType._lookupNonTerminal(map, args[i]); |
+ } |
+ let result = map.get(returnType); |
+ if (result !== void 0) return result; |
+ result = new FunctionType(returnType, args, [], {}); |
+ map.set(returnType, result); |
+ return result; |
+ } |
/** |
* Construct a function type. There are two arrow constructors, |
* distinguished by the "definite" flag. |
@@ -136,15 +232,47 @@ final FunctionType = JS('', ''' |
* |
* The definite arrow (definite is true) leaves arguments unchanged. |
* |
- * We eagerly canonize the argument types to avoid having to deal with |
+ * We eagerly normalize the argument types to avoid having to deal with |
* this logic in multiple places. |
* |
- * TODO(leafp): Figure out how to present this to the user. How |
- * should these be printed out? |
+ * This code does best effort canonicalization. It does not guarantee |
+ * that all instances will share. |
+ * |
*/ |
- constructor(definite, returnType, args, optionals, named) { |
+ static create(definite, returnType, args, extra) { |
+ // Note that if extra is ever passed as an empty array |
+ // or an empty map, we can end up with semantically |
+ // identical function types that don't canonicalize |
+ // to the same object since we won't fall into this |
+ // fast path. |
+ if (extra === void 0 && args.length < 3) { |
+ return FunctionType._createSmall( |
+ args.length, definite, returnType, args); |
+ } |
+ args = FunctionType._canonicalizeArray( |
+ definite, args, $_fnTypeArrayArgMap); |
+ let keys; |
+ let create; |
+ if (extra === void 0) { |
+ keys = [returnType, args]; |
+ create = () => new FunctionType(returnType, args, [], {}); |
+ } else if (extra instanceof Array) { |
+ let optionals = |
+ FunctionType._canonicalizeArray(definite, extra, $_fnTypeArrayArgMap); |
+ keys = [returnType, args, optionals]; |
+ create = |
+ () => new FunctionType(returnType, args, optionals, {}); |
+ } else { |
+ let named = |
+ FunctionType._canonicalizeNamed(definite, extra, $_fnTypeNamedArgMap); |
+ keys = [returnType, args, named]; |
+ create = () => new FunctionType(returnType, args, [], named); |
+ } |
+ return FunctionType._memoizeArray($_fnTypeTypeMap, keys, create); |
+ } |
+ |
+ constructor(returnType, args, optionals, named) { |
super(); |
- this.definite = definite; |
this.returnType = returnType; |
this.args = args; |
this.optionals = optionals; |
@@ -157,7 +285,7 @@ final FunctionType = JS('', ''' |
for (var i = 0; i < array.length; ++i) { |
var arg = array[i]; |
if (arg instanceof Array) { |
- metadata.push(arg.slice(1)); |
+ metadata.push(arg[1]); |
result.push(arg[0]); |
} else { |
metadata.push([]); |
@@ -169,32 +297,11 @@ final FunctionType = JS('', ''' |
this.args = process(this.args, this.metadata); |
this.optionals = process(this.optionals, this.metadata); |
// TODO(vsm): Add named arguments. |
- this._canonize(); |
- } |
- _canonize() { |
- if (this.definite) return; |
- |
- function replace(a) { |
- return (a == $dynamic) ? $bottom : a; |
- } |
- |
- this.args = this.args.map(replace); |
- |
- if (this.optionals.length > 0) { |
- this.optionals = this.optionals.map(replace); |
- } |
- |
- if (Object.keys(this.named).length > 0) { |
- let r = {}; |
- for (let name of $getOwnPropertyNames(this.named)) { |
- r[name] = replace(this.named[name]); |
- } |
- this.named = r; |
- } |
} |
} |
'''); |
+ |
final Typedef = JS('', ''' |
class Typedef extends $AbstractFunctionType { |
constructor(name, closure) { |
@@ -204,10 +311,6 @@ final Typedef = JS('', ''' |
this._functionType = null; |
} |
- get definite() { |
- return this._functionType.definite; |
- } |
- |
get name() { |
return this._name; |
} |
@@ -255,26 +358,12 @@ _functionType(definite, returnType, args, extra) => JS('', '''(() => { |
// Return a function that makes the type. |
function makeGenericFnType(...types) { |
let parts = fnTypeParts(...types); |
- return $_functionType($definite, parts[0], parts[1], parts[2]); |
+ return $FunctionType.create($definite, parts[0], parts[1], parts[2]); |
} |
makeGenericFnType[$_typeFormalCount] = fnTypeParts.length; |
return makeGenericFnType; |
} |
- |
- // TODO(vsm): Cache / memomize? |
- let optionals; |
- let named; |
- if ($extra === void 0) { |
- optionals = []; |
- named = {}; |
- } else if ($extra instanceof Array) { |
- optionals = $extra; |
- named = {}; |
- } else { |
- optionals = []; |
- named = $extra; |
- } |
- return new $FunctionType($definite, $returnType, $args, optionals, named); |
+ return $FunctionType.create($definite, $returnType, $args, $extra); |
})()'''); |
/// |
@@ -470,7 +559,7 @@ _subtypeMemo(f) => JS('', '''(() => { |
/// Returns undefined if [t1] </: [t2] in strong mode, but spec |
/// mode may differ |
final isSubtype = |
- JS('', '$_subtypeMemo((t1, t2) => $_isSubtype(t1, t2, true))'); |
+ JS('', '$_subtypeMemo((t1, t2) => (t1 === t2) || $_isSubtype(t1, t2, true))'); |
_isBottom(type) => JS('bool', '# == #', type, bottom); |