Skip to content

Commit

Permalink
[dart2js] Lower String.codeUnitAt to charCodeAt
Browse files Browse the repository at this point in the history
Change-Id: Id4408a761304fc2fcb7ce3bd353c626958971ffb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/300500
Reviewed-by: Sigmund Cherem <[email protected]>
Commit-Queue: Stephen Adams <[email protected]>
  • Loading branch information
rakudrama authored and Commit Queue committed May 5, 2023
1 parent 7cf3aa9 commit d3da431
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 48 deletions.
13 changes: 13 additions & 0 deletions pkg/compiler/lib/src/ssa/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4500,6 +4500,8 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
_handleLateWriteOnceCheck(invocation);
} else if (name == '_lateInitializeOnceCheck') {
_handleLateInitializeOnceCheck(invocation);
} else if (name == 'HCharCodeAt') {
_handleCharCodeAt(invocation);
} else {
reporter.internalError(
_elementMap.getSpannable(targetElement, invocation),
Expand Down Expand Up @@ -5170,6 +5172,17 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
push(HStringConcat(inputs[0], inputs[1], _abstractValueDomain.stringType));
}

void _handleCharCodeAt(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 2, maxPositional: 2)) {
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
List<HInstruction> inputs = _visitPositionalArguments(invocation.arguments);
push(HCharCodeAt(inputs[0], inputs[1], _abstractValueDomain.uint31Type));
}

void _handleForeignTypeRef(ir.StaticInvocation invocation) {
if (_unexpectedForeignArguments(invocation,
minPositional: 0, maxPositional: 0, typeArgumentCount: 1)) {
Expand Down
9 changes: 9 additions & 0 deletions pkg/compiler/lib/src/ssa/codegen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2940,6 +2940,15 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
.withSourceInformation(node.sourceInformation));
}

@override
void visitCharCodeAt(HCharCodeAt node) {
use(node.receiver);
js.Expression receiver = pop();
use(node.index);
push(js.js('#.charCodeAt(#)', [receiver, pop()]).withSourceInformation(
node.sourceInformation));
}

void checkTypeOf(HInstruction input, String cmp, String typeName,
SourceInformation? sourceInformation) {
use(input);
Expand Down
75 changes: 38 additions & 37 deletions pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,44 @@ class IndexSpecializer extends InvokeDynamicSpecializer {
}
}

class CodeUnitAtSpecializer extends InvokeDynamicSpecializer {
const CodeUnitAtSpecializer();

@override
constant_system.BinaryOperation operation() {
return constant_system.codeUnitAt;
}

@override
HInstruction? tryConvertToBuiltin(
HInvokeDynamic instruction,
HGraph graph,
GlobalTypeInferenceResults results,
JCommonElements commonElements,
JClosedWorld closedWorld,
OptimizationTestLog? log) {
final abstractValueDomain = closedWorld.abstractValueDomain;
HInstruction receiver = instruction.getDartReceiver(closedWorld);
if (receiver.isStringOrNull(abstractValueDomain).isPotentiallyFalse) {
return null;
}
HInstruction index = instruction.inputs.last;
if (index.isInteger(abstractValueDomain).isPotentiallyFalse) {
return null;
}

HInstruction checkedIndex = index;
if (requiresBoundsCheck(instruction, closedWorld)) {
checkedIndex =
insertBoundsCheck(instruction, receiver, index, closedWorld, log);
}
final converted =
HCharCodeAt(receiver, checkedIndex, abstractValueDomain.uint31Type);
log?.registerCodeUnitAt(instruction);
return converted;
}
}

class RemoveLastSpecializer extends InvokeDynamicSpecializer {
const RemoveLastSpecializer();

Expand Down Expand Up @@ -1436,43 +1474,6 @@ class LessEqualSpecializer extends RelationalSpecializer {
}
}

class CodeUnitAtSpecializer extends InvokeDynamicSpecializer {
const CodeUnitAtSpecializer();

@override
constant_system.BinaryOperation operation() {
return constant_system.codeUnitAt;
}

@override
HInstruction? tryConvertToBuiltin(
HInvokeDynamic instruction,
HGraph graph,
GlobalTypeInferenceResults results,
JCommonElements commonElements,
JClosedWorld closedWorld,
OptimizationTestLog? log) {
// TODO(sra): Implement a builtin HCodeUnitAt instruction and the same index
// bounds checking optimizations as for HIndex.
HInstruction receiver = instruction.getDartReceiver(closedWorld);
if (receiver
.isStringOrNull(closedWorld.abstractValueDomain)
.isDefinitelyTrue) {
// Even if there is no builtin equivalent instruction, we know
// String.codeUnitAt does not have any side effect (other than throwing),
// and that it can be GVN'ed.
clearAllSideEffects(instruction);
if (instruction.inputs.last
.isPositiveInteger(closedWorld.abstractValueDomain)
.isDefinitelyTrue) {
redirectSelector(instruction, '_codeUnitAt', commonElements);
}
log?.registerCodeUnitAt(instruction);
}
return null;
}
}

