blob: 7b22686299e6371e0570c17f9479fb42c1caef3f [file] [log] [blame]
Olli Etuaho853dc1a2014-11-06 17:25:48 +02001//
2// Copyright (c) 2002-2014 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7#include "compiler/translator/EmulatePrecision.h"
8
9namespace
10{
11
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030012static void writeVectorPrecisionEmulationHelpers(TInfoSinkBase &sink,
13 const ShShaderOutput outputLanguage,
14 const unsigned int size)
Olli Etuaho853dc1a2014-11-06 17:25:48 +020015{
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030016 std::stringstream vecTypeStrStr;
17 if (outputLanguage == SH_ESSL_OUTPUT)
18 vecTypeStrStr << "highp ";
19 vecTypeStrStr << "vec" << size;
20 std::string vecType = vecTypeStrStr.str();
Olli Etuaho853dc1a2014-11-06 17:25:48 +020021
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030022 sink <<
23 vecType << " angle_frm(in " << vecType << " v) {\n"
24 " v = clamp(v, -65504.0, 65504.0);\n"
25 " " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n"
26 " bvec" << size << " isNonZero = greaterThanEqual(exponent, vec" << size << "(-25.0));\n"
27 " v = v * exp2(-exponent);\n"
28 " v = sign(v) * floor(abs(v));\n"
29 " return v * exp2(exponent) * vec" << size << "(isNonZero);\n"
30 "}\n";
Olli Etuaho853dc1a2014-11-06 17:25:48 +020031
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030032 sink <<
33 vecType << " angle_frl(in " << vecType << " v) {\n"
34 " v = clamp(v, -2.0, 2.0);\n"
35 " v = v * 256.0;\n"
36 " v = sign(v) * floor(abs(v));\n"
37 " return v * 0.00390625;\n"
38 "}\n";
Olli Etuaho853dc1a2014-11-06 17:25:48 +020039}
40
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030041static void writeMatrixPrecisionEmulationHelper(TInfoSinkBase &sink,
42 const ShShaderOutput outputLanguage,
43 const unsigned int columns,
44 const unsigned int rows,
45 const char *functionName)
46{
47 std::stringstream matTypeStrStr;
48 if (outputLanguage == SH_ESSL_OUTPUT)
49 matTypeStrStr << "highp ";
50 matTypeStrStr << "mat" << columns;
51 if (rows != columns)
52 {
53 matTypeStrStr << "x" << rows;
54 }
55
56 std::string matType = matTypeStrStr.str();
57
58 sink << matType << " " << functionName << "(in " << matType << " m) {\n"
59 " " << matType << " rounded;\n";
60
61 for (unsigned int i = 0; i < columns; ++i)
62 {
63 sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n";
64 }
65
66 sink << " return rounded;\n"
67 "}\n";
68}
69
70static void writeCommonPrecisionEmulationHelpers(TInfoSinkBase &sink,
71 const int shaderVersion,
72 const ShShaderOutput outputLanguage)
Olli Etuaho853dc1a2014-11-06 17:25:48 +020073{
74 // Write the angle_frm functions that round floating point numbers to
75 // half precision, and angle_frl functions that round them to minimum lowp
76 // precision.
77
78 // Unoptimized version of angle_frm for single floats:
79 //
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030080 // int webgl_maxNormalExponent(in int exponentBits) {
Olli Etuaho853dc1a2014-11-06 17:25:48 +020081 // int possibleExponents = int(exp2(float(exponentBits)));
82 // int exponentBias = possibleExponents / 2 - 1;
83 // int allExponentBitsOne = possibleExponents - 1;
84 // return (allExponentBitsOne - 1) - exponentBias;
85 // }
86 //
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030087 // float angle_frm(in float x) {
Olli Etuaho853dc1a2014-11-06 17:25:48 +020088 // int mantissaBits = 10;
89 // int exponentBits = 5;
90 // float possibleMantissas = exp2(float(mantissaBits));
91 // float mantissaMax = 2.0 - 1.0 / possibleMantissas;
92 // int maxNE = webgl_maxNormalExponent(exponentBits);
93 // float max = exp2(float(maxNE)) * mantissaMax;
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030094 // if (x > max) {
Olli Etuaho853dc1a2014-11-06 17:25:48 +020095 // return max;
96 // }
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +030097 // if (x < -max) {
Olli Etuaho853dc1a2014-11-06 17:25:48 +020098 // return -max;
99 // }
100 // float exponent = floor(log2(abs(x)));
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300101 // if (abs(x) == 0.0 || exponent < -float(maxNE)) {
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200102 // return 0.0 * sign(x)
103 // }
104 // x = x * exp2(-(exponent - float(mantissaBits)));
105 // x = sign(x) * floor(abs(x));
106 // return x * exp2(exponent - float(mantissaBits));
107 // }
108
109 // All numbers with a magnitude less than 2^-15 are subnormal, and are
110 // flushed to zero.
111
112 // Note the constant numbers below:
113 // a) 65504 is the maximum possible mantissa (1.1111111111 in binary) times
114 // 2^15, the maximum normal exponent.
115 // b) 10.0 is the number of mantissa bits.
116 // c) -25.0 is the minimum normal half-float exponent -15.0 minus the number
117 // of mantissa bits.
118 // d) + 1e-30 is to make sure the argument of log2() won't be zero. It can
119 // only affect the result of log2 on x where abs(x) < 1e-22. Since these
120 // numbers will be flushed to zero either way (2^-15 is the smallest
121 // normal positive number), this does not introduce any error.
122
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300123 std::string floatType = "float";
124 if (outputLanguage == SH_ESSL_OUTPUT)
125 floatType = "highp float";
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200126
127 sink <<
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300128 floatType << " angle_frm(in " << floatType << " x) {\n"
129 " x = clamp(x, -65504.0, 65504.0);\n"
130 " " << floatType << " exponent = floor(log2(abs(x) + 1e-30)) - 10.0;\n"
131 " bool isNonZero = (exponent >= -25.0);\n"
132 " x = x * exp2(-exponent);\n"
133 " x = sign(x) * floor(abs(x));\n"
134 " return x * exp2(exponent) * float(isNonZero);\n"
135 "}\n";
Olli Etuahoa42e8b22016-06-29 15:49:22 +0300136
137 sink <<
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300138 floatType << " angle_frl(in " << floatType << " x) {\n"
139 " x = clamp(x, -2.0, 2.0);\n"
140 " x = x * 256.0;\n"
141 " x = sign(x) * floor(abs(x));\n"
142 " return x * 0.00390625;\n"
143 "}\n";
144
145 writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 2);
146 writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 3);
147 writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 4);
148 if (shaderVersion > 100)
149 {
150 for (unsigned int columns = 2; columns <= 4; ++columns)
151 {
152 for (unsigned int rows = 2; rows <= 4; ++rows)
153 {
154 writeMatrixPrecisionEmulationHelper(sink, outputLanguage, columns, rows,
155 "angle_frm");
156 writeMatrixPrecisionEmulationHelper(sink, outputLanguage, columns, rows,
157 "angle_frl");
158 }
159 }
160 }
161 else
162 {
163 for (unsigned int size = 2; size <= 4; ++size)
164 {
165 writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, size, "angle_frm");
166 writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, size, "angle_frl");
167 }
168 }
Olli Etuahoa42e8b22016-06-29 15:49:22 +0300169}
170
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300171static void writeCompoundAssignmentPrecisionEmulation(
172 TInfoSinkBase& sink, ShShaderOutput outputLanguage,
173 const char *lType, const char *rType, const char *opStr, const char *opNameStr)
Olli Etuahoa42e8b22016-06-29 15:49:22 +0300174{
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300175 std::string lTypeStr = lType;
176 std::string rTypeStr = rType;
177 if (outputLanguage == SH_ESSL_OUTPUT)
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200178 {
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300179 std::stringstream lTypeStrStr;
180 lTypeStrStr << "highp " << lType;
181 lTypeStr = lTypeStrStr.str();
182 std::stringstream rTypeStrStr;
183 rTypeStrStr << "highp " << rType;
184 rTypeStr = rTypeStrStr.str();
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200185 }
186
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300187 // Note that y should be passed through angle_frm at the function call site,
188 // but x can't be passed through angle_frm there since it is an inout parameter.
189 // So only pass x and the result through angle_frm here.
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200190 sink <<
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300191 lTypeStr << " angle_compound_" << opNameStr << "_frm(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
192 " x = angle_frm(angle_frm(x) " << opStr << " y);\n"
193 " return x;\n"
194 "}\n";
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200195 sink <<
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300196 lTypeStr << " angle_compound_" << opNameStr << "_frl(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
197 " x = angle_frl(angle_frm(x) " << opStr << " y);\n"
198 " return x;\n"
199 "}\n";
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200200}
201
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200202bool canRoundFloat(const TType &type)
203{
Olli Etuaho61b81ac2016-06-28 14:15:20 +0300204 return type.getBasicType() == EbtFloat && !type.isArray() &&
205 (type.getPrecision() == EbpLow || type.getPrecision() == EbpMedium);
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200206}
207
208TIntermAggregate *createInternalFunctionCallNode(TString name, TIntermNode *child)
209{
210 TIntermAggregate *callNode = new TIntermAggregate();
Olli Etuaho59f9a642015-08-06 20:38:26 +0300211 callNode->setOp(EOpFunctionCall);
212 TName nameObj(TFunction::mangleName(name));
213 nameObj.setInternal(true);
214 callNode->setNameObj(nameObj);
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200215 callNode->getSequence()->push_back(child);
216 return callNode;
217}
218
219TIntermAggregate *createRoundingFunctionCallNode(TIntermTyped *roundedChild)
220{
221 TString roundFunctionName;
222 if (roundedChild->getPrecision() == EbpMedium)
223 roundFunctionName = "angle_frm";
224 else
225 roundFunctionName = "angle_frl";
Olli Etuaho3820e9c2016-07-04 16:01:15 +0300226 TIntermAggregate *callNode = createInternalFunctionCallNode(roundFunctionName, roundedChild);
227 callNode->setType(roundedChild->getType());
228 return callNode;
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200229}
230
231TIntermAggregate *createCompoundAssignmentFunctionCallNode(TIntermTyped *left, TIntermTyped *right, const char *opNameStr)
232{
233 std::stringstream strstr;
234 if (left->getPrecision() == EbpMedium)
235 strstr << "angle_compound_" << opNameStr << "_frm";
236 else
237 strstr << "angle_compound_" << opNameStr << "_frl";
238 TString functionName = strstr.str().c_str();
239 TIntermAggregate *callNode = createInternalFunctionCallNode(functionName, left);
240 callNode->getSequence()->push_back(right);
241 return callNode;
242}
243
Olli Etuaho1be88702015-01-19 16:56:44 +0200244bool parentUsesResult(TIntermNode* parent, TIntermNode* node)
245{
246 if (!parent)
247 {
248 return false;
249 }
250
251 TIntermAggregate *aggParent = parent->getAsAggregate();
252 // If the parent's op is EOpSequence, the result is not assigned anywhere,
253 // so rounding it is not needed. In particular, this can avoid a lot of
254 // unnecessary rounding of unused return values of assignment.
255 if (aggParent && aggParent->getOp() == EOpSequence)
256 {
257 return false;
258 }
259 if (aggParent && aggParent->getOp() == EOpComma && (aggParent->getSequence()->back() != node))
260 {
261 return false;
262 }
263 return true;
264}
265
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200266} // namespace anonymous
267
Olli Etuaho217fe6e2015-08-05 13:25:08 +0300268EmulatePrecision::EmulatePrecision(const TSymbolTable &symbolTable, int shaderVersion)
269 : TLValueTrackingTraverser(true, true, true, symbolTable, shaderVersion),
270 mDeclaringVariables(false)
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200271{}
272
273void EmulatePrecision::visitSymbol(TIntermSymbol *node)
274{
Olli Etuahoa26ad582015-08-04 13:51:47 +0300275 if (canRoundFloat(node->getType()) && !mDeclaringVariables && !isLValueRequiredHere())
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200276 {
277 TIntermNode *parent = getParentNode();
278 TIntermNode *replacement = createRoundingFunctionCallNode(node);
279 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
280 }
281}
282
283
284bool EmulatePrecision::visitBinary(Visit visit, TIntermBinary *node)
285{
286 bool visitChildren = true;
287
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200288 TOperator op = node->getOp();
289
290 // RHS of initialize is not being declared.
291 if (op == EOpInitialize && visit == InVisit)
292 mDeclaringVariables = false;
293
294 if ((op == EOpIndexDirectStruct || op == EOpVectorSwizzle) && visit == InVisit)
295 visitChildren = false;
296
297 if (visit != PreVisit)
298 return visitChildren;
299
300 const TType& type = node->getType();
301 bool roundFloat = canRoundFloat(type);
302
303 if (roundFloat) {
304 switch (op) {
305 // Math operators that can result in a float may need to apply rounding to the return
306 // value. Note that in the case of assignment, the rounding is applied to its return
307 // value here, not the value being assigned.
308 case EOpAssign:
309 case EOpAdd:
310 case EOpSub:
311 case EOpMul:
312 case EOpDiv:
313 case EOpVectorTimesScalar:
314 case EOpVectorTimesMatrix:
315 case EOpMatrixTimesVector:
316 case EOpMatrixTimesScalar:
317 case EOpMatrixTimesMatrix:
318 {
319 TIntermNode *parent = getParentNode();
Olli Etuaho1be88702015-01-19 16:56:44 +0200320 if (!parentUsesResult(parent, node))
321 {
322 break;
323 }
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200324 TIntermNode *replacement = createRoundingFunctionCallNode(node);
325 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
326 break;
327 }
328
329 // Compound assignment cases need to replace the operator with a function call.
330 case EOpAddAssign:
331 {
Olli Etuahoe92507b2016-07-04 11:20:10 +0300332 mEmulateCompoundAdd.insert(
333 TypePair(type.getBuiltInTypeNameString(),
334 node->getRight()->getType().getBuiltInTypeNameString()));
335 TIntermNode *parent = getParentNode();
336 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
337 node->getLeft(), node->getRight(), "add");
338 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
339 break;
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200340 }
341 case EOpSubAssign:
342 {
Olli Etuahoe92507b2016-07-04 11:20:10 +0300343 mEmulateCompoundSub.insert(
344 TypePair(type.getBuiltInTypeNameString(),
345 node->getRight()->getType().getBuiltInTypeNameString()));
346 TIntermNode *parent = getParentNode();
347 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
348 node->getLeft(), node->getRight(), "sub");
349 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
350 break;
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200351 }
352 case EOpMulAssign:
353 case EOpVectorTimesMatrixAssign:
354 case EOpVectorTimesScalarAssign:
355 case EOpMatrixTimesScalarAssign:
356 case EOpMatrixTimesMatrixAssign:
357 {
Olli Etuahoe92507b2016-07-04 11:20:10 +0300358 mEmulateCompoundMul.insert(
359 TypePair(type.getBuiltInTypeNameString(),
360 node->getRight()->getType().getBuiltInTypeNameString()));
361 TIntermNode *parent = getParentNode();
362 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
363 node->getLeft(), node->getRight(), "mul");
364 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
365 break;
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200366 }
367 case EOpDivAssign:
368 {
Olli Etuahoe92507b2016-07-04 11:20:10 +0300369 mEmulateCompoundDiv.insert(
370 TypePair(type.getBuiltInTypeNameString(),
371 node->getRight()->getType().getBuiltInTypeNameString()));
372 TIntermNode *parent = getParentNode();
373 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
374 node->getLeft(), node->getRight(), "div");
375 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
376 break;
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200377 }
378 default:
379 // The rest of the binary operations should not need precision emulation.
380 break;
381 }
382 }
383 return visitChildren;
384}
385
386bool EmulatePrecision::visitAggregate(Visit visit, TIntermAggregate *node)
387{
388 bool visitChildren = true;
389 switch (node->getOp())
390 {
391 case EOpSequence:
392 case EOpConstructStruct:
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200393 case EOpFunction:
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200394 break;
395 case EOpPrototype:
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200396 visitChildren = false;
397 break;
398 case EOpParameters:
399 visitChildren = false;
400 break;
401 case EOpInvariantDeclaration:
402 visitChildren = false;
403 break;
404 case EOpDeclaration:
405 // Variable declaration.
406 if (visit == PreVisit)
407 {
408 mDeclaringVariables = true;
409 }
410 else if (visit == InVisit)
411 {
412 mDeclaringVariables = true;
413 }
414 else
415 {
416 mDeclaringVariables = false;
417 }
418 break;
419 case EOpFunctionCall:
420 {
421 // Function call.
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200422 if (visit == PreVisit)
423 {
Olli Etuaho1be88702015-01-19 16:56:44 +0200424 // User-defined function return values are not rounded, this relies on that
425 // calculations producing the value were rounded.
426 TIntermNode *parent = getParentNode();
Olli Etuahoa26ad582015-08-04 13:51:47 +0300427 if (canRoundFloat(node->getType()) && !isInFunctionMap(node) &&
428 parentUsesResult(parent, node))
Olli Etuaho1be88702015-01-19 16:56:44 +0200429 {
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200430 TIntermNode *replacement = createRoundingFunctionCallNode(node);
431 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
432 }
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200433 }
434 break;
435 }
436 default:
Olli Etuaho1be88702015-01-19 16:56:44 +0200437 TIntermNode *parent = getParentNode();
438 if (canRoundFloat(node->getType()) && visit == PreVisit && parentUsesResult(parent, node))
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200439 {
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200440 TIntermNode *replacement = createRoundingFunctionCallNode(node);
441 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
442 }
443 break;
444 }
445 return visitChildren;
446}
447
448bool EmulatePrecision::visitUnary(Visit visit, TIntermUnary *node)
449{
450 switch (node->getOp())
451 {
452 case EOpNegative:
453 case EOpVectorLogicalNot:
454 case EOpLogicalNot:
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200455 case EOpPostIncrement:
456 case EOpPostDecrement:
457 case EOpPreIncrement:
458 case EOpPreDecrement:
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200459 break;
460 default:
461 if (canRoundFloat(node->getType()) && visit == PreVisit)
462 {
463 TIntermNode *parent = getParentNode();
464 TIntermNode *replacement = createRoundingFunctionCallNode(node);
465 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
466 }
467 break;
468 }
469
470 return true;
471}
472
Olli Etuaho61b81ac2016-06-28 14:15:20 +0300473void EmulatePrecision::writeEmulationHelpers(TInfoSinkBase &sink,
474 const int shaderVersion,
475 const ShShaderOutput outputLanguage)
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200476{
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300477 // Other languages not yet supported
478 ASSERT(outputLanguage == SH_GLSL_COMPATIBILITY_OUTPUT ||
479 IsGLSL130OrNewer(outputLanguage) ||
480 outputLanguage == SH_ESSL_OUTPUT);
481 writeCommonPrecisionEmulationHelpers(sink, shaderVersion, outputLanguage);
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200482
483 EmulationSet::const_iterator it;
484 for (it = mEmulateCompoundAdd.begin(); it != mEmulateCompoundAdd.end(); it++)
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300485 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "+", "add");
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200486 for (it = mEmulateCompoundSub.begin(); it != mEmulateCompoundSub.end(); it++)
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300487 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "-", "sub");
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200488 for (it = mEmulateCompoundDiv.begin(); it != mEmulateCompoundDiv.end(); it++)
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300489 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "/", "div");
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200490 for (it = mEmulateCompoundMul.begin(); it != mEmulateCompoundMul.end(); it++)
Olli Etuaho3fdaf6f2016-07-13 15:07:41 +0300491 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "*", "mul");
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200492}
493