Skip to content

Commit 15792df

Browse files
rakudramaCommit Queue
authored andcommitted
[dart2js] Use indexes for operation names and verbs
Change-Id: Ic954a7e20062aa8bf1c0f622ee9b0656bc563ba5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/375024 Commit-Queue: Stephen Adams <[email protected]> Reviewed-by: Mayank Patke <[email protected]>
1 parent 88b6ee2 commit 15792df

File tree

15 files changed

+429
-68
lines changed

15 files changed

+429
-68
lines changed

pkg/compiler/lib/src/ssa/builder.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5424,7 +5424,7 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
54245424
void _handleArrayFlagsCheck(
54255425
ir.StaticInvocation invocation, SourceInformation? sourceInformation) {
54265426
if (_unexpectedForeignArguments(invocation,
5427-
minPositional: 4, maxPositional: 4, typeArgumentCount: 1)) {
5427+
minPositional: 4, maxPositional: 5, typeArgumentCount: 1)) {
54285428
// Result expected on stack.
54295429
stack.add(graph.addConstantNull(closedWorld));
54305430
return;
@@ -5434,6 +5434,7 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
54345434
final arrayFlags = inputs[1];
54355435
final checkFlags = inputs[2];
54365436
final operation = inputs[3];
5437+
final verb = inputs.length > 4 ? inputs[4] : null;
54375438

54385439
// TODO(sra): Use the flags to improve in the AbstractValue, which may
54395440
// contain powerset domain bits outside of the conventional type
@@ -5444,7 +5445,7 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
54445445
instructionType ??= _abstractValueDomain.dynamicType;
54455446

54465447
push(HArrayFlagsCheck(
5447-
array, arrayFlags, checkFlags, operation, instructionType)
5448+
array, arrayFlags, checkFlags, operation, verb, instructionType)
54485449
..sourceInformation = sourceInformation);
54495450
}
54505451

pkg/compiler/lib/src/ssa/codegen.dart

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3486,18 +3486,21 @@ class SsaCodeGenerator implements HVisitor<void>, HBlockInformationVisitor {
34863486
test = js.js('# & #', [arrayFlags, checkFlags]);
34873487
}
34883488

