Skip to content

Commit f4780a6

Browse files
liamappelbecommit-bot@chromium.org
authored andcommitted
[vm] Late modifier for non-static final fields.
Bug: #38841 Change-Id: Ib2b3189b20bd056b6378fa5d7a6166acef9a5c67 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/124465 Reviewed-by: Régis Crelier <regis@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Liam Appelbe <liama@google.com>
1 parent 23770fc commit f4780a6

File tree

4 files changed

+125
-2
lines changed

4 files changed

+125
-2
lines changed

runtime/vm/compiler/backend/flow_graph_compiler.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@ bool FlowGraphCompiler::TryIntrinsifyHelper() {
12091209
// Only intrinsify getter if the field cannot contain a mutable double.
12101210
// Reading from a mutable double box requires allocating a fresh double.
12111211
if (field.is_instance() && !field.needs_load_guard() &&
1212+
!field.is_late() &&
12121213
(FLAG_precompiled_mode || !IsPotentialUnboxedField(field))) {
12131214
SpecialStatsBegin(CombinedCodeStatistics::kTagIntrinsics);
12141215
GenerateGetterIntrinsic(compiler::target::Field::OffsetOf(field));

runtime/vm/compiler/frontend/kernel_to_il.cc

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,61 @@ Fragment FlowGraphBuilder::LoadLateField(const Field& field,
465465
return instructions;
466466
}
467467

468+
Fragment FlowGraphBuilder::StoreLateInstanceField(const Field& field,
469+
LocalVariable* instance,
470+
LocalVariable* setter_value) {
471+
// Implicit setters for non-final late fields are the same as non-late fields.
472+
if (!field.is_final()) {
473+
return StoreInstanceFieldGuarded(field,
474+
StoreInstanceFieldInstr::Kind::kOther);
475+
}
476+
477+
// If a late final field has an initializer, the setter always throws. This
478+
// case is typically caught as a compile error, but there are ways to avoid
479+
// that error, so we also need this runtime error.
480+
const TokenPosition position = field.token_pos();
481+
if (field.has_initializer()) {
482+
Fragment instructions;
483+
instructions += Drop();
484+
instructions += Drop();
485+
instructions += ThrowLateInitializationError(
486+
position, String::ZoneHandle(Z, field.name()));
487+
return instructions;
488+
}
489+
490+
// Late final fields with no initializer can be written to once.
491+
Fragment instructions;
492+
TargetEntryInstr *is_uninitialized, *is_initialized;
493+
494+
// Check whether the field has been initialized already.
495+
instructions += Drop();
496+
instructions += LoadField(field);
497+
instructions += Constant(Object::sentinel());
498+
instructions += BranchIfStrictEqual(&is_uninitialized, &is_initialized);
499+
500+
JoinEntryInstr* join = BuildJoinEntry();
501+
502+
{
503+
// If the field isn't initialized, set it to the new value.
504+
Fragment initialize(is_uninitialized);
505+
initialize += LoadLocal(instance);
506+
initialize += LoadLocal(setter_value);
507+
initialize +=
508+
StoreInstanceFieldGuarded(field, StoreInstanceFieldInstr::Kind::kOther);
509+
initialize += Goto(join);
510+
}
511+
512+
{
513+
// If the field is already initialized, throw a LateInitializationError.
514+
Fragment already_initialized(is_initialized);
515+
already_initialized += ThrowLateInitializationError(
516+
position, String::ZoneHandle(Z, field.name()));
517+
already_initialized += Goto(join);
518+
}
519+
520+
return Fragment(instructions.entry, join);
521+
}
522+
468523
Fragment FlowGraphBuilder::ThrowLateInitializationError(TokenPosition position,
469524
const String& name) {
470525
const Class& klass = Class::ZoneHandle(
@@ -2568,8 +2623,13 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFieldAccessor(
25682623
AssertAssignableInstr::kParameterCheck);
25692624
}
25702625
if (is_method) {
2571-
body += StoreInstanceFieldGuarded(field,
2572-
StoreInstanceFieldInstr::Kind::kOther);
2626+
if (field.is_late()) {
2627+
body += StoreLateInstanceField(
2628+
field, parsed_function_->ParameterVariable(0), setter_value);
2629+
} else {
2630+
body += StoreInstanceFieldGuarded(
2631+
field, StoreInstanceFieldInstr::Kind::kOther);
2632+
}
25732633
} else {
25742634
body += StoreStaticField(TokenPosition::kNoSource, field);
25752635
}

runtime/vm/compiler/frontend/kernel_to_il.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
133133
Fragment RethrowException(TokenPosition position, int catch_try_index);
134134
Fragment LoadLocal(LocalVariable* variable);
135135
Fragment LoadLateField(const Field& field, LocalVariable* instance);
136+
Fragment StoreLateInstanceField(const Field& field,
137+
LocalVariable* instance,
138+
LocalVariable* setter_value);
136139
Fragment InitInstanceField(const Field& field);
137140
Fragment InitStaticField(const Field& field);
138141
Fragment NativeCall(const String* name, const Function* function);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2019, 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+
// SharedOptions=--enable-experiment=non-nullable
6+
import 'package:expect/expect.dart';
7+
8+
int initCalls = 0;
9+
int init() {
10+
++initCalls;
11+
return 123;
12+
}
13+
14+
class Base {
15+
late final int fieldWithInit;
16+
}
17+
18+
// A inherits from Base to bypass the compile time error caused by assigning to
19+
// a late final field that has an initializer. This allows us to test the
20+
// runtime exception.
21+
class A extends Base {
22+
late final int fieldWithInit = init();
23+
}
24+
25+
class B {
26+
late final int fieldWithNoInit;
27+
}
28+
29+
main() {
30+
Base a = A();
31+
Expect.equals(0, initCalls);
32+
Expect.equals(123, a.fieldWithInit);
33+
Expect.equals(1, initCalls);
34+
Expect.equals(123, a.fieldWithInit);
35+
Expect.equals(1, initCalls);
36+
Expect.throws(() => {a.fieldWithInit = 456},
37+
(error) => error is LateInitializationError);
38+
Expect.equals(1, initCalls);
39+
Expect.equals(123, a.fieldWithInit);
40+
Expect.equals(1, initCalls);
41+
initCalls = 0;
42+
43+
Base a2 = A();
44+
Expect.equals(0, initCalls);
45+
Expect.throws(() => {a2.fieldWithInit = 456},
46+
(error) => error is LateInitializationError);
47+
Expect.equals(0, initCalls);
48+
Expect.equals(123, a2.fieldWithInit);
49+
Expect.equals(1, initCalls);
50+
51+
B b = B();
52+
Expect.throws(
53+
() => b.fieldWithNoInit, (error) => error is LateInitializationError);
54+
b.fieldWithNoInit = 123;
55+
Expect.equals(123, b.fieldWithNoInit);
56+
Expect.throws(() => {b.fieldWithNoInit = 456},
57+
(error) => error is LateInitializationError);
58+
Expect.equals(123, b.fieldWithNoInit);
59+
}

0 commit comments

Comments
 (0)