Speichermodell
Zuordnungsverfolgung, Lesen initialisierter Werte, Zeigerarithmetik, Layout und String-Speicherregeln.
Wave Foundation
Zuordnungsverfolgung
Ein verfolgter Zeiger verknüpft eine Adresse mit einer Zuordnungs-ID, Generation, Grenzen, Offset und Zugriffsberechtigungen. Das Speichermodell verfolgt auch die Lebensdauer und den Initialisierungsstatus der Zuweisung. Der Zugang muss diese Bedingungen erfüllen; Ein Verstoß führt zu einer Falle.
Auch wenn dieselbe physische Adresse wiederverwendet wird, trennen die einzelnen Generationen jede Lebensdauer. Das bloße Vorhandensein einer Adresse bestimmt nicht, dass der Zeiger gültig ist oder dass der Aufrufer Zugriff auf diesen Speicherplatz hat.
Die Adresse native umfasst 64 Bit. Separate shadow metadata werden zusammen mit dem Zeiger durch Kopieren/Speichern/Aufrufen/Zurück übergeben. Der anfängliche Speicherbereich native besteht aus nachverfolgbaren Stapel- und globalen Zuordnungen. C Grenzen erfordern einen expliziten Adapter und die Übertragung des Eigentums an beliebigem externen Speicher ist in diesem Bereich nicht enthalten.
Initialisierung und Lesen
Durch die Deklaration eines Speicherplatzes wird der Wert nicht initialisiert. Überprüft, ob der tatsächlich gelesene Bytebereich initialisiert wurde. Wenn auch nur ein Teil des Werts im nicht initialisierten Zustand gelesen wird, ist er trap. Das Leseergebnis wird nicht durch 0 oder einen nicht spezifizierten Wert ersetzt.
Die Initialisierungsprüfung des gelesenen Werts schließt das Byte padding aus. Wenn beispielsweise alle Felder einer Struktur initialisiert sind, sind die Lesewerte nicht einfach deshalb falsch, weil leere Bytes, die aus der Feldausrichtung resultieren, nicht initialisiert sind.
Die Speicherkopie trägt den Initialisierungsstatus zusammen mit den Bytes. Durch das Kopieren von nicht initialisiertem Speicherplatz wird dieser nicht in initialisierten Speicherplatz umgewandelt. Beim späteren Lesen von Werten vom Ziel werden die gleichen Prüfungen durchgeführt wie beim Lesen der Quelle.
Die bloße Tatsache, dass das physikalische Byte von BSS 0 ist, erlaubt keine Initialisierung der Variable IR.
IR im initialisierten Skalarspeicherplatz
Das folgende Modul wurde als builder konfiguriert und hat den Prüfer bestanden. store vor dem Lesen des Werts, und alle drei Speicheranweisungen geben eine Zweierpotenzsortierung ungleich Null an.
module {
format_version 2
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "initialized_local": whale () -> i32, linkage internal
fn @initialized_local() -> i32, id @f0 {
entry:
%v0: ptr<i32> = alloca i32, align 4
%v1: i32 = const i32 42
store i32 %v1, ptr<i32> %v0, align 4
%v2: i32 = load i32, ptr<i32> %v0, align 4
ret i32 %v2
}
}
alloca erstellt i32-Speicher, store schreibt 42 und load definiert den zurückzugebenden Wert. Dies wird vom typisierten IR-Drucker ausgegeben, nicht von einer nativen Ausführungsverfolgung. Eine Ausrichtung von 3 ist ein Validierungsfehler. Das Entfernen des Speichers würde bei diesem Speichermodell einen nicht initialisierten Lese-Trap erfordern, aber die Initialisierungsverfolgung und die entsprechenden Laufzeit-Trap-Prüfungen sind noch nicht implementiert. Das Bestehen des aktuellen Verifizierers allein stellt nicht sicher, dass dieser Lesevorgang sicher ist.
Vergleich mit Zeigerarithmetik
Adressberechnung overflow ist trap. Sie können einen one-past-Zeiger erstellen, der direkt auf den Zuordnungsbereich zeigt, aber Sie können über ihn nicht auf den Speicher zugreifen.
Zeigergleichheit verwendet Zuordnungsidentität, nicht nur eine numerische Adresse. Das Ordnen oder Subtrahieren von Zeigern aus verschiedenen Zuordnungen führt zu einer Falle. Durch die Rekonstruktion einer Adresse aus einer Ganzzahl werden die Zugriffsberechtigungen nicht wiederhergestellt.
GEP
GEP berechnet Adressen auf Element- und Feldbasis. Dies ist kein Befehl zum Lesen eines Werts.
Der erste Index ist die Elementeinheit offset des Typs, auf den der Basiszeiger zeigt. Der nachfolgende Index wählt ein Array-Element oder ein Struktur-/Tupelfeld aus. Der Feldindex einer Struktur/eines Tupels ist die Feldreihenfolgenummer zum Zeitpunkt der Kompilierung, nicht das Byte offset. Der ausgewählte Typ bestimmt den resultierenden Zeigertyp.
Wenn der Basistyp ptr<array<i32, 4>> ist, wählt der Index [0, 2] das dritte i32-Element des Arrays aus, was zu ptr<i32> führt. Wenn Sie den ersten Index als 1 angeben, wird i32 um ein Element und nicht um ein Element im Array verschoben. Dieses Beispiel veranschaulicht die Indexsemantik und nicht die Textbefehlssyntax.
Das anfängliche native-Ziel lehnt Zeigerarithmetik für Elemente der Größe 0 ab. Durch die Berechnung der Adresse entfallen nicht die Lebensdauer-, Bereichs-, Initialisierungs- und Berechtigungsprüfungen, die für den nachfolgenden Zugriff erforderlich sind.
Datenlayout
Das Ausgabeziel bestimmt die Größe·Ausrichtung·Feld offset·Array stride. Die Reihenfolge der Felder in Strukturen und Tupeln behält ihre Deklarationsreihenfolge bei. Es sollte nicht davon ausgegangen werden, dass das Layout des Hosts, auf dem der Compiler ausgeführt wird, das Layout des Ausgabeziels ist.
| Wert | Regeln speichern |
|---|---|
| Bool, signed i1, unsigned u1 | Mindestens 1 Byte |
| Leere Struktur/Tupel | Größe 0, Ausrichtung 1 |
| Array | Ziellayout bestimmt Element stride |
| Struktur/Tupel | Behalten Sie die Deklarationsreihenfolge und die Ausrichtungsanforderungen des Ziels bei |
Die abgeschlossene Ausrichtung von IR ist eine Potenz von 2, nicht 0. Die automatische Ausrichtung muss vor der Generierung dieses IR bestimmt werden. Die Layouts packed·union·bitfield werden in diesem Profil nicht unterstützt und sollten abgelehnt werden.
Anfrage zum Ausgabelayout
Rust API berechnet das Speicherlayout unabhängig vom Build-Host. Im folgenden Beispiel gibt es 7 Bytes padding vor dem Feld u64 und 6 Bytes am Ende padding.
use ir::{allocation_align, layout_of, Target, Type};
fn main() {
let target = Target::X86_64WhaleLinux;
let record = Type::Struct(vec![Type::U8, Type::U64, Type::U16]);
let layout = layout_of(&record, target).unwrap();
assert_eq!((layout.size, layout.align), (24, 8));
assert_eq!(layout.field_offsets, [0, 8, 16]);
let array = Type::Array(Box::new(record), 3);
let layout = layout_of(&array, target).unwrap();
assert_eq!((layout.size, layout.align), (72, 8));
assert_eq!(layout.element_stride, Some(24));
assert_eq!(allocation_align(&array, target).unwrap(), 16);
}
struct{u8, u64, u16}: size 24, natural alignment 8
field 0: byte 0
field 1: byte 8
field 2: byte 16
array of 3: size 72, element stride 24
standalone array placement alignment: 16
Die von layout_of zurückgegebene Größe und der Schritt eines Arrays umfassen nachfolgendes Auffüllen. Strukturen und Tupel verwenden dieselben Regeln für die Feldreihenfolge. Bool, i1 und u1 belegen jeweils ein Byte. Leere Strukturen und Tupel haben Größe 0 und Ausrichtung 1; Ein Array mit der Länge Null behält die natürliche Ausrichtung seines Elements bei. void hat kein Speicherlayout, während ptr<void> 8 Bytes belegt.
Verwenden Sie eine natürliche Sortierung für Felder und Array-Elemente. allocation_align wendet die Regel SysV AMD64 an, die eine mindestens 16-Byte-Ausrichtung erfordert, auf unabhängige lokale/globale Arrays mit 16 Byte oder mehr. Die Ausrichtung von Array-Feldern oder dem Element stride wird dadurch nicht erhöht. AST lowering verwendet diese Stapelsortierungssuche für die lokale Speicherung.
Betragsmultiplikation, Feldaddition offset, overflow in der padding-Berechnung ergibt LayoutError::Overflow. layout_of hat ein Verschachtelungslimit für komplexe Typen von 128 Ebenen und layout_of_with_limit ermöglicht dem Aufrufer, das Limit anzugeben. Es wird kein Speicherplatz zugewiesen, der der Anzahl der Array-Elemente entspricht. pointer_stride lehnt pointee mit der Größe 0 in der native-Zeigerarithmetik ab, aber das Speicherlayout dieses Typs selbst ist gültig. Packed·union·bitfield verfügt über keinen unterstützten Typausdruck. Diese gespeicherte Layoutsuche implementiert keine komplexen Typaufrufkonventionen oder die Überprüfung von Laufzeitgrenzen.
Zeichenfolge und C-Grenze
Eine Zeichenfolge ist eine unveränderliche Folge von Bytes mit einer bestimmten Länge. Die Standardkodierung ist UTF-8. Ermöglicht ein internes NUL und hängt die Endung NUL nicht automatisch an. O0 kombiniert String-Objekte mit demselben Inhalt nicht automatisch.
Daher beträgt die Länge der Bytefolge bestehend aus A, NUL und B 3. Kann nicht für explizite C-Stringkonvertierungen verwendet werden, die das interne NUL ablehnen. Das Frontend sollte dies nicht stillschweigend auf A kürzen.
Externe C·Rohadresse·Inline Assembly ist eine separate Vertragsgrenze. Bei der Laufzeitüberprüfung des Trace-Speichers kann nicht garantiert werden, dass alle fehlerhaften Verhaltensweisen im externen Code erkannt werden.