OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of js_backend; | 5 part of js_backend; |
6 | 6 |
7 class ConstantEmitter implements ConstantVisitor { | 7 class ConstantEmitter { |
8 final Compiler compiler; | 8 ConstantReferenceEmitter _referenceEmitter; |
9 final Namer namer; | 9 ConstantInitializerEmitter _initializerEmitter; |
10 | 10 |
11 CodeBuffer buffer; | 11 ConstantEmitter(Compiler compiler, Namer namer) { |
12 bool shouldEmitCanonicalVersion; | 12 _referenceEmitter = new ConstantReferenceEmitter(compiler, namer); |
13 | 13 _initializerEmitter = new ConstantInitializerEmitter( |
14 ConstantEmitter(this.compiler, this.namer); | 14 compiler, namer, _referenceEmitter); |
15 | |
16 /** | |
17 * Unless the constant can be emitted multiple times (as for numbers and | |
18 * strings) use the canonical name. | |
19 */ | |
20 void emitCanonicalVersionOfConstant(Constant constant, CodeBuffer newBuffer) { | |
21 shouldEmitCanonicalVersion = true; | |
22 buffer = newBuffer; | |
23 _visit(constant); | |
24 } | 15 } |
25 | 16 |
26 /** | 17 /** |
27 * Emit the JavaScript code of the constant. If the constant must be | 18 * Constructs an expression that is a reference to the constant. Uses a |
28 * canonicalized this method emits the initialization value. | 19 * canonical name unless the constant can be emitted multiple times (as for |
| 20 * numbers and strings). |
29 */ | 21 */ |
30 void emitJavaScriptCodeForConstant(Constant constant, CodeBuffer newBuffer) { | 22 js.Expression reference(Constant constant) { |
31 shouldEmitCanonicalVersion = false; | 23 return _referenceEmitter.generate(constant); |
32 buffer = newBuffer; | |
33 _visit(constant); | |
34 } | 24 } |
35 | 25 |
36 _visit(Constant constant) { | 26 /** |
37 constant.accept(this); | 27 * Constructs an expression like [reference], but the expression is valid |
| 28 * during isolate initialization. |
| 29 */ |
| 30 js.Expression referenceInInitializationContext(Constant constant) { |
| 31 return _referenceEmitter.generateInInitializationContext(constant); |
38 } | 32 } |
39 | 33 |
40 void visitSentinel(SentinelConstant constant) { | 34 /** |
41 if (shouldEmitCanonicalVersion) { | 35 * Constructs an expression used to initialize a canonicalized constant. |
42 buffer.add(namer.CURRENT_ISOLATE); | 36 */ |
| 37 js.Expression initializationExpression(Constant constant) { |
| 38 return _initializerEmitter.generate(constant); |
| 39 } |
| 40 } |
| 41 |
| 42 /** |
| 43 * Visitor for generating JavaScript expressions to refer to [Constant]s. |
| 44 * Do not use directly, use methods from [ConstantEmitter]. |
| 45 */ |
| 46 class ConstantReferenceEmitter implements ConstantVisitor<js.Expression> { |
| 47 final Compiler compiler; |
| 48 final Namer namer; |
| 49 bool inIsolateInitializationContext = false; |
| 50 |
| 51 ConstantReferenceEmitter(this.compiler, this.namer); |
| 52 |
| 53 js.Expression generate(Constant constant) { |
| 54 inIsolateInitializationContext = false; |
| 55 return _visit(constant); |
| 56 } |
| 57 |
| 58 js.Expression generateInInitializationContext(Constant constant) { |
| 59 inIsolateInitializationContext = true; |
| 60 return _visit(constant); |
| 61 } |
| 62 |
| 63 js.Expression _visit(Constant constant) { |
| 64 return constant.accept(this); |
| 65 } |
| 66 |
| 67 js.Expression visitSentinel(SentinelConstant constant) { |
| 68 return new js.VariableUse(namer.CURRENT_ISOLATE); |
| 69 } |
| 70 |
| 71 js.Expression visitFunction(FunctionConstant constant) { |
| 72 return inIsolateInitializationContext |
| 73 ? new js.VariableUse(namer.isolatePropertiesAccess(constant.element)) |
| 74 : new js.VariableUse(namer.isolateAccess(constant.element)); |
| 75 } |
| 76 |
| 77 js.Expression visitNull(NullConstant constant) { |
| 78 return new js.LiteralNull(); |
| 79 } |
| 80 |
| 81 js.Expression visitInt(IntConstant constant) { |
| 82 return new js.LiteralNumber('${constant.value}'); |
| 83 } |
| 84 |
| 85 js.Expression visitDouble(DoubleConstant constant) { |
| 86 double value = constant.value; |
| 87 if (value.isNaN) { |
| 88 return new js.LiteralNumber("(0/0)"); |
| 89 } else if (value == double.INFINITY) { |
| 90 return new js.LiteralNumber("(1/0)"); |
| 91 } else if (value == -double.INFINITY) { |
| 92 return new js.LiteralNumber("(-1/0)"); |
43 } else { | 93 } else { |
44 compiler.internalError( | 94 return new js.LiteralNumber("$value"); |
45 "The parameter sentinel constant does not need specific JS code"); | |
46 } | 95 } |
47 } | 96 } |
48 | 97 |
49 void visitFunction(FunctionConstant constant) { | 98 js.Expression visitTrue(TrueConstant constant) { |
50 if (shouldEmitCanonicalVersion) { | 99 if (compiler.enableMinification) { |
51 buffer.add(namer.isolatePropertiesAccess(constant.element)); | 100 // Use !0 for true. |
| 101 return new js.Prefix("!", new js.LiteralNumber("0")); |
52 } else { | 102 } else { |
53 compiler.internalError( | 103 return new js.LiteralBool(true); |
54 "The function constant does not need specific JS code"); | 104 } |
| 105 |
| 106 } |
| 107 |
| 108 js.Expression visitFalse(FalseConstant constant) { |
| 109 if (compiler.enableMinification) { |
| 110 // Use !1 for false. |
| 111 return new js.Prefix("!", new js.LiteralNumber("1")); |
| 112 } else { |
| 113 return new js.LiteralBool(false); |
55 } | 114 } |
56 } | 115 } |
57 | 116 |
58 void visitNull(NullConstant constant) { | |
59 buffer.add("null"); | |
60 } | |
61 | |
62 void visitInt(IntConstant constant) { | |
63 buffer.add(constant.value.toString()); | |
64 } | |
65 | |
66 void visitDouble(DoubleConstant constant) { | |
67 double value = constant.value; | |
68 if (value.isNaN) { | |
69 buffer.add("(0/0)"); | |
70 } else if (value == double.INFINITY) { | |
71 buffer.add("(1/0)"); | |
72 } else if (value == -double.INFINITY) { | |
73 buffer.add("(-1/0)"); | |
74 } else { | |
75 buffer.add("$value"); | |
76 } | |
77 } | |
78 | |
79 void visitTrue(TrueConstant constant) { | |
80 buffer.add("true"); | |
81 } | |
82 | |
83 void visitFalse(FalseConstant constant) { | |
84 buffer.add("false"); | |
85 } | |
86 | |
87 /** | 117 /** |
88 * Write the contents of the quoted string to a [CodeBuffer] in | 118 * Write the contents of the quoted string to a [CodeBuffer] in |
89 * a form that is valid as JavaScript string literal content. | 119 * a form that is valid as JavaScript string literal content. |
90 * The string is assumed quoted by double quote characters. | 120 * The string is assumed quoted by double quote characters. |
91 */ | 121 */ |
92 void visitString(StringConstant constant) { | 122 js.Expression visitString(StringConstant constant) { |
93 buffer.add('"'); | 123 // TODO(sra): If the string is long *and repeated* (and not on a hot path) |
94 writeJsonEscapedCharsOn(constant.value.slowToString(), buffer); | 124 // then it should be assigned to a name. We don't have reference counts (or |
95 buffer.add('"'); | 125 // profile information) here, so this is the wrong place. |
96 } | 126 StringBuffer sb = new StringBuffer(); |
97 | 127 writeJsonEscapedCharsOn(constant.value.slowToString(), sb); |
98 void emitCanonicalVersion(Constant constant) { | 128 return new js.LiteralString('"$sb"'); |
| 129 } |
| 130 |
| 131 js.Expression emitCanonicalVersion(Constant constant) { |
99 String name = namer.constantName(constant); | 132 String name = namer.constantName(constant); |
100 buffer.add(namer.isolatePropertiesAccessForConstant(name)); | 133 if (inIsolateInitializationContext) { |
101 } | 134 // $ISOLATE.$ISOLATE_PROPERTIES.$name |
102 | 135 return new js.PropertyAccess.field( |
103 void visitList(ListConstant constant) { | 136 new js.PropertyAccess.field( |
104 if (shouldEmitCanonicalVersion) { | 137 new js.VariableUse(namer.ISOLATE), |
105 emitCanonicalVersion(constant); | 138 namer.ISOLATE_PROPERTIES), |
| 139 name); |
106 } else { | 140 } else { |
107 shouldEmitCanonicalVersion = true; | 141 return new js.PropertyAccess.field( |
108 buffer.add("${namer.ISOLATE}.makeConstantList"); | 142 new js.VariableUse(namer.CURRENT_ISOLATE), |
109 buffer.add("(["); | 143 name); |
110 for (int i = 0; i < constant.entries.length; i++) { | 144 } |
111 if (i != 0) buffer.add(", "); | 145 } |
112 _visit(constant.entries[i]); | 146 |
113 } | 147 js.Expression visitList(ListConstant constant) { |
114 buffer.add("])"); | 148 return emitCanonicalVersion(constant); |
115 } | 149 } |
| 150 |
| 151 js.Expression visitMap(MapConstant constant) { |
| 152 return emitCanonicalVersion(constant); |
| 153 } |
| 154 |
| 155 js.Expression visitType(TypeConstant constant) { |
| 156 return emitCanonicalVersion(constant); |
| 157 } |
| 158 |
| 159 js.Expression visitConstructed(ConstructedConstant constant) { |
| 160 return emitCanonicalVersion(constant); |
| 161 } |
| 162 } |
| 163 |
| 164 /** |
| 165 * Visitor for generating JavaScript expressions to initialize [Constant]s. |
| 166 * Do not use directly; use methods from [ConstantEmitter]. |
| 167 */ |
| 168 class ConstantInitializerEmitter implements ConstantVisitor<js.Expression> { |
| 169 final Compiler compiler; |
| 170 final Namer namer; |
| 171 final ConstantReferenceEmitter referenceEmitter; |
| 172 |
| 173 ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter); |
| 174 |
| 175 js.Expression generate(Constant constant) { |
| 176 return _visit(constant); |
| 177 } |
| 178 |
| 179 js.Expression _visit(Constant constant) { |
| 180 return constant.accept(this); |
| 181 } |
| 182 |
| 183 js.Expression _reference(Constant constant) { |
| 184 return referenceEmitter.generateInInitializationContext(constant); |
| 185 } |
| 186 |
| 187 js.Expression visitSentinel(SentinelConstant constant) { |
| 188 compiler.internalError( |
| 189 "The parameter sentinel constant does not need specific JS code"); |
| 190 } |
| 191 |
| 192 js.Expression visitFunction(FunctionConstant constant) { |
| 193 compiler.internalError( |
| 194 "The function constant does not need specific JS code"); |
| 195 } |
| 196 |
| 197 js.Expression visitNull(NullConstant constant) { |
| 198 return _reference(constant); |
| 199 } |
| 200 |
| 201 js.Expression visitInt(IntConstant constant) { |
| 202 return _reference(constant); |
| 203 } |
| 204 |
| 205 js.Expression visitDouble(DoubleConstant constant) { |
| 206 return _reference(constant); |
| 207 } |
| 208 |
| 209 js.Expression visitTrue(TrueConstant constant) { |
| 210 return _reference(constant); |
| 211 } |
| 212 |
| 213 js.Expression visitFalse(FalseConstant constant) { |
| 214 return _reference(constant); |
| 215 } |
| 216 |
| 217 js.Expression visitString(StringConstant constant) { |
| 218 // TODO(sra): Some larger strings are worth sharing. |
| 219 return _reference(constant); |
| 220 } |
| 221 |
| 222 js.Expression visitList(ListConstant constant) { |
| 223 return new js.Call( |
| 224 new js.PropertyAccess.field( |
| 225 new js.VariableUse(namer.ISOLATE), |
| 226 'makeConstantList'), |
| 227 [new js.ArrayInitializer.from(_array(constant.entries))]); |
116 } | 228 } |
117 | 229 |
118 String getJsConstructor(ClassElement element) { | 230 String getJsConstructor(ClassElement element) { |
119 return namer.isolatePropertiesAccess(element); | 231 return namer.isolatePropertiesAccess(element); |
120 } | 232 } |
121 | 233 |
122 void visitMap(MapConstant constant) { | 234 js.Expression visitMap(MapConstant constant) { |
123 if (shouldEmitCanonicalVersion) { | 235 js.Expression jsMap() { |
124 emitCanonicalVersion(constant); | 236 List<js.Property> properties = <js.Property>[]; |
| 237 int valueIndex = 0; |
| 238 for (int i = 0; i < constant.keys.entries.length; i++) { |
| 239 StringConstant key = constant.keys.entries[i]; |
| 240 if (key.value == MapConstant.PROTO_PROPERTY) continue; |
| 241 |
| 242 // Keys in literal maps must be emitted in place. |
| 243 js.Literal keyExpression = _visit(key); |
| 244 js.Expression valueExpression = |
| 245 _reference(constant.values[valueIndex++]); |
| 246 properties.add(new js.Property(keyExpression, valueExpression)); |
| 247 } |
| 248 if (valueIndex != constant.values.length) { |
| 249 compiler.internalError("Bad value count."); |
| 250 } |
| 251 return new js.ObjectInitializer(properties); |
| 252 } |
| 253 |
| 254 void badFieldCountError() { |
| 255 compiler.internalError( |
| 256 "Compiler and ConstantMap disagree on number of fields."); |
| 257 } |
| 258 |
| 259 ClassElement classElement = constant.type.element; |
| 260 |
| 261 List<js.Expression> arguments = <js.Expression>[]; |
| 262 |
| 263 // The arguments of the JavaScript constructor for any given Dart class |
| 264 // are in the same order as the members of the class element. |
| 265 int emittedArgumentCount = 0; |
| 266 classElement.implementation.forEachInstanceField( |
| 267 (ClassElement enclosing, Element field) { |
| 268 if (field.name == MapConstant.LENGTH_NAME) { |
| 269 arguments.add( |
| 270 new js.LiteralNumber('${constant.keys.entries.length}')); |
| 271 } else if (field.name == MapConstant.JS_OBJECT_NAME) { |
| 272 arguments.add(jsMap()); |
| 273 } else if (field.name == MapConstant.KEYS_NAME) { |
| 274 arguments.add(_reference(constant.keys)); |
| 275 } else if (field.name == MapConstant.PROTO_VALUE) { |
| 276 assert(constant.protoValue != null); |
| 277 arguments.add(_reference(constant.protoValue)); |
| 278 } else { |
| 279 badFieldCountError(); |
| 280 } |
| 281 emittedArgumentCount++; |
| 282 }, |
| 283 includeBackendMembers: true, |
| 284 includeSuperMembers: true); |
| 285 |
| 286 if ((constant.protoValue == null && emittedArgumentCount != 3) || |
| 287 (constant.protoValue != null && emittedArgumentCount != 4)) { |
| 288 badFieldCountError(); |
| 289 } |
| 290 |
| 291 return new js.New( |
| 292 new js.VariableUse(getJsConstructor(classElement)), |
| 293 arguments); |
| 294 } |
| 295 |
| 296 js.Expression visitType(TypeConstant constant) { |
| 297 SourceString helperSourceName = const SourceString('createRuntimeType'); |
| 298 Element helper = compiler.findHelper(helperSourceName); |
| 299 JavaScriptBackend backend = compiler.backend; |
| 300 String helperName = backend.namer.getName(helper); |
| 301 DartType type = constant.representedType; |
| 302 Element element = type.element; |
| 303 String typeName; |
| 304 if (type.kind == TypeKind.INTERFACE) { |
| 305 typeName = |
| 306 backend.rti.getStringRepresentation(type, expandRawType: true); |
125 } else { | 307 } else { |
126 void writeJsMap() { | 308 assert(type.kind == TypeKind.TYPEDEF); |
127 buffer.add("{"); | 309 typeName = element.name.slowToString(); |
128 int valueIndex = 0; | 310 } |
129 for (int i = 0; i < constant.keys.entries.length; i++) { | 311 return new js.Call( |
130 StringConstant key = constant.keys.entries[i]; | 312 new js.PropertyAccess.field( |
131 if (key.value == MapConstant.PROTO_PROPERTY) continue; | 313 new js.VariableUse(namer.CURRENT_ISOLATE), |
132 | 314 helperName), |
133 if (valueIndex != 0) buffer.add(", "); | 315 [new js.LiteralString("'$typeName'")]); |
134 | 316 } |
135 // Keys in literal maps must be emitted in place. | 317 |
136 emitJavaScriptCodeForConstant(key, buffer); | 318 js.Expression visitConstructed(ConstructedConstant constant) { |
137 | 319 return new js.New( |
138 buffer.add(": "); | 320 new js.VariableUse(getJsConstructor(constant.type.element)), |
139 emitCanonicalVersionOfConstant(constant.values[valueIndex++], buffer); | 321 _array(constant.fields)); |
140 } | 322 } |
141 buffer.add("}"); | 323 |
142 if (valueIndex != constant.values.length) { | 324 List<js.Expression> _array(List<Constant> values) { |
143 compiler.internalError("Bad value count."); | 325 List<js.Expression> valueList = <js.Expression>[]; |
144 } | 326 for (int i = 0; i < values.length; i++) { |
145 } | 327 valueList.add(_reference(values[i])); |
146 | 328 } |
147 void badFieldCountError() { | 329 return valueList; |
148 compiler.internalError( | |
149 "Compiler and ConstantMap disagree on number of fields."); | |
150 } | |
151 | |
152 shouldEmitCanonicalVersion = true; | |
153 | |
154 ClassElement classElement = constant.type.element; | |
155 buffer.add("new "); | |
156 buffer.add(getJsConstructor(classElement)); | |
157 buffer.add("("); | |
158 // The arguments of the JavaScript constructor for any given Dart class | |
159 // are in the same order as the members of the class element. | |
160 int emittedArgumentCount = 0; | |
161 classElement.implementation.forEachInstanceField( | |
162 (ClassElement enclosing, Element field) { | |
163 if (emittedArgumentCount != 0) buffer.add(", "); | |
164 if (field.name == MapConstant.LENGTH_NAME) { | |
165 buffer.add(constant.keys.entries.length); | |
166 } else if (field.name == MapConstant.JS_OBJECT_NAME) { | |
167 writeJsMap(); | |
168 } else if (field.name == MapConstant.KEYS_NAME) { | |
169 emitCanonicalVersionOfConstant(constant.keys, buffer); | |
170 } else if (field.name == MapConstant.PROTO_VALUE) { | |
171 assert(constant.protoValue != null); | |
172 emitCanonicalVersionOfConstant(constant.protoValue, buffer); | |
173 } else { | |
174 badFieldCountError(); | |
175 } | |
176 emittedArgumentCount++; | |
177 }, | |
178 includeBackendMembers: true, | |
179 includeSuperMembers: true); | |
180 if ((constant.protoValue == null && emittedArgumentCount != 3) || | |
181 (constant.protoValue != null && emittedArgumentCount != 4)) { | |
182 badFieldCountError(); | |
183 } | |
184 buffer.add(")"); | |
185 } | |
186 } | |
187 | |
188 void visitType(TypeConstant constant) { | |
189 if (shouldEmitCanonicalVersion) { | |
190 emitCanonicalVersion(constant); | |
191 } else { | |
192 SourceString helperSourceName = | |
193 const SourceString('createRuntimeType'); | |
194 Element helper = compiler.findHelper(helperSourceName); | |
195 JavaScriptBackend backend = compiler.backend; | |
196 String helperName = backend.namer.getName(helper); | |
197 DartType type = constant.representedType; | |
198 Element element = type.element; | |
199 String typeName; | |
200 if (type.kind == TypeKind.INTERFACE) { | |
201 typeName = | |
202 backend.rti.getStringRepresentation(type, expandRawType: true); | |
203 } else { | |
204 assert(type.kind == TypeKind.TYPEDEF); | |
205 typeName = element.name.slowToString(); | |
206 } | |
207 buffer.add("${namer.CURRENT_ISOLATE}.$helperName('$typeName')"); | |
208 } | |
209 } | |
210 | |
211 void visitConstructed(ConstructedConstant constant) { | |
212 if (shouldEmitCanonicalVersion) { | |
213 emitCanonicalVersion(constant); | |
214 } else { | |
215 shouldEmitCanonicalVersion = true; | |
216 | |
217 buffer.add("new "); | |
218 buffer.add(getJsConstructor(constant.type.element)); | |
219 buffer.add("("); | |
220 for (int i = 0; i < constant.fields.length; i++) { | |
221 if (i != 0) buffer.add(", "); | |
222 _visit(constant.fields[i]); | |
223 } | |
224 buffer.add(")"); | |
225 } | |
226 } | 330 } |
227 } | 331 } |
OLD | NEW |