blob: 94c37e73706b7d889cb7af4c08c0724dd2cda9c0 [file] [log] [blame]
// Copyright (c) 2022, 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:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:compiler/src/source_file_provider.dart';
import 'package:ffi/ffi.dart';
// int open(const char *path, int oflag, ...);
@FfiNative<Int32 Function(Pointer<Utf8>, Int32)>("open")
external int open(Pointer<Utf8> filename, int flags);
// int close(int fd);
@FfiNative<Int32 Function(Int32)>("close")
external int close(int fd);
// void* mmap(void* addr, size_t length,
// int prot, int flags,
// int fd, off_t offset)
@FfiNative<
Pointer<Uint8> Function(
Pointer<Uint8>, IntPtr, Int32, Int32, Int32, IntPtr)>("mmap")
external Pointer<Uint8> mmap(
Pointer<Uint8> address, int len, int prot, int flags, int fd, int offset);
// int munmap(void *addr, size_t length)
@FfiNative<IntPtr Function(Pointer<Uint8> address, IntPtr len)>("munmap")
external int munmap(Pointer<Uint8> address, int len);
final processSymbols = DynamicLibrary.process();
final munmapNative = processSymbols.lookup<Void>('munmap');
final closeNative = processSymbols.lookup<Void>('close');
final freeNative = processSymbols.lookup<Void>('free');
// int mprotect(void *addr, size_t len, int prot)
@FfiNative<Int32 Function(Pointer<Uint8>, IntPtr, Int32)>("mprotect")
external int mprotect(Pointer<Uint8> addr, int len, int prot);
// DART_EXPORT Dart_Handle
// Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type,
// void* data,
// intptr_t length,
// void* peer,
// intptr_t external_allocation_size,
// Dart_HandleFinalizer callback)
typedef Dart_NewExternalTypedDataWithFinalizerNative = Handle Function(
Int32, Pointer<Void>, IntPtr, Pointer<Void>, IntPtr, Pointer<Void>);
typedef Dart_NewExternalTypedDataWithFinalizerDart = Object Function(
int, Pointer<Void>, int, Pointer<Void>, int, Pointer<Void>);
final Dart_NewExternalTypedDataWithFinalizer = processSymbols.lookupFunction<
Dart_NewExternalTypedDataWithFinalizerNative,
Dart_NewExternalTypedDataWithFinalizerDart>(
'Dart_NewExternalTypedDataWithFinalizer');
const int kPageSize = 4096;
const int kProtRead = 1;
const int kProtWrite = 2;
const int kProtExec = 4;
const int kMapPrivate = 2;
const int kMapAnon = 0x20;
const int kMapFailed = -1;
// We need to attach the finalizer which calls close() and
final finalizerAddress = () {
final finalizerStub = mmap(nullptr, kPageSize, kProtRead | kProtWrite,
kMapPrivate | kMapAnon, -1, 0);
finalizerStub.cast<Uint8>().asTypedList(kPageSize).setAll(0, <int>[
// Regenerate by running dart mmap.dart gen
// ASM_START
// #include <cstddef>
// #include <cstdint>
//
// struct PeerData {
// int (*close)(int);
// int (*munmap)(void*, size_t);
// int (*free)(void*);
// void* mapping;
// intptr_t size;
// intptr_t fd;
// };
//
// extern "C" void finalizer(void* callback_data, void* peer) {
// auto data = static_cast<PeerData*>(peer);
// data->munmap(data->mapping, data->size);
// data->close(data->fd);
// data->free(peer);
// }
//
0x55, 0x48, 0x89, 0xf5, 0x48, 0x8b, 0x76, 0x20, 0x48, 0x8b, 0x7d, 0x18, //
0xff, 0x55, 0x08, 0x8b, 0x7d, 0x28, 0xff, 0x55, 0x00, 0x48, 0x8b, 0x45, //
0x10, 0x48, 0x89, 0xef, 0x5d, 0xff, 0xe0, //
// ASM_END
]);
if (mprotect(finalizerStub, kPageSize, kProtRead | kProtExec) != 0) {
throw 'Failed to write executable code to the memory.';
}
return finalizerStub.cast<Void>();
}();
class PeerData extends Struct {
external Pointer<Void> close;
external Pointer<Void> munmap;
external Pointer<Void> free;
external Pointer<Uint8> mapping;
@IntPtr()
external int size;
@IntPtr()
external int fd;
}
Uint8List toExternalDataWithFinalizer(
Pointer<Uint8> memory, int size, int length, int fd) {
final peer = malloc.allocate<PeerData>(sizeOf<PeerData>());
peer.ref.close = closeNative;
peer.ref.munmap = munmapNative;
peer.ref.free = freeNative;
peer.ref.mapping = memory;
peer.ref.size = size;
peer.ref.fd = fd;
return Dart_NewExternalTypedDataWithFinalizer(
/*Dart_TypedData_kUint8*/ 2,
memory.cast(),
length,
peer.cast(),
size,
finalizerAddress,
) as Uint8List;
}
Uint8List viewOfFile(String filename, bool zeroTerminated) {
final cfilename = filename.toNativeUtf8();
final int fd = open(cfilename, 0);
malloc.free(cfilename);
if (fd == 0) throw 'failed to open';
try {
final length = File(filename).lengthSync();
int lengthRoundedUp = (length + kPageSize - 1) & ~(kPageSize - 1);
final result =
mmap(nullptr, lengthRoundedUp, kProtRead, kMapPrivate, fd, 0);
if (result.address == kMapFailed) throw 'failed to map';
try {
if (zeroTerminated) {
if (length == lengthRoundedUp) {
// In the rare case we need a zero-terminated list and the file size
// is exactly page-aligned we need to allocate a new list with extra
// room for the terminating 0.
return Uint8List(length + 1)
..setRange(
0,
length,
toExternalDataWithFinalizer(
result, lengthRoundedUp, length, fd));
}
return toExternalDataWithFinalizer(
result, lengthRoundedUp, length + 1, fd);
}
return toExternalDataWithFinalizer(result, lengthRoundedUp, length, fd);
} catch (_) {
munmap(result, lengthRoundedUp);
rethrow;
}
} catch (e) {
close(fd);
rethrow;
}
}
class MemoryMapSourceFileByteReader implements SourceFileByteReader {
const MemoryMapSourceFileByteReader();
@override
Uint8List getBytes(String filename, {bool zeroTerminated = true}) {
if (Platform.isLinux) {
try {
return viewOfFile(filename, zeroTerminated);
} catch (e) {
return readAll(filename, zeroTerminated: zeroTerminated);
}
} else {
return readAll(filename, zeroTerminated: zeroTerminated);
}
}
}