3489-
final operation = node.operation;
3490-
// Most common operation is "[]=", so 'pass' that by leaving it out.
3491-
if (operation
3492-
case HConstant(constant: StringConstantValue(stringValue: '[]='))) {
3493-
_pushCallStatic(_commonElements.throwUnsupportedOperation, [array],
3494-
node.sourceInformation);
3495-
} else {
3496-
use(operation);
3497-
_pushCallStatic(_commonElements.throwUnsupportedOperation, [array, pop()],
3498-
node.sourceInformation);
3489+
List<js.Expression> arguments = [array];
3490+
3491+
if (node.hasOperation) {
3492+
use(node.operation);
3493+
arguments.add(pop());
34993494
}
35003495

3496+
if (node.hasVerb) {
3497+
use(node.verb);
3498+
arguments.add(pop());
3499+
}
3500+
3501+
_pushCallStatic(_commonElements.throwUnsupportedOperation, arguments,
3502+
node.sourceInformation);
3503+
35013504
js.Statement check;
35023505
if (test == null) {
35033506
check = js.js.statement('#;', pop());

pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,11 @@ class IndexAssignSpecializer extends InvokeDynamicSpecializer {
246246
HInstruction mask =
247247
graph.addConstantInt(ArrayFlags.unmodifiableCheck, closedWorld);
248248
HInstruction name = graph.addConstantString('[]=', closedWorld);
249+
HInstruction verb = graph.addConstantString('modify', closedWorld);
249250
final instructionType = receiver.instructionType;
250-
final checkFlags =
251-
HArrayFlagsCheck(receiver, getFlags, mask, name, instructionType)
252-
..sourceInformation = instruction.sourceInformation;
251+
final checkFlags = HArrayFlagsCheck(
252+
receiver, getFlags, mask, name, verb, instructionType)
253+
..sourceInformation = instruction.sourceInformation;
253254
instruction.block!.addBefore(instruction, checkFlags);
254255
checkFlags.instructionType = checkFlags.computeInstructionType(
255256
instructionType, abstractValueDomain);

pkg/compiler/lib/src/ssa/nodes.dart

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,12 @@ abstract class HInstruction implements SpannableWithEntity {
14011401
replacement.usedBy.add(this);
14021402
}
14031403

1404+
/// Remove a single input.
1405+
void removeInput(int index) {
1406+
inputs[index].usedBy.remove(this);
1407+
inputs.removeAt(index);
1408+
}
1409+
14041410
void replaceAllUsersDominatedBy(
14051411
HInstruction cursor, HInstruction newInstruction) {
14061412
DominatedUses.of(this, cursor).replaceWith(newInstruction);
@@ -4707,21 +4713,37 @@ class HTypeBind extends HInstruction implements HRtiInstruction {
47074713
///
47084714
/// a = ...
47094715
/// f = HArrayFlagsGet(a);
4710-
/// a2 = HArrayFlagsCheck(a, f, ArrayFlags.unmodifiableCheck, "[]=")
4716+
/// a2 = HArrayFlagsCheck(a, f, ArrayFlags.unmodifiableCheck, "[]=", "modify")
47114717
/// a2[i] = 0
47124718
///
47134719
/// HArrayFlagsGet is a separate instruction so that 'loading' the flags from
47144720
/// the Array can by hoisted.
47154721
class HArrayFlagsCheck extends HCheck {
4716-
HArrayFlagsCheck(HInstruction array, HInstruction arrayFlags,
4717-
HInstruction checkFlags, HInstruction operation, AbstractValue type)
4718-
: super([array, arrayFlags, checkFlags, operation], type);
4722+
HArrayFlagsCheck(
4723+
HInstruction array,
4724+
HInstruction arrayFlags,
4725+
HInstruction checkFlags,
4726+
HInstruction? operation,
4727+
HInstruction? verb,
4728+
AbstractValue type)
4729+
: super([
4730+
array,
4731+
arrayFlags,
4732+
checkFlags,
4733+
if (operation != null) operation,
4734+
if (verb != null) verb,
4735+
], type);
47194736

47204737
HInstruction get array => inputs[0];
47214738
HInstruction get arrayFlags => inputs[1];
47224739
HInstruction get checkFlags => inputs[2];
4740+
4741+
bool get hasOperation => inputs.length > 3;
47234742
HInstruction get operation => inputs[3];
47244743

4744+
bool get hasVerb => inputs.length > 4;
4745+
HInstruction get verb => inputs[4];
4746+
47254747
// The checked type is the input type, refined to match the flags.
47264748
AbstractValue computeInstructionType(
47274749
AbstractValue inputType, AbstractValueDomain domain) {

pkg/compiler/lib/src/ssa/optimize.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,57 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
26752675
}
26762676
}
26772677

2678+
// The 'operation' and 'verb' strings can be replaced with an index into a
2679+
// small table to known operations or verbs. This makes the call-sites
2680+
// smaller, so is worthwhile for calls to HArrayFlagsCheck that are inlined
2681+
// into multiple places.
2682+
//
2683+
// A trailing zero index (verb, or verb and operation) can be omitted from
2684+
// the instruction.
2685+
//
2686+
// When both indexes are replaced by indexes, the indexes are combined into
2687+
// a single value.
2688+
//
2689+
// finalIndex = verbIndex * numberOfOperationIndexes + operationIndex
2690+
2691+
int? verbIndex; // Verb index if nonzero.
2692+
2693+
if (node.hasVerb) {
2694+
if (node.verb
2695+
case HConstant(constant: StringConstantValue(:final stringValue))) {
2696+
final index = ArrayFlags.verbToIndex[stringValue];
2697+
if (index != null) {
2698+
if (index == 0) {
2699+
node.removeInput(4);
2700+
} else {
2701+
final replacement = _graph.addConstantInt(index, _closedWorld);
2702+
node.replaceInput(4, replacement);
2703+
verbIndex = index;
2704+
}
2705+
}
2706+
}
2707+
}
2708+
2709+
if (node.hasOperation) {
2710+
if (node.operation
2711+
case HConstant(constant: StringConstantValue(:final stringValue))) {
2712+
var index = ArrayFlags.operationNameToIndex[stringValue];
2713+
if (index != null) {
2714+
if (index == 0 && !node.hasVerb) {
2715+
node.removeInput(3);
2716+
} else {
2717+
if (verbIndex != null) {
2718+
// Encode combined indexes and remove 'verb' input.
2719+
index += verbIndex * ArrayFlags.operationNameToIndex.length;
2720+
node.removeInput(4);
2721+
}
2722+
final replacement = _graph.addConstantInt(index, _closedWorld);
2723+
node.replaceInput(3, replacement);
2724+
}
2725+
}
2726+
}
2727+
}
2728+
26782729
return node;
26792730
}
26802731

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
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.
4+
5+
// Test of various space-saving contractions of calls to
6+
// `throwUnsupportedOperation`.
7+
8+
import 'dart:_foreign_helper' show ArrayFlags, HArrayFlagsCheck;
9+
10+
@pragma('dart2js:never-inline')
11+
// The operation and verb are both elided.
12+
/*member: indexSetter:function() {
13+
A.throwUnsupportedOperation(B.List_empty);
14+
}*/
15+
indexSetter() {
16+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
17+
'[]=', 'modify');
18+
}
19+
20+
@pragma('dart2js:never-inline')
21+
// The operation is reduced to an index but cannot be elided because it is
22+
// followed by a non-elided verb.
23+
/*member: indexSetterUnusualVerb:function() {
24+
A.throwUnsupportedOperation(B.List_empty, 0, "change contents of");
25+
}*/
26+
indexSetterUnusualVerb() {
27+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
28+
'[]=', 'change contents of');
29+
}
30+
31+
@pragma('dart2js:never-inline')
32+
// The verb is elided.
33+
/*member: unusualOperationModify:function() {
34+
A.throwUnsupportedOperation(B.List_empty, "rub");
35+
}*/
36+
unusualOperationModify() {
37+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
38+
'rub', 'modify');
39+
}
40+
41+
@pragma('dart2js:never-inline')
42+
// The operation is left as a string and verb is
43+
/*member: unusualOperationRemoveFrom:function() {
44+
A.throwUnsupportedOperation(B.List_empty, "rub", 1);
45+
}*/
46+
unusualOperationRemoveFrom() {
47+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
48+
'rub', 'remove from');
49+
}
50+
51+
@pragma('dart2js:never-inline')
52+
// The operation and verb are left as strings.
53+
/*member: unusualOperationAndVerb:function() {
54+
A.throwUnsupportedOperation(B.List_empty, "rub", "burnish");
55+
}*/
56+
unusualOperationAndVerb() {
57+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
58+
'rub', 'burnish');
59+
}
60+
61+
@pragma('dart2js:never-inline')
62+
// The operation is reduced to an index and the verb is elided.
63+
/*member: knownOperationModify:function() {
64+
A.throwUnsupportedOperation(B.List_empty, 10);
65+
}*/
66+
knownOperationModify() {
67+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
68+
'setUint16', 'modify');
69+
}
70+
71+
@pragma('dart2js:never-inline')
72+
// The operation and verb are combined to a single number.
73+
/*member: knownOperationAndVerb:function() {
74+
A.throwUnsupportedOperation(B.List_empty, 16);
75+
}*/
76+
knownOperationAndVerb() {
77+
HArrayFlagsCheck(const [], ArrayFlags.constant, ArrayFlags.unmodifiableCheck,
78+
'removeWhere', 'remove from');
79+
}
80+
81+
/*member: main:ignore*/
82+
main() {
83+
indexSetter();
84+
indexSetterUnusualVerb();
85+
unusualOperationModify();
86+
unusualOperationRemoveFrom();
87+
unusualOperationAndVerb();
88+
knownOperationModify();
89+
knownOperationAndVerb();
90+
}