class CompareToSpecializer extends InvokeDynamicSpecializer {
const CompareToSpecializer();

Expand Down
46 changes: 43 additions & 3 deletions pkg/compiler/lib/src/ssa/nodes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ abstract class HVisitor<R> {
R visitBitXor(HBitXor node);
R visitBoundsCheck(HBoundsCheck node);
R visitBreak(HBreak node);
R visitCharCodeAt(HCharCodeAt node);
R visitConstant(HConstant node);
R visitContinue(HContinue node);
R visitCreate(HCreate node);
Expand Down Expand Up @@ -467,6 +468,8 @@ class HBaseVisitor<R> extends HGraphVisitor implements HVisitor<R> {
R visitBreak(HBreak node) => visitJump(node);
@override
R visitContinue(HContinue node) => visitJump(node);
@override
R visitCharCodeAt(HCharCodeAt node) => visitInstruction(node);
R visitCheck(HCheck node) => visitInstruction(node);
@override
R visitConstant(HConstant node) => visitInstruction(node);
Expand Down Expand Up @@ -1128,6 +1131,8 @@ abstract class HInstruction implements SpannableWithEntity {
static const int LATE_WRITE_ONCE_CHECK_TYPECODE = 62;
static const int LATE_INITIALIZE_ONCE_CHECK_TYPECODE = 63;

static const int CHAR_CODE_AT_TYPECODE = 64;

HInstruction(this.inputs, this.instructionType);
@override
Entity? get sourceEntity => sourceElement;
Expand Down Expand Up @@ -3475,7 +3480,8 @@ class HIndex extends HInstruction {
HInstruction get index => inputs[1];

// Implicit dependency on HBoundsCheck or constraints on index.
// TODO(27272): Make HIndex dependent on bounds checking.
// TODO(27272): Make HIndex dependent on positions of eliminated bounds
// checks.
@override
bool get isMovable => false;

Expand Down Expand Up @@ -3515,7 +3521,34 @@ class HIndexAssign extends HInstruction {
HInstruction get value => inputs[2];

// Implicit dependency on HBoundsCheck or constraints on index.
// TODO(27272): Make HIndex dependent on bounds checking.
// TODO(27272): Make HIndex dependent on eliminated bounds checks.
@override
bool get isMovable => false;

@override
HInstruction getDartReceiver(JClosedWorld closedWorld) => receiver;
@override
bool onlyThrowsNSM() => true;
@override
bool canThrow(AbstractValueDomain domain) =>
receiver.isNull(domain).isPotentiallyTrue;
}

class HCharCodeAt extends HInstruction {
HCharCodeAt(HInstruction receiver, HInstruction index, AbstractValue type)
: super([receiver, index], type);

@override
String toString() => 'HCharCodeAt';
@override
R accept<R>(HVisitor<R> visitor) => visitor.visitCharCodeAt(this);

HInstruction get receiver => inputs[0];
HInstruction get index => inputs[1];

// Implicit dependency on HBoundsCheck or constraints on index.
// TODO(27272): Make HCharCodeAt dependent on positions of eliminated bounds
// checks.
@override
bool get isMovable => false;

Expand All @@ -3526,6 +3559,13 @@ class HIndexAssign extends HInstruction {
@override
bool canThrow(AbstractValueDomain domain) =>
receiver.isNull(domain).isPotentiallyTrue;

@override
int typeCode() => HInstruction.CHAR_CODE_AT_TYPECODE;
@override
bool typeEquals(other) => other is HCharCodeAt;
@override
bool dataEquals(HCharCodeAt other) => true;
}

/// HLateValue is a late-stage instruction that can be used to force a value
Expand All @@ -3534,7 +3574,7 @@ class HIndexAssign extends HInstruction {
/// HLateValue is useful for naming values that would otherwise be generated at
/// use site, for example, if 'this' is used many times, replacing uses of
/// 'this' with HLateValue(HThis) will have the effect of copying 'this' to a
/// temporary will reduce the size of minified code.
/// temporary which will reduce the size of minified code.
class HLateValue extends HLateInstruction {
HLateValue(HInstruction target) : super([target], target.instructionType);

Expand Down
8 changes: 8 additions & 0 deletions pkg/compiler/lib/src/ssa/optimize.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,14 @@ class SsaInstructionSimplifier extends HBaseVisitor<HInstruction>
return node;
}

@override
HInstruction visitCharCodeAt(HCharCodeAt node) {
final folded =
foldBinary(constant_system.codeUnitAt, node.receiver, node.index);
if (folded != null) return folded;
return node;
}

/// Returns the guarded receiver.
HInstruction maybeGuardWithNullCheck(
HInstruction receiver, HInvokeDynamic node, FieldEntity? field) {
Expand Down
7 changes: 7 additions & 0 deletions pkg/compiler/lib/src/ssa/tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ class HInstructionStringifier implements HVisitor<String> {
return "Break: (B${target.id})";
}

@override
String visitCharCodeAt(HCharCodeAt node) {
String receiver = temporaryId(node.receiver);
String index = temporaryId(node.index);
return "CharCodeAt: $receiver.charCodeAt($index)";
}

@override
String visitConstant(HConstant constant) => "Constant: ${constant.constant}";

Expand Down
73 changes: 65 additions & 8 deletions pkg/compiler/test/codegen/data/codeUnitAt_folding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@pragma('dart2js:noInline')
@pragma('dart2js:never-inline')
/*member: foo1:function() {
return 72;
}*/
Expand All @@ -13,34 +13,91 @@ foo1() {
// Constant folds to 'return 72;'
}

@pragma('dart2js:noInline')
@pragma('dart2js:never-inline')
/*spec|canary.member: foo2:function() {
return B.JSString_methods.codeUnitAt$1("Hello", A._asInt(1.5));
return B.JSString_methods.codeUnitAt$1("Hello", A._asInt("x"));
}*/
/*prod.member: foo2:function() {
return B.JSString_methods.codeUnitAt$1("Hello", 1.5);
return B.JSString_methods.codeUnitAt$1("Hello", "x");
}*/
foo2() {
var a = 'Hello';
dynamic b = 1.5;
dynamic b = 'x';
return a.codeUnitAt(b);
// No folding of index type error.
}

@pragma('dart2js:noInline')
@pragma('dart2js:never-inline')
/*member: foo3:function() {
return B.JSString_methods._codeUnitAt$1("Hello", 55);
return A.ioore("Hello", 55);
return "Hello".charCodeAt(55);
}*/
foo3() {
var a = 'Hello';
dynamic b = 55;
return a.codeUnitAt(b);
// No folding of index range error.
// Index always out of range.
// The code after the always-fail check is unfortunate.
}

@pragma('dart2js:never-inline')
/*member: foo4:function(i) {
if (!(i >= 0 && i < 5))
return A.ioore("Hello", i);
return "Hello".charCodeAt(i);
}*/
foo4(int i) {
return 'Hello'.codeUnitAt(i);
// Normal bounds check.
}

@pragma('dart2js:never-inline')
/*member: foo5:function(i) {
if (!(i < 5))
return A.ioore("Hello", i);
return "Hello".charCodeAt(i);
}*/
foo5(int i) {
return 'Hello'.codeUnitAt(i);
// High-only bounds check.
}

@pragma('dart2js:never-inline')
@pragma('dart2js:index-bounds:trust')
/*member: foo6:function(i) {
return "Hello".charCodeAt(i);
}*/
foo6(int i) {
return 'Hello'.codeUnitAt(i);
// No bound check, as requested.
}

@pragma('dart2js:never-inline')
@pragma('dart2js:index-bounds:trust')
/*spec|canary.member: foo7:function(i) {
return "Hello".charCodeAt(A._asInt(i));
}*/
/*prod.member: foo7:function(i) {
return B.JSString_methods.codeUnitAt$1("Hello", i);
}*/
foo7(dynamic i) {
return 'Hello'.codeUnitAt(i);
// No folding of index type error even when bounds check removed.
}

/*member: main:ignore*/
main() {
foo1();
foo2();
foo3();
foo4(-9);
foo4(0);
foo4(100);
foo5(0);
foo5(100);
foo6(-9);
foo6(0);
foo6(100);
foo7(0);
foo7('x');
}

0 comments on commit d3da431

Please sign in to comment.