Version 2.13.0-211.13.beta
* Cherry-pick aaa66ed77ea8b7582ab478ad4425dadcec4a5b02 to beta
* Cherry-pick ea50eeb4beb07d8e0a672d6fb52d356b523a6392 to beta
* Cherry-pick 825b59ae6d77bb94b5434d8637ba85448206caa4 to beta
* Cherry-pick 8800decca48b7062f2f7a862c7605c5efff0f6e4 to beta
* Cherry-pick 8e2539d24c93ebb238c8681250ab67c4bf19c4e9 to beta
* Cherry-pick d41f9a738e3d3d35b5cbeeec1974ef8e23093c65 to beta
* Cherry-pick 8156646b0c92f0e1d83ef62a84745c37c4cd1d72 to beta
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index dcec2b1..a01a3b9 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -3585,8 +3585,30 @@
js_ast.Statement _visitStatement(Statement s) {
if (s == null) return null;
var result = s.accept(this);
+
+ // In most cases, a Dart expression statement with a child expression
+ // compile to a JS expression statement with a child expression.
+ //
+ // ExpressionStatement js_ast.ExpressionStatement
+ // | --> compiles to --> |
+ // Expression js_ast.Expression
+ //
+ // Both the expression statement and child expression nodes contain their
+ // own source location information.
+ //
+ // In the case of a debugger() call, the code compiles to a single node.
+ //
+ // ExpressionStatement js_ast.DebuggerStatement
+ // | --> compiles to -->
+ // Expression
+ //
+ // The js_ast.DebuggerStatement already has the correct source information
+ // attached so we avoid overwriting with the incorrect source location from
+ // [s].
// TODO(jmesserly): is the `is! Block` still necessary?
- if (s is! Block) result.sourceInformation = _nodeStart(s);
+ if (!(s is Block || result is js_ast.DebuggerStatement)) {
+ result.sourceInformation = _nodeStart(s);
+ }
// The statement might be the target of a break or continue with a label.
var name = _labelNames[s];
@@ -5087,6 +5109,7 @@
// If we know that the left type uses identity for equality, we can
// sometimes emit better code, either `===` or `==`.
var isEnum = leftType is InterfaceType && leftType.classNode.isEnum;
+
var usesIdentity = _typeRep.isPrimitive(leftType) ||
isEnum ||
_isNull(left) ||
@@ -5290,12 +5313,14 @@
js_ast.Node _emitDebuggerCall(StaticInvocation node) {
var args = node.arguments.named;
var isStatement = node.parent is ExpressionStatement;
+ var debuggerStatement =
+ js_ast.DebuggerStatement().withSourceInformation(_nodeStart(node));
if (args.isEmpty) {
// Inline `debugger()` with no arguments, as a statement if possible,
// otherwise as an immediately invoked function.
return isStatement
- ? js.statement('debugger;')
- : js.call('(() => { debugger; return true})()');
+ ? debuggerStatement
+ : js.call('(() => { #; return true})()', [debuggerStatement]);
}
// The signature of `debugger()` is:
@@ -5320,8 +5345,9 @@
// order, then extract the `when` argument.
: js.call('#.when', js_ast.ObjectInitializer(jsArgs));
return isStatement
- ? js.statement('if (#) debugger;', when)
- : js.call('# && (() => { debugger; return true })()', when);
+ ? js.statement('if (#) #;', [when, debuggerStatement])
+ : js.call(
+ '# && (() => { #; return true })()', [when, debuggerStatement]);
}
/// Emits the target of a [StaticInvocation], [StaticGet], or [StaticSet].
diff --git a/pkg/dev_compiler/lib/src/kernel/native_types.dart b/pkg/dev_compiler/lib/src/kernel/native_types.dart
index dd7bdc8..5a81641 100644
--- a/pkg/dev_compiler/lib/src/kernel/native_types.dart
+++ b/pkg/dev_compiler/lib/src/kernel/native_types.dart
@@ -49,6 +49,8 @@
_addExtensionType(coreTypes.doubleClass, true);
_addExtensionType(coreTypes.boolClass, true);
_addExtensionType(coreTypes.stringClass, true);
+ // Allow `Function.==` to be recognized as a symbolized member.
+ _addExtensionType(coreTypes.functionClass, false);
var sdk = coreTypes.index;
_addExtensionTypes(sdk.getLibrary('dart:_interceptors'));
diff --git a/pkg/dev_compiler/test/sourcemap/testfiles/multiple_debugger_calls.dart b/pkg/dev_compiler/test/sourcemap/testfiles/multiple_debugger_calls.dart
new file mode 100644
index 0000000..f976b63
--- /dev/null
+++ b/pkg/dev_compiler/test/sourcemap/testfiles/multiple_debugger_calls.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+// @dart = 2.9
+
+import 'dart:developer';
+
+// Regression test: https://github.com/dart-lang/sdk/issues/45544
+
+var b = true;
+void main() {
+ // Ensure multiple debugger calls of different varieties map to different
+ // unique locations.
+ print('1');
+ /*s:1*/ debugger();
+ print('2');
+ /*s:2*/ debugger(when: b);
+ print('3');
+ foo(/*s:3*/ debugger());
+ print('4');
+ /*s:4*/ debugger(when: b);
+ print('5');
+ foo(/*s:5*/ debugger(when: b));
+ print('6');
+ /*s:6*/ debugger();
+ print('7');
+ foo(/*s:7*/ debugger(when: b));
+ print('8');
+ foo(/*s:8*/ debugger());
+}
+
+void foo(bool _) => null;
diff --git a/pkg/vm/lib/transformations/type_flow/signature_shaking.dart b/pkg/vm/lib/transformations/type_flow/signature_shaking.dart
index 20a0412..c6c7608 100644
--- a/pkg/vm/lib/transformations/type_flow/signature_shaking.dart
+++ b/pkg/vm/lib/transformations/type_flow/signature_shaking.dart
@@ -498,6 +498,12 @@
super.visitProcedure(node);
}
+ @override
+ void visitField(Field node) {
+ typeContext = StaticTypeContext(node, shaker.typeFlowAnalysis.environment);
+ super.visitField(node);
+ }
+
static void forEachArgumentRev(Arguments args, _ProcedureInfo info,
void Function(Expression, _ParameterInfo) fun) {
for (int i = args.named.length - 1; i >= 0; i--) {
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index 9135f50..1b237e9 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -175,6 +175,7 @@
"backend/typed_data_aot_test.cc",
"backend/yield_position_test.cc",
"cha_test.cc",
+ "relocation_test.cc",
"ffi/native_type_vm_test.cc",
"frontend/kernel_binary_flowgraph_test.cc",
"write_barrier_elimination_test.cc",
diff --git a/runtime/vm/compiler/relocation.cc b/runtime/vm/compiler/relocation.cc
index ff5ea61..e2f44aa 100644
--- a/runtime/vm/compiler/relocation.cc
+++ b/runtime/vm/compiler/relocation.cc
@@ -20,6 +20,47 @@
false,
"Generate always trampolines (for testing purposes).");
+DEFINE_FLAG(int,
+ lower_tail_pc_relative_call_distance,
+ -1,
+ "Lower tail call distance.");
+DEFINE_FLAG(int,
+ upper_tail_pc_relative_call_distance,
+ -1,
+ "Upper tail call distance.");
+DEFINE_FLAG(int, lower_pc_relative_call_distance, -1, "Lower call distance.");
+DEFINE_FLAG(int, upper_pc_relative_call_distance, -1, "Upper call distance.");
+
+struct TailCallDistanceLimits {
+ static intptr_t Lower() {
+ if (FLAG_lower_tail_pc_relative_call_distance != -1) {
+ return FLAG_lower_tail_pc_relative_call_distance;
+ }
+ return PcRelativeTailCallPattern::kLowerCallingRange;
+ }
+ static intptr_t Upper() {
+ if (FLAG_upper_tail_pc_relative_call_distance != -1) {
+ return FLAG_upper_tail_pc_relative_call_distance;
+ }
+ return PcRelativeTailCallPattern::kUpperCallingRange;
+ }
+};
+
+struct CallDistanceLimits {
+ static intptr_t Lower() {
+ if (FLAG_lower_pc_relative_call_distance != -1) {
+ return FLAG_lower_pc_relative_call_distance;
+ }
+ return PcRelativeCallPattern::kLowerCallingRange;
+ }
+ static intptr_t Upper() {
+ if (FLAG_upper_pc_relative_call_distance != -1) {
+ return FLAG_upper_pc_relative_call_distance;
+ }
+ return PcRelativeCallPattern::kUpperCallingRange;
+ }
+};
+
const intptr_t kTrampolineSize =
Utils::RoundUp(PcRelativeTrampolineJumpPattern::kLengthInBytes,
compiler::target::Instructions::kBarePayloadAlignment);
@@ -46,7 +87,7 @@
// * the maximum number of calls
// * the maximum offset into a target instruction
//
- FindInstructionAndCallLimits();
+ FindLargestInstruction();
// Emit all instructions and do relocations on the way.
for (intptr_t i = 0; i < code_objects_->length(); ++i) {
@@ -65,7 +106,8 @@
// If we have forward/backwards calls which are almost out-of-range, we'll
// create trampolines now.
- BuildTrampolinesForAlmostOutOfRangeCalls();
+ BuildTrampolinesForAlmostOutOfRangeCalls(
+ /*force=*/(i == (code_objects_->length() - 1)));
}
// We're guaranteed to have all calls resolved, since
@@ -101,7 +143,7 @@
// however we might need it to write information into V8 snapshot profile.
}
-void CodeRelocator::FindInstructionAndCallLimits() {
+void CodeRelocator::FindLargestInstruction() {
auto zone = thread_->zone();
auto& current_caller = Code::Handle(zone);
auto& call_targets = Array::Handle(zone);
@@ -122,48 +164,10 @@
kind_type_and_offset_ = call.Get<Code::kSCallTableKindAndOffset>();
const auto kind =
Code::KindField::decode(kind_type_and_offset_.Value());
- const auto return_pc_offset =
- Code::OffsetField::decode(kind_type_and_offset_.Value());
- const auto call_entry_point =
- Code::EntryPointField::decode(kind_type_and_offset_.Value());
-
if (kind == Code::kCallViaCode) {
continue;
}
-
- destination_ = GetTarget(call);
num_calls++;
-
- // A call site can decide to jump not to the beginning of a function but
- // rather jump into it at a certain (positive) offset.
- int32_t offset_into_target = 0;
- if (kind == Code::kPcRelativeCall || kind == Code::kPcRelativeTTSCall) {
- const intptr_t call_instruction_offset =
- return_pc_offset - PcRelativeCallPattern::kLengthInBytes;
- PcRelativeCallPattern call(current_caller.PayloadStart() +
- call_instruction_offset);
- ASSERT(call.IsValid());
- offset_into_target = call.distance();
- } else {
- ASSERT(kind == Code::kPcRelativeTailCall);
- const intptr_t call_instruction_offset =
- return_pc_offset - PcRelativeTailCallPattern::kLengthInBytes;
- PcRelativeTailCallPattern call(current_caller.PayloadStart() +
- call_instruction_offset);
- ASSERT(call.IsValid());
- offset_into_target = call.distance();
- }
-
- const uword destination_payload = destination_.PayloadStart();
- const uword entry_point = call_entry_point == Code::kUncheckedEntry
- ? destination_.UncheckedEntryPoint()
- : destination_.EntryPoint();
-
- offset_into_target += (entry_point - destination_payload);
-
- if (offset_into_target > max_offset_into_target_) {
- max_offset_into_target_ = offset_into_target;
- }
}
if (num_calls > max_calls_) {
@@ -323,8 +327,11 @@
auto map_entry = text_offsets_.Lookup(callee);
if (map_entry == nullptr) return false;
- ResolveCall(unresolved_call);
- return true;
+ if (IsTargetInRangeFor(unresolved_call, map_entry->value)) {
+ ResolveCall(unresolved_call);
+ return true;
+ }
+ return false;
}
void CodeRelocator::ResolveUnresolvedCallsTargeting(
@@ -411,11 +418,11 @@
const auto forward_distance =
target_text_offset - unresolved_call->text_offset;
if (unresolved_call->is_tail_call) {
- return PcRelativeTailCallPattern::kLowerCallingRange < forward_distance &&
- forward_distance < PcRelativeTailCallPattern::kUpperCallingRange;
+ return TailCallDistanceLimits::Lower() < forward_distance &&
+ forward_distance < TailCallDistanceLimits::Upper();
} else {
- return PcRelativeCallPattern::kLowerCallingRange < forward_distance &&
- forward_distance < PcRelativeCallPattern::kUpperCallingRange;
+ return CallDistanceLimits::Lower() < forward_distance &&
+ forward_distance < CallDistanceLimits::Upper();
}
}
@@ -471,7 +478,7 @@
return destination_.ptr();
}
-void CodeRelocator::BuildTrampolinesForAlmostOutOfRangeCalls() {
+void CodeRelocator::BuildTrampolinesForAlmostOutOfRangeCalls(bool force) {
while (!all_unresolved_calls_.IsEmpty()) {
UnresolvedCall* unresolved_call = all_unresolved_calls_.First();
@@ -484,7 +491,7 @@
kTrampolineSize *
(unresolved_calls_by_destination_.Length() + max_calls_);
if (IsTargetInRangeFor(unresolved_call, future_boundary) &&
- !FLAG_always_generate_trampolines_for_testing) {
+ !FLAG_always_generate_trampolines_for_testing && !force) {
break;
}
diff --git a/runtime/vm/compiler/relocation.h b/runtime/vm/compiler/relocation.h
index e9b6d44..77b914c 100644
--- a/runtime/vm/compiler/relocation.h
+++ b/runtime/vm/compiler/relocation.h
@@ -161,7 +161,7 @@
void Relocate(bool is_vm_isolate);
- void FindInstructionAndCallLimits();
+ void FindLargestInstruction();
bool AddInstructionsToText(CodePtr code);
void ScanCallTargets(const Code& code,
@@ -183,7 +183,7 @@
intptr_t destination_text);
void ResolveTrampoline(UnresolvedTrampoline* unresolved_trampoline);
- void BuildTrampolinesForAlmostOutOfRangeCalls();
+ void BuildTrampolinesForAlmostOutOfRangeCalls(bool force);
intptr_t FindDestinationInText(const InstructionsPtr destination,
intptr_t offset_into_target);
@@ -207,7 +207,6 @@
intptr_t max_instructions_size_ = 0;
// The maximum number of pc-relative calls in an instructions object.
intptr_t max_calls_ = 0;
- intptr_t max_offset_into_target_ = 0;
// Data structures used for relocation.
intptr_t next_text_offset_ = 0;
diff --git a/runtime/vm/compiler/relocation_test.cc b/runtime/vm/compiler/relocation_test.cc
new file mode 100644
index 0000000..9e101cb
--- /dev/null
+++ b/runtime/vm/compiler/relocation_test.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+#include "platform/assert.h"
+
+#include "vm/allocation.h"
+#include "vm/code_patcher.h"
+#include "vm/compiler/assembler/assembler.h"
+#include "vm/compiler/relocation.h"
+#include "vm/instructions.h"
+#include "vm/longjump.h"
+#include "vm/unit_test.h"
+
+#define __ assembler->
+
+namespace dart {
+
+#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
+
+DECLARE_FLAG(bool, dual_map_code);
+DECLARE_FLAG(int, lower_pc_relative_call_distance);
+DECLARE_FLAG(int, upper_pc_relative_call_distance);
+
+struct RelocatorTestHelper {
+ explicit RelocatorTestHelper(Thread* thread)
+ : thread(thread),
+ locker(thread, thread->isolate_group()->program_lock()),
+ safepoint_and_growth_scope(thread) {
+ // So the relocator uses the correct instruction size layout.
+ FLAG_precompiled_mode = true;
+ FLAG_use_bare_instructions = true;
+
+ FLAG_lower_pc_relative_call_distance = -128;
+ FLAG_upper_pc_relative_call_distance = 128;
+ }
+ ~RelocatorTestHelper() {
+ FLAG_use_bare_instructions = false;
+ FLAG_precompiled_mode = false;
+ }
+
+ void CreateInstructions(std::initializer_list<intptr_t> sizes) {
+ for (auto size : sizes) {
+ codes.Add(&Code::Handle(AllocationInstruction(size)));
+ }
+ }
+
+ CodePtr AllocationInstruction(uintptr_t size) {
+ const auto& instructions = Instructions::Handle(
+ Instructions::New(size, /*has_monomorphic=*/false));
+
+ uword addr = instructions.PayloadStart();
+ for (uintptr_t i = 0; i < (size / 4); ++i) {
+ *reinterpret_cast<uint32_t*>(addr + 4 * i) =
+ static_cast<uint32_t>(kBreakInstructionFiller);
+ }
+
+ const auto& code = Code::Handle(Code::New(0));
+ code.SetActiveInstructions(instructions, 0);
+ code.set_instructions(instructions);
+ return code.ptr();
+ }
+
+ void EmitPcRelativeCallFunction(intptr_t idx, intptr_t to_idx) {
+ const Code& code = *codes[idx];
+ const Code& target = *codes[to_idx];
+
+ EmitCodeFor(code, [&](compiler::Assembler* assembler) {
+#if defined(TARGET_ARCH_ARM64)
+ __ SetupDartSP();
+ __ EnterFrame(0);
+#elif defined(TARGET_ARCH_ARM)
+ SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(
+ __ EnterFrame((1 << LR), 0));
+#endif
+ __ GenerateUnRelocatedPcRelativeCall();
+ AddPcRelativeCallTargetAt(__ CodeSize(), code, target);
+#if defined(TARGET_ARCH_ARM64)
+ __ LeaveFrame();
+#elif defined(TARGET_ARCH_ARM)
+ RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(
+ __ LeaveFrame((1 << LR)));
+#endif
+ __ Ret();
+ });
+ }
+
+ void EmitReturn42Function(intptr_t idx) {
+ const Code& code = *codes[idx];
+ EmitCodeFor(code, [&](compiler::Assembler* assembler) {
+#if defined(TARGET_ARCH_X64)
+ __ LoadImmediate(RAX, 42);
+#elif defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+ __ LoadImmediate(R0, 42);
+#endif
+ __ Ret();
+ });
+ }
+
+ void EmitCodeFor(const Code& code,
+ std::function<void(compiler::Assembler* assembler)> fun) {
+ const auto& inst = Instructions::Handle(code.instructions());
+
+ compiler::Assembler assembler(nullptr);
+ fun(&assembler);
+
+ const uword addr = inst.PayloadStart();
+ memmove(reinterpret_cast<void*>(addr),
+ reinterpret_cast<void*>(assembler.CodeAddress(0)),
+ assembler.CodeSize());
+
+ if (FLAG_write_protect_code && FLAG_dual_map_code) {
+ auto& instructions = Instructions::Handle(code.instructions());
+ instructions ^= OldPage::ToExecutable(instructions.ptr());
+ code.set_instructions(instructions);
+ }
+ }
+
+ void AddPcRelativeCallTargetAt(intptr_t offset,
+ const Code& code,
+ const Code& target) {
+ const auto& kind_and_offset = Smi::Handle(
+ Smi::New(Code::KindField::encode(Code::kPcRelativeCall) |
+ Code::EntryPointField::encode(Code::kDefaultEntry) |
+ Code::OffsetField::encode(offset)));
+ AddCall(code, target, kind_and_offset);
+ }
+
+ void AddCall(const Code& code,
+ const Code& target,
+ const Smi& kind_and_offset) {
+ auto& call_targets = Array::Handle(code.static_calls_target_table());
+ if (call_targets.IsNull()) {
+ call_targets = Array::New(Code::kSCallTableEntryLength);
+ } else {
+ call_targets = Array::Grow(
+ call_targets, call_targets.Length() + Code::kSCallTableEntryLength);
+ }
+
+ StaticCallsTable table(call_targets);
+ auto entry = table[table.Length() - 1];
+ entry.Set<Code::kSCallTableKindAndOffset>(kind_and_offset);
+ entry.Set<Code::kSCallTableCodeOrTypeTarget>(target);
+ entry.Set<Code::kSCallTableFunctionTarget>(
+ Function::Handle(Function::null()));
+ code.set_static_calls_target_table(call_targets);
+ }
+
+ void BuildImageAndRunTest(
+ std::function<void(const GrowableArray<ImageWriterCommand>&, uword*)>
+ fun) {
+ auto& image = Instructions::Handle();
+ uword entrypoint = 0;
+ {
+ GrowableArray<CodePtr> raw_codes;
+ for (auto code : codes) {
+ raw_codes.Add(code->ptr());
+ }
+
+ GrowableArray<ImageWriterCommand> commands;
+ CodeRelocator::Relocate(thread, &raw_codes, &commands,
+ /*is_vm_isolate=*/false);
+
+ uword expected_offset = 0;
+ fun(commands, &expected_offset);
+
+ image = BuildImage(&commands);
+ entrypoint = image.EntryPoint() + expected_offset;
+
+ for (intptr_t i = 0; i < commands.length(); ++i) {
+ if (commands[i].op == ImageWriterCommand::InsertBytesOfTrampoline) {
+ delete[] commands[i].insert_trampoline_bytes.buffer;
+ commands[i].insert_trampoline_bytes.buffer = nullptr;
+ }
+ }
+ }
+ typedef intptr_t (*Fun)() DART_UNUSED;
+#if defined(TARGET_ARCH_X64)
+ EXPECT_EQ(42, reinterpret_cast<Fun>(entrypoint)());
+#elif defined(TARGET_ARCH_ARM)
+ EXPECT_EQ(42, EXECUTE_TEST_CODE_INT32(Fun, entrypoint));
+#elif defined(TARGET_ARCH_ARM64)
+ EXPECT_EQ(42, EXECUTE_TEST_CODE_INT64(Fun, entrypoint));
+#endif
+ }
+
+ InstructionsPtr BuildImage(GrowableArray<ImageWriterCommand>* commands) {
+ intptr_t size = 0;
+ for (intptr_t i = 0; i < commands->length(); ++i) {
+ switch ((*commands)[i].op) {
+ case ImageWriterCommand::InsertBytesOfTrampoline:
+ size += (*commands)[i].insert_trampoline_bytes.buffer_length;
+ break;
+ case ImageWriterCommand::InsertInstructionOfCode:
+ size += ImageWriter::SizeInSnapshot(Code::InstructionsOf(
+ (*commands)[i].insert_instruction_of_code.code));
+ break;
+ }
+ }
+
+ auto& instructions = Instructions::Handle(
+ Instructions::New(size, /*has_monomorphic=*/false));
+ {
+ uword addr = instructions.PayloadStart();
+ for (intptr_t i = 0; i < commands->length(); ++i) {
+ switch ((*commands)[i].op) {
+ case ImageWriterCommand::InsertBytesOfTrampoline: {
+ const auto entry = (*commands)[i].insert_trampoline_bytes;
+ const auto current_size = entry.buffer_length;
+ memmove(reinterpret_cast<void*>(addr), entry.buffer, current_size);
+ addr += current_size;
+ break;
+ }
+ case ImageWriterCommand::InsertInstructionOfCode: {
+ const auto entry = (*commands)[i].insert_instruction_of_code;
+ const auto current_size =
+ ImageWriter::SizeInSnapshot(Code::InstructionsOf(entry.code));
+ const auto alias_offset =
+ OldPage::Of(Code::InstructionsOf(entry.code))->AliasOffset();
+ memmove(
+ reinterpret_cast<void*>(addr),
+ reinterpret_cast<void*>(Instructions::PayloadStart(
+ Code::InstructionsOf(entry.code)) -
+ alias_offset),
+ current_size);
+ addr += current_size;
+ break;
+ }
+ }
+ }
+
+ if (FLAG_write_protect_code) {
+ const uword address = UntaggedObject::ToAddr(instructions.ptr());
+ const auto size = instructions.ptr()->untag()->HeapSize();
+ instructions =
+ Instructions::RawCast(OldPage::ToExecutable(instructions.ptr()));
+
+ const auto prot = FLAG_dual_map_code ? VirtualMemory::kReadOnly
+ : VirtualMemory::kReadExecute;
+ VirtualMemory::Protect(reinterpret_cast<void*>(address), size, prot);
+ }
+ CPU::FlushICache(instructions.PayloadStart(), instructions.Size());
+ }
+ return instructions.ptr();
+ }
+
+ Thread* thread;
+ SafepointWriteRwLocker locker;
+ ForceGrowthSafepointOperationScope safepoint_and_growth_scope;
+ GrowableArray<const Code*> codes;
+};
+
+ISOLATE_UNIT_TEST_CASE(CodeRelocator_DirectForwardCall) {
+ RelocatorTestHelper helper(thread);
+ helper.CreateInstructions({32, 36, 32});
+ helper.EmitPcRelativeCallFunction(0, 2);
+ helper.EmitReturn42Function(2);
+ helper.BuildImageAndRunTest(
+ [&](const GrowableArray<ImageWriterCommand>& commands,
+ uword* entry_point) {
+ EXPECT_EQ(3, commands.length());
+
+ // This makes an in-range forward call.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[1].op);
+ // This is is the target of the forwards call.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+
+ *entry_point = commands[0].expected_offset;
+ });
+}
+
+ISOLATE_UNIT_TEST_CASE(CodeRelocator_OutOfRangeForwardCall) {
+ RelocatorTestHelper helper(thread);
+ helper.CreateInstructions(
+ {32, FLAG_upper_pc_relative_call_distance - 32 + 4, 32});
+ helper.EmitPcRelativeCallFunction(0, 2);
+ helper.EmitReturn42Function(2);
+ helper.BuildImageAndRunTest([&](const GrowableArray<ImageWriterCommand>&
+ commands,
+ uword* entry_point) {
+ EXPECT_EQ(4, commands.length());
+
+ // This makes an out-of-range forward call.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
+ // This is the last change the relocator thinks it can ensure the
+ // out-of-range call above can call a trampoline - so it injets it here and
+ // no later.
+ EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[1].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+ // This is the target of the forwwards call.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
+
+ *entry_point = commands[0].expected_offset;
+ });
+}
+
+ISOLATE_UNIT_TEST_CASE(CodeRelocator_DirectBackwardCall) {
+ RelocatorTestHelper helper(thread);
+ helper.CreateInstructions({32, 32, 32});
+ helper.EmitReturn42Function(0);
+ helper.EmitPcRelativeCallFunction(2, 0);
+ helper.BuildImageAndRunTest(
+ [&](const GrowableArray<ImageWriterCommand>& commands,
+ uword* entry_point) {
+ EXPECT_EQ(3, commands.length());
+
+ // This is the backwards call target.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[1].op);
+ // This makes an in-range backwards call.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+
+ *entry_point = commands[2].expected_offset;
+ });
+}
+
+ISOLATE_UNIT_TEST_CASE(CodeRelocator_OutOfRangeBackwardCall) {
+ RelocatorTestHelper helper(thread);
+ helper.CreateInstructions({32, 32, 32, 32 + 4, 32, 32, 32, 32, 32});
+ helper.EmitReturn42Function(0);
+ helper.EmitPcRelativeCallFunction(4, 0);
+ helper.BuildImageAndRunTest([&](const GrowableArray<ImageWriterCommand>&
+ commands,
+ uword* entry_point) {
+ EXPECT_EQ(10, commands.length());
+
+ // This is the backwards call target.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[1].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
+ // This makes an out-of-range backwards call. The relocator will make the
+ // call go to a trampoline instead. It will delay insertion of the
+ // trampoline until it almost becomes out-of-range.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[4].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[5].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[6].op);
+ // This is the last change the relocator thinks it can ensure the
+ // out-of-range call above can call a trampoline - so it injets it here and
+ // no later.
+ EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[7].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[8].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[9].op);
+
+ *entry_point = commands[4].expected_offset;
+ });
+}
+
+ISOLATE_UNIT_TEST_CASE(CodeRelocator_OutOfRangeBackwardCall2) {
+ RelocatorTestHelper helper(thread);
+ helper.CreateInstructions({32, 32, 32, 32 + 4, 32});
+ helper.EmitReturn42Function(0);
+ helper.EmitPcRelativeCallFunction(4, 0);
+ helper.BuildImageAndRunTest(
+ [&](const GrowableArray<ImageWriterCommand>& commands,
+ uword* entry_point) {
+ EXPECT_EQ(6, commands.length());
+
+ // This is the backwards call target.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[1].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
+ // This makes an out-of-range backwards call. The relocator will make
+ // the call go to a trampoline instead. It will delay insertion of the
+ // trampoline until it almost becomes out-of-range.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[4].op);
+ // There's no other instructions coming, so the relocator will resolve
+ // any pending out-of-range calls by inserting trampolines at the end.
+ EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[5].op);
+
+ *entry_point = commands[4].expected_offset;
+ });
+}
+
+UNIT_TEST_CASE(PCRelativeCallPatterns) {
+ {
+ uint8_t instruction[PcRelativeCallPattern::kLengthInBytes];
+
+ PcRelativeCallPattern pattern(reinterpret_cast<uword>(&instruction));
+
+ pattern.set_distance(PcRelativeCallPattern::kLowerCallingRange);
+ EXPECT_EQ(PcRelativeCallPattern::kLowerCallingRange, pattern.distance());
+
+ pattern.set_distance(PcRelativeCallPattern::kUpperCallingRange);
+ EXPECT_EQ(PcRelativeCallPattern::kUpperCallingRange, pattern.distance());
+ }
+ {
+ uint8_t instruction[PcRelativeTailCallPattern::kLengthInBytes];
+
+ PcRelativeTailCallPattern pattern(reinterpret_cast<uword>(&instruction));
+
+ pattern.set_distance(PcRelativeTailCallPattern::kLowerCallingRange);
+ EXPECT_EQ(PcRelativeTailCallPattern::kLowerCallingRange,
+ pattern.distance());
+
+ pattern.set_distance(PcRelativeTailCallPattern::kUpperCallingRange);
+ EXPECT_EQ(PcRelativeTailCallPattern::kUpperCallingRange,
+ pattern.distance());
+ }
+}
+
+#endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
+
+} // namespace dart
diff --git a/runtime/vm/instructions_arm.h b/runtime/vm/instructions_arm.h
index a1e1f9b..5bfc9bd 100644
--- a/runtime/vm/instructions_arm.h
+++ b/runtime/vm/instructions_arm.h
@@ -193,8 +193,10 @@
class PcRelativeCallPatternBase : public ValueObject {
public:
// 24 bit signed integer which will get multiplied by 4.
- static const intptr_t kLowerCallingRange = -(1 << 25) + Instr::kPCReadOffset;
- static const intptr_t kUpperCallingRange = (1 << 25) - 1;
+ static constexpr intptr_t kLowerCallingRange =
+ -(1 << 25) + Instr::kPCReadOffset;
+ static constexpr intptr_t kUpperCallingRange =
+ (1 << 25) - Instr::kInstrSize + Instr::kPCReadOffset;
explicit PcRelativeCallPatternBase(uword pc) : pc_(pc) {}
diff --git a/runtime/vm/instructions_arm64.h b/runtime/vm/instructions_arm64.h
index b450522..ecfecb6 100644
--- a/runtime/vm/instructions_arm64.h
+++ b/runtime/vm/instructions_arm64.h
@@ -203,8 +203,8 @@
class PcRelativePatternBase : public ValueObject {
public:
// 26 bit signed integer which will get multiplied by 4.
- static const intptr_t kLowerCallingRange = -(1 << 27);
- static const intptr_t kUpperCallingRange = (1 << 27) - 1;
+ static constexpr intptr_t kLowerCallingRange = -(1 << 27);
+ static constexpr intptr_t kUpperCallingRange = (1 << 27) - Instr::kInstrSize;
explicit PcRelativePatternBase(uword pc) : pc_(pc) {}
diff --git a/runtime/vm/instructions_x64.h b/runtime/vm/instructions_x64.h
index efc8abd..a65ba55 100644
--- a/runtime/vm/instructions_x64.h
+++ b/runtime/vm/instructions_x64.h
@@ -110,8 +110,8 @@
// callq *[rip+offset]
class PcRelativeCallPattern : public InstructionPattern<PcRelativeCallPattern> {
public:
- static const intptr_t kLowerCallingRange = -(DART_UINT64_C(1) << 31);
- static const intptr_t kUpperCallingRange = (DART_UINT64_C(1) << 31) - 1;
+ static constexpr intptr_t kLowerCallingRange = -(DART_UINT64_C(1) << 31);
+ static constexpr intptr_t kUpperCallingRange = (DART_UINT64_C(1) << 31) - 1;
explicit PcRelativeCallPattern(uword pc) : InstructionPattern(pc) {}
@@ -169,11 +169,11 @@
kLengthInBytes;
}
- void set_distance(int32_t distance) {
+ void set_distance(intptr_t distance) {
// [distance] is relative to the start of the instruction, x64 considers the
// offset relative to next PC.
StoreUnaligned(reinterpret_cast<int32_t*>(pattern_start_ + 1),
- distance - kLengthInBytes);
+ static_cast<int32_t>(distance - kLengthInBytes));
}
bool IsValid() const {
@@ -187,8 +187,8 @@
class PcRelativeTailCallPattern : public PcRelativeTrampolineJumpPattern {
public:
- static const intptr_t kLowerCallingRange = -(1ul << 31) + kLengthInBytes;
- static const intptr_t kUpperCallingRange = (1ul << 31) - 1;
+ static constexpr intptr_t kLowerCallingRange = -(DART_INT64_C(1) << 31) + kLengthInBytes;
+ static constexpr intptr_t kUpperCallingRange = (DART_INT64_C(1) << 31) - 1;
explicit PcRelativeTailCallPattern(uword pc)
: PcRelativeTrampolineJumpPattern(pc) {}
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 4f4fb6c..20558b3 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -5334,6 +5334,8 @@
void set_stats(CodeStatistics* stats) const;
private:
+ friend struct RelocatorTestHelper;
+
void SetSize(intptr_t value) const {
ASSERT(value >= 0);
StoreNonPointer(&untag()->size_and_flags_,
@@ -6392,6 +6394,8 @@
friend class UntaggedObject; // For UntaggedObject::SizeFromClass().
friend class UntaggedCode;
+ friend struct RelocatorTestHelper;
+
enum {
kOptimizedBit = 0,
kForceOptimizedBit = 1,
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
index d0e0ba2..041dfd5 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
@@ -220,14 +220,6 @@
}
@patch
-class OSError {
- @patch
- static int inProgressErrorCode() {
- throw new UnsupportedError("OSError.inProgressErrorCode");
- }
-}
-
-@patch
class _IOCrypto {
@patch
static Uint8List getRandomBytes(int count) {
diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
index fb7052f..77a2530 100644
--- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
@@ -228,14 +228,6 @@
}
@patch
-class OSError {
- @patch
- static int inProgressErrorCode() {
- throw new UnsupportedError("OSError.inProgressErrorCode");
- }
-}
-
-@patch
class _Platform {
@patch
static int _numberOfProcessors() {
diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart
index a20a0bb..6c3a77a 100644
--- a/sdk/lib/_internal/vm/bin/socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/socket_patch.dart
@@ -374,6 +374,9 @@
// implicit constructor.
class _NativeSocketNativeWrapper extends NativeFieldWrapperClass1 {}
+/// Returns error code that corresponds to EINPROGRESS OS error.
+int get _inProgressErrorCode native "OSError_inProgressErrorCode";
+
// The _NativeSocket class encapsulates an OS socket.
class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject {
// Bit flags used when communicating between the eventhandler and
@@ -802,7 +805,7 @@
try {
socket.port;
} catch (e) {
- if (e is OSError && e.errorCode == OSError.inProgressErrorCode()) {
+ if (e is OSError && e.errorCode == _inProgressErrorCode) {
// Ignore the error, proceed with waiting for a socket to become open.
// In non-blocking mode connect might not be established away, socket
// have to be waited for.
diff --git a/sdk/lib/io/common.dart b/sdk/lib/io/common.dart
index a2d4d73..252d602 100644
--- a/sdk/lib/io/common.dart
+++ b/sdk/lib/io/common.dart
@@ -48,9 +48,6 @@
/// Constant used to indicate that no OS error code is available.
static const int noErrorCode = -1;
- /// Returns error code that corresponds to EINPROGRESS OS error.
- external static int inProgressErrorCode();
-
/// Error message supplied by the operating system. This will be empty if no
/// message is associated with the error.
final String message;
diff --git a/tests/language/function/regress_45601_test.dart b/tests/language/function/regress_45601_test.dart
new file mode 100644
index 0000000..0a096f0
--- /dev/null
+++ b/tests/language/function/regress_45601_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+import "package:expect/expect.dart";
+
+// Regression test for https://github.com/dart-lang/sdk/issues/45601
+// When comparing values of type `Function` for equality DDC was generating
+// code that would throw at runtime.
+
+class Wrapper {
+ Wrapper(this.function);
+
+ final Function function;
+
+ @override
+ bool operator ==(Object other) =>
+ other is Wrapper && function == other.function;
+
+ @override
+ int get hashCode => function.hashCode;
+}
+
+void main() {
+ final map = <Wrapper, int>{};
+ final ref = Wrapper(main);
+ map[ref] = 42;
+ Expect.equals(42, map[ref]);
+
+ testStaticEquality();
+ testDynamicEquality();
+}
+
+void fn<T>(T t) => null;
+
+/// Ensure `==` calls on function values that are statically typed as `Function`
+/// or `Function?` work as expected.
+void testStaticEquality() {
+ Function staticFunction = fn;
+ Expect.isTrue(staticFunction == fn);
+ Expect.isFalse(staticFunction == main);
+
+ Function? staticFunction2 = null;
+ Expect.isFalse(staticFunction2 == staticFunction);
+ staticFunction2 = fn;
+ Expect.isTrue(staticFunction2 == staticFunction);
+}
+
+/// Ensure `==` calls on function values that are statically typed as `dynamic`
+/// work as expected.
+void testDynamicEquality() {
+ dynamic dynamicFunction = fn;
+ Expect.isTrue(dynamicFunction == fn);
+ Expect.isFalse(dynamicFunction == main);
+
+ dynamic dynamicFunction2 = null;
+ Expect.isFalse(dynamicFunction2 == dynamicFunction);
+ dynamicFunction2 = fn;
+ Expect.isTrue(dynamicFunction2 == dynamicFunction);
+}
diff --git a/tests/language/regress/regress45642_test.dart b/tests/language/regress/regress45642_test.dart
new file mode 100644
index 0000000..9222e13
--- /dev/null
+++ b/tests/language/regress/regress45642_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+class Utils {
+ static final foo = Foo(() {});
+}
+
+class Foo {
+ Foo(func);
+}
+
+void main() {
+ print(Utils.foo); // should print: Instance of 'Foo'
+}
diff --git a/tests/language_2/function/regress_45601_test.dart b/tests/language_2/function/regress_45601_test.dart
new file mode 100644
index 0000000..e1a8c19
--- /dev/null
+++ b/tests/language_2/function/regress_45601_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+import "package:expect/expect.dart";
+
+// Regression test for https://github.com/dart-lang/sdk/issues/45601
+// When comparing values of type `Function` for equality DDC was generating
+// code that would throw at runtime.
+
+void fn<T>(T t) => null;
+
+void main() {
+ testStaticEquality();
+ testDynamicEquality();
+}
+
+/// Ensure `==` calls on function values that are statically typed as `Function`
+/// work as expected.
+void testStaticEquality() {
+ Function staticFunction = fn;
+ Expect.isTrue(staticFunction == fn);
+ Expect.isFalse(staticFunction == main);
+
+ Function staticFunction2 = null;
+ Expect.isFalse(staticFunction2 == staticFunction);
+ staticFunction2 = fn;
+ Expect.isTrue(staticFunction2 == staticFunction);
+}
+
+/// Ensure `==` calls on function values that are statically typed as `dynamic`
+/// work as expected.
+void testDynamicEquality() {
+ dynamic dynamicFunction = fn;
+ Expect.isTrue(dynamicFunction == fn);
+ Expect.isFalse(dynamicFunction == main);
+
+ dynamic dynamicFunction2 = null;
+ Expect.isFalse(dynamicFunction2 == dynamicFunction);
+ dynamicFunction2 = fn;
+ Expect.isTrue(dynamicFunction2 == dynamicFunction);
+}
diff --git a/tests/language_2/regress/regress45642_test.dart b/tests/language_2/regress/regress45642_test.dart
new file mode 100644
index 0000000..9222e13
--- /dev/null
+++ b/tests/language_2/regress/regress45642_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+class Utils {
+ static final foo = Foo(() {});
+}
+
+class Foo {
+ Foo(func);
+}
+
+void main() {
+ print(Utils.foo); // should print: Instance of 'Foo'
+}
diff --git a/tools/VERSION b/tools/VERSION
index 8618025..b4ce199 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
MINOR 13
PATCH 0
PRERELEASE 211
-PRERELEASE_PATCH 6
\ No newline at end of file
+PRERELEASE_PATCH 13
\ No newline at end of file