pkg/compiler/test/codegen/data/unmodifiable_bytedata.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ returnModifiable2() {
5252
/*member: guaranteedFail:function() {
5353
var a = new DataView(new ArrayBuffer(10));
5454
a.$flags = 3;
55-
A.throwUnsupportedOperation(a, "setInt32");
55+
A.throwUnsupportedOperation(a, 8);
5656
a.setInt32(0, 100, false);
5757
a.setUint32(4, 2000, false);
5858
return a;
@@ -67,7 +67,7 @@ guaranteedFail() {
6767

6868
@pragma('dart2js:never-inline')
6969
/*member: multipleWrites:function(data) {
70-
data.$flags & 2 && A.throwUnsupportedOperation(data, "setFloat64");
70+
data.$flags & 2 && A.throwUnsupportedOperation(data, 13);
7171
data.setFloat64(0, 1.23, false);
7272
data.setFloat32(8, 1.23, false);
7373
return data;
@@ -83,7 +83,7 @@ multipleWrites(ByteData data) {
8383
/*member: hoistedLoad:function(data) {
8484
var t1, i;
8585
for (t1 = data.$flags | 0, i = 0; i < data.byteLength; i += 2) {
86-
t1 & 2 && A.throwUnsupportedOperation(data, "setUint16");
86+
t1 & 2 && A.throwUnsupportedOperation(data, 10);
8787
data.setUint16(i, 100, true);
8888
}
8989
return data;

pkg/compiler/test/rti/emission/list.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
/*spec.class: global#JSArray:checkedInstance,checks=[$isIterable],instance*/
5+
/*spec.class: global#JSArray:checkedInstance,checks=[$isIterable,$isList],instance*/
66
/*prod.class: global#JSArray:checks=[$isIterable],instance*/
77

88
/*class: global#Iterable:checkedInstance*/

pkg/js_runtime/lib/synced/array_flags.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,64 @@ class ArrayFlags {
6060

6161
/// Bit to check for constant JSArray.
6262
static const int constantCheck = 4;
63+
64+
/// A List of operation names for HArrayFlagsCheck, encoded in a string of
65+
/// names separated by semicolons.
66+
///
67+
/// The optimizer may replace the string with an index into this
68+
/// table. Generally this makes the generated code smaller. A name does not
69+
/// need to be in this table. It makes sense for this table to include only
70+
/// names that occur in more than one HArrayFlagsCheck, either as written in
71+
/// js_runtime, or occuring multiple times via inlining.
72+
static const operationNames = //
73+
'[]=' // This is the most common operation so it comes first.
74+
';add'
75+
';removeWhere'
76+
';retainWhere'
77+
';removeRange'
78+
';setRange'
79+
';setInt8'
80+
';setInt16'
81+
';setInt32'
82+
';setUint8'
83+
';setUint16'
84+
';setUint32'
85+
';setFloat32'
86+
';setFloat64'
87+
//
88+
;
89+
90+
/// A list of 'verbs' for HArrayFlagsCheck, encoded as a string of phrases
91+
/// separated by semicolons. The 'verb' fills a span of the error message, for
92+
/// example, "remove from" in the message
93+
///
94+
/// Cannot remove from an unmodifiable list.
95+
/// ^^^^^^^^^^^
96+
///
97+
/// The optimizer may replace the string with an index into this
98+
/// table. Generally this makes the program smaller. A 'verb' does not need to
99+
/// be in this table. It makes sense to only have verbs that are used by
100+
/// several calls to HArrayFlagsCheck, either as written in js_runtime, or
101+
/// occuring multiple times via inlining.
102+
static const verbs = //
103+
'modify' // This is the verb for '[]=' so it comes first.
104+
';remove from'
105+
';add to'
106+
//
107+
;
108+
109+
/// A view of [operationNames] as a map from the name to the index of the name
110+
/// in the list.
111+
static final Map<String, int> operationNameToIndex = _invert(operationNames);
112+
113+
/// A view of [verbs] as a map from the verb to the index of the verb in the
114+
/// list.
115+
static final Map<String, int> verbToIndex = _invert(verbs);
116+
117+
static Map<String, int> _invert(String joinedStrings) {
118+
return {
119+
for (final entry in joinedStrings.split(';').asMap().entries)
120+
entry.value: entry.key,
121+
};
122+
}
63123
}

sdk/lib/_internal/js_runtime/lib/foreign_helper.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,15 @@ external int HArrayFlagsGet(Object array);
336336
/// intervening HArrayFlagsSet that might invalidate the check.
337337
@pragma('dart2js:as:trust')
338338
T HArrayFlagsCheck<T>(
339-
Object array, int arrayFlags, int checkFlags, String operation) {
339+
Object array, int arrayFlags, int checkFlags, String operation,
340+
[String verb = 'modify']) {
340341
// This body is unused but serves as a model for global for impacts and
341342
// analysis.
342343
if (arrayFlags & checkFlags != 0) {
343-
throwUnsupportedOperation(array, operation);
344+
if (operation == '[]=') throwUnsupportedOperation(array);
345+
if (operation == 'setUint32') throwUnsupportedOperation(array, 1);
346+
if (verb == 'remove from') throwUnsupportedOperation(array, operation, 1);
347+
throwUnsupportedOperation(array, operation, verb);
344348
}
345349
return array as T;
346350
}

0 commit comments

Comments
 (0)