[vm] Fix vm:align-loops pragma
Use aligned text offset for instructions when resolving static calls during relocation. The vm/dart/align_loops_test is adjusted to cover this issue: previous we only verified alignment but did not actually run the generated code, so the bug was undetected. Issue https://github.com/dart-lang/sdk/issues/55522 TEST=vm/dart/align_loops_test R=alexmarkov@google.com Change-Id: I56521c104f91b85150584539f58191cf4592f4f5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365144 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
ed5aeab889
commit
84fed14131
@@ -2,90 +2,72 @@
|
||||
// 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 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
// This test verifies that vm:align-loops pragma works as expected.
|
||||
//
|
||||
// * This test should run without crashing in AOT mode.
|
||||
// * `align_loops_verify_alignment_test.dart` will AOT compile this test
|
||||
// and then verify that [alignedFunction1] and [alignedFunction2] are
|
||||
// aligned.
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:native_stack_traces/elf.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'use_flag_test_helper.dart';
|
||||
// Having a static call to this function verifies that relocation works
|
||||
// as expected even when caller needs to be aligned.
|
||||
@pragma('vm:never-inline')
|
||||
void printOk() {
|
||||
print("ok");
|
||||
}
|
||||
|
||||
void checkAligned(Symbol sym) {
|
||||
// We only expect to run this test on X64 Linux.
|
||||
final expectedAlignment = 32;
|
||||
if ((sym.value & (expectedAlignment - 1)) != 0) {
|
||||
throw 'Symbol $sym has value ${sym.value} which is not aligned by '
|
||||
'$expectedAlignment';
|
||||
@pragma('vm:never-inline')
|
||||
int foo(Uint8List list) {
|
||||
printOk();
|
||||
var result = 0;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
printOk();
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> testAOT(String dillPath, {bool useAsm = false}) async {
|
||||
await withTempDir('align-loops-test-${useAsm ? 'asm' : 'elf'}',
|
||||
(String tempDir) async {
|
||||
// Generate the snapshot
|
||||
final snapshotPath = path.join(tempDir, 'libtest.so');
|
||||
final commonSnapshotArgs = [dillPath];
|
||||
|
||||
if (useAsm) {
|
||||
final assemblyPath = path.join(tempDir, 'test.S');
|
||||
|
||||
await run(genSnapshot, <String>[
|
||||
'--snapshot-kind=app-aot-assembly',
|
||||
'--assembly=$assemblyPath',
|
||||
...commonSnapshotArgs,
|
||||
]);
|
||||
|
||||
await assembleSnapshot(assemblyPath, snapshotPath);
|
||||
} else {
|
||||
await run(genSnapshot, <String>[
|
||||
'--snapshot-kind=app-aot-elf',
|
||||
'--elf=$snapshotPath',
|
||||
...commonSnapshotArgs,
|
||||
]);
|
||||
}
|
||||
|
||||
print("Snapshot generated at $snapshotPath.");
|
||||
|
||||
final elf = Elf.fromFile(snapshotPath)!;
|
||||
// The very first symbol should be aligned by 32 bytes because it is
|
||||
// the start of the instructions section.
|
||||
checkAligned(elf.staticSymbols.first);
|
||||
for (var symbol in elf.staticSymbols) {
|
||||
if (symbol.name.startsWith('alignedFunction')) {
|
||||
checkAligned(symbol);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void main() async {
|
||||
// Only run this test on Linux X64 for simplicity.
|
||||
if (!(Platform.isLinux && buildDir.endsWith('X64'))) {
|
||||
return;
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:align-loops')
|
||||
int alignedFunction1(Uint8List list) {
|
||||
printOk();
|
||||
var result = 0;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
|
||||
await withTempDir('align_loops', (String tempDir) async {
|
||||
final testProgram = path.join(sdkDir, 'runtime', 'tests', 'vm', 'dart',
|
||||
'align_loops_test_program.dart');
|
||||
|
||||
final aotDillPath = path.join(tempDir, 'aot_test.dill');
|
||||
await run(genKernel, <String>[
|
||||
'--aot',
|
||||
'--platform',
|
||||
platformDill,
|
||||
...Platform.executableArguments
|
||||
.where((arg) => arg.startsWith('--enable-experiment=')),
|
||||
'-o',
|
||||
aotDillPath,
|
||||
testProgram
|
||||
]);
|
||||
|
||||
await Future.wait([
|
||||
// Test unstripped ELF generation directly.
|
||||
testAOT(aotDillPath),
|
||||
testAOT(aotDillPath, useAsm: true),
|
||||
]);
|
||||
});
|
||||
printOk();
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
int baz(Uint8List list) {
|
||||
printOk();
|
||||
var result = 1;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
printOk();
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:align-loops')
|
||||
int alignedFunction2(Uint8List list) {
|
||||
printOk();
|
||||
var result = 2;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
printOk();
|
||||
return result;
|
||||
}
|
||||
|
||||
void main(List<String> args) {
|
||||
final v = Uint8List(10);
|
||||
foo(v);
|
||||
alignedFunction1(v);
|
||||
baz(v);
|
||||
alignedFunction2(v);
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) 2024, 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 'dart:typed_data';
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
int foo(Uint8List list) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:align-loops')
|
||||
int alignedFunction1(Uint8List list) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
int baz(Uint8List list) {
|
||||
var result = 1;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:align-loops')
|
||||
int alignedFunction2(Uint8List list) {
|
||||
var result = 2;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result ^= list[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
int benchmark(String name, int Function(Uint8List) f, Uint8List list) {
|
||||
final sw = Stopwatch()..start();
|
||||
int result = 0;
|
||||
int n = 0;
|
||||
while (sw.elapsedMilliseconds < 2000) {
|
||||
result ^= f(list);
|
||||
n++;
|
||||
}
|
||||
print('$name: ${sw.elapsedMilliseconds / n}');
|
||||
return result;
|
||||
}
|
||||
|
||||
void main() {
|
||||
final v = Uint8List(1024 * 1024 * 10);
|
||||
// Note: we don't use tear-offs for alignedFunctionX because that would
|
||||
// lead to two symbols both called alignedFunctionX in the resulting ELF:
|
||||
// one for tear-off and one for the actual function. This would make it
|
||||
// harder to verify that alignedFunction1 itself is correctly aligned.
|
||||
benchmark('foo', foo, v);
|
||||
benchmark('alignedFunction1', (list) => alignedFunction1(list), v);
|
||||
benchmark('baz', baz, v);
|
||||
benchmark('alignedFunction2', (list) => alignedFunction2(list), v);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2024, 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 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:native_stack_traces/elf.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'use_flag_test_helper.dart';
|
||||
|
||||
void checkAligned(Symbol sym) {
|
||||
// We only expect to run this test on X64 Linux.
|
||||
final expectedAlignment = 32;
|
||||
if ((sym.value & (expectedAlignment - 1)) != 0) {
|
||||
throw 'Symbol $sym has value ${sym.value} which is not aligned by '
|
||||
'$expectedAlignment';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> testAOT(String dillPath, {bool useAsm = false}) async {
|
||||
await withTempDir('align-loops-test-${useAsm ? 'asm' : 'elf'}',
|
||||
(String tempDir) async {
|
||||
// Generate the snapshot
|
||||
final snapshotPath = path.join(tempDir, 'libtest.so');
|
||||
final commonSnapshotArgs = [dillPath];
|
||||
|
||||
if (useAsm) {
|
||||
final assemblyPath = path.join(tempDir, 'test.S');
|
||||
|
||||
await run(genSnapshot, <String>[
|
||||
'--snapshot-kind=app-aot-assembly',
|
||||
'--assembly=$assemblyPath',
|
||||
...commonSnapshotArgs,
|
||||
]);
|
||||
|
||||
await assembleSnapshot(assemblyPath, snapshotPath);
|
||||
} else {
|
||||
await run(genSnapshot, <String>[
|
||||
'--snapshot-kind=app-aot-elf',
|
||||
'--elf=$snapshotPath',
|
||||
...commonSnapshotArgs,
|
||||
]);
|
||||
}
|
||||
|
||||
print("Snapshot generated at $snapshotPath.");
|
||||
|
||||
final elf = Elf.fromFile(snapshotPath)!;
|
||||
// The very first symbol should be aligned by 32 bytes because it is
|
||||
// the start of the instructions section.
|
||||
checkAligned(elf.staticSymbols.first);
|
||||
for (var symbol in elf.staticSymbols) {
|
||||
if (symbol.name.startsWith('alignedFunction')) {
|
||||
checkAligned(symbol);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void main() async {
|
||||
// Only run this test on Linux X64 for simplicity.
|
||||
if (!(Platform.isLinux && buildDir.endsWith('X64'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await withTempDir('align_loops', (String tempDir) async {
|
||||
final testProgram = path.join(
|
||||
sdkDir, 'runtime', 'tests', 'vm', 'dart', 'align_loops_test.dart');
|
||||
|
||||
final aotDillPath = path.join(tempDir, 'aot_test.dill');
|
||||
await run(genKernel, <String>[
|
||||
'--aot',
|
||||
'--platform',
|
||||
platformDill,
|
||||
...Platform.executableArguments
|
||||
.where((arg) => arg.startsWith('--enable-experiment=')),
|
||||
'-o',
|
||||
aotDillPath,
|
||||
testProgram
|
||||
]);
|
||||
|
||||
await Future.wait([
|
||||
// Test unstripped ELF generation directly.
|
||||
testAOT(aotDillPath),
|
||||
testAOT(aotDillPath, useAsm: true),
|
||||
]);
|
||||
});
|
||||
}
|
||||
@@ -88,8 +88,8 @@ void CodeRelocator::Relocate(bool is_vm_isolate) {
|
||||
for (intptr_t i = 0; i < code_objects_->length(); ++i) {
|
||||
current_caller = (*code_objects_)[i];
|
||||
|
||||
const intptr_t code_text_offset = next_text_offset_;
|
||||
if (!AddInstructionsToText(current_caller.ptr())) {
|
||||
intptr_t code_text_offset;
|
||||
if (!AddInstructionsToText(current_caller.ptr(), &code_text_offset)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -144,7 +144,8 @@ void CodeRelocator::Relocate(bool is_vm_isolate) {
|
||||
// however we might need it to write information into V8 snapshot profile.
|
||||
}
|
||||
|
||||
bool CodeRelocator::AddInstructionsToText(CodePtr code) {
|
||||
bool CodeRelocator::AddInstructionsToText(CodePtr code,
|
||||
intptr_t* code_text_offset) {
|
||||
InstructionsPtr instructions = Code::InstructionsOf(code);
|
||||
|
||||
// If two [Code] objects point to the same [Instructions] object, we'll just
|
||||
@@ -163,6 +164,7 @@ bool CodeRelocator::AddInstructionsToText(CodePtr code) {
|
||||
next_text_offset_ += padding_size;
|
||||
}
|
||||
|
||||
*code_text_offset = next_text_offset_;
|
||||
text_offsets_.Insert({instructions, next_text_offset_});
|
||||
commands_->Add(ImageWriterCommand(next_text_offset_, code));
|
||||
next_text_offset_ += ImageWriter::SizeInSnapshot(instructions);
|
||||
|
||||
@@ -163,7 +163,7 @@ class CodeRelocator : public StackResource {
|
||||
|
||||
void FindLargestInstruction();
|
||||
|
||||
bool AddInstructionsToText(CodePtr code);
|
||||
bool AddInstructionsToText(CodePtr code, intptr_t* code_text_offset);
|
||||
void ScanCallTargets(const Code& code,
|
||||
const Array& call_targets,
|
||||
intptr_t code_text_offset);
|
||||
|
||||
Reference in New Issue
Block a user