[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:
Vyacheslav Egorov
2024-05-02 07:18:20 +00:00
committed by Commit Queue
parent ed5aeab889
commit 84fed14131
5 changed files with 158 additions and 151 deletions
+61 -79
View File
@@ -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),
]);
});
}
+5 -3
View File
@@ -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);
+1 -1
View File
@@ -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);