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 { | 7 class ConstantEmitter implements ConstantVisitor { |
8 ConstantReferenceEmitter _referenceEmitter; | 8 final Compiler compiler; |
9 ConstantInitializerEmitter _initializerEmitter; | 9 final Namer namer; |
10 | 10 |
11 ConstantEmitter(Compiler compiler, Namer namer) { | 11 CodeBuffer buffer; |
12 _referenceEmitter = new ConstantReferenceEmitter(compiler, namer); | 12 bool shouldEmitCanonicalVersion; |
13 _initializerEmitter = new ConstantInitializerEmitter( | 13 |
14 compiler, namer, _referenceEmitter); | 14 ConstantEmitter(this.compiler, this.namer); |
| 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); |
15 } | 24 } |
16 | 25 |
17 /** | 26 /** |
18 * Constructs an expression that is a reference to the constant. Uses a | 27 * Emit the JavaScript code of the constant. If the constant must be |
19 * canonical name unless the constant can be emitted multiple times (as for | 28 * canonicalized this method emits the initialization value. |
20 * numbers and strings). | |
21 */ | 29 */ |
22 js.Expression reference(Constant constant) { | 30 void emitJavaScriptCodeForConstant(Constant constant, CodeBuffer newBuffer) { |
23 return _referenceEmitter.generate(constant); | 31 shouldEmitCanonicalVersion = false; |
| 32 buffer = newBuffer; |
| 33 _visit(constant); |
| 34 } |
| 35 |
| 36 _visit(Constant constant) { |
| 37 constant.accept(this); |
| 38 } |
| 39 |
| 40 void visitSentinel(SentinelConstant constant) { |
| 41 if (shouldEmitCanonicalVersion) { |
| 42 buffer.add(namer.CURRENT_ISOLATE); |
| 43 } else { |
| 44 compiler.internalError( |
| 45 "The parameter sentinel constant does not need specific JS code"); |
| 46 } |
| 47 } |
| 48 |
| 49 void visitFunction(FunctionConstant constant) { |
| 50 if (shouldEmitCanonicalVersion) { |
| 51 buffer.add(namer.isolatePropertiesAccess(constant.element)); |
| 52 } else { |
| 53 compiler.internalError( |
| 54 "The function constant does not need specific JS code"); |
| 55 } |
| 56 } |
| 57 |
| 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"); |
24 } | 85 } |
25 | 86 |
26 /** | 87 /** |
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); | |
32 } | |
33 | |
34 /** | |
35 * Constructs an expression used to initialize a canonicalized constant. | |
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)"); | |
93 } else { | |
94 return new js.LiteralNumber("$value"); | |
95 } | |
96 } | |
97 | |
98 js.Expression visitTrue(TrueConstant constant) { | |
99 if (compiler.enableMinification) { | |
100 // Use !0 for true. | |
101 return new js.Prefix("!", new js.LiteralNumber("0")); | |
102 } else { | |
103 return new js.LiteralBool(true); | |
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); | |
114 } | |
115 } | |
116 | |
117 /** | |
118 * Write the contents of the quoted string to a [CodeBuffer] in | 88 * Write the contents of the quoted string to a [CodeBuffer] in |
119 * a form that is valid as JavaScript string literal content. | 89 * a form that is valid as JavaScript string literal content. |
120 * The string is assumed quoted by double quote characters. | 90 * The string is assumed quoted by double quote characters. |
121 */ | 91 */ |
122 js.Expression visitString(StringConstant constant) { | 92 void visitString(StringConstant constant) { |
123 // TODO(sra): If the string is long *and repeated* (and not on a hot path) | 93 buffer.add('"'); |
124 // then it should be assigned to a name. We don't have reference counts (or | 94 writeJsonEscapedCharsOn(constant.value.slowToString(), buffer); |
125 // profile information) here, so this is the wrong place. | 95 buffer.add('"'); |
126 StringBuffer sb = new StringBuffer(); | |
127 writeJsonEscapedCharsOn(constant.value.slowToString(), sb); | |
128 return new js.LiteralString('"$sb"'); | |
129 } | 96 } |
130 | 97 |
131 js.Expression emitCanonicalVersion(Constant constant) { | 98 void emitCanonicalVersion(Constant constant) { |
132 String name = namer.constantName(constant); | 99 String name = namer.constantName(constant); |
133 if (inIsolateInitializationContext) { | 100 buffer.add(namer.isolatePropertiesAccessForConstant(name)); |
134 // $ISOLATE.$ISOLATE_PROPERTIES.$name | |
135 return new js.PropertyAccess.field( | |
136 new js.PropertyAccess.field( | |
137 new js.VariableUse(namer.ISOLATE), | |
138 namer.ISOLATE_PROPERTIES), | |
139 name); | |
140 } else { | |
141 return new js.PropertyAccess.field( | |
142 new js.VariableUse(namer.CURRENT_ISOLATE), | |
143 name); | |
144 } | |
145 } | 101 } |
146 | 102 |
147 js.Expression visitList(ListConstant constant) { | 103 void visitList(ListConstant constant) { |
148 return emitCanonicalVersion(constant); | 104 if (shouldEmitCanonicalVersion) { |
149 } | 105 emitCanonicalVersion(constant); |
150 | 106 } else { |
151 js.Expression visitMap(MapConstant constant) { | 107 shouldEmitCanonicalVersion = true; |
152 return emitCanonicalVersion(constant); | 108 buffer.add("${namer.ISOLATE}.makeConstantList"); |
153 } | 109 buffer.add("(["); |
154 | 110 for (int i = 0; i < constant.entries.length; i++) { |
155 js.Expression visitType(TypeConstant constant) { | 111 if (i != 0) buffer.add(", "); |
156 return emitCanonicalVersion(constant); | 112 _visit(constant.entries[i]); |
157 } | 113 } |
158 | 114 buffer.add("])"); |
159 js.Expression visitConstructed(ConstructedConstant constant) { | 115 } |
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))]); | |
228 } | 116 } |
229 | 117 |
230 String getJsConstructor(ClassElement element) { | 118 String getJsConstructor(ClassElement element) { |
231 return namer.isolatePropertiesAccess(element); | 119 return namer.isolatePropertiesAccess(element); |
232 } | 120 } |
233 | 121 |
234 js.Expression visitMap(MapConstant constant) { | 122 void visitMap(MapConstant constant) { |
235 js.Expression jsMap() { | 123 if (shouldEmitCanonicalVersion) { |
236 List<js.Property> properties = <js.Property>[]; | 124 emitCanonicalVersion(constant); |
237 int valueIndex = 0; | 125 } else { |
238 for (int i = 0; i < constant.keys.entries.length; i++) { | 126 void writeJsMap() { |
239 StringConstant key = constant.keys.entries[i]; | 127 buffer.add("{"); |
240 if (key.value == MapConstant.PROTO_PROPERTY) continue; | 128 int valueIndex = 0; |
| 129 for (int i = 0; i < constant.keys.entries.length; i++) { |
| 130 StringConstant key = constant.keys.entries[i]; |
| 131 if (key.value == MapConstant.PROTO_PROPERTY) continue; |
241 | 132 |
242 // Keys in literal maps must be emitted in place. | 133 if (valueIndex != 0) buffer.add(", "); |
243 js.Literal keyExpression = _visit(key); | 134 |
244 js.Expression valueExpression = | 135 // Keys in literal maps must be emitted in place. |
245 _reference(constant.values[valueIndex++]); | 136 emitJavaScriptCodeForConstant(key, buffer); |
246 properties.add(new js.Property(keyExpression, valueExpression)); | 137 |
| 138 buffer.add(": "); |
| 139 emitCanonicalVersionOfConstant(constant.values[valueIndex++], buffer); |
| 140 } |
| 141 buffer.add("}"); |
| 142 if (valueIndex != constant.values.length) { |
| 143 compiler.internalError("Bad value count."); |
| 144 } |
247 } | 145 } |
248 if (valueIndex != constant.values.length) { | 146 |
249 compiler.internalError("Bad value count."); | 147 void badFieldCountError() { |
| 148 compiler.internalError( |
| 149 "Compiler and ConstantMap disagree on number of fields."); |
250 } | 150 } |
251 return new js.ObjectInitializer(properties); | 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(")"); |
252 } | 185 } |
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 } | 186 } |
295 | 187 |
296 js.Expression visitType(TypeConstant constant) { | 188 void visitType(TypeConstant constant) { |
297 SourceString helperSourceName = const SourceString('createRuntimeType'); | 189 if (shouldEmitCanonicalVersion) { |
298 Element helper = compiler.findHelper(helperSourceName); | 190 emitCanonicalVersion(constant); |
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); | |
307 } else { | 191 } else { |
308 assert(type.kind == TypeKind.TYPEDEF); | 192 SourceString helperSourceName = |
309 typeName = element.name.slowToString(); | 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')"); |
310 } | 208 } |
311 return new js.Call( | |
312 new js.PropertyAccess.field( | |
313 new js.VariableUse(namer.CURRENT_ISOLATE), | |
314 helperName), | |
315 [new js.LiteralString("'$typeName'")]); | |
316 } | 209 } |
317 | 210 |
318 js.Expression visitConstructed(ConstructedConstant constant) { | 211 void visitConstructed(ConstructedConstant constant) { |
319 return new js.New( | 212 if (shouldEmitCanonicalVersion) { |
320 new js.VariableUse(getJsConstructor(constant.type.element)), | 213 emitCanonicalVersion(constant); |
321 _array(constant.fields)); | 214 } else { |
322 } | 215 shouldEmitCanonicalVersion = true; |
323 | 216 |
324 List<js.Expression> _array(List<Constant> values) { | 217 buffer.add("new "); |
325 List<js.Expression> valueList = <js.Expression>[]; | 218 buffer.add(getJsConstructor(constant.type.element)); |
326 for (int i = 0; i < values.length; i++) { | 219 buffer.add("("); |
327 valueList.add(_reference(values[i])); | 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(")"); |
328 } | 225 } |
329 return valueList; | |
330 } | 226 } |
331 } | 227 } |
OLD | NEW |