@@ -1424,29 +1424,44 @@ abstract class AstCodeGenerator
14241424 b.end ();
14251425 }
14261426
1427- // Compare against all case values
1428- for (SwitchCase c in node.cases) {
1429- for (Expression exp in c.expressions) {
1430- if (exp is NullLiteral ||
1431- exp is ConstantExpression && exp.constant is NullConstant ) {
1432- // Null already checked, skip
1433- } else {
1434- switchInfo.compare (
1435- switchValueNonNullableLocal,
1436- () => translateExpression (exp, switchInfo.nonNullableType),
1437- );
1438- b.br_if (switchLabels[c]! );
1427+ final brTable = switchInfo.brTable;
1428+ if (brTable != null ) {
1429+ // Map each entry in the range to the appropriate jump table entry.
1430+ final indexBlocks = < w.Label > [];
1431+ final caseMap = brTable.caseMap;
1432+ final defaultLabel =
1433+ defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
1434+ for (int i = 0 ; i < brTable.rangeSize; i++ ) {
1435+ final c = caseMap[i];
1436+ indexBlocks.add (c == null ? defaultLabel : switchLabels[c]! );
1437+ }
1438+ brTable.brTableExpr (switchValueNonNullableLocal);
1439+ b.br_table (indexBlocks, defaultLabel);
1440+ } else {
1441+ // Compare against all case values
1442+ for (SwitchCase c in node.cases) {
1443+ for (Expression exp in c.expressions) {
1444+ if (exp is NullLiteral ||
1445+ exp is ConstantExpression && exp.constant is NullConstant ) {
1446+ // Null already checked, skip
1447+ } else {
1448+ switchInfo.compare (
1449+ switchValueNonNullableLocal,
1450+ () => translateExpression (exp, switchInfo.nonNullableType),
1451+ );
1452+ b.br_if (switchLabels[c]! );
1453+ }
14391454 }
14401455 }
1441- }
14421456
1443- // No explicit cases matched
1444- if (node.isExplicitlyExhaustive) {
1445- b.unreachable ();
1446- } else {
1447- w.Label defaultLabel =
1448- defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
1449- b.br (defaultLabel);
1457+ // No explicit cases matched
1458+ if (node.isExplicitlyExhaustive) {
1459+ b.unreachable ();
1460+ } else {
1461+ w.Label defaultLabel =
1462+ defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
1463+ b.br (defaultLabel);
1464+ }
14501465 }
14511466
14521467 // Emit case bodies
@@ -4297,6 +4312,47 @@ class SwitchBackwardJumpInfo {
42974312 : defaultLoopLabel = null ;
42984313}
42994314
4315+ /// Info needed to represent a switch statement using a br_table instruction.
4316+ ///
4317+ /// This is used for switches on integers and enums (using their indicies). We
4318+ /// map each option to an index in the jump table and then jump directly to the
4319+ /// target case. This is much faster than iteratively comparing each cases's
4320+ /// expression to the switch expression.
4321+ ///
4322+ /// Sometimes switches over ranges of ints are sparse enough that a table would
4323+ /// bloat the code compared to the iterative comparison.
4324+ class BrTableInfo {
4325+ // This is the minimum ratio of br_table indicies to actual mapped cases. If
4326+ // this gets too large then most of the br_table indicies are unmapped and the
4327+ // extra code size is not worth using a br_table.
4328+ static const double _minTableSparseness = 2 ;
4329+ // This is the maximum size where it's always worth it to use a br_table.
4330+ // If the br_table is bigger than this then we start to check sparseness.
4331+ // Below this point we always accept the potential code size hit.
4332+ static const int _maxSparseSize = 50 ;
4333+
4334+ final int rangeSize;
4335+ final void Function (w.Local switchExprLocal) brTableExpr;
4336+ final Map <int , SwitchCase > caseMap;
4337+
4338+ BrTableInfo ._(this .rangeSize, this .caseMap, this .brTableExpr)
4339+ : assert (! isTooSparse (rangeSize, caseMap));
4340+
4341+ /// Heuristically validate whether the provided table would be too sparse and
4342+ /// if so return null. Otherwise return the expected table.
4343+ static BrTableInfo ? build (int rangeSize, Map <int , SwitchCase > caseMap,
4344+ void Function (w.Local switchExprLocal) brTableExpr) {
4345+ // Validate the table density and size is worth putting into a br_table.
4346+ if (isTooSparse (rangeSize, caseMap)) return null ;
4347+
4348+ return BrTableInfo ._(rangeSize, caseMap, brTableExpr);
4349+ }
4350+
4351+ static bool isTooSparse (int rangeSize, Map <int , SwitchCase > caseMap) =>
4352+ (rangeSize / caseMap.length) > _minTableSparseness &&
4353+ rangeSize > _maxSparseSize;
4354+ }
4355+
43004356class SwitchInfo {
43014357 /// Non-nullable Wasm type of the `switch` expression. Used when the
43024358 /// expression is not nullable, and after the null check.
@@ -4323,6 +4379,11 @@ class SwitchInfo {
43234379 /// The `null: ...` case, if exists.
43244380 late final SwitchCase ? nullCase;
43254381
4382+ /// Info needed to compile this switch statement into a wasm br_table. If null
4383+ /// this switch statement should not use a br_table and should use comparison
4384+ /// based case matching instead.
4385+ BrTableInfo ? brTable;
4386+
43264387 SwitchInfo (AstCodeGenerator codeGen, SwitchStatement node) {
43274388 final translator = codeGen.translator;
43284389
@@ -4354,6 +4415,17 @@ class SwitchInfo {
43544415 translator.typeEnvironment.isSubtypeOf (codeGen.dartTypeOf (e),
43554416 translator.coreTypes.typeNonNullableRawType));
43564417
4418+ void useCompareIdentity () {
4419+ // Object identity switch
4420+ nonNullableType = translator.topTypeNonNullable;
4421+ nullableType = translator.topType;
4422+ compare = (switchExprLocal, pushCaseExpr) {
4423+ codeGen.b.local_get (switchExprLocal);
4424+ pushCaseExpr ();
4425+ codeGen.call (translator.coreTypes.identicalProcedure.reference);
4426+ };
4427+ }
4428+
43574429 if (node.cases.every ((c) =>
43584430 c.expressions.isEmpty && c.isDefault ||
43594431 c.expressions.every ((e) =>
@@ -4453,6 +4525,39 @@ class SwitchInfo {
44534525 nonNullableType = w.NumType .i64;
44544526 nullableType =
44554527 translator.classInfo[translator.boxedIntClass]! .nullableType;
4528+
4529+ // Calculate the range covered by the cases and create the jump table.
4530+ int ? minValue;
4531+ int ? maxValue;
4532+ Map <int , SwitchCase > caseMap = {};
4533+ for (final c in node.cases) {
4534+ for (final e in c.expressions) {
4535+ final value = e is IntLiteral
4536+ ? e.value
4537+ : ((e as ConstantExpression ).constant as IntConstant ).value;
4538+ caseMap[value] = c;
4539+ if (minValue == null || value < minValue) minValue = value;
4540+ if (maxValue == null || value > maxValue) maxValue = value;
4541+ }
4542+ }
4543+ if (maxValue != null ) {
4544+ final range = maxValue - minValue! + 1 ;
4545+ if (minValue != 0 ) {
4546+ caseMap = caseMap.map ((i, c) => MapEntry (i - minValue! , c));
4547+ }
4548+ brTable = BrTableInfo .build (range, caseMap, (switchExprLocal) {
4549+ codeGen.b.local_get (switchExprLocal);
4550+ // Normalize the value on 0 if necessary.
4551+ if (minValue != 0 ) {
4552+ codeGen.b.i64_const (minValue! );
4553+ codeGen.b.i64_sub ();
4554+ }
4555+ // Now that we've normalized to 0 it should be safe to switch to i32.
4556+ codeGen.b.i32_wrap_i64 ();
4557+ });
4558+ }
4559+
4560+ // Provide a compare as a fallback in case the range is too sparse.
44564561 compare = (switchExprLocal, pushCaseExpr) {
44574562 codeGen.b.local_get (switchExprLocal);
44584563 pushCaseExpr ();
@@ -4467,15 +4572,61 @@ class SwitchInfo {
44674572 pushCaseExpr ();
44684573 codeGen.call (translator.jsStringEquals.reference);
44694574 };
4575+ } else if (switchExprClass.isEnum) {
4576+ // If this is an applicable switch over enums, create a jump table.
4577+ bool isValid = true ;
4578+ final caseMap = < int , SwitchCase > {};
4579+ int ? minIndex;
4580+ int maxIndex = 0 ;
4581+ outer:
4582+ for (final c in node.cases) {
4583+ for (final e in c.expressions) {
4584+ if (e is ! ConstantExpression ) {
4585+ isValid = false ;
4586+ break outer;
4587+ }
4588+ final constant = e.constant;
4589+ if (constant is ! InstanceConstant ) {
4590+ isValid = false ;
4591+ break outer;
4592+ }
4593+ if (constant.classNode != switchExprClass) {
4594+ isValid = false ;
4595+ break outer;
4596+ }
4597+ final enumIndex =
4598+ (constant.fieldValues[translator.enumIndexField.fieldReference]
4599+ as IntConstant )
4600+ .value;
4601+ caseMap[enumIndex] = c;
4602+ if (enumIndex > maxIndex) maxIndex = enumIndex;
4603+ if (minIndex == null || enumIndex < minIndex) minIndex = enumIndex;
4604+ }
4605+ }
4606+
4607+ if (isValid && minIndex != null ) {
4608+ final range = maxIndex - minIndex + 1 ;
4609+ brTable = BrTableInfo .build (range, caseMap, (switchExprLocal) {
4610+ codeGen.b.local_get (switchExprLocal);
4611+ codeGen.call (translator.enumIndexField.getterReference);
4612+ if (minIndex != 0 ) {
4613+ codeGen.b.i64_const (minIndex! );
4614+ codeGen.b.i64_sub ();
4615+ }
4616+ // Now that we've normalized to 0 it should be safe to switch to i32.
4617+ codeGen.b.i32_wrap_i64 ();
4618+ });
4619+
4620+ nonNullableType =
4621+ translator.classInfo[switchExprClass]! .nonNullableType;
4622+ nullableType = translator.classInfo[switchExprClass]! .nullableType;
4623+ }
4624+
4625+ if (brTable == null ) {
4626+ useCompareIdentity ();
4627+ }
44704628 } else {
4471- // Object identity switch
4472- nonNullableType = translator.topTypeNonNullable;
4473- nullableType = translator.topType;
4474- compare = (switchExprLocal, pushCaseExpr) {
4475- codeGen.b.local_get (switchExprLocal);
4476- pushCaseExpr ();
4477- codeGen.call (translator.coreTypes.identicalProcedure.reference);
4478- };
4629+ useCompareIdentity ();
44794630 }
44804631
44814632 _initializeSpecialCases (node);
0 commit comments