Numerische Operationen
Beschreibt Ganzzahloperationen wrap, checked, Verschiebung, Typkonvertierungsfehler und Gleitkommaergebnisse.
Wave Foundation
ganzzahlige Darstellung
Eine N-Bit-Ganzzahl hat N-Wertbits. Vorzeichenlose Ganzzahlen reichen von 0 bis 2^N − 1 und vorzeichenbehaftete Ganzzahlen reichen von −2^(N−1) bis 2^(N−1) − 1. Das signedness der Operation bestimmt die Interpretation der Bitfolge.
Die folgende Tabelle erläutert die Berechnungsergebnisse. IR Das Codebeispiel verwendet die Darstellung des aktuellen Druckers.
Addition, Subtraktion, Multiplikation
Die grundlegenden Ganzzahlen add·sub·mul enthalten die unteren N Bits des Ergebnisses. overflow verursacht nicht trap. Die Operation checked gibt das gleiche Ergebnis wrap sowie Bool zurück, das angibt, ob das mathematische Ergebnis außerhalb des Bereichs des entsprechenden signed oder unsigned liegt.
| Betrieb | Wrap Ergebnis | Checked overflow |
|---|---|---|
| u8: 255 + 1 | 0 | true |
| i8: 127 + 1 | −128 | true |
| u8: 0 − 1 | 255 | true |
| i8: 12 × 3 | 36 | false |
Frontends für Sprachen, die die Ausführung bei overflow unterbrechen, müssen ein explizites trap_if für das overflow-Ergebnis der checked-Operation verwenden. Der Standardvorgang wendet nicht implizit die overflow-Richtlinie der Quellsprache an.
Wrap und IR drücken eine explizite Prüfung aus
Speichern Sie dieses vollständige Format-3-Modul als integer-operations.wir; Textreader und skalarer Interpreter akzeptieren es.
module {
format_version 4
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "add_u8": whale () -> u8, linkage internal
declare @f1 "require_no_overflow": whale () -> u8, linkage internal
declare @f2 "minimum_division": whale () -> i8, linkage internal
declare @f3 "minimum_remainder": whale () -> i8, linkage internal
declare @f4 "negative_shift": whale () -> i8, linkage internal
fn @f0 "add_u8"() -> u8, entry %b0 {
%b0 "entry":
%v0: u8 = const u8 255
%v1: u8 = const u8 1
%v2: u8 = add u8 %v0, %v1
ret u8 %v2
}
fn @f1 "require_no_overflow"() -> u8, entry %b1 {
%b1 "entry":
%v3: u8 = const u8 255
%v4: u8 = const u8 1
%v5: tuple<u8, bool> = uadd_chk u8 %v3, %v4
%v6: u8 = extract %v5, 0
%v7: bool = extract %v5, 1
trap_if bool %v7, reason="integer overflow"
ret u8 %v6
}
fn @f2 "minimum_division"() -> i8, entry %b0 {
%b0 "entry":
%v0: i8 = const i8 -128
%v1: i8 = const i8 -1
%v2: i8 = sdiv i8 %v0, %v1
ret i8 %v2
}
fn @f3 "minimum_remainder"() -> i8, entry %b0 {
%b0 "entry":
%v0: i8 = const i8 -128
%v1: i8 = const i8 -1
%v2: i8 = srem i8 %v0, %v1
ret i8 %v2
}
fn @f4 "negative_shift"() -> i8, entry %b0 {
%b0 "entry":
%v0: i8 = const i8 -1
%v1: i8 = const i8 -1
%v2: i8 = shl i8 %v0, %v1
ret i8 %v2
}
}
add_u8 liefert 0, die unteren acht Bits von 256. minimum_division liefert −128 und minimum_remainder 0. negative_shift interpretiert −1 als unsigned count 255 und verwendet 255 mod 8 = 7. Führen Sie die Funktionen über explizite IDs aus:
whale ir run integer-operations.wir --function @f0
whale ir run integer-operations.wir --function @f2
whale ir run integer-operations.wir --function @f3
whale ir run integer-operations.wir --function @f4
u8 0
i8 -128
i8 0
i8 -128
require_no_overflow extrahiert das umgebrochene Ergebnis an Index 0 und das Bool-overflow-Flag an Index 1. Das explizite trap_if stoppt vor der Rückgabe. Der folgende Befehl endet mit Status 1 und schreibt die Diagnose nach stderr:
whale ir run integer-operations.wir --function @f1
Error: integer-operations.wir: trap at @f1 %b1 instruction 5: "integer overflow" (after 6 steps)
Division und Rest
Eine ganzzahlige Division oder ein Rest durch Null führt zu einer Falle. Durch Teilen des minimalen vorzeichenbehafteten Werts durch −1 wird auf den minimalen Wert umgebrochen. Der Rest ist in diesem Fall Null.
| Betrieb | Ergebnis |
|---|---|
| i8: −128 / −1 | −128 |
| i8: −128 % −1 | 0 |
| Durch die ganze Zahl 0 dividieren | trap |
| Rest für Ganzzahl 0 | trap |
Verschiebung
Beim Verschieben des Bitwerts N wird die Bitfolge von count als unsigned interpretiert und der durch N geteilte Rest verwendet. Es generiert kein trap, nur weil count außerhalb des Bereichs von 0 bis N−1 liegt.
Bei 8-Bit-Werten sind count 0·8·16 alle 0-Bit-Verschiebungen. Die Bitfolge 11111111 von 8 Bit count wird um 7 Bit verschoben. Dies gilt auch dann, wenn diese Bitfolge signed −1 darstellt. unsigned Die Analyse erfolgt vor den restlichen Berechnungen.
Ausgangssprachen, die negative oder übermäßige count ablehnen, müssen vor der Verschiebung ein explizites Häkchen setzen.
Typkonvertierung
| Konvertierung | Bedeutung |
|---|---|
| Zero extension | Erhöhen Sie die Breite, indem Sie höherwertige Bits mit 0 füllen |
| Sign extension | Erhöhen Sie die Breite, indem Sie das Vorzeichenbit duplizieren |
| Bitschneiden | Behalten Sie nur die niederwertigen Bits bei, die der Zielbreite entsprechen |
| Beat-Neuinterpretation | Interpretieren derselben Bitfolge als unterschiedliche Typen |
| Verlustfreie numerische Konvertierung | Wenn es nicht unter Beibehaltung des numerischen Werts ausgedrückt werden kann, trap |
Wenn Sie beispielsweise 8-Bit 11111111 in 16-Bit zero umwandeln, wird extension zu 0000000011111111 und sign extension wird zu 1111111111111111. Auch wenn die Eingabebits gleich sind, handelt es sich um unterschiedliche Operationen.
Die Konvertierung von Float zu Int schneidet in Richtung Null ab und überprüft dann den Ganzzahlbereich. NaN und Unendlichkeit verursachen eine Falle. Bei der Konvertierung in i8 wird 127,9 zu 127, während 128,0 Traps sind. Bool wird in die Ganzzahl 0 oder 1 konvertiert. i1 mit Vorzeichen kann nicht 1 darstellen und kann daher nicht das Ziel dieser Konvertierung sein.
Durch das Konvertieren einer Adresse in eine Ganzzahl wird der Zeigerzugriff auf diese Ganzzahl nicht wiederhergestellt. Bitte beachten Sie Zeigergültigkeit.
Geprüfte Formen von Casts und checked-Operationen
Die Prüfung vergleicht den tatsächlichen Operandentyp mit src_ty und kontrolliert anschließend die erlaubten Kategorien und Breiten des Opcodes. Auch die Ergebnistypangabe muss zur Definition passen. Es gibt keine impliziten Konvertierungen.
| Opcode | Erlaubte Typen |
|---|---|
zext, sext |
Ganzzahlen außer Bool; Zielbreite strikt größer |
zext (Bool) |
Bool zu Ganzzahl 0/1; alle Ganzzahlziele außer vorzeichenbehaftetem i1, einschließlich u1 |
trunc |
Ganzzahlen außer Bool; Zielbreite strikt kleiner |
fext, ftrunc |
Gleitkommawerte; Zielbreite strikt größer / kleiner |
itof_s, itof_u |
Vorzeichenbehaftete / vorzeichenlose Ganzzahl zu Gleitkomma |
ftoi_s, ftoi_u |
Gleitkomma zu vorzeichenbehafteter / vorzeichenloser Ganzzahl |
bitcast |
Gleich breite Ganzzahl-/Gleitkommaskalare oder Datenzeiger zu Datenzeiger |
ptrtoint, inttoptr |
Datenzeiger zu Ganzzahl / Ganzzahl zu Datenzeiger |
Ganzzahlerweiterung und -kürzung sind Bitoperationen; die Ganzzahloperanden dürfen unterschiedliche Vorzeicheninterpretationen haben. Bool ist ein eigener logischer Typ: Vorzeichenerweiterung, Kürzung und bitcast zu i1/u1 sind verboten. Casts von Aggregaten und Funktionszeigern werden abgelehnt. Die Prüfung der Zeiger-/Ganzzahlkategorien begründet weder eine gültige Allokation noch Zugriffsrechte. Laufzeitprüfungen für Konvertierungen und das Maschinen-Lowering bleiben eigenständige offene Arbeiten.
Dieses vollständige Rust-Programm gibt zwei gültige Konvertierungen aus und bestätigt, dass das Ersetzen von zext durch fext abgelehnt wird.
use ir::*;
fn main() {
let mut builder = ModuleBuilder::new("x86_64-whale-linux", DataLayout::default_64bit_le());
let mut function = builder.begin_function("widen", vec![("byte".into(), Type::U8)], Type::Void);
function.ret(None);
function.finish();
let mut module = builder.finish();
let function = &mut module.functions[0];
function.value_types.extend([(ValueId(1), Type::U32), (ValueId(2), Type::I32)]);
function.blocks[0].instructions.extend([
Instruction::Cast {
dst: ValueId(1), op: CastOp::ZExt,
src_ty: Type::U8, src: ValueId(0), dst_ty: Type::U32,
},
Instruction::Cast {
dst: ValueId(2), op: CastOp::Bitcast,
src_ty: Type::U32, src: ValueId(1), dst_ty: Type::I32,
},
]);
verify_module(&module).unwrap();
print!("{}", print_module(&module));
// An integer source cannot be annotated as a floating widening operation.
if let Instruction::Cast { op, .. } = &mut module.functions[0].blocks[0].instructions[0] {
*op = CastOp::FExt;
}
assert!(matches!(verify_module(&module), Err(VerifyError::InvalidCast { .. })));
}
module {
format_version 4
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "widen": whale (u8) -> void, linkage internal
fn @f0 "widen"(%v0 "byte": u8) -> void, entry %b0 {
%b0 "entry":
%v1: u32 = zext u8 %v0 to u32
%v2: i32 = bitcast u32 %v1 to i32
ret void
}
}
Die folgenden Anweisungsfragmente sind absichtlich ungültige IR.
%v1: i32 = ftoi_s f64 %v0 to i32 // Invalid when %v0 is actually Bool.
%v2: i16 = zext i32 %v3 to i16 // zext cannot narrow.
%v4: i1 = zext bool %v5 to i1 // Signed i1 cannot represent true as 1.
Ein abweichender tatsächlicher Quelltyp führt zu OperandTypeMismatch; ein unerlaubtes Typenpaar zu InvalidCast mit Opcode und beiden Typen. Kein Prüfungsfehler fügt Konvertierungen ein oder verändert das Modul.
sadd_chk, ssub_chk und smul_chk verlangen vorzeichenbehaftete Ganzzahlen; uadd_chk, usub_chk und umul_chk vorzeichenlose. Beide Operanden müssen exakt zum Operationstyp T passen; das Ergebnis muss tuple<T, bool> sein. Extraktion erfordert ein Tupel, einen vorhandenen Feldindex und dessen exakten Typ. Insbesondere wird die Extraktion des Überlauffelds als i1 abgelehnt. Die obigen Wrap-/Überlaufergebnisse bleiben der Ausführungsvertrag; diese Tests prüfen die Struktur, nicht die Ausführung durch ein implementiertes Backend.
Gleitkomma-Arithmetik
Gleitkommawerte haben die exakte Bitfolge f16·f32·f64. Die Operation rundet auf den nächsten Wert in der angegebenen Breite und verwendet, wenn er genau in der Mitte liegt, ties-to-even, das einen Wert mit geraden niedrigstwertigen Bits der signifikanten Ziffern auswählt.
Die Standardoperation ist fast-math, implizit FMA, was keine erzwungene Nullbehandlung kleiner Werte zulässt. Bei der Multiplikation mit anschließender Addition bleiben die jeweiligen Rundungsschritte erhalten und das Backend sollte sie nicht implizit in einer Operation kombinieren.
Das Ergebnis einer numerischen Operation kann NaN oder unendlich sein. NaNs aus mathematischen Operationen und Breitenänderungen werden auf eine feste Menge stiller NaN pro Breite normalisiert. Durch Speichern und Kopieren bleiben die ursprünglichen NaN-Bits erhalten. Daher ist das Verhalten unterschiedlich, wenn NaN-Nutzdaten ohne Rechenoperationen an den Speicher übergeben werden und wenn sie berechnet werden.
Es werden keine Gleitkomma-Statusflags verfügbar gemacht. Obwohl die grundlegende Gleitkomma-Arithmetik NaN·unendliche Ergebnisse ermöglicht, wendet die float→int-Konvertierung die oben genannten trap-Regeln an.
Speichern Sie genaue Konstanten
Verwenden Sie FloatBits variant oder eine beliebige Folge hexadezimaler Bits mit der exakten Breite. Die Gleichheit der gespeicherten Werte wird mit einer Bitfolge verglichen, die negative 0 und NaN payload enthält. Der Prüfer lehnt ab, wenn die Breiten der Typen IR und payload unterschiedlich sind.
use ir::{FloatBits, ModuleBuilder, Target, Type};
fn main() {
let bits = FloatBits::parse(32, "0xffc01234").unwrap();
assert_eq!(bits, FloatBits::F32(0xffc01234));
let target = Target::X86_64WhaleLinux;
let mut module = ModuleBuilder::new(target.name(), target.data_layout());
let mut function = module.begin_function("payload", vec![], Type::F32);
let value = function.const_float_bits(Type::F32, bits);
function.ret(Some(value));
function.finish();
let module = module.finish();
ir::verify_module(&module).unwrap();
assert!(ir::print_module(&module).contains("const f32 0xffc01234"));
println!("{}", bits);
}
0xffc01234
Eine f16-, f32- oder f64-Bitfolge beginnt mit 0x, gefolgt von genau 4, 8 bzw. 16 Hexadezimalziffern. 0x80000000 steht für f32 negative Null; 0x7f800000 steht für positive Unendlichkeit. const_float konvertiert einen Host-f64-Wert numerisch; Verwenden Sie const_float_bits, um die ursprünglichen Bits beizubehalten. Eine exakte Speicherdarstellung impliziert kein vollständiges Gleitkomma-Ausführungs-Backend. Die Arithmetik zur Kompilierungszeit verwendet immer noch Zwischenprodukte des Hosts f64, sodass der vollständige Rundungsvertrag für jede deklarierte Breite noch nicht implementiert ist.