Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(40)

Unified Diff: pkg/compiler/lib/src/native/behavior.dart

Issue 1034263002: Parse more properties of native code fragments. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tests/compiler/dart2js/js_spec_string_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/compiler/lib/src/native/behavior.dart
diff --git a/pkg/compiler/lib/src/native/behavior.dart b/pkg/compiler/lib/src/native/behavior.dart
index 5b0462dbf86f415565de272557346f46eac25763..f1a011e763a3491f9a0caa26bb87ae654d1925e8 100644
--- a/pkg/compiler/lib/src/native/behavior.dart
+++ b/pkg/compiler/lib/src/native/behavior.dart
@@ -15,6 +15,29 @@ class SpecialType {
int get hashCode => name.hashCode;
}
+/// Description of the exception behaviour of native code.
+///
+/// TODO(sra): Replace with something that better supports specialization on
+/// first argument properties.
+class NativeThrowBehavior {
+ static const NativeThrowBehavior NEVER = const NativeThrowBehavior._(0);
+ static const NativeThrowBehavior MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS =
+ const NativeThrowBehavior._(1);
+ static const NativeThrowBehavior MAY = const NativeThrowBehavior._(2);
+ static const NativeThrowBehavior MUST = const NativeThrowBehavior._(3);
+
+ final int _bits;
+ const NativeThrowBehavior._(this._bits);
+
+ String toString() {
+ if (this == NEVER) return 'never';
+ if (this == MAY) return 'may';
+ if (this == MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS) return 'null(1)';
+ if (this == MUST) return 'must';
+ return 'NativeThrowBehavior($_bits)';
+ }
+}
+
/**
* A summary of the behavior of a native element.
*
@@ -59,10 +82,20 @@ class NativeBehavior {
final SideEffects sideEffects = new SideEffects.empty();
+ NativeThrowBehavior throwBehavior = NativeThrowBehavior.MAY;
+
+ bool isAllocation = false;
+ bool useGvn = false;
+
String toString() {
- return 'NativeBehavior(returns: ${typesReturned}, '
+ return 'NativeBehavior('
+ 'returns: ${typesReturned}, '
'creates: ${typesInstantiated}, '
- 'sideEffects: ${sideEffects})';
+ 'sideEffects: ${sideEffects}, '
+ 'throws: ${throwBehavior}'
+ '${isAllocation ? ", isAllocation" : ""}'
+ '${useGvn ? ", useGvn" : ""}'
+ ')';
}
@@ -73,19 +106,27 @@ class NativeBehavior {
/// the [setSideEffects] method is not invoked.
///
/// Two forms of the string is supported:
+ ///
/// 1) A single type string of the form 'void', '', 'var' or 'T1|...|Tn'
/// which defines the types returned and for the later form also created by
/// the call to JS.
- /// 2) A sequence of the form
- /// '<type-tag>:<type-string>;<effect-tag>:<effect-string>'
- /// where <type-tag> is either 'returns' or 'creates' and where
- /// <type-string> is a type string like in 1). The type string marked by
- /// 'returns' defines the types returned and 'creates' defines the types
- /// created by the call to JS.
///
- /// The <effect-tag> is either 'effects' or 'depends' and
- /// <effect-string> is either 'all', 'none' or a comma-separated list of
- /// 'no-index', 'no-instance', 'no-static'.
+ /// 2) A sequence of <tag>:<value> pairs of the following kinds
+ ///
+ /// <type-tag>:<type-string>
+ /// <effect-tag>:<effect-string>
+ /// throws:<throws-string>
+ /// gvn:<gvn-string>
+ /// new:<new-string>
+ ///
+ /// A <type-tag> is either 'returns' or 'creates' and <type-string> is a
+ /// type string like in 1). The type string marked by 'returns' defines the
+ /// types returned and 'creates' defines the types created by the call to
+ /// JS.
+ ///
+ /// An <effect-tag> is either 'effects' or 'depends' and <effect-string> is
+ /// either 'all', 'none' or a comma-separated list of 'no-index',
+ /// 'no-instance', 'no-static'.
///
/// The flag 'all' indicates that the call affects/depends on every
/// side-effect. The flag 'none' indicates that the call does not affect
@@ -98,6 +139,20 @@ class NativeBehavior {
/// indicated with 'no-static'. The flags 'effects' and 'depends' must be
/// used in unison (either both are present or none is).
///
+ /// The <throws-string> values are 'never', 'may', 'must', and 'null(1)'.
+ /// The default if unspecified is 'may'. 'null(1)' means that the template
+ /// expression throws if and only if the first template parameter is `null`
+ /// or `undefined`.
+ /// TODO(sra): Can we simplify to must/may/never and add null(1) by
+ /// inspection as an orthogonal attribute?
+ ///
+ /// <gvn-string> values are 'true' and 'false'. The default if unspecified
+ /// is 'false'.
+ ///
+ /// <new-string> values are 'true' and 'false'. The default if unspecified
+ /// is 'false'. A 'true' value means that each evaluation returns a fresh
+ /// (new) object that cannot be unaliased with existing objects.
herhut 2015/04/08 07:43:33 Did you mean aliased here?
+ ///
/// Each tag kind (including the 'type-tag's) can only occur once in the
/// sequence.
///
@@ -106,20 +161,29 @@ class NativeBehavior {
/// the types defined by the specification string, and [objectType] and
/// [nullType] define the types for `Object` and `Null`, respectively. The
/// latter is used for the type strings of the form '' and 'var'.
- // TODO(johnniwinther): Use ';' as a separator instead of a terminator.
+ /// [validTags] can be used to restrict which tags are accepted.
static void processSpecString(
DiagnosticListener listener,
Spannable spannable,
String specString,
- {void setSideEffects(SideEffects newEffects),
+ {Iterable<String> validTags,
+ void setSideEffects(SideEffects newEffects),
+ void setThrows(NativeThrowBehavior throwKind),
+ void setIsAllocation(bool isAllocation),
+ void setUseGvn(bool useGvn),
dynamic resolveType(String typeString),
- List typesReturned, List typesInstantiated,
+ List typesReturned,
+ List typesInstantiated,
objectType, nullType}) {
+ const List<String> knownTags = const [
+ 'creates', 'returns', 'depends', 'effects',
+ 'throws', 'gvn', 'new'];
+
/// Resolve a type string of one of the three forms:
/// * 'void' - in which case [onVoid] is called,
/// * '' or 'var' - in which case [onVar] is called,
- /// * 'T1|...|Tn' - in which case [onType] is called for each Ti.
+ /// * 'T1|...|Tn' - in which case [onType] is called for each resolved Ti.
void resolveTypesString(String typesString,
{onVoid(), onVar(), onType(type)}) {
// Various things that are not in fact types.
@@ -136,131 +200,190 @@ class NativeBehavior {
return;
}
for (final typeString in typesString.split('|')) {
- onType(resolveType(typeString));
+ onType(resolveType(typeString.trim()));
}
}
+ if (!specString.contains(';') && !specString.contains(':')) {
+ // Form (1), types or pseudo-types like 'void' and 'var'.
+ resolveTypesString(specString.trim(), onVar: () {
+ typesReturned.add(objectType);
+ typesReturned.add(nullType);
+ }, onType: (type) {
+ typesInstantiated.add(type);
+ typesReturned.add(type);
+ });
+ return;
+ }
- if (specString.contains(':')) {
- /// Find and remove a substring of the form 'tag:<string>;' from
- /// [specString].
- String getTagString(String tag) {
- String marker = '$tag:';
- int startPos = specString.indexOf(marker);
- if (startPos == -1) return null;
- int endPos = specString.indexOf(';', startPos);
- if (endPos == -1) return null;
- String typeString =
- specString.substring(startPos + marker.length, endPos);
- specString = '${specString.substring(0, startPos)}'
- '${specString.substring(endPos + 1)}'.trim();
- return typeString;
- }
+ List<String> specs = specString.split(';')
+ .map((s) => s.trim())
+ .toList();
+ if (specs.last == "") specs.removeLast(); // Allow separator to terminate.
- String returns = getTagString('returns');
- if (returns != null) {
- resolveTypesString(returns, onVar: () {
- typesReturned.add(objectType);
- typesReturned.add(nullType);
- }, onType: (type) {
- typesReturned.add(type);
- });
- }
+ assert(validTags == null || validTags.toSet().removeAll(validTags).isEmpty);
+ if (validTags == null) validTags = knownTags;
- String creates = getTagString('creates');
- if (creates != null) {
- resolveTypesString(creates, onVoid: () {
- listener.internalError(spannable,
- "Invalid type string 'creates:$creates'");
- }, onVar: () {
- listener.internalError(spannable,
- "Invalid type string 'creates:$creates'");
- }, onType: (type) {
- typesInstantiated.add(type);
- });
- }
+ Map<String, String> values = <String, String>{};
- String effects = getTagString('effects');
- String depends = getTagString('depends');
- if (effects != null && depends == null ||
- effects == null && depends != null) {
+ for (String spec in specs) {
+ List<String> tagAndValue = spec.split(':');
+ if (tagAndValue.length != 2) {
listener.internalError(spannable,
- "Invalid JS spec string. "
- "'effects' and 'depends' must occur together.");
+ "Invalid <tag>:<value> pair '$spec'.");
}
+ String tag = tagAndValue[0].trim();
+ String value = tagAndValue[1].trim();
- if (effects != null) {
- SideEffects sideEffects = new SideEffects();
- if (effects == "none") {
- sideEffects.clearAllSideEffects();
- } else if (effects == "all") {
- // Don't do anything.
+ if (validTags.contains(tag)) {
+ if (values[tag] == null) {
+ values[tag] = value;
} else {
- List<String> splitEffects = effects.split(",");
- if (splitEffects.isEmpty) {
- listener.internalError(spannable, "Missing side-effect flag.");
- }
- for (String effect in splitEffects) {
- switch (effect) {
- case "no-index":
- sideEffects.clearChangesIndex();
- break;
- case "no-instance":
- sideEffects.clearChangesInstanceProperty();
- break;
- case "no-static":
- sideEffects.clearChangesStaticProperty();
- break;
- default:
- listener.internalError(spannable,
- "Unrecognized side-effect flag: $effect.");
- }
- }
+ listener.internalError(spannable, "Duplicate tag '$tag'.");
}
-
- if (depends == "none") {
- sideEffects.clearAllDependencies();
- } else if (depends == "all") {
- // Don't do anything.
+ } else {
+ if (knownTags.contains(tag)) {
+ listener.internalError(spannable, "Tag '$tag' is not valid here.");
} else {
- List<String> splitDependencies = depends.split(",");
- if (splitDependencies.isEmpty) {
- listener.internalError(spannable,
- "Missing side-effect dependency flag.");
- }
- for (String dependency in splitDependencies) {
- switch (dependency) {
- case "no-index":
- sideEffects.clearDependsOnIndexStore();
- break;
- case "no-instance":
- sideEffects.clearDependsOnInstancePropertyStore();
- break;
- case "no-static":
- sideEffects.clearDependsOnStaticPropertyStore();
- break;
- default:
- listener.internalError(spannable,
- "Unrecognized side-effect flag: $dependency.");
- }
- }
+ listener.internalError(spannable, "Unknown tag '$tag'.");
}
-
- setSideEffects(sideEffects);
}
+ }
- if (!specString.isEmpty) {
- listener.internalError(spannable, "Invalid JS spec string.");
+ // Enum-like tags are looked up in a map. True signature is:
+ //
+ // T tagValueLookup<T>(String tag, Map<String, T> map);
+ //
+ dynamic tagValueLookup(String tag, Map<String, dynamic> map) {
+ String tagString = values[tag];
+ if (tagString == null) return null;
+ var value = map[tagString];
+ if (value == null) {
+ listener.internalError(spannable,
+ "Unknown '$tag' specification: '$tagString'");
}
- } else {
- resolveTypesString(specString, onVar: () {
+ return value;
+ }
+
+ String returns = values['returns'];
+ if (returns != null) {
+ resolveTypesString(returns, onVar: () {
typesReturned.add(objectType);
typesReturned.add(nullType);
}, onType: (type) {
- typesInstantiated.add(type);
typesReturned.add(type);
});
}
+
+ String creates = values['creates'];
+ if (creates != null) {
+ resolveTypesString(creates, onVoid: () {
+ listener.internalError(spannable,
+ "Invalid type string 'creates:$creates'");
+ }, onVar: () {
+ listener.internalError(spannable,
+ "Invalid type string 'creates:$creates'");
+ }, onType: (type) {
+ typesInstantiated.add(type);
+ });
+ }
+
+ const throwsOption = const <String, NativeThrowBehavior>{
+ 'never': NativeThrowBehavior.NEVER,
+ 'null(1)': NativeThrowBehavior.MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS,
+ 'may': NativeThrowBehavior.MAY,
+ 'must': NativeThrowBehavior.MUST };
+
+ const boolOptions = const<String, bool>{'true': true, 'false': false};
+
+ SideEffects sideEffects = processEffects(listener, spannable,
+ values['effects'], values['depends']);
+ NativeThrowBehavior throwsKind = tagValueLookup('throws', throwsOption);
+ bool isAllocation = tagValueLookup('new', boolOptions);
+ bool useGvn = tagValueLookup('gvn', boolOptions);
+
+ if (isAllocation == true && useGvn == true) {
+ listener.internalError(spannable, "'new' and 'gvn' are incompatible");
+ }
+
+ if (sideEffects != null) setSideEffects(sideEffects);
+ if (throwsKind != null) setThrows(throwsKind);
+ if (isAllocation != null) setIsAllocation(isAllocation);
+ if (useGvn != null) setUseGvn(useGvn);
+ }
+
+ static SideEffects processEffects(
+ DiagnosticListener listener,
+ Spannable spannable,
+ String effects,
+ String depends) {
+
+ if (effects == null && depends == null) return null;
+
+ if (effects == null || depends == null) {
+ listener.internalError(spannable,
+ "Invalid JS spec string. "
+ "'effects' and 'depends' must occur together.");
+ return null;
+ }
+
+ SideEffects sideEffects = new SideEffects();
+ if (effects == "none") {
+ sideEffects.clearAllSideEffects();
+ } else if (effects == "all") {
+ // Don't do anything.
+ } else {
+ List<String> splitEffects = effects.split(",");
+ if (splitEffects.isEmpty) {
+ listener.internalError(spannable, "Missing side-effect flag.");
+ }
+ for (String effect in splitEffects) {
+ switch (effect) {
+ case "no-index":
+ sideEffects.clearChangesIndex();
+ break;
+ case "no-instance":
+ sideEffects.clearChangesInstanceProperty();
+ break;
+ case "no-static":
+ sideEffects.clearChangesStaticProperty();
+ break;
+ default:
+ listener.internalError(spannable,
+ "Unrecognized side-effect flag: '$effect'.");
+ }
+ }
+ }
+
+ if (depends == "none") {
+ sideEffects.clearAllDependencies();
+ } else if (depends == "all") {
+ // Don't do anything.
+ } else {
+ List<String> splitDependencies = depends.split(",");
+ if (splitDependencies.isEmpty) {
+ listener.internalError(spannable,
+ "Missing side-effect dependency flag.");
+ }
+ for (String dependency in splitDependencies) {
+ switch (dependency) {
+ case "no-index":
+ sideEffects.clearDependsOnIndexStore();
+ break;
+ case "no-instance":
+ sideEffects.clearDependsOnInstancePropertyStore();
+ break;
+ case "no-static":
+ sideEffects.clearDependsOnStaticPropertyStore();
+ break;
+ default:
+ listener.internalError(spannable,
+ "Unrecognized side-effect flag: '$dependency'.");
+ }
+ }
+ }
+
+ return sideEffects;
}
static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) {
@@ -308,20 +431,39 @@ class NativeBehavior {
behavior.sideEffects.setTo(newEffects);
}
+ bool throwBehaviorFromSpecString = false;
+ void setThrows(NativeThrowBehavior throwBehavior) {
+ throwBehaviorFromSpecString = true;
+ behavior.throwBehavior = throwBehavior;
+ }
+
+ void setIsAllocation(bool isAllocation) {
+ behavior.isAllocation = isAllocation;
+ }
+
+ void setUseGvn(bool useGvn) {
+ behavior.useGvn = useGvn;
+ }
+
processSpecString(compiler, jsCall,
- specString,
- setSideEffects: setSideEffects,
- resolveType: resolveType,
- typesReturned: behavior.typesReturned,
- typesInstantiated: behavior.typesInstantiated,
- objectType: compiler.objectClass.computeType(compiler),
- nullType: compiler.nullClass.computeType(compiler));
+ specString,
+ setSideEffects: setSideEffects,
+ setThrows: setThrows,
+ setIsAllocation: setIsAllocation,
+ setUseGvn: setUseGvn,
+ resolveType: resolveType,
+ typesReturned: behavior.typesReturned,
+ typesInstantiated: behavior.typesInstantiated,
+ objectType: compiler.objectClass.computeType(compiler),
+ nullType: compiler.nullClass.computeType(compiler));
if (!sideEffectsAreEncodedInSpecString) {
new SideEffectsVisitor(behavior.sideEffects)
.visit(behavior.codeTemplate.ast);
}
+ // TODO(sra): Simplify [throwBehavior] using [sideEffects].
+
return behavior;
}
@@ -343,7 +485,7 @@ class NativeBehavior {
// We don't check the given name. That needs to be done at a later point.
// This is, because we want to allow non-literals as names.
if (argNodes.tail.isEmpty) {
- compiler.internalError(jsGlobalCall, 'Embedded Global is missing name');
+ compiler.internalError(jsGlobalCall, 'Embedded Global is missing name.');
}
if (!argNodes.tail.tail.isEmpty) {
@@ -370,15 +512,9 @@ class NativeBehavior {
jsGlobalCall);
}
- void setSideEffects(SideEffects newEffects) {
- compiler.internalError(jsGlobalCall,
- 'Embedded global calls may not have any side-effect overwrites: '
- '$specString');
- }
-
processSpecString(compiler, jsGlobalCall,
specString,
- setSideEffects: setSideEffects,
+ validTags: const ['returns', 'creates'],
resolveType: resolveType,
typesReturned: behavior.typesReturned,
typesInstantiated: behavior.typesInstantiated,
« no previous file with comments | « no previous file | tests/compiler/dart2js/js_spec_string_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698