diff --git a/sys/risc-v/rv64csr.vadl b/sys/risc-v/rv64csr.vadl index 4eb893ce6..6701aeeec 100644 --- a/sys/risc-v/rv64csr.vadl +++ b/sys/risc-v/rv64csr.vadl @@ -4,7 +4,8 @@ import rv3264im::{RV3264Base, RV3264M} with ("ArchSize=Arch64") instruction set architecture RV64IZicsr extending RV3264Base = { enumeration CsrDef : Bits<12> = // defined control and status register indices - { mstatus = 0x300 // 768 Machine STATUS + { fcsr = 0x003 // 003 Floating-Point Control and Status Register (frm + fflags) + , mstatus = 0x300 // 768 Machine STATUS , misa = 0x301 // 769 Machine ISA , mie = 0x304 // 772 Machine Interrupt Enable register , mtvec = 0x305 // 773 Machine Trap VECtor base address @@ -20,7 +21,8 @@ instruction set architecture RV64IZicsr extending RV3264Base = { using CsrImplIndex = Bits // index type for implemented CSR registers enumeration CsrImpl : CsrImplIndex = // implemented control and status register indices - { mstatus // 0x300 Machine STATUS + { fcsr // 0x003 003 Floating-Point Control and Status Register (frm + fflags) + , mstatus // 0x300 Machine STATUS , misa // 0x301 Machine ISA , mie // 0x304 Machine Interrupt Enable , mtvec // 0x305 Machine Trap VECtor base address @@ -35,7 +37,8 @@ instruction set architecture RV64IZicsr extending RV3264Base = { function CsrDefToImpl (csr : Bits<12>) -> CsrImplIndex = // map defined CSR index to implemented CSR index match csr with - { CsrDef::mstatus => CsrImpl::mstatus // 0x300 Machine STATUS + { CsrDef::fcsr => CsrImpl::fcsr // 0x003 Floating-Point Control and Status Register (frm + fflags) + , CsrDef::mstatus => CsrImpl::mstatus // 0x300 Machine STATUS , CsrDef::misa => CsrImpl::misa // 0x301 Machine ISA , CsrDef::mie => CsrImpl::mie // 0x304 Machine Interrupt Enable , CsrDef::mtvec => CsrImpl::mtvec // 0x305 Machine Trap VECtor base address @@ -50,7 +53,8 @@ instruction set architecture RV64IZicsr extending RV3264Base = { function CsrName(index : Bits<12>) -> String = match index with - { CsrDef::mstatus => "mstatus" // Machine STATUS + { CsrDef::fcsr => "fcsr" // Floating-Point Control and Status Register (frm + fflags) + , CsrDef::mstatus => "mstatus" // Machine STATUS , CsrDef::misa => "misa" // Machine ISA , CsrDef::mie => "mie" // Machine Interrupt Enable , CsrDef::mtvec => "mtvec" // Machine Trap VECtor base address diff --git a/sys/risc-v/rv64d.vadl b/sys/risc-v/rv64d.vadl new file mode 100644 index 000000000..138f13466 --- /dev/null +++ b/sys/risc-v/rv64d.vadl @@ -0,0 +1,35 @@ + +import rv64fd::{RV64IFD} with ("ExtensionFD=D") + +instruction set architecture RV64ID extending RV64IFD = {} + +[ htif ] +processor Spike implements RV64ID = { + constant reset_vec_addr = 0x1000 + + reset = { + PC := reset_vec_addr + } + + [ firmware ] + [ base: 0x80000000 ] + memory region [RAM] DRAM in MEM + + memory region [ROM] MROM in MEM = { + MEM<4>(0x1000) := 0x00000297 // auipc t0, 0x0 + MEM<4>(0x1004) := 0x02828613 // addi a2, t0, 40 + // TODO: this is not quite right: + // this processor has no zicsr extension + MEM<4>(0x1008) := 0x00000013 // addi x0, x0, 0 + MEM<4>(0x100c) := 0x0202b583 // ld a1, 32(t0) + MEM<4>(0x1010) := 0x0182b283 // ld t0, 24(t0) + MEM<4>(0x1014) := 0x00028067 // jr t0 + // store start_addr in memory (0x80000000) + MEM<4>(0x1018) := 0x80000000 // lo32(start_addr) + MEM<4>(0x101c) := 0x00000000 // hi32(start_addr) + // we do not yet support a fdt, but we set the address, + // to keep the registers consistent with upstream + MEM<4>(0x1020) := 0x87e00000 // lo32(fdt_addr) + MEM<4>(0x1024) := 0x00000000 // hi32(fdt_addr) + } +} diff --git a/sys/risc-v/rv64f.vadl b/sys/risc-v/rv64f.vadl new file mode 100644 index 000000000..c7b1b36f8 --- /dev/null +++ b/sys/risc-v/rv64f.vadl @@ -0,0 +1,35 @@ + +import rv64fd::{RV64IFD} + +instruction set architecture RV64IF extending RV64IFD = {} + +[ htif ] +processor Spike implements RV64IF = { + constant reset_vec_addr = 0x1000 + + reset = { + PC := reset_vec_addr + } + + [ firmware ] + [ base: 0x80000000 ] + memory region [RAM] DRAM in MEM + + memory region [ROM] MROM in MEM = { + MEM<4>(0x1000) := 0x00000297 // auipc t0, 0x0 + MEM<4>(0x1004) := 0x02828613 // addi a2, t0, 40 + // TODO: this is not quite right: + // this processor has no zicsr extension + MEM<4>(0x1008) := 0x00000013 // addi x0, x0, 0 + MEM<4>(0x100c) := 0x0202b583 // ld a1, 32(t0) + MEM<4>(0x1010) := 0x0182b283 // ld t0, 24(t0) + MEM<4>(0x1014) := 0x00028067 // jr t0 + // store start_addr in memory (0x80000000) + MEM<4>(0x1018) := 0x80000000 // lo32(start_addr) + MEM<4>(0x101c) := 0x00000000 // hi32(start_addr) + // we do not yet support a fdt, but we set the address, + // to keep the registers consistent with upstream + MEM<4>(0x1020) := 0x87e00000 // lo32(fdt_addr) + MEM<4>(0x1024) := 0x00000000 // hi32(fdt_addr) + } +} diff --git a/sys/risc-v/rv64fd.vadl b/sys/risc-v/rv64fd.vadl new file mode 100644 index 000000000..60755c9a8 --- /dev/null +++ b/sys/risc-v/rv64fd.vadl @@ -0,0 +1,398 @@ + +import rv64csr::{RV64IZicsr} + +instruction set architecture RV64IFD extending RV64IZicsr = { + + // TODO: generate illegal instruction exception when float is not enabled + + model ExtensionFD () : Id = {F} + model InstructionsFD (f : IsaDefs, d : IsaDefs) : IsaDefs = { + match : IsaDefs ($ExtensionFD = F => $f; _ => $f $d) + } + + model FSize() : Id = { + match : Id ($ExtensionFD = F => FSize32; _ => FSize64) + } + + using ConstTy = UInt<8> + constant FLEN : ConstTy = $FSize + constant FSize32 : ConstTy = 32 + constant FSize64 : ConstTy = 64 + constant FSize128 : ConstTy = 128 + + using FP16 = Bits<16> + using FP32 = Bits<32> + using FP64 = Bits<64> + using FP128 = Bits<128> + + using SIntH = SInt<16> + using UIntH = UInt<16> + using SIntD = SInt<64> + using UIntD = UInt<64> + using SIntQ = SInt<128> + using UIntQ = UInt<128> + + using FRegs = Bits + + using Bits2 = Bits<2> + + register F : Index -> FRegs + + [ sticky fe flag invalid : nv ] + [ sticky fe flag div_by_zero : dz ] + [ sticky fe flag overflow : of ] + [ sticky fe flag underflow : uf ] + [ sticky fe flag inexact : nx ] + register FCSR : FCsrFormat + //alias register fcsr : FCsrFormat = CSR(CsrDefToImpl(CsrDef::fcsr)) + + format FCsrFormat : Bits<32> = + { reserved [31..8] + , frm [7..5] // Rounding mode + , nv [4] // Float exception flag: Invalid operation + , dz [3] // Float exception flag: Division by zero + , of [2] // Float exception flag: Overflow + , uf [1] // Float exception flag: Underflow + , nx [0] // Float exception flag: Inexact + } + + // rounding modes + enumeration Frm : Bits3 = + { rne = 0b000 // Round to Nearest, ties to Even + , rtz = 0b001 // Round to Zero + , rdn = 0b010 // Round Down (towards -infinity) + , rup = 0b011 // Round UP (towards infinity) + , rmm = 0b100 // Round to Nearest, ties to Max Magnitude + // 0b101 // reserved in FRtype.funct3 + // 0b110 // reserved in FRtype.funct3 + , dyn = 0b111 // reserved in FRtype.funct3; in instruction: Dynamic rounding (uses FRtype.funct3) + } + + // format (precision) + enumeration Fmt : Bits2 = + { s = 0b00 // single precision (32 -bit) + , d = 0b01 // double precision (64 -bit) + , h = 0b10 // half precision (16 -bit) + , q = 0b11 // quad precision (128-bit) + } + + model FrmName(rm : Ex) : Str = { + match : Str + ( $rm = Frm::rne => "rne" + ; $rm = Frm::rtz => "rtz" + ; $rm = Frm::rdn => "rdn" + ; $rm = Frm::rup => "rup" + ; $rm = Frm::rmm => "rmm" + ; _ => "" + ) + } + + format FRtype : Inst = // Rtype register 3 operand instruction format (for float ops) + { funct5 : Bits5 // [31..27] 5 bit function code + , fmt : Bits2 // [26..25] 2 bit format code + , rs2 : Index // [24..20] 2nd source register index / shamt + , rs1 : Index // [19..15] 1st source register index + , funct3 : Bits3 // [14..12] 3 bit function code + , rd : Index // [11..7] destination register index + , opcode : Bits7 // [6..0] 7 bit operation code + , shamt = rs2 as UInt // 5 bit unsigned shift ammount + } + + format R4type : Inst = // R4type register 4 operand instruction format + { rs3 : Bits5 // [31..27] 3rd source register index + , fmt : Bits2 // [26..25] 2 bit format + , rs2 : Index // [24..20] 2nd source register index + , rs1 : Index // [19..15] 1st source register index + , funct3 : Bits3 // [14..12] 3 bit function code + , rd : Index // [11..7] destination register index + , opcode : Bits7 // [6..0] 7 bit operation code + } + + record FRtypeRec (name : Id, mne : Str, rm : Ex, fmt : Ex, funct5 : Bin, instr : Stat) + + model FRtypeInstr (c : FRtypeRec, enc : Encs, asm : IsaDefs) : IsaDefs = { + instruction $c.name : FRtype = $c.instr + encoding $c.name = {opcode = 0b101'0011, funct3 = $c.rm, fmt = $c.fmt, funct5 = $c.funct5, $enc} + $asm + } + + model FRtypeInstr1 (c : FRtypeRec, rs2 : Bin) : IsaDefs = { + $FRtypeInstr($c; rs2 = $rs2; assembly $c.name = ($c.mne, " ", register(rd), ",", register(rs1))) + } + + model FRtypeInstr1rm (c : FRtypeRec, rs2 : Bin) : IsaDefs = { + $FRtypeInstr($c; rs2 = $rs2; assembly $c.name = ($c.mne, " ", register(rd), ",", register(rs1), ",", $FrmName($c.rm))) + } + + model FRtypeInstr2 (c : FRtypeRec) : IsaDefs = { + $FRtypeInstr($c; none; assembly $c.name = ($c.mne, " ", register(rd), ",", register(rs1), ",", register(rs2))) + } + + model FRtypeInstr2rm (c : FRtypeRec) : IsaDefs = { + $FRtypeInstr($c; none; assembly $c.name = ($c.mne, " ", register(rd), ",", register(rs1), ",", register(rs2), ",", $FrmName($c.rm))) + } + + [ canonical sNaN : 0x7fa00000 ] + [ canonical qNaN : 0x7fc00000 ] + [ IEEE : 32 ] + float-type IEEE32 + + [ canonical sNaN : 0x7fa00000'00000000 ] + [ canonical qNaN : 0x7fc00000'00000000 ] + [ IEEE : 64 ] + float-type IEEE64 + + record FSizeRec (suffix : Id, mvSuffix : Id, cvtSuffix : Id, fmt : Ex, ty : Id, sTy : Id, uTy : Id, fTy : Id, size : Int) + + // TODO: IEEE16 and IEEE128 are not yet implemented + // TODO: naming of half and quad (see 'x' values) + model FSize16 () : FSizeRec = {(x ; x ; x ; Fmt::h ; FP16 ; SIntH ; UIntH ; IEEE16 ; 16 )} + model FSize32 () : FSizeRec = {(S ; W ; W ; Fmt::s ; FP32 ; SIntW ; UIntW ; IEEE32 ; 32 )} + model FSize64 () : FSizeRec = {(D ; D ; L ; Fmt::d ; FP64 ; SIntD ; UIntD ; IEEE64 ; 64 )} + model FSize128 () : FSizeRec = {(x ; x ; x ; Fmt::q ; FP128 ; SIntQ ; UIntQ ; IEEE128 ; 128)} + + function NaNBoxHS(val : FP16) -> FP32 = (0xffff , val) as FP32 + function NaNBoxSD(val : FP32) -> FP64 = (0xffff'ffff , val) as FP64 + function NaNBoxDQ(val : FP64) -> FP128 = (0xffff'ffff'ffff'ffff, val) as FP128 + + model NaNBoxQ (val : Ex) : Ex = { $val } + model NaNBoxD (val : Ex) : Ex = { match : Ex ($FSize = FSize64 => $val; _ => $NaNBoxQ(NaNBoxDQ($val))) } + model NaNBoxS (val : Ex) : Ex = { match : Ex ($FSize = FSize32 => $val; _ => $NaNBoxD(NaNBoxSD($val))) } + model NaNBoxH (val : Ex) : Ex = { $NaNBoxS(NaNBoxHS($val)) } + + model NaNBox (size : FSizeRec, val : Ex) : Ex = { + match : Ex ( + $size.fTy = IEEE16 => $NaNBoxH($val); + $size.fTy = IEEE32 => $NaNBoxS($val); + $size.fTy = IEEE64 => $NaNBoxD($val); + _ => $NaNBoxQ($val) + ) + } + + model-type BoolModelId = (Id, Id) -> Id + model-type BoolModelStr = (Str, Str) -> Str + model UnsignedId (u : Id, s : Id) : Id = { $u } + model SignedId (u : Id, s : Id) : Id = { $s } + model UnsignedStr (u : Str, s : Str) : Str = { $u } + model SignedStr (u : Str, s : Str) : Str = { $s } + record BoolModelRec (id : BoolModelId, str : BoolModelStr) + model Unsigned () : BoolModelRec = {(UnsignedId ; UnsignedStr)} + model Signed () : BoolModelRec = {(SignedId ; SignedStr )} + + function frm(rm : Bits3) -> Bits3 = + match (match rm with { Frm::dyn => FCSR.frm , _ => rm }) with + { Frm::rne => 0 // these are QEMU's rounding mode encodings, we should use our own + , Frm::rtz => 3 + , Frm::rdn => 1 + , Frm::rup => 2 + , Frm::rmm => 4 + , _ => 0 // this should never happen + } + + model FRtypeInstrBiArith (name : Id, size : FSizeRec, fun : Id, funct5 : Bin, rm : Ex) : IsaDefs = { + $FRtypeInstr2rm (( + AsId($name, $size.suffix, $FrmName($rm)) ; AsStr($name, ".", $size.suffix) ; + $rm ; $size.fmt ; $funct5 ; + F(rd) := $NaNBox($size ; VADL::$fun::<$size.fTy>(F(rs1) as $size.ty, F(rs2) as $size.ty, frm($rm))) + )) + } + + model FRtypeInstrMinMax (name : Id, size : FSizeRec, fun : Id, rm : Ex) : IsaDefs = { + $FRtypeInstr2 (( + AsId($name, $size.suffix) ; AsStr($name, ".", $size.suffix) ; + $rm ; $size.fmt ; 0b0'0101 ; + F(rd) := $NaNBox($size ; VADL::$fun::<$size.fTy>(F(rs1) as $size.ty, F(rs2) as $size.ty)) + )) + } + + model FRtypeInstrSqrt (name : Id, size : FSizeRec, fun : Id, rm : Ex) : IsaDefs = { + $FRtypeInstr1rm (( + AsId($name, $size.suffix, $FrmName($rm)) ; AsStr($name, ".", $size.suffix) ; + $rm ; $size.fmt ; 0b0'1011 ; + F(rd) := $NaNBox($size ; VADL::$fun::<$size.fTy>(F(rs1) as $size.ty, frm($rm))) + ) ; 0b0'0000 ) + } + + model FRtypeInstrCmp (name : Id, size : FSizeRec, fun : Id, rm : Ex) : IsaDefs = { + $FRtypeInstr2 (( + AsId($name, $size.suffix) ; AsStr($name, ".", $size.suffix) ; + $rm ; $size.fmt ; 0b1'0100 ; + X(rd) := VADL::$fun::<$size.fTy>(F(rs1) as $size.ty, F(rs2) as $size.ty) as UIntR // zero extend + )) + } + + model FRtypeInstrSgn (name : Id, size : FSizeRec, sgn : Ex, rm : Ex) : IsaDefs = { + $FRtypeInstr2 (( + AsId($name, $size.suffix) ; AsStr($name, ".", $size.suffix) ; + $rm ; $size.fmt ; 0b0'0100 ; + let lower = F(rs1)(($size.size-2)..0) in + let rs1Sgn = F(rs1)($size.size-1) in + let rs2Sgn = F(rs2)($size.size-1) in + F(rd) := $NaNBox($size ; ($sgn, lower) as $size.ty) + )) + } + + model FRtypeInstrMvF2X (name : Id, size : FSizeRec) : IsaDefs = { + $FRtypeInstr1 (( + AsId($name, "X", $size.mvSuffix) ; AsStr($name, ".X.", $size.mvSuffix) ; + 0b000 ; $size.fmt ; 0b1'1100 ; + // first cast to the correct float type and then sign extend + X(rd) := F(rs1) as $size.ty as SIntR + ) ; 0b0'0000 ) + } + + model FRtypeInstrMvX2F (name : Id, size : FSizeRec) : IsaDefs = { + $FRtypeInstr1 (( + AsId($name, $size.mvSuffix, "X") ; AsStr($name, ".", $size.mvSuffix, ".X") ; + 0b000 ; $size.fmt ; 0b1'1110 ; + F(rd) := $NaNBox($size ; X(rs1) as $size.ty) + ) ; 0b0'0000 ) + } + + model FRtypeInstrCvtX2F (name : Id, size : FSizeRec, iSize : FSizeRec, u : BoolModelRec, rs2 : Bin, rm : Ex) : IsaDefs = { + $FRtypeInstr1rm (( + AsId($name, $size.suffix, $iSize.cvtSuffix, $u.str("U" ; ""), $FrmName($rm)) ; + AsStr($name, ".", $size.suffix, ".", $iSize.mvSuffix, $u.str("U" ; "")) ; + $rm ; $size.fmt ; 0b1'1010 ; + F(rd) := $NaNBox($size ; VADL::$u.id(fcvtuf ; fcvtsf)::<$size.fTy>(X(rs1) as $u.id($iSize.uTy ; $iSize.sTy), frm($rm))) + ) ; $rs2 ) + } + + model FRtypeInstrCvtF2X (name : Id, size : FSizeRec, iSize : FSizeRec, u : BoolModelRec, rs2 : Bin, rm : Ex) : IsaDefs = { + $FRtypeInstr1rm (( + AsId($name, $iSize.cvtSuffix, $u.str("U" ; ""), $size.suffix, $FrmName($rm)) ; + AsStr($name, ".", $iSize.mvSuffix, $u.str("U" ; ""), ".", $size.suffix) ; + $rm ; $size.fmt ; 0b1'1000 ; + // always sign extend the result, even unsigned results + X(rd) := VADL::$u.id(fcvtfu ; fcvtfs)::<$size.fTy, $iSize.size>(F(rs1) as $size.ty, frm($rm)) as SIntR + ) ; $rs2 ) + } + + model FRtypeInstrCvtF2F (name : Id, iSize : FSizeRec, size : FSizeRec, rs2 : Bin, rm : Ex) : IsaDefs = { + $FRtypeInstr1rm (( + AsId($name, $size.suffix, $iSize.suffix, $FrmName($rm)) ; + AsStr($name, ".", $size.suffix, ".", $iSize.suffix) ; + $rm ; $size.fmt ; 0b0'1000 ; + F(rd) := $NaNBox($size ; VADL::fcvt::<$iSize.fTy, $size.fTy>(F(rs1) as $iSize.ty, frm($rm))) + ) ; $rs2 ) + } + + model FRtypeInstrClass (name : Id, size : FSizeRec) : IsaDefs = { + instruction AsId($name, $size.suffix) : FRtype = + let f = F(rs1) as $size.ty in + // TODO: this is from QEMU's risc-v vector_helper.c fclass_s(...) + // what predicates should we implement? + let neg = VADL::fisneg::<$size.fTy>(f) in + X(rd) := if VADL::fisinf ::<$size.fTy>(f) then ( if neg then 1 << 0 else 1 << 7 ) else + if VADL::fiszero ::<$size.fTy>(f) then ( if neg then 1 << 3 else 1 << 4 ) else + if VADL::fisdenorm::<$size.fTy>(f) then ( if neg then 1 << 2 else 1 << 5 ) else + if VADL::fissnan ::<$size.fTy>(f) then 1 << 8 else + if VADL::fisqnan ::<$size.fTy>(f) then 1 << 9 else + ( if neg then 1 << 1 else 1 << 6 ) + encoding AsId($name, $size.suffix) = {opcode = 0b101'0011, funct3 = 0b001, rs2 = 0b0'0000, fmt = $size.fmt, funct5 = 0b111'00} + assembly AsId($name, $size.suffix) = (AsStr($name), ".", AsStr($size.suffix), " ", register(rd), ",", register(rs1)) + } + + model FLtypeInstr (name : Id, size : FSizeRec, funct3 : Bin) : IsaDefs = { + instruction $name : Itype = + let addr = X(rs1) + immS in + let bytes = $size.size / 8 in + F(rd) := $NaNBox($size ; MEM(addr) as $size.ty) + encoding $name = {opcode = 0b000'0111, funct3 = $funct3} + assembly $name = (mnemonic, " ", register(rd), ",", decimal(imm as SInt<12>), "(", register(rs1), ")") + } + + model FStypeInstr (name : Id, size : FSizeRec, funct3 : Bin) : IsaDefs = { + instruction $name : Stype = + let addr = X(rs1) + immS in + let bytes = $size.size / 8 in + MEM(addr) := F(rs2) as $size.ty + encoding $name = {opcode = 0b010'0111, funct3 = $funct3} + assembly $name = (mnemonic, " ", register(rs2), ",", decimal(imm as SInt<12>), "(", register(rs1), ")") + } + + model FR4typeInstr (name : Id, size : FSizeRec, fun : Id, rm : Ex, opcode : Bin) : IsaDefs = { + instruction AsId($name, $size.suffix, $FrmName($rm)) : R4type = + F(rd) := $NaNBox($size ; VADL::$fun::<$size.fTy>(F(rs1) as $size.ty, F(rs2) as $size.ty, F(rs3) as $size.ty, frm($rm))) + encoding AsId($name, $size.suffix, $FrmName($rm)) = {opcode = $opcode, funct3 = $rm, fmt = $size.fmt} + assembly AsId($name, $size.suffix, $FrmName($rm)) = (AsStr($name, ".", $size.suffix), " ", register(rd), ",", register(rs1), ",", register(rs2), ",", $FrmName($rm)) + } + + model CommonRoundedInstrs (size : FSizeRec, rm : Ex) : IsaDefs = { + $FRtypeInstrBiArith (FADD ; $size ; fadd ; 0b0'0000 ; $rm) + $FRtypeInstrBiArith (FSUB ; $size ; fsub ; 0b0'0001 ; $rm) + $FRtypeInstrBiArith (FMUL ; $size ; fmul ; 0b0'0010 ; $rm) + $FRtypeInstrBiArith (FDIV ; $size ; fdiv ; 0b0'0011 ; $rm) + + $FRtypeInstrSqrt (FSQRT ; $size ; fsqrt ; $rm) + + $FR4typeInstr (FMADD ; $size ; fmadd ; $rm ; 0b100'0011) + $FR4typeInstr (FMSUB ; $size ; fmsub ; $rm ; 0b100'0111) + $FR4typeInstr (FNMADD ; $size ; fnmadd ; $rm ; 0b100'1111) + $FR4typeInstr (FNMSUB ; $size ; fnmsub ; $rm ; 0b100'1011) + + $FRtypeInstrCvtX2F (FCVT ; $size ; $FSize32 ; (SignedId ; SignedStr) ; 0b0'0000 ; $rm) + $FRtypeInstrCvtX2F (FCVT ; $size ; $FSize32 ; (UnsignedId ; UnsignedStr) ; 0b0'0001 ; $rm) + $FRtypeInstrCvtX2F (FCVT ; $size ; $FSize64 ; (SignedId ; SignedStr) ; 0b0'0010 ; $rm) + $FRtypeInstrCvtX2F (FCVT ; $size ; $FSize64 ; (UnsignedId ; UnsignedStr) ; 0b0'0011 ; $rm) + + $FRtypeInstrCvtF2X (FCVT ; $size ; $FSize32 ; (SignedId ; SignedStr) ; 0b0'0000 ; $rm) + $FRtypeInstrCvtF2X (FCVT ; $size ; $FSize32 ; (UnsignedId ; UnsignedStr) ; 0b0'0001 ; $rm) + $FRtypeInstrCvtF2X (FCVT ; $size ; $FSize64 ; (SignedId ; SignedStr) ; 0b0'0010 ; $rm) + $FRtypeInstrCvtF2X (FCVT ; $size ; $FSize64 ; (UnsignedId ; UnsignedStr) ; 0b0'0011 ; $rm) + } + + model AllCommonRoundedInstrs (size : FSizeRec) : IsaDefs = { + $CommonRoundedInstrs ($size ; Frm::rne) + $CommonRoundedInstrs ($size ; Frm::rtz) + $CommonRoundedInstrs ($size ; Frm::rdn) + $CommonRoundedInstrs ($size ; Frm::rup) + $CommonRoundedInstrs ($size ; Frm::rmm) + $CommonRoundedInstrs ($size ; Frm::dyn) + } + + model CommonInstrs (size : FSizeRec) : IsaDefs = { + $AllCommonRoundedInstrs ($size) + + $FRtypeInstrMinMax (FMIN ; $size ; fmin ; 0b000) + $FRtypeInstrMinMax (FMAX ; $size ; fmax ; 0b001) + + //// TODO: specify somehow that lt, le are signaling and eq is a quiet comparison + $FRtypeInstrCmp (FLE ; $size ; fle ; 0b000) + $FRtypeInstrCmp (FLT ; $size ; flt ; 0b001) + $FRtypeInstrCmp (FEQ ; $size ; feq ; 0b010) + + $FRtypeInstrSgn (FSGNJ ; $size ; ( rs2Sgn) ; 0b000) + $FRtypeInstrSgn (FSGNJN ; $size ; ( 1 - rs2Sgn) ; 0b001) + $FRtypeInstrSgn (FSGNJX ; $size ; (rs1Sgn ^ rs2Sgn) ; 0b010) + + $FRtypeInstrMvF2X (FMV ; $size) + $FRtypeInstrMvX2F (FMV ; $size) + + $FRtypeInstrClass (FCLASS ; $size) + } + + $InstructionsFD ( + $FLtypeInstr (FLW ; $FSize32 ; 0b010) + $FStypeInstr (FSW ; $FSize32 ; 0b010) + $CommonInstrs ($FSize32) + ; + $FLtypeInstr (FLD ; $FSize64 ; 0b011) + $FStypeInstr (FSD ; $FSize64 ; 0b011) + $CommonInstrs ($FSize64) + // TODO: shorten this + $FRtypeInstrCvtF2F (FCVT ; $FSize32 ; $FSize64 ; 0b0'0000 ; Frm::rne) + $FRtypeInstrCvtF2F (FCVT ; $FSize32 ; $FSize64 ; 0b0'0000 ; Frm::rtz) + $FRtypeInstrCvtF2F (FCVT ; $FSize32 ; $FSize64 ; 0b0'0000 ; Frm::rdn) + $FRtypeInstrCvtF2F (FCVT ; $FSize32 ; $FSize64 ; 0b0'0000 ; Frm::rup) + $FRtypeInstrCvtF2F (FCVT ; $FSize32 ; $FSize64 ; 0b0'0000 ; Frm::rmm) + $FRtypeInstrCvtF2F (FCVT ; $FSize32 ; $FSize64 ; 0b0'0000 ; Frm::dyn) + $FRtypeInstrCvtF2F (FCVT ; $FSize64 ; $FSize32 ; 0b0'0001 ; Frm::rne) + $FRtypeInstrCvtF2F (FCVT ; $FSize64 ; $FSize32 ; 0b0'0001 ; Frm::rtz) + $FRtypeInstrCvtF2F (FCVT ; $FSize64 ; $FSize32 ; 0b0'0001 ; Frm::rdn) + $FRtypeInstrCvtF2F (FCVT ; $FSize64 ; $FSize32 ; 0b0'0001 ; Frm::rup) + $FRtypeInstrCvtF2F (FCVT ; $FSize64 ; $FSize32 ; 0b0'0001 ; Frm::rmm) + $FRtypeInstrCvtF2F (FCVT ; $FSize64 ; $FSize32 ; 0b0'0001 ; Frm::dyn) + ) + +} diff --git a/vadl-frontend/main/vadl/ast/AnnotationTable.java b/vadl-frontend/main/vadl/ast/AnnotationTable.java index 7659bd8dd..f54794449 100644 --- a/vadl-frontend/main/vadl/ast/AnnotationTable.java +++ b/vadl-frontend/main/vadl/ast/AnnotationTable.java @@ -24,6 +24,7 @@ import com.google.errorprone.annotations.concurrent.LazyInit; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -47,6 +48,7 @@ import vadl.ast.nodes.DerivedFormatField; import vadl.ast.nodes.EncodingDefinition; import vadl.ast.nodes.Expr; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.FormatField; import vadl.ast.nodes.GroupDefinition; import vadl.ast.nodes.Identifier; @@ -69,6 +71,7 @@ import vadl.types.BitsType; import vadl.types.Type; import vadl.utils.Pair; +import vadl.utils.functionInterfaces.QuadConsumer; import vadl.utils.functionInterfaces.TriConsumer; import vadl.viam.Abi; import vadl.viam.ArtificialResource; @@ -77,6 +80,8 @@ import vadl.viam.Counter; import vadl.viam.Encoding; import vadl.viam.Endianness; +import vadl.viam.FloatExceptionFlag; +import vadl.viam.FloatFormat; import vadl.viam.Format; import vadl.viam.Group; import vadl.viam.Instruction; @@ -204,7 +209,7 @@ public class AnnotationTable { // this handled in the VIAM lowering when constructing the ArtificialResource .build(); - annotationOn(RegisterDefinition.class, "execution state", FormatFieldAnnotation::new) + annotationOn(RegisterDefinition.class, "execution state", ExecutionStateAnnotation::new) .check((def, annotation, lowering) -> annotation.typeCheckTarget(def)) .applyAst((def, annotation) -> annotation.calcBitSlice(def)) .applyViam((def, annotation, lowering) -> { @@ -330,6 +335,64 @@ public class AnnotationTable { }) .build(); + /// FLOAT RELATED /// + + annotationOn(FloatTypeDefinition.class, "IEEE", ConstantAnnotation::new) + .applyAst((def, annotation) -> def.size = annotation.constant.value().intValue()) + .applyViam((def, annotation, lowering) -> { + var encoding = FloatFormat.Encoding.ieee(annotation.constant.value().intValue()); + ensure(encoding != null, + () -> error("Invalid IEEE encoding size", annotation) + .description("The following sizes are supported: %s", + Arrays.stream(FloatFormat.Encoding.values()) + .map(e -> Integer.toString(e.size)).collect(Collectors.joining(", "))) + ); + ((FloatFormat) def).setEncoding(encoding); + }).build(); + + annotationOn(FloatTypeDefinition.class, "canonical sNaN", ConstantAnnotation::new) + .applyViam((def, annotation, lowering) -> + ((FloatFormat) def).setCanonicalSNaN(annotation.constant.toViamConstant())).build(); + + annotationOn(FloatTypeDefinition.class, "canonical qNaN", ConstantAnnotation::new) + .applyViam((def, annotation, lowering) -> + ((FloatFormat) def).setCanonicalQNaN(annotation.constant.toViamConstant())).build(); + + QuadConsumer + applyViamFloatFlag; + applyViamFloatFlag = (reg, annotation, sticky, flag) -> { + var idx = annotation.index; + if (reg.hasAnnotation(vadl.viam.annotations.FloatFlagAnnotation.class)) { + var ann = reg.expectAnnotation(vadl.viam.annotations.FloatFlagAnnotation.class); + var setFlag = ann.get(idx); + ensure(setFlag == null, () -> error( + "Bit already mapped as " + (ann.isSticky(idx) ? "" : "non ") + + "sticky " + requireNonNull(setFlag).name + " flag", + annotation + )); + ann.set(idx, sticky, flag); + } else { + var ann = new vadl.viam.annotations.FloatFlagAnnotation(); + ann.set(idx, sticky, flag); + reg.addAnnotation(ann); + } + }; + + for (var flag : FloatExceptionFlag.values()) { + annotationOn(RegisterDefinition.class, "fe flag " + flag.name, FloatFlagAnnotation::new) + .check((def, annotation, lowering) -> annotation.typeCheckTarget(def)) + .applyViam((def, annotation, lowering) -> + applyViamFloatFlag.accept((RegisterTensor) def, annotation, false, flag)) + .build(); + + annotationOn(RegisterDefinition.class, "sticky fe flag " + flag.name, + FloatFlagAnnotation::new) + .check((def, annotation, lowering) -> annotation.typeCheckTarget(def)) + .applyViam((def, annotation, lowering) -> + applyViamFloatFlag.accept((RegisterTensor) def, annotation, true, flag)) + .build(); + } + /// PROCESSOR RELATED /// annotationOn(ProcessorDefinition.class, "htif", EnableAnnotation::new) @@ -1103,6 +1166,95 @@ public String usageString() { } } +/** + * An annotation that can be applied to registers of type {@link FormatType}. It can be used + * to reference one format field of the type. The bit-size of the format field must be 1. + * + *

Usage examples: + *

+ * [ sticky fe flag overflow : ov ]
+ * register reg : Format
+ * format Format : Bits<8> { ov [7], ... }
+ * 
+ */ +class FloatFlagAnnotation extends FormatFieldAnnotation { + + @LazyInit + Identifier field; + + @LazyInit + int index; + + @Override + void typeCheck(AnnotationDefinition definition, TypeChecker typeChecker) { + super.typeCheck(definition, typeChecker); + verifyValuesCnt(definition, 1); + field = (Identifier) definition.values.getFirst(); + } + + @Override + void typeCheckTarget(TypedNode target) { + super.typeCheckTarget(target); + var format = ((FormatType) target.type()).format; + var range = requireNonNull(format.getFieldRange(field.name)); + Diagnostic.ensure(range.from() == range.to(), () -> + error("Float flag can only be one bit", field)); + index = range.from(); + } + + @Override + String annotationName() { + return "Float exception flag annotation"; + } + + @Override + public String usageString() { + return "[ " + name + " : ]"; + } +} + +/** + * An annotation that can be applied to registers of type {@link BitsType}. If the register's + * type is a {@link FormatType}, then this annotation can be used to reference its format fields. + * + *

Usage examples: + *

+ * [ execution state ]
+ * register reg : Bits<8>
+ *
+ * [ execution state : f0, f1 ]
+ * register reg : Format
+ * format Format : Bits<8> { f0 [7], f1 [6], ... }
+ * 
+ */ +class ExecutionStateAnnotation extends FormatFieldAnnotation { + + void calcBitSlice(TypedNode target) { + if (fields.isEmpty()) { + var width = ((BitsType) target.type()).bitWidth(); + slice = Constant.BitSlice.of(width - 1, 0); + return; + } + var format = requireNonNull((FormatType) target.type()).format; + slice = new Constant.BitSlice( + fields.stream() + .map(field -> requireNonNull(format.getFieldRange(field.name))) + .map(range -> new Constant.BitSlice.Part(range.from(), range.to())) + .toArray(Constant.BitSlice.Part[]::new) + ); + } + + @Override + String annotationName() { + return "Execution state annotation"; + } + + @Override + public String usageString() { + return "[ " + name + " : , ... ]"; + } +} + /** * An annotation that can be applied to anything that has a type which is a {@link BitsType}. * If the target's type is a {@link FormatType}, then this annotation can be used to reference @@ -1118,7 +1270,7 @@ public String usageString() { * format Format : Bits<8> { f0 [7], f1 [6], ... } * */ -class FormatFieldAnnotation extends Annotation { +abstract class FormatFieldAnnotation extends Annotation { @LazyInit List fields; @@ -1145,14 +1297,14 @@ void typeCheckTarget(TypedNode target) { if (fields.isEmpty()) { Diagnostic.ensure(target.type() instanceof BitsType, () -> error("Annotation target has invalid type", this).description(""" - Execution state annotation can only be applied to simple \ - register definitions (no register files or tensors)""")); + %s can only be applied to simple register \ + definitions (no register files or tensors)""", annotationName())); return; } Diagnostic.ensure(target.type() instanceof FormatType, () -> error("Annotation target has invalid type", this).description(""" - Execution state annotation with format fields can only \ - be applied to register definitions with a format type""")); + %s with format fields can only be applied \ + to register definitions with a format type""", annotationName())); var format = ((FormatType) target.type()).format; fields.forEach(field -> { @@ -1170,25 +1322,7 @@ void typeCheckTarget(TypedNode target) { }); } - void calcBitSlice(TypedNode target) { - if (fields.isEmpty()) { - var width = ((BitsType) target.type()).bitWidth(); - slice = Constant.BitSlice.of(width - 1, 0); - return; - } - var format = requireNonNull((FormatType) target.type()).format; - slice = new Constant.BitSlice( - fields.stream() - .map(field -> requireNonNull(format.getFieldRange(field.name))) - .map(range -> new Constant.BitSlice.Part(range.from(), range.to())) - .toArray(Constant.BitSlice.Part[]::new) - ); - } - - @Override - public String usageString() { - return "[ " + name + " : , ... ]"; - } + abstract String annotationName(); } /** diff --git a/vadl-frontend/main/vadl/ast/AstUtils.java b/vadl-frontend/main/vadl/ast/AstUtils.java index c6ca9ae89..6ce7d3b8c 100644 --- a/vadl-frontend/main/vadl/ast/AstUtils.java +++ b/vadl-frontend/main/vadl/ast/AstUtils.java @@ -73,9 +73,7 @@ static BuiltInTable.BuiltIn getBuiltIn(String name, List argTypes) { name = "sdec"; } - String finalBuiltinName = name; - var matchingBuiltin = nameLookupTable.get(finalBuiltinName); - return matchingBuiltin; + return nameLookupTable.get(name); } static BuiltInTable.BuiltIn getOperatorBuiltIn(Operator operator, List argTypes) { diff --git a/vadl-frontend/main/vadl/ast/BehaviorLowering.java b/vadl-frontend/main/vadl/ast/BehaviorLowering.java index b138894d5..489a1c062 100644 --- a/vadl-frontend/main/vadl/ast/BehaviorLowering.java +++ b/vadl-frontend/main/vadl/ast/BehaviorLowering.java @@ -63,6 +63,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -125,13 +126,13 @@ import vadl.utils.BigIntUtils; import vadl.utils.Either; import vadl.utils.Pair; -import vadl.utils.SourceLocation; import vadl.utils.WithLocation; import vadl.viam.ArtificialResource; import vadl.viam.Constant; import vadl.viam.Counter; import vadl.viam.Definition; import vadl.viam.ExceptionDef; +import vadl.viam.FloatFormat; import vadl.viam.Format; import vadl.viam.Function; import vadl.viam.Instruction; @@ -912,6 +913,11 @@ private ExpressionNode visitIdentifiable(Expr expr) { return new ConstantNode(value); } + if (computedTarget instanceof FloatTypeDefinition floatType) { + var format = (FloatFormat) viamLowering.fetch(floatType).orElseThrow(); + return new ConstantNode(new Constant.FloatType(format)); + } + // Enum field if (computedTarget instanceof EnumerationDefinition.Entry enumField) { // Inline the value of the enum @@ -1376,6 +1382,26 @@ public ExpressionNode visitStageCall(CallIndexExpr expr, StageDefinition stageDe return new ReadStageOutputNode(output); } + private Constant visitConstArg(Expr expr) { + Node origin = null; + if (expr instanceof Identifier identifier) { + origin = requireNonNull(identifier.target()); + } else if (expr instanceof IdentifierPath path) { + origin = requireNonNull(path.target()); + } + + // TODO: the constant evaluator can only evaluate integers currently. Once other stuff like + // strings and float-types are supported, we do not need this function anymore and can + // directly call the constant evaluator. + // !!! There is a similar method in TypeChecker + if (origin instanceof FloatTypeDefinition floatType) { + var format = (FloatFormat) viamLowering.fetch(floatType).orElseThrow(); + return new Constant.FloatType(format); + } + + return constantEvaluator.eval(expr).toViamConstant(); + } + @Override public ExpressionNode visit(CallIndexExpr expr) { @@ -1385,6 +1411,12 @@ public ExpressionNode visit(CallIndexExpr expr) { return visitStageCall(expr, stageDefinition); } + var symbolArgs = expr.symbolArgs(); + + var constArgs = symbolArgs != null + ? symbolArgs.stream().map(this::visitConstArg).toList() + : List.of(); + var argGroups = expr.args(); final var args = new NodeList(AstUtils.argumentCount(argGroups)); AstUtils.forEachArgument(argGroups, arg -> args.add(this.fetch(arg))); @@ -1395,11 +1427,9 @@ public ExpressionNode visit(CallIndexExpr expr) { // Builtin Call if (expr.computedBuiltIn != null) { if (BuiltInTable.ASM_PARSER_BUILT_INS.contains(expr.computedBuiltIn)) { - exprBeforeSlice = new AsmBuiltInCall(expr.computedBuiltIn, args, - typeBeforeSlice); + exprBeforeSlice = new AsmBuiltInCall(expr.computedBuiltIn, args, typeBeforeSlice); } else { - exprBeforeSlice = new BuiltInCall(expr.computedBuiltIn, args, - typeBeforeSlice); + exprBeforeSlice = new BuiltInCall(expr.computedBuiltIn, constArgs, args, typeBeforeSlice); } } else { exprBeforeSlice = switch (expr.computedTarget()) { @@ -1432,9 +1462,8 @@ yield readRegisterTensorDirect(((Counter) lowered).registerTensor(), aliasArgs, } case MemoryDefinition memDef -> { - var sizeExpr = expr.target.size(); - var words = sizeExpr != null - ? constantEvaluator.eval(sizeExpr).value().intValueExact() + var words = symbolArgs != null + ? constantEvaluator.eval(symbolArgs.getFirst()).value().intValueExact() : 1; yield new ReadMemNode((Memory) viamLowering.fetch(memDef).orElseThrow(), words, args.getFirst(), typeBeforeSlice.asDataType()); @@ -1745,9 +1774,9 @@ public SubgraphContext visit(AssignmentStatement statement) { } }); - var sizeExpr = callTarget.target.size(); - callSize = sizeExpr != null - ? constantEvaluator.eval(sizeExpr).value().intValueExact() + var symbolArgs = callTarget.target.symbolArgs(); + callSize = symbolArgs != null + ? constantEvaluator.eval(symbolArgs.getFirst()).value().intValueExact() : null; } else if (statement.target instanceof Identifier identTarget) { targetDef = (vadl.ast.nodes.Definition) requireNonNull(identTarget.target()); diff --git a/vadl-frontend/main/vadl/ast/ConstantEvaluator.java b/vadl-frontend/main/vadl/ast/ConstantEvaluator.java index 247320919..e5543722b 100644 --- a/vadl-frontend/main/vadl/ast/ConstantEvaluator.java +++ b/vadl-frontend/main/vadl/ast/ConstantEvaluator.java @@ -164,10 +164,18 @@ public ConstantValue evalBuiltin(BuiltInTable.BuiltIn builtin, List (Constant) c.toViamConstant()).toList()) + .compute(List.of(), args.stream().map(c -> (Constant) c.toViamConstant()).toList()) .orElseThrow(() -> new EvaluationError( "Built-in function `%s` cannot be constant evaluated (yet).".formatted( builtin.name()), diff --git a/vadl-frontend/main/vadl/ast/MacroExpander.java b/vadl-frontend/main/vadl/ast/MacroExpander.java index d387ae9e1..614583c3a 100644 --- a/vadl-frontend/main/vadl/ast/MacroExpander.java +++ b/vadl-frontend/main/vadl/ast/MacroExpander.java @@ -73,6 +73,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallIndex; import vadl.ast.nodes.ForallStatement; @@ -522,7 +523,12 @@ public Expr visit(CastExpr expr) { @Override public Expr visit(SymbolExpr expr) { - return new SymbolExpr(expandExpr(expr.path), expandExpr(expr.size), copyLoc(expr.location)); + var symbolArgs = new ArrayList(expr.symbolArgs.size()); + for (var i = 0; i < expr.symbolArgs.size(); i++) { + var symbolArg = expr.symbolArgs.get(i); + symbolArgs.add(expandExpr(symbolArg)); + } + return new SymbolExpr(expandExpr(expr.path), symbolArgs, copyLoc(expr.location)); } @Override @@ -707,6 +713,14 @@ public Definition visit(ConstantDefinition definition) { ); } + @Override + public Definition visit(FloatTypeDefinition definition) { + return new FloatTypeDefinition( + expandExpr(definition.identifier), + copyLoc(definition.loc) + ); + } + @Override public Definition visit(FormatDefinition definition) { var fields = new ArrayList(definition.fields.size()); diff --git a/vadl-frontend/main/vadl/ast/ModelRemover.java b/vadl-frontend/main/vadl/ast/ModelRemover.java index 40b6525f7..c1f7b4c86 100644 --- a/vadl-frontend/main/vadl/ast/ModelRemover.java +++ b/vadl-frontend/main/vadl/ast/ModelRemover.java @@ -47,6 +47,7 @@ import vadl.ast.nodes.EncodingFormatField; import vadl.ast.nodes.EnumerationDefinition; import vadl.ast.nodes.ExceptionDefinition; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.FormatDefinition; import vadl.ast.nodes.FunctionDefinition; import vadl.ast.nodes.GroupDefinition; @@ -112,6 +113,11 @@ public Definition visit(ConstantDefinition definition) { return definition; } + @Override + public Definition visit(FloatTypeDefinition definition) { + return definition; + } + @Override public Definition visit(FormatDefinition definition) { return definition; diff --git a/vadl-frontend/main/vadl/ast/SymbolTable.java b/vadl-frontend/main/vadl/ast/SymbolTable.java index 74e1fa18c..8cefe39da 100644 --- a/vadl-frontend/main/vadl/ast/SymbolTable.java +++ b/vadl-frontend/main/vadl/ast/SymbolTable.java @@ -52,6 +52,7 @@ import vadl.ast.nodes.ExistsInExpr; import vadl.ast.nodes.ExistsInThenExpr; import vadl.ast.nodes.Expr; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -1178,6 +1179,16 @@ public Void visit(InstructionSetDefinition definition) { return null; } + @Override + public Void visit(FloatTypeDefinition definition) { + beforeTravel(definition); + // FloatTypeDefinition has no @Child fields, so it's not in NodeChildrenRegistry, + // and we need to manually visit annotations + definition.annotations.forEach(this::travel); + afterTravel(definition); + return null; + } + @Override public Void visit(InstructionDefinition definition) { // Import all symbols from the format. diff --git a/vadl-frontend/main/vadl/ast/TypeChecker.java b/vadl-frontend/main/vadl/ast/TypeChecker.java index f744482ce..e7f7c52dc 100644 --- a/vadl-frontend/main/vadl/ast/TypeChecker.java +++ b/vadl-frontend/main/vadl/ast/TypeChecker.java @@ -90,6 +90,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -175,6 +176,7 @@ import vadl.types.ConcreteRelationType; import vadl.types.DataType; import vadl.types.FetchResultType; +import vadl.types.FloatStatusType; import vadl.types.GroupType; import vadl.types.InstructionType; import vadl.types.MicroArchitectureType; @@ -936,21 +938,28 @@ List applyCastToArgs(List args) { /// A tiny custom cache for built-in function type checking. private static class BuiltInCheckCache { - private Map, BuiltInCheckResult>> store = - new HashMap<>(); + private Map, Map, BuiltInCheckResult>>> + store = new HashMap<>(); @Nullable - private BuiltInCheckResult get(BuiltInTable.BuiltIn builtIn, List argTypes) { + private BuiltInCheckResult get(BuiltInTable.BuiltIn builtIn, List constArgs, + List argTypes) { var inner = store.get(builtIn); if (inner == null) { return null; } - return inner.get(argTypes); + var innerInner = inner.get(constArgs); + if (innerInner == null) { + return null; + } + return innerInner.get(argTypes); } - private void put(BuiltInTable.BuiltIn builtIn, List argTypes, BuiltInCheckResult result) { + private void put(BuiltInTable.BuiltIn builtIn, List constArgs, List argTypes, + BuiltInCheckResult result) { var inner = store.computeIfAbsent(builtIn, k -> new HashMap<>()); - inner.put(argTypes, result); + var innerInner = inner.computeIfAbsent(constArgs, k -> new HashMap<>()); + innerInner.put(argTypes, result); } } @@ -960,18 +969,19 @@ private void put(BuiltInTable.BuiltIn builtIn, List argTypes, BuiltInCheck /// Check if the built-in function call, but doesn't care which kind of expression it arises from /// binary expressions, unary expressions or direct calls. /// The passed arguments have to be already checked! - private BuiltInCheckResult checkBuiltin(BuiltInTable.BuiltIn builtIn, List args, - WithLocation location) { + private BuiltInCheckResult checkBuiltin(BuiltInTable.BuiltIn builtIn, List constArgs, + List args, WithLocation location) { List argTypes = new ArrayList<>(args.size()); for (int i = 0; i < args.size(); i++) { argTypes.add(args.get(i).type()); } - var cached = builtInCheckCache.get(builtIn, argTypes); + var constArgValues = constArgs.stream().map(this::evalConstArg).toList(); + var cached = builtInCheckCache.get(builtIn, constArgValues, argTypes); if (cached != null) { return cached; } - var result = unCachedCheckBuiltin(builtIn, args, location); + var result = unCachedCheckBuiltin(builtIn, constArgValues, args, location); // We cannot cache if the result is a constant but not all input types were also constant. // This is quite rare but here we simply cannot determine the result type simply based on the @@ -980,18 +990,20 @@ private BuiltInCheckResult checkBuiltin(BuiltInTable.BuiltIn builtIn, List return result; } - builtInCheckCache.put(builtIn, argTypes, result); + builtInCheckCache.put(builtIn, constArgValues, argTypes, result); return result; } - private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, List args, + private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, + List constArgs, List args, WithLocation location) { - if (!(args.size() == builtIn.argTypeClasses().size() || (builtIn.signature().hasVarArgs() - && args.size() >= builtIn.argTypeClasses().size()))) { + int minArgCount = builtIn.argTypeClasses().size(); + if (!(args.size() == minArgCount + || (builtIn.signature().hasVarArgs() && args.size() >= minArgCount))) { throw addErrorAndStopChecking( error("Type Mismatch", location) .locationDescription(location, - "Expected %d arguments but got %d.", builtIn.argTypeClasses().size(), args.size()) + "Expected %d arguments but got %d.", minArgCount, args.size()) .build()); } @@ -1254,11 +1266,17 @@ private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, Li var originalArgTypes = argTypes; argTypes = args.stream().map(Expr::type).toList(); - - if (!builtIn.takes(argTypes)) { + if (!builtIn.takes(constArgs, argTypes)) { // FIXME: Further improve these error messages. var areSomeConst = originalArgTypes.stream().anyMatch(ConstantType.class::isInstance); - var calledTypes = String.join(", ", argTypes.stream().map(Type::toString).toList()); + var calledTypes = "(%s)".formatted( + String.join(", ", argTypes.stream().map(Type::toString).toList())); + if (!constArgs.isEmpty()) { + calledTypes = "<%s>%s".formatted( + String.join(", ", constArgs.stream().map(Constant::toString).toList()), + calledTypes + ); + } addErrorAndStopChecking( error("Type Mismatch", location) .locationDescription(location, "The builtin has the signature `%s` but got `%s`.", @@ -1268,7 +1286,42 @@ private BuiltInCheckResult unCachedCheckBuiltin(BuiltInTable.BuiltIn builtIn, Li .build()); } - return new BuiltInCheckResult(argTypes, builtIn.returns(argTypes)); + return new BuiltInCheckResult(argTypes, builtIn.returns(constArgs, argTypes)); + } + + private Constant evalConstArg(Expr expr) { + Node origin = null; + String name = null; + if (expr instanceof Identifier identifier) { + origin = requireNonNull(identifier.target()); + name = identifier.name; + } else if (expr instanceof IdentifierPath path) { + origin = requireNonNull(path.target()); + name = path.toString(); + } + + // TODO: the constant evaluator can only evaluate integers currently. Once other stuff like + // strings and float-types are supported, we do not need this function anymore and can + // directly call the constant evaluator. + // !!! There is a similar method in BehaviorLowering + if (origin instanceof FloatTypeDefinition floatType) { + check(floatType); + if (floatType.size == null) { + addErrorAndStopChecking( + error("Missing float-type encoding size", floatType) + .help("Annotate with e.g. `[ IEEE : ]`") + .build() + ); + } + return new Constant.FloatType(requireNonNull(floatType.size), requireNonNull(name)); + } + + return constantEvaluator.eval(expr).toViamConstant(); + } + + @Override + public Void visit(FloatTypeDefinition definition) { + return null; } @Override @@ -3216,6 +3269,12 @@ private void visitIdentifiable(Expr expr) { return; } + if (origin instanceof FloatTypeDefinition floatTypeDef) { + check(floatTypeDef); + expr.type = floatTypeDef.type(); + return; + } + if (origin instanceof RangeFormatField field) { // FIXME: Unfortonatley the format fields need to be specified in declare-after-use for now expr.type = field.type; @@ -3452,7 +3511,7 @@ public Void visit(BinaryExpr expr) { // operator and not in the builtin function we want to call. checkResult = checkLogicalBuiltIn(expr.left, expr.right, expr); } else { - checkResult = checkBuiltin(builtin, List.of(expr.left, expr.right), expr); + checkResult = checkBuiltin(builtin, List.of(), List.of(expr.left, expr.right), expr); } if (checkResult.castedArgTypes != null) { @@ -3839,7 +3898,7 @@ public Void visit(UnaryExpr expr) { }; expr.computedTarget = builtin; - var result = checkBuiltin(builtin, List.of(expr.operand), expr); + var result = checkBuiltin(builtin, List.of(), List.of(expr.operand), expr); if (result.castedArgTypes != null) { expr.operand = result.applyCastToArgs(List.of(expr.operand)).get(0); } @@ -4198,6 +4257,18 @@ private void visitSubCall(CallIndexExpr expr, Type typeBeforeSubCall) { var fieldType = Type.bool(); visitSliceIndexCall(expr, fieldType, subCall.argsIndices); type = expr.type; + } else if (type instanceof FloatStatusType) { + var allowedStatusfields = List.of("nv", "dz", "of", "uf", "nx"); + if (!allowedStatusfields.contains(fieldName)) { + var suggestions = Levenshtein.sortAll(fieldName, allowedStatusfields); + addErrorAndStopChecking( + error("Unknown float status field `%s`".formatted(fieldName), expr) + .suggestions(suggestions) + .build()); + } + var fieldType = Type.bool(); + visitSliceIndexCall(expr, fieldType, subCall.argsIndices); + type = expr.type; } else if (type instanceof InstructionType) { var allowedStatusfields = List.of("address", "read", "unknown", "compute", "verify", "write", "readOrForward", @@ -4305,6 +4376,9 @@ private void processCallOfBuiltIn(CallIndexExpr expr) { // Builtin function List args = !expr.argsIndices.isEmpty() ? expr.argsIndices.getFirst().values : new ArrayList<>(); + var symbolArgs = expr.symbolArgs(); + var constArgs = symbolArgs == null ? List.of() : symbolArgs; + checkExpressions(constArgs); var argTypes = checkExpressions(args); var builtin = AstUtils.getBuiltIn(expr.target.path().pathToString(), argTypes); @@ -4317,7 +4391,7 @@ private void processCallOfBuiltIn(CallIndexExpr expr) { expr.computedBuiltIn = builtin; - var checkResult = checkBuiltin(builtin, args, expr); + var checkResult = checkBuiltin(builtin, constArgs, args, expr); if (checkResult.castedArgTypes != null) { expr.replaceArgsFor(0, checkResult.applyCastToArgs(args)); } @@ -4451,8 +4525,18 @@ private void processCallOfTarget(CallIndexExpr expr, Node callTarget) { expr.typeBeforeSlice = typedNode.type(); } - var targetSizeExpr = expr.target.size(); - if (targetSizeExpr != null) { + var targetSymbolArgs = expr.target.symbolArgs(); + if (targetSymbolArgs != null) { + if (targetSymbolArgs.size() != 1) { + var err = error("Invalid scaling arguments", expr); + if (!targetSymbolArgs.isEmpty()) { + var loc = targetSymbolArgs.stream().map(WithLocation::location) + .reduce(SourceLocation::join).get(); + err = err.locationDescription(loc, "Expected exactly one scaling argument."); + } + throw addErrorAndStopChecking(err.build()); + } + var targetSizeExpr = targetSymbolArgs.getFirst(); if (!(expr.typeBeforeSlice() instanceof BitsType exprType)) { throw addErrorAndStopChecking(error("Invalid scaling type", targetSizeExpr) .locationDescription(targetSizeExpr, "Result type `%s` cannot be scaled.", diff --git a/vadl-frontend/main/vadl/ast/Ungrouper.java b/vadl-frontend/main/vadl/ast/Ungrouper.java index 21927451b..03315d767 100644 --- a/vadl-frontend/main/vadl/ast/Ungrouper.java +++ b/vadl-frontend/main/vadl/ast/Ungrouper.java @@ -63,6 +63,7 @@ import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; import vadl.ast.nodes.ExprVisitor; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.ForallExpr; import vadl.ast.nodes.ForallStatement; import vadl.ast.nodes.ForallThenExpr; @@ -284,7 +285,7 @@ public Expr visit(CastExpr expr) { @Override public Expr visit(SymbolExpr expr) { - expr.size = expr.size.accept(this); + expr.symbolArgs.replaceAll(e -> e.accept(this)); return expr; } @@ -360,6 +361,12 @@ public Expr visit(ResourceReferenceExression expr) { return expr; } + @Override + public Void visit(FloatTypeDefinition definition) { + ungroupAnnotations(definition); + return null; + } + @Override public Void visit(ConstantDefinition definition) { ungroupAnnotations(definition); diff --git a/vadl-frontend/main/vadl/ast/ViamLowering.java b/vadl-frontend/main/vadl/ast/ViamLowering.java index 88e5efe88..b8861c13b 100644 --- a/vadl-frontend/main/vadl/ast/ViamLowering.java +++ b/vadl-frontend/main/vadl/ast/ViamLowering.java @@ -78,6 +78,7 @@ import vadl.ast.nodes.ExpandedAliasDefSequenceCallExpr; import vadl.ast.nodes.ExpandedSequenceCallExpr; import vadl.ast.nodes.Expr; +import vadl.ast.nodes.FloatTypeDefinition; import vadl.ast.nodes.FormatDefinition; import vadl.ast.nodes.FormatField; import vadl.ast.nodes.FunctionDefinition; @@ -141,6 +142,7 @@ import vadl.viam.Counter; import vadl.viam.Encoding; import vadl.viam.ExceptionDef; +import vadl.viam.FloatFormat; import vadl.viam.Format; import vadl.viam.Function; import vadl.viam.Group; @@ -1095,6 +1097,13 @@ public Optional visit(CacheDefinition definition) { definition.getClass().getSimpleName())); } + @Override + public Optional visit(FloatTypeDefinition definition) { + var identifier = + new vadl.viam.Identifier(definition.viamId, definition.identifier().location()); + return Optional.of(new FloatFormat(identifier)); + } + @Override public Optional visit(ConstantDefinition definition) { // Do nothing on purpose. @@ -1391,7 +1400,7 @@ private void setFieldAccessPredicate(PredicateFormatField predField) { } /** - * Get the field encoding for the {@link vadl.ast.EncodingFormatField} + * Get the field encoding for the {@link EncodingFormatField} * with the kind {@code ENCODING}. */ @SuppressWarnings("LineLength") @@ -1656,6 +1665,7 @@ private InstructionSetArchitecture visitAndMergeIsa(InstructionSetDefinition def mergedDef.definitions.stream().map(this::fetch).flatMap(Optional::stream) .toList(); var formats = filterAndCastToInstance(allDefinitions, Format.class); + var floatFormats = filterAndCastToInstance(allDefinitions, FloatFormat.class); var functions = filterAndCastToInstance(allDefinitions, Function.class); var operations = filterAndCastToInstance(allDefinitions, Operation.class); var relocations = filterAndCastToInstance(allDefinitions, Relocation.class); @@ -1700,6 +1710,7 @@ private InstructionSetArchitecture visitAndMergeIsa(InstructionSetDefinition def programCounter, memories, artificialResources, + floatFormats, group ); diff --git a/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java b/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java index 70615e3ac..3604a0a34 100644 --- a/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/CallIndexExpr.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Objects; import java.util.function.Consumer; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import vadl.ast.TensorType; import vadl.types.BuiltInTable; @@ -193,8 +194,9 @@ public IsId path() { } @Override - public @Nullable Expr size() { - return target.size(); + @Nullable + public List symbolArgs() { + return target.symbolArgs(); } @Override diff --git a/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java b/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java index 042da34f8..fc38b13f4 100644 --- a/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java +++ b/vadl-frontend/main/vadl/ast/nodes/DefinitionVisitor.java @@ -68,6 +68,8 @@ public interface DefinitionVisitor { public R visit(ExceptionDefinition definition); + public R visit(FloatTypeDefinition definition); + public R visit(FormatDefinition definition); public R visit(DerivedFormatField definition); diff --git a/vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java b/vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java new file mode 100644 index 000000000..dd331396b --- /dev/null +++ b/vadl-frontend/main/vadl/ast/nodes/FloatTypeDefinition.java @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.ast.nodes; + +import java.util.Objects; +import javax.annotation.Nullable; +import vadl.types.Type; +import vadl.utils.SourceLocation; + +@SuppressWarnings({"MissingJavadocType", "MissingJavadocMethod"}) +public class FloatTypeDefinition extends Definition implements IdentifiableNode, TypedNode { + public IdentifierOrPlaceholder identifier; + + @Nullable + public Integer size; + + public SourceLocation loc; + + public FloatTypeDefinition(IdentifierOrPlaceholder identifier, SourceLocation loc) { + this.identifier = identifier; + this.loc = loc; + } + + @Override + public Identifier identifier() { + return (Identifier) identifier; + } + + @Override + public SourceLocation location() { + return loc; + } + + @Override + public SyntaxType syntaxType() { + return BasicSyntaxType.COMMON_DEFS; + } + + @Override + public void prettyPrint(int indent, StringBuilder builder) { + prettyPrintAnnotations(indent, builder); + builder.append(prettyIndentString(indent)); + builder.append("float-type %s".formatted(identifier().name)); + builder.append("\n"); + } + + @Override + public R accept(DefinitionVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + FloatTypeDefinition that = (FloatTypeDefinition) o; + return Objects.equals(identifier, that.identifier); + } + + @Override + public int hashCode() { + return Objects.hashCode(identifier); + } + + @Override + public Type type() { + return Type.floatType(); + } +} diff --git a/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java b/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java index 0afa56bde..10862b439 100644 --- a/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/IsCallExpr.java @@ -16,6 +16,7 @@ package vadl.ast.nodes; +import java.util.List; import javax.annotation.Nullable; import vadl.utils.WithLocation; @@ -24,7 +25,7 @@ public sealed interface IsCallExpr extends WithLocation permits CallIndexExpr, I public IsId path(); @Nullable - public Expr size(); + public List symbolArgs(); public void prettyPrint(int indent, StringBuilder builder); } diff --git a/vadl-frontend/main/vadl/ast/nodes/IsId.java b/vadl-frontend/main/vadl/ast/nodes/IsId.java index d8dabab2e..178292015 100644 --- a/vadl-frontend/main/vadl/ast/nodes/IsId.java +++ b/vadl-frontend/main/vadl/ast/nodes/IsId.java @@ -16,6 +16,7 @@ package vadl.ast.nodes; +import java.util.List; import javax.annotation.Nullable; @SuppressWarnings({"MissingJavadocType", "MissingJavadocMethod"}) @@ -28,7 +29,8 @@ public default IsId path() { } @Override - public default @Nullable Expr size() { + @Nullable + public default List symbolArgs() { return null; } diff --git a/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java b/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java index 9366995a8..73e6410e5 100644 --- a/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/IsSymExpr.java @@ -16,6 +16,7 @@ package vadl.ast.nodes; +import java.util.List; import javax.annotation.Nullable; @SuppressWarnings({"MissingJavadocType", "MissingJavadocMethod"}) @@ -25,5 +26,5 @@ public sealed interface IsSymExpr extends IsCallExpr permits SymbolExpr, IsId { @Override @Nullable - public Expr size(); + public List symbolArgs(); } diff --git a/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java b/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java index 0d5cfdfa3..98a52299e 100644 --- a/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java +++ b/vadl-frontend/main/vadl/ast/nodes/RecursiveAstVisitor.java @@ -247,6 +247,14 @@ public Void visit(ExceptionDefinition definition) { return null; } + @Override + public Void visit(FloatTypeDefinition definition) { + beforeTravel(definition); + definition.forEachChild(this::travel); + afterTravel(definition); + return null; + } + @Override public Void visit(FormatDefinition definition) { beforeTravel(definition); diff --git a/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java b/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java index b62dbda77..db8d03dd8 100644 --- a/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java +++ b/vadl-frontend/main/vadl/ast/nodes/SymbolExpr.java @@ -16,24 +16,29 @@ package vadl.ast.nodes; +import java.util.List; import java.util.Objects; import vadl.javaannotations.ast.Child; import vadl.utils.SourceLocation; /** - * A representation of terms of form {@code "MEM<9>"}. + * A representation of terms of form {@code MEM<9>} or {@code VADL::fcvts::}. + * These terms always have at least one argument in the pointy brackets. */ @SuppressWarnings("MissingJavadocMethod") public final class SymbolExpr extends Expr implements IsSymExpr { @Child public IsId path; + /** + * The list of arguments in the pointy brackets. Always contains at least one element. + */ @Child - public Expr size; + public List symbolArgs; public SourceLocation location; - public SymbolExpr(IsId path, Expr size, SourceLocation location) { + public SymbolExpr(IsId path, List symbolArgs, SourceLocation location) { this.path = path; - this.size = size; + this.symbolArgs = symbolArgs; this.location = location; } @@ -43,8 +48,8 @@ public IsId path() { } @Override - public Expr size() { - return size; + public List symbolArgs() { + return symbolArgs; } @Override @@ -60,11 +65,25 @@ public SyntaxType syntaxType() { @Override public void prettyPrintExpr(int indent, StringBuilder builder, Precedence parentPrec) { path.prettyPrint(indent, builder); - var prefix = size instanceof BinaryExpr ? "<( " : "< "; - var suffix = size instanceof BinaryExpr ? " )>" : " >"; - builder.append(prefix); - size.prettyPrintExpr(indent, builder, Precedence.NoPrecedence); - builder.append(suffix); + if (symbolArgs.size() > 1) { + builder.append("::"); + } + builder.append("< "); + boolean first = true; + for (var arg : symbolArgs) { + if (!first) { + builder.append(", "); + } + if (arg instanceof BinaryExpr) { + builder.append("("); + } + arg.prettyPrintExpr(0, builder, Precedence.NoPrecedence); + if (arg instanceof BinaryExpr) { + builder.append(")"); + } + first = false; + } + builder.append(" >"); } @Override @@ -83,13 +102,13 @@ public boolean equals(Object o) { } SymbolExpr that = (SymbolExpr) o; - return path.equals(that.path) && Objects.equals(size, that.size); + return path.equals(that.path) && symbolArgs.equals(that.symbolArgs); } @Override public int hashCode() { int result = path.hashCode(); - result = 31 * result + Objects.hashCode(size); + result = 31 * result + Objects.hashCode(symbolArgs); return result; } } diff --git a/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java b/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java index 2c51afa4f..a208ad9d3 100644 --- a/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java +++ b/vadl-frontend/main/vadl/ast/nodes/TypeLiteral.java @@ -49,8 +49,8 @@ public TypeLiteral(IsId baseType, List sizeIndices, SourceLocation loc) { public TypeLiteral(IsSymExpr symExpr) { this.baseType = symExpr.path(); - var size = symExpr.size(); - this.sizeIndices = size == null ? List.of() : List.of(size); + var symbolArgs = symExpr.symbolArgs(); + this.sizeIndices = symbolArgs == null ? List.of() : List.of(symbolArgs.getFirst()); this.loc = symExpr.location(); } diff --git a/vadl-frontend/main/vadl/ast/vadl.ATG b/vadl-frontend/main/vadl/ast/vadl.ATG index 352afcbed..3ebe0d9fb 100644 --- a/vadl-frontend/main/vadl/ast/vadl.ATG +++ b/vadl-frontend/main/vadl/ast/vadl.ATG @@ -181,6 +181,7 @@ TOKENS FALSE = "false". FETCH = "fetch". FILE = "file". + FLOAT_TYPE = "float-type". FOLD = "fold". FOR = "for". FORALL = "forall". @@ -417,6 +418,7 @@ The #commonDefinitionList production rule lists (beside macro definitions) all c commonDefinition (. def = DUMMY_DEF; .) = constantDefinition + | floatTypeDefinition | formatDefinition | enumerationDefinition | usingDefinition @@ -516,6 +518,11 @@ elements. A single annotation is described in the #annotation production rule. expression (. def = new ConstantDefinition(id, type, expr, startLocation.join(lastTokenLoc())); .) . + floatTypeDefinition (. var startLocation = nextTokenLoc(); .) + = FLOAT_TYPE + identifierOrPlaceholder (. def = new FloatTypeDefinition(id, startLocation.join(lastTokenLoc())); .) + . + formatDefinition (. var startLoc = nextTokenLoc(); var fields = new ArrayList(); .) = FORMAT identifierOrPlaceholder @@ -1797,15 +1804,27 @@ A micro architecture definition (#microArchitectureDefinition) is shown in line = IF (isIdentifierToken(la) || isMacroReplacementOfType(this, BasicSyntaxType.ID)) identifierPath [ - IF (la.kind == _SYM_LT) - SYM_LT (. var lessLoc = lastTokenLoc(); .) - term - [ - IF (!allowLtOp || la.kind == _SYM_GT) - SYM_GT (. expr = new SymbolExpr(path, term, path.location().join(lastTokenLoc())); .) - ] (. if (expr.equals(DUMMY_EXPR)) expr = new BinaryExpr((Expr) path, new BinOp(Operator.Less, lessLoc), term); .) - ] (. if (expr.equals(DUMMY_EXPR)) expr = (Expr) path; .) - | macroReplacement (. expr = castExpr(this, node); .) + IF (la.kind == _SYM_NAMESPACE || la.kind == _SYM_LT) (. var values = new ArrayList(); .) + ( + IF (la.kind == _SYM_NAMESPACE) + SYM_NAMESPACE + SYM_LT + term (. values.add(firstTerm); .) + { + SYM_COMMA + term (. values.add(term); .) + } + SYM_GT (. expr = new SymbolExpr(path, values, path.location().join(lastTokenLoc())); .) + | + SYM_LT (. var lessLoc = lastTokenLoc(); .) + term (. values.add(term); .) + [ + IF (!allowLtOp || la.kind == _SYM_GT) + SYM_GT (. expr = new SymbolExpr(path, values, path.location().join(lastTokenLoc())); .) + ] (. if (expr.equals(DUMMY_EXPR)) expr = new BinaryExpr((Expr) path, new BinOp(Operator.Less, lessLoc), term); .) + ) + ] (. if (expr.equals(DUMMY_EXPR)) expr = (Expr) path; .) + | macroReplacement (. expr = castExpr(this, node); .) . binaryOperator (. op = null; .) @@ -1917,6 +1936,7 @@ A micro architecture definition (#microArchitectureDefinition) is shown in line identifierPath (. List segments = new ArrayList<>(); .) = identifierOrPlaceholder (. segments.add(id); .) { + IF (la.kind == _SYM_NAMESPACE && scanner.Peek().kind != _SYM_LT) // symbol expression can look like id::path::<>() SYM_NAMESPACE identifierOrPlaceholder (. segments.add(next); .) } (. if (segments.size() == 1) path = id; diff --git a/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl b/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl index a6df3b0fb..b53590407 100644 --- a/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl +++ b/vadl-frontend/test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl @@ -28,7 +28,7 @@ function abc(x: Bits<8>) -> Bits<8> = VADL::adds(1, x) // ╭── test/resources/frontend-snapshots/typechecker/invalidBuiltitinCalls.vadl:8:39 // │ // 8 │ function abc(x: Bits<8>) -> Bits<8> = VADL::adds(1, x) -// │ ^^^^^^^^^^^^^^^^ The builtin has the signature `(BitsType, BitsType) -> StructType` but got `Bits<1>, Bits<8>`. +// │ ^^^^^^^^^^^^^^^^ The builtin has the signature `(BitsType, BitsType) -> StructType` but got `(Bits<1>, Bits<8>)`. // │ help: Try casting some of the constant arguments to explicit types. // │ // diff --git a/vadl-test/resources/cosim_configs/riscv_config.toml b/vadl-test/resources/cosim_configs/riscv_config.toml new file mode 100644 index 000000000..ca3593dee --- /dev/null +++ b/vadl-test/resources/cosim_configs/riscv_config.toml @@ -0,0 +1,250 @@ +# SPDX-FileCopyrightText : © 2025 TU Wien +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# General settings for the QEMU plugin system which set up the co-simulation +[qemu] + +# The path to the compiled cosimulation qemu-plugin +plugin="/opt/qemu/lib/qemu/libcosimulation.so" + +# Whether to ignore registers that are not defined by a [qemu.gdb_reg_map] mapping +# This option is useful to test the current implementation of a simulator which might not yet have all registers implemented against another (complete) implementation +# Filters in conjunction with `ignore_registers` +ignore_unset_registers = true + +# Ignore specific registers +# Filters in conjunction with `ignore_unset_registers` +ignore_registers = [] + + +# Defines a list of clients to test against +# A single client is also possible, e.g. to check if a crash or similar occurs +# In most use-cases, 2 clients (one test-simulator and one reference-simulator) are used +[[qemu.clients]] + +# Optional: A custom name for a client +# Will default to the index of the client in this list +name = "VADL" + +# The executable of the ISS +exec = "/opt/qemu/bin/qemu-system-rv64id" + +# The path to the compiled file to use for testing +test_exec="./test-execs/test_vadl" + +# "bios" | "kernel": Defines where the test-executable is passed to when starting the QEMU-client +pass_test_exec_to = "bios" + +# Applies additional arguments to the ISS-executable, e.g. `qemu-system-riscv64 -nographic -d plugin` +additional_args = [ + "-plugin", + "/opt/qemu/lib/qemu/libstoptrigger.so,addr=0x80000010", + "-nographic", + "-d", "plugin" +] + +# A hint for the cosimulator whether the client uses little or big endian +# This setting does not change the execution in any way and is only used in case clients have differing endianess +# but should still be compared "correctly" +# This field is optional, not setting it means the register-data is being compared left-to-right (i.e. big-endian by default) +# This setting also *does not* have an effect on tracing, data will still be stored in the original endianess + +# This setting must match the `TARGET_BIG_ENDIAN` definition in `gen-arch-softmmu.mak` +# If the vadl memory definition is annotated with `[ big endian : Condition ]`, this setting should be set to +# "big" or be ommitted. If it is annotated with `[ little endian : Condition ]` or not annotated with any +# of the two, this setting should be explicitly set to "little". +endian = "little" + +# The following three options configure which instructions from the `test_exec` executable should actually be tested. +# NOTE: Only applies to `layer = "insn" | "tb-strict"` +# NOTE: This option is must be set per client to be able to account for different setup-codes per ISS +# Skips the first n instructions + +skip_n_instructions = 9 + +# Settings for running the qemu-clients with gdb-debugging enabled. +# Use these settings when possible instead of configuring the flags in the additional_args array since otherwise the runner will mark the client as finished because it did not respond in time. +[qemu.clients.gdb] +enable = false +# The target_type can be optionally set ("chardev" | "port"), default is "chardev" +# For reference see the QEMU GDB usage documentation: https://qemu-project.gitlab.io/qemu/system/gdb.html +# For more info regarding chardev/unix sockets see: https://qemu-project.gitlab.io/qemu/system/gdb.html#using-unix-sockets +# Using a chardev is preferred to ensure that the target is always available for cosimulation +# NOTE: It is not necessary (and will lead to errors if done anyway) to manually create the char-device, simply use a path that is available and +# QEMU will automatically handle creating the device-file. +# If target_type is "port" then simply enter the port (e.g. "tcp:1234") into the remote_target field, QEMU will try to listen on the port automatically +target_type = "chardev" +remote_target = "/tmp/gdb-cosim-VADL" + +[[qemu.clients]] +name = "UPSTREAM" + +exec = "/opt/qemu/bin/qemu-system-riscv64" + +test_exec="./test-execs/test_upstream" + +pass_test_exec_to = "bios" + +additional_args = [ + "-plugin", + "/opt/qemu/lib/qemu/libstoptrigger.so,addr=0x80000010", + "-nographic", + "-M", "virt", + "-d", "plugin" +] + +endian = "little" + +skip_n_instructions = 9 + +[qemu.clients.gdb] +enable = false +target_type = "chardev" +remote_target = "/tmp/gdb-cosim-VADL" + + +# Defines a custom map where the key (e.g. x0) is mapped to another value (e.g. zero) +[qemu.gdb_reg_map] +pc = "pc" + +x0 = "zero" +x1 = "ra" +x2 = "sp" +x3 = "gp" +x4 = "tp" +x5 = "t0" +x6 = "t1" +x7 = "t2" +x8 = "fp" +x9 = "s1" +x10 = "a0" +x11 = "a1" +x12 = "a2" +x13 = "a3" +x14 = "a4" +x15 = "a5" +x16 = "a6" +x17 = "a7" +x18 = "s2" +x19 = "s3" +x20 = "s4" +x21 = "s5" +x22 = "s6" +x23 = "s7" +x24 = "s8" +x25 = "s9" +x26 = "s10" +x27 = "s11" +x28 = "t3" +x29 = "t4" +x30 = "t5" +x31 = "t6" + +f0 = "ft0" +f1 = "ft1" +f2 = "ft2" +f3 = "ft3" +f4 = "ft4" +f5 = "ft5" +f6 = "ft6" +f7 = "ft7" +f8 = "fs0" +f9 = "fs1" +f10 = "fa0" +f11 = "fa1" +f12 = "fa2" +f13 = "fa3" +f14 = "fa4" +f15 = "fa5" +f16 = "fa6" +f17 = "fa7" +f18 = "fs2" +f19 = "fs3" +f20 = "fs4" +f21 = "fs5" +f22 = "fs6" +f23 = "fs7" +f24 = "fs8" +f25 = "fs9" +f26 = "fs10" +f27 = "fs11" +f28 = "ft8" +f29 = "ft9" +f30 = "ft10" +f31 = "ft11" + +# TODO: add remaining csr registers +fcsr = "fcsr" + +# Defines the test-source and how to test +[testing] + +# The testing-protocol defines how the clients are run and tested against eachother +[testing.protocol] +# Defines an *execution-step* of a test-run. +# "insn": The execution-step is the *execution of a single instruction*, e.g. `addi t4,zero,2`. +# This layer is independent of how an ISS generates translation-blocks for qemu. +# This is the most thorough but also slowest option. +# +# NOTE: the following is not yet implemented. +# "tb": The execution-step is the *execution of a single or multiple translation-blocks*. +# Multiple translation-blocks might be executed in a single step if another client executed a larger. +# (but potentially equivalent to multiple smaller TBs) translation-block. +# This allows instruction-equivalent clients to "synchronize" even if the generated translation-blocks differ. +# This option is faster than "insn" but less thorough. +# +# "tb-strict": The execution-step is the *execution of a single translation-blocks*. +# The same as "tb" but without the synchronization logic. Meaning that equal translation-blocks are assumed. +# This option is useful if the instructions of the ISS are already correct and the TB-Block generator needs to be tested. +layer = "insn" + +# Whether reads/writes from/to memory should also be compared +with_memory_checks = true + +# "lockstep": All clients are run and compared one *execution-step* at a time. +# This means that the test will exit on the first divergence (or at the end if no diffs where found) +# Currently, this is the only implemented mode. +mode = "lockstep" + + +# Execute all remaining instructions (overrides `stop_after_n_instructions` if set to true) +execute_all_remaining_instructions = false + +# Execute the next (after skipped) n instructions +stop_after_n_instructions = 100 + +# Where the test result should be saved and in which format +[testing.protocol.out] +# file = "./cosim-run/result/result.json" + +# "short" | "full" +verbosity = "full" + +[logging] +enable = true +# tracing log-levels as defined here: https://docs.rs/tracing/latest/tracing/struct.Level.html#implementations +level = "trace" + +# The directory will also contain files for the stdout and stderr of each client +dir = "." +file = "cosim.log" + +# Clears the logfile every time the program is run +clear_on_rerun = true + +[dev] +# Prints the loaded configuration if set to true and exits +dry_run = false diff --git a/vadl-test/resources/cosim_scripts/riscv/compiler.py b/vadl-test/resources/cosim_scripts/riscv/compiler.py new file mode 100644 index 000000000..078ddf92a --- /dev/null +++ b/vadl-test/resources/cosim_scripts/riscv/compiler.py @@ -0,0 +1,110 @@ +import os +import subprocess +from pathlib import Path + +AS = "riscv64-unknown-elf-as" +LD = "riscv64-unknown-elf-ld" +OBJDUMP = "riscv64-unknown-elf-objdump" + +def compile(id: str, asm: str, debug: bool = True) -> dict: + asm_path = build_assembly(id, asm) + linker_path = build_linker_script(id) + + obj = _tmp_file(id, f"obj-{id}.o") + elf = _tmp_file(id, f"elf-{id}") + assemble(AS, asm_path, obj) + link(LD, linker_path, obj, elf) + + result = { + "asm": asm_path, + "lnscript": linker_path, + "obj": obj, + "elf": elf + } + + if debug: + objdump_file = _tmp_file(id, f"elf-{id}.dump") + objdump(OBJDUMP, elf, objdump_file) + result.update({ + "objdump": objdump_file + }) + + return result + + +def assemble(as_cmd: str, asm_path: Path, obj_out: Path) -> None: + proc = subprocess.run([ + # TODO: check if these first two args are ok + as_cmd, "-march=rv64imd", "-mabi=lp64", "-o", str(obj_out), str(asm_path)], + ) + if proc.returncode != 0: + raise RuntimeError(f"Assembly failed ({as_cmd}): {proc.stderr.decode()}") + +def link(ld_cmd: str, linker_script: Path, obj_in: Path, elf_out: Path) -> None: + proc = subprocess.run([ + ld_cmd, "-T", str(linker_script), "-o", str(elf_out), str(obj_in)], + ) + if proc.returncode != 0: + raise RuntimeError(f"Linking failed ({ld_cmd}): {proc.stderr.decode()}") + +def build_assembly(id: str, core: str) -> Path: + asm_out = _tmp_file(id, f"asm-{id}.s") + + # We load the tests into the RAM region at 0x40000000, because loading into the + # firmware region does not work, because aarch32/virt.vadl already has firmware. + + content = f""" + .globl _start + .section .text + _start: + addiw t0,zero,1 # load address 0x8000000014 + slli t0,t0,0x1f + addi t0,t0,0x14 + jr t0 + nop # here is 0x8000000010 + {core} # here is 0x8000000014 + addiw t0,zero,1 # load address 0x8000000010 + slli t0,t0,0x1f + addi t0,t0,0x10 + # Jump to nop triggers simulation termination (stoptrigger plugin) + jr t0 + """ + with open(asm_out, "w") as f: + f.write(content) + return asm_out + + +def build_linker_script(id: str) -> Path: + linker_out = _tmp_file(id, f"linker-{id}.ld") + + content = """ + ENTRY(_start) + + PHDRS + { + text_seg PT_LOAD FLAGS(5); /* R + X = 4 + 1 */ + } + + SECTIONS + { + . = 0x80000000; + .text ALIGN(4) : { *(.text) } :text_seg + } + """ + with open(linker_out, "w") as f: + f.write(content) + return linker_out + +def objdump(objdump_bin: str, obj_file: Path, out_file: Path): + with out_file.open("wb") as f: + proc = subprocess.run( + [objdump_bin, "-D", str(obj_file)], + stdout=f, + ) + if proc.returncode != 0: + raise RuntimeError(proc.stderr.decode()) + +def _tmp_file(id: str, name: str) -> Path: + build_dir = f"/tmp/build-{id}/" + os.makedirs(build_dir, exist_ok=True) + return Path(f"{build_dir}/{name}") diff --git a/vadl-test/resources/cosim_scripts/riscv/main.py b/vadl-test/resources/cosim_scripts/riscv/main.py new file mode 100644 index 000000000..cee859084 --- /dev/null +++ b/vadl-test/resources/cosim_scripts/riscv/main.py @@ -0,0 +1,91 @@ +import argparse +from pathlib import Path +import subprocess +from concurrent.futures import ProcessPoolExecutor, as_completed +import yaml +import os +import compiler +import shutil +import sys +import threading + +def dump_debug_info(tid: str, results: Path, comp: dict): + debug_dir = results / f"{tid}_debug" + os.makedirs(debug_dir, exist_ok=True) + shutil.copy(comp["asm"], debug_dir) + shutil.copy(comp["lnscript"], debug_dir) + shutil.copy(comp["elf"], debug_dir) + shutil.copy(comp["objdump"], debug_dir) + +def run_cosim(elf: str, out: Path, cosim_config: Path): + e = os.environ.copy() + e["RUST_BACKTRACE"] = "1" + subprocess.run([ + "vadl-cosim-broker", + "--config", cosim_config, + "--test-exec", elf, + "--output-file", str(out) + ], + env=e, + ) + +def compile_test(t: dict, results: Path) -> dict: + tid = str(t["id"]) + debug = t["debug"] + comp = compiler.compile(tid, str(t["asm_core"]), debug) + if debug: + dump_debug_info(tid, results, comp) + return comp + +def run_test(t: dict, results: Path, cosim_config: Path): + tid = t["id"] + comp = compile_test(t, results) + try: + run_cosim(str(comp["elf"]), results / f"result-{tid}", cosim_config) + except Exception as e: + print(f"error for test=\"{tid}\": ", e) + +def report_progress(completed: int, total: int): + width = 30 + filled = width if total == 0 else int(width * completed / total) + bar = "#" * filled + "-" * (width - filled) + print(f"[{bar}] {completed}/{total}", file=sys.stderr, flush=True) + +def main(testsuite_path: Path): + config = yaml.safe_load(testsuite_path.read_text()) + results = Path(config.get("result_dir", "/work/results")) + results.mkdir(parents=True, exist_ok=True) + + num_cores = os.cpu_count() + if num_cores is None: + num_cores = 1 # safe fallback + + cosim_config = config.get("cosim_config", "/cosim_config/riscv_config.toml") + tests = config.get("tests", []) + total_tests = len(tests) + + if total_tests == 0: + report_progress(0, 0) + return + + with ProcessPoolExecutor(num_cores) as executor: + futures = [ + executor.submit(run_test, t, results, cosim_config) + for t in tests + ] + + report_progress(0, total_tests) + completed = 0 + lock = threading.Lock() + for future in as_completed(futures): + future.result() + with lock: + completed += 1 + if completed % 100 == 0: + report_progress(completed, total_tests) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("config") + args = parser.parse_args() + main(Path(args.config)) diff --git a/vadl/main/resources/templates/iss/target/gen-arch/cpu.c b/vadl/main/resources/templates/iss/target/gen-arch/cpu.c index e361b7bfe..4a4b59cf2 100644 --- a/vadl/main/resources/templates/iss/target/gen-arch/cpu.c +++ b/vadl/main/resources/templates/iss/target/gen-arch/cpu.c @@ -8,6 +8,7 @@ #include "trace.h" #include "tcg/debug-assert.h" #include "hw/qdev-properties.h" +#include "fpu/softfloat-helpers.h" #include "vadl-builtins.h" #include "vadl-iss-builtins.h" @@ -100,7 +101,7 @@ static void [(${gen_arch_lower})]_cpu_reset_hold(Object *obj, ResetType type) } [# th:each="access : ${base_clear_cpu_accessors}"] - [(${access.name})](env); [/] + [(${access.name})](env);[/] [(${reset})] } diff --git a/vadl/main/resources/templates/iss/target/gen-arch/cpu.h b/vadl/main/resources/templates/iss/target/gen-arch/cpu.h index 99034e623..798c6f47b 100644 --- a/vadl/main/resources/templates/iss/target/gen-arch/cpu.h +++ b/vadl/main/resources/templates/iss/target/gen-arch/cpu.h @@ -4,6 +4,7 @@ #include "cpu-qom.h" #include "exec/cpu-defs.h" #include "qemu/typedefs.h" +#include "qemu/cpu-float.h" #include "cpu-bits.h" #define CPU_RESOLVING_TYPE TYPE_[(${gen_arch_upper})]_CPU @@ -32,6 +33,10 @@ typedef struct CPUArchState { [# th:each="exc : ${exc_info.exceptions}"] [# th:each="p : ${exc.params}"] [(${p.c_type})] [(${p.name_in_cpu})]; [/][/] + + [# th:each="fmt : ${float_formats}"] + float_status fp_status_[(${fmt.name})];[/] + } CPU[(${gen_arch_upper})]State; diff --git a/vadl/main/resources/templates/iss/target/gen-arch/helper.c b/vadl/main/resources/templates/iss/target/gen-arch/helper.c index b0df5e67a..7eb8788aa 100644 --- a/vadl/main/resources/templates/iss/target/gen-arch/helper.c +++ b/vadl/main/resources/templates/iss/target/gen-arch/helper.c @@ -6,6 +6,7 @@ #include "qemu/log-for-trace.h" #include "qemu/qemu-print.h" #include "cpu-bits.h" +#include "fpu/softfloat.h" #include "vadl-builtins.h" #include "vadl-iss-builtins.h" @@ -33,3 +34,126 @@ void helper_unsupported(CPU[(${gen_arch_upper})]State *env) { [(${instr})] [/] +// float helpers + +void prep_float_status(CPU[(${gen_arch_upper})]State *env, float_status *s, uint32_t rm) { + uint16_t flags = 0xffff; + // un-set non sticky flags + [# th:each="reg : ${register_tensors}"][# th:each="flag : ${reg.non_sticky_fe_flags}"] + flags &= ~(1 << [(${flag.flag_idx})]);[/][/] + // un-set sticky flags that are not set + [# th:each="reg : ${register_tensors}"][# th:each="flag : ${reg.sticky_fe_flags}"] + flags &= ~((1 - ((env->[(${reg.name_lower})] >> [(${flag.idx})]) & 1)) << [(${flag.flag_idx})]);[/][/] + set_float_exception_flags(flags, s); + set_float_rounding_mode(rm, s); + // TODO: this disables nan-propagation. this will be configurable via the vadl spec at some point + set_default_nan_mode(1, s); +} + +void set_float_status(CPU[(${gen_arch_upper})]State *env, float_status *s) { + // DEV NOTE: for now we write directly to the flags register. This means that the helper is not pure and + // thus slower. In the future, we should optimize this. + uint16_t flags = get_float_exception_flags(s); + [# th:each="reg : ${register_tensors}"][# th:each="flag : ${reg.sticky_fe_flags}"] + env->[(${reg.name_lower})] |= (flags >> [(${flag.flag_idx})] & 1) << [(${flag.idx})];[/][/] + [# th:each="reg : ${register_tensors}"][# th:each="flag : ${reg.non_sticky_fe_flags}"] + env->[(${reg.name_lower})] &= ~((1 - (flags >> [(${flag.flag_idx})] & 1)) << [(${flag.idx})]);[/][/] +} + +#define FLOAT_HELPER_BODY(RET_TY, CALL, FMT, RM) \ + float_status *s = &env->fp_status_##FMT; \ + prep_float_status(env, s, RM); \ + RET_TY result = CALL; \ + set_float_status(env, s); \ + return result; + +#define FLOAT_HELPER_1(S, FMT, NAME, QEMU_FUN) \ + uint##S##_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint32_t rm) { \ + FLOAT_HELPER_BODY(uint##S##_t, float##S##_##QEMU_FUN(rs1, s), FMT, rm) \ + } + +#define FLOAT_HELPER_2(S, FMT, NAME, QEMU_FUN) \ + uint##S##_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint##S##_t rs2, uint32_t rm) { \ + FLOAT_HELPER_BODY(uint##S##_t, float##S##_##QEMU_FUN(rs1, rs2, s), FMT, rm) \ + } + +#define FLOAT_HELPER_MINMAX(S, FMT, NAME, QEMU_FUN) \ + uint##S##_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint##S##_t rs2) { \ + FLOAT_HELPER_BODY(uint##S##_t, float##S##_##QEMU_FUN(rs1, rs2, s), FMT, 0) \ + } + +#define FLOAT_HELPER_3(S, FMT, NAME, QEMU_FUN, FLAGS) \ + uint##S##_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint##S##_t rs2, uint##S##_t rs3, \ + uint32_t rm) { \ + FLOAT_HELPER_BODY(uint##S##_t, float##S##_##QEMU_FUN(rs1, rs2, rs3, FLAGS, s), FMT, rm) \ + } + +#define FLOAT_HELPER_F2I(S, FMT, INT_S, INT_FMT, NAME) \ + uint##INT_S##_t helper_##FMT##_##INT_S##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint32_t rm) { \ + FLOAT_HELPER_BODY(uint##INT_S##_t, float##S##_to_##INT_FMT(rs1, s), FMT, rm) \ + } + +#define FLOAT_HELPER_I2F(S, FMT, INT_S, INT_FMT, NAME) \ + uint##S##_t helper_##FMT##_##INT_S##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##INT_S##_t rs1, uint32_t rm) { \ + FLOAT_HELPER_BODY(uint##S##_t, INT_FMT##_to_##float##S(rs1, s), FMT, rm) \ + } + +#define FLOAT_HELPER_F2F(S, FMT, S2, FMT2, NAME) \ + uint##S2##_t helper_##FMT##_##FMT2##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint32_t rm) { \ + FLOAT_HELPER_BODY(uint##S2##_t, float##S##_to_##float##S2(rs1, s), FMT, rm) \ + } + +// TODO: optimize fe flags (maybe prep can be omitted; or flags set to avoid recomputation) +#define FLOAT_HELPER_CMP(S, FMT, NAME, QEMU_FUN) \ + uint64_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1, uint##S##_t rs2) { \ + FLOAT_HELPER_BODY(bool, float##S##_##QEMU_FUN(rs1, rs2, s), FMT, 0) \ + } + +#define FLOAT_HELPER_CLASSS(S, FMT, NAME, QEMU_FUN) \ + uint64_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1) { \ + FLOAT_HELPER_BODY(bool, float##S##_##QEMU_FUN(rs1, s), FMT, 0) \ + } + +#define FLOAT_HELPER_CLASS(S, FMT, NAME, QEMU_FUN) \ + uint64_t helper_##FMT##_##NAME(CPU[(${gen_arch_upper})]State *env, \ + uint##S##_t rs1) { \ + FLOAT_HELPER_BODY(bool, float##S##_##QEMU_FUN(rs1), FMT, 0) \ + } + +// TODO: risc-v specifies eq as quiet. other ISAs might want to configure this + +[# th:each="c : ${float_builtins.fsqrt}"]FLOAT_HELPER_1([(${c[0].bit_size})], [(${c[0].name})], fsqrt, sqrt) +[/][# th:each="c : ${float_builtins.fadd}"]FLOAT_HELPER_2([(${c[0].bit_size})], [(${c[0].name})], fadd, add) +[/][# th:each="c : ${float_builtins.fsub}"]FLOAT_HELPER_2([(${c[0].bit_size})], [(${c[0].name})], fsub, sub) +[/][# th:each="c : ${float_builtins.fmul}"]FLOAT_HELPER_2([(${c[0].bit_size})], [(${c[0].name})], fmul, mul) +[/][# th:each="c : ${float_builtins.fdiv}"]FLOAT_HELPER_2([(${c[0].bit_size})], [(${c[0].name})], fdiv, div) +[/][# th:each="c : ${float_builtins.fmadd}"]FLOAT_HELPER_3([(${c[0].bit_size})], [(${c[0].name})], fmadd, muladd, 0) +[/][# th:each="c : ${float_builtins.fmsub}"]FLOAT_HELPER_3([(${c[0].bit_size})], [(${c[0].name})], fmsub, muladd, float_muladd_negate_c) +[/][# th:each="c : ${float_builtins.fnmadd}"]FLOAT_HELPER_3([(${c[0].bit_size})], [(${c[0].name})], fnmadd, muladd, float_muladd_negate_c | float_muladd_negate_product) +[/][# th:each="c : ${float_builtins.fnmsub}"]FLOAT_HELPER_3([(${c[0].bit_size})], [(${c[0].name})], fnmsub, muladd, float_muladd_negate_product) +[/][# th:each="c : ${float_builtins.fmin}"]FLOAT_HELPER_MINMAX([(${c[0].bit_size})], [(${c[0].name})], fmin, minimum_number) +[/][# th:each="c : ${float_builtins.fmax}"]FLOAT_HELPER_MINMAX([(${c[0].bit_size})], [(${c[0].name})], fmax, maximum_number) +[/][# th:each="c : ${float_builtins.flt}"]FLOAT_HELPER_CMP([(${c[0].bit_size})], [(${c[0].name})], flt, lt) +[/][# th:each="c : ${float_builtins.fle}"]FLOAT_HELPER_CMP([(${c[0].bit_size})], [(${c[0].name})], fle, le) +[/][# th:each="c : ${float_builtins.feq}"]FLOAT_HELPER_CMP([(${c[0].bit_size})], [(${c[0].name})], feq, eq_quiet) +[/][# th:each="c : ${float_builtins.fcvt}"]FLOAT_HELPER_F2F([(${c[0].bit_size})], [(${c[0].name})], [(${c[1].bit_size})], [(${c[1].name})], fcvt) +[/][# th:each="c : ${float_builtins.fcvtfs}"]FLOAT_HELPER_F2I([(${c[0].bit_size})], [(${c[0].name})], [(${c[1]})], int[(${c[1]})], fcvtfs) +[/][# th:each="c : ${float_builtins.fcvtfu}"]FLOAT_HELPER_F2I([(${c[0].bit_size})], [(${c[0].name})], [(${c[1]})], uint[(${c[1]})], fcvtfu) +[/][# th:each="c : ${float_builtins.fcvtsf}"]FLOAT_HELPER_I2F([(${c[0].bit_size})], [(${c[0].name})], [(${c[1]})], int[(${c[1]})], fcvtsf) +[/][# th:each="c : ${float_builtins.fcvtuf}"]FLOAT_HELPER_I2F([(${c[0].bit_size})], [(${c[0].name})], [(${c[1]})], uint[(${c[1]})], fcvtuf) +[/][# th:each="c : ${float_builtins.fisinf}"]FLOAT_HELPER_CLASS([(${c[0].bit_size})], [(${c[0].name})], fisinf, is_infinity) +[/][# th:each="c : ${float_builtins.fiszero}"]FLOAT_HELPER_CLASS([(${c[0].bit_size})], [(${c[0].name})], fiszero, is_zero) +[/][# th:each="c : ${float_builtins.fisneg}"]FLOAT_HELPER_CLASS([(${c[0].bit_size})], [(${c[0].name})], fisneg, is_neg) +[/][# th:each="c : ${float_builtins.fisdenorm}"]FLOAT_HELPER_CLASS([(${c[0].bit_size})], [(${c[0].name})], fisdenorm, is_denormal) // TODO: less efficient than is_zero_or_denormal +[/][# th:each="c : ${float_builtins.fissnan}"]FLOAT_HELPER_CLASSS([(${c[0].bit_size})], [(${c[0].name})], fissnan, is_signaling_nan) +[/][# th:each="c : ${float_builtins.fisqnan}"]FLOAT_HELPER_CLASSS([(${c[0].bit_size})], [(${c[0].name})], fisqnan, is_quiet_nan) +[/] diff --git a/vadl/main/resources/templates/iss/target/gen-arch/helper.h b/vadl/main/resources/templates/iss/target/gen-arch/helper.h index 107003b8d..6dac6cd96 100644 --- a/vadl/main/resources/templates/iss/target/gen-arch/helper.h +++ b/vadl/main/resources/templates/iss/target/gen-arch/helper.h @@ -9,4 +9,33 @@ DEF_HELPER_1(unsupported, noreturn, env) // helper definitions for instructions [# th:each="instr : ${instr_helper_defs}"] [(${instr})] +[/] + +// float helpers + +[# th:each="c : ${float_builtins.fsqrt}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_fsqrt, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fadd}"]DEF_HELPER_FLAGS_4([(${c[0].name})]_fadd, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fsub}"]DEF_HELPER_FLAGS_4([(${c[0].name})]_fsub, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fmul}"]DEF_HELPER_FLAGS_4([(${c[0].name})]_fmul, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fdiv}"]DEF_HELPER_FLAGS_4([(${c[0].name})]_fdiv, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fmadd}"]DEF_HELPER_FLAGS_5([(${c[0].name})]_fmadd, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fmsub}"]DEF_HELPER_FLAGS_5([(${c[0].name})]_fmsub, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fnmadd}"]DEF_HELPER_FLAGS_5([(${c[0].name})]_fnmadd, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fnmsub}"]DEF_HELPER_FLAGS_5([(${c[0].name})]_fnmsub, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})], i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fmin}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_fmin, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fmax}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_fmax, 0, i[(${c[0].bit_size})], env, i[(${c[0].bit_size})], i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.flt}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_flt, 0, i64, env, i[(${c[0].bit_size})], i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fle}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_fle, 0, i64, env, i[(${c[0].bit_size})], i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.feq}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_feq, 0, i64, env, i[(${c[0].bit_size})], i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fcvt}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_[(${c[1].name})]_fcvt, 0, i[(${c[1].bit_size})], env, i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fcvtfs}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_[(${c[1]})]_fcvtfs, 0, i[(${c[1]})], env, i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fcvtfu}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_[(${c[1]})]_fcvtfu, 0, i[(${c[1]})], env, i[(${c[0].bit_size})], i32) +[/][# th:each="c : ${float_builtins.fcvtsf}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_[(${c[1]})]_fcvtsf, 0, i[(${c[0].bit_size})], env, i[(${c[1]})], i32) +[/][# th:each="c : ${float_builtins.fcvtuf}"]DEF_HELPER_FLAGS_3([(${c[0].name})]_[(${c[1]})]_fcvtuf, 0, i[(${c[0].bit_size})], env, i[(${c[1]})], i32) +[/][# th:each="c : ${float_builtins.fisinf}"]DEF_HELPER_FLAGS_2([(${c[0].name})]_fisinf, 0, i64, env, i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fiszero}"]DEF_HELPER_FLAGS_2([(${c[0].name})]_fiszero, 0, i64, env, i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fisneg}"]DEF_HELPER_FLAGS_2([(${c[0].name})]_fisneg, 0, i64, env, i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fisdenorm}"]DEF_HELPER_FLAGS_2([(${c[0].name})]_fisdenorm, 0, i64, env, i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fissnan}"]DEF_HELPER_FLAGS_2([(${c[0].name})]_fissnan, 0, i64, env, i[(${c[0].bit_size})]) +[/][# th:each="c : ${float_builtins.fisqnan}"]DEF_HELPER_FLAGS_2([(${c[0].name})]_fisqnan, 0, i64, env, i[(${c[0].bit_size})]) [/] \ No newline at end of file diff --git a/vadl/main/vadl/dump/InfoUtils.java b/vadl/main/vadl/dump/InfoUtils.java index dc6610443..ceb8b6396 100644 --- a/vadl/main/vadl/dump/InfoUtils.java +++ b/vadl/main/vadl/dump/InfoUtils.java @@ -16,6 +16,7 @@ package vadl.dump; +import com.google.common.html.HtmlEscapers; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -230,7 +231,7 @@ public static Info.Expandable createCodeBlockExpandable(String title, """
%s
             
- """.formatted(code) + """.formatted(HtmlEscapers.htmlEscaper().escape(code)) ); } diff --git a/vadl/main/vadl/iss/passes/common/IssFloatBuiltinCollectionPass.java b/vadl/main/vadl/iss/passes/common/IssFloatBuiltinCollectionPass.java new file mode 100644 index 000000000..2fdc17020 --- /dev/null +++ b/vadl/main/vadl/iss/passes/common/IssFloatBuiltinCollectionPass.java @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.iss.passes.common; + +import static vadl.viam.ViamError.ensure; + +import java.io.IOException; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import javax.annotation.CheckForNull; +import vadl.configuration.IssConfiguration; +import vadl.iss.passes.AbstractIssPass; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.types.BuiltInTable; +import vadl.utils.GraphUtils; +import vadl.utils.ViamUtils; +import vadl.viam.Constant; +import vadl.viam.Specification; +import vadl.viam.graph.dependency.BuiltInCall; +import vadl.viam.graph.dependency.ExpressionNode; + +/** + * Analyzes all VIAM behaviors and creates a list of all called float built-ins and + * for each a set of occurring configurations. The configuration is usually just the constant + * parameters the builtin has been called with (i.e. the parameters in the angle brackets {@code + * VADL::builtin::<...>()}). + * + *

For some builtins, additional information is added to the configuration (e.g. for + * {@link BuiltInTable#FCVTSF} and {@link BuiltInTable#FCVTUF}, the bit-size of the operand + * is added). + * + *

The configuration can later be used to only emit helper functions that are actually required. + */ +public class IssFloatBuiltinCollectionPass extends AbstractIssPass { + + public IssFloatBuiltinCollectionPass(IssConfiguration configuration) { + super(configuration); + } + + @Override + public PassName getName() { + return PassName.of("Float Built-in Collection"); + } + + /** + * Output of the pass. + * {@code floatBuiltIns} saves all occurring float built-ins and for each a set of all unique + * constant parameters. + */ + public record Output(Map>> floatBuiltIns) { + } + + @Override + public Object execute(PassResults passResults, Specification viam) throws IOException { + IdentityHashMap>> floatBuiltIns = + new IdentityHashMap<>(); + + ViamUtils.findAllBehaviors(viam).forEach(g -> g.getNodes(BuiltInCall.class) + .filter(call -> BuiltInTable.FLOAT_BUILT_INS.contains(call.builtIn())) + .forEach(call -> handleFloatBuiltin(call, floatBuiltIns))); + + return new Output(floatBuiltIns); + } + + private void handleFloatBuiltin(BuiltInCall call, + IdentityHashMap>> + floatBuiltIns) { + // TODO: here we should check if the constant parameters form a supported float built-in config + // e.g. VADL::fcvt::(...) is not valid, but this should be checked in the + // frontend. But we may not support 4-bit floats, so VADL::fadd::(...) is valid, + // but not supported. + + // TODO: currently this treats every float-type declaration as a unique config. but if two use + // the same encoding (and relevant settings), we could convert the format to the encoding + // and avoid unnecessary helper declarations. + + var builtin = call.builtIn(); + var config = call.constArgs(); + if (builtin == BuiltInTable.FCVTSF || builtin == BuiltInTable.FCVTUF) { + // Node: For these builtins, the size of the int arguments is inferred by the type-checker. + // But the helper emitter needs to know the size, so it gets added to the config. + var size = ((ExpressionNode) call.inputs().findFirst().get()).type().asDataType().bitWidth(); + ensure(size == 32 || size == 64, "Expected VADL::fcvt[su]f input size to be either 32 or 64"); + // the old config may be an immutable list + config = Stream.concat(config.stream(), Stream.of(GraphUtils.intU(size, 32))).toList(); + call.setConstArgs(config); + } + floatBuiltIns.computeIfAbsent(builtin, b -> new HashSet<>()).add(config); + } + +} diff --git a/vadl/main/vadl/iss/passes/common/IssNormalizationPass.java b/vadl/main/vadl/iss/passes/common/IssNormalizationPass.java index d06ce406a..3a2117bb5 100644 --- a/vadl/main/vadl/iss/passes/common/IssNormalizationPass.java +++ b/vadl/main/vadl/iss/passes/common/IssNormalizationPass.java @@ -810,6 +810,131 @@ public void handleCTO(BuiltInCall input) { throw graphError(input, "Normalization not yet implemented for this built-in"); } + @Override + public void handleFSQRT(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFADD(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFSUB(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFMUL(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFDIV(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFMADD(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFMSUB(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFNMADD(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFNMSUB(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFMIN(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFMAX(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFLT(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFLE(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFEQ(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFCVT(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFCVTFS(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFCVTFU(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFCVTSF(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFCVTUF(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFISINF(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFISZERO(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFISNEG(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFISDENORM(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFISSNAN(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + + @Override + public void handleFISQNAN(BuiltInCall input) { + // do nothing (float ops are done by helper function, which handle everything) + } + @Override public void handleConcat(BuiltInCall input) { // do nothing (result is already fine) diff --git a/vadl/main/vadl/iss/passes/extensions/RegInfo.java b/vadl/main/vadl/iss/passes/extensions/RegInfo.java index cf2e9582b..2e684dd03 100644 --- a/vadl/main/vadl/iss/passes/extensions/RegInfo.java +++ b/vadl/main/vadl/iss/passes/extensions/RegInfo.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.stream.IntStream; import javax.annotation.Nullable; import vadl.configuration.IssConfiguration; @@ -38,8 +39,10 @@ import vadl.viam.Constant; import vadl.viam.Definition; import vadl.viam.DefinitionExtension; +import vadl.viam.FloatExceptionFlag; import vadl.viam.RegisterResource; import vadl.viam.RegisterTensor; +import vadl.viam.annotations.FloatFlagAnnotation; import vadl.viam.annotations.TbStateRegisterAnnotation; import vadl.viam.graph.Node; import vadl.viam.graph.dependency.ConstantNode; @@ -186,6 +189,18 @@ private Map slicePart(int lsb, int msb) { ); } + private List> feFlags( + Function> flags) { + if (!reg().hasAnnotation(FloatFlagAnnotation.class)) { + return List.of(); + } + var ann = reg().expectAnnotation(FloatFlagAnnotation.class); + return flags.apply(ann).entrySet().stream().map(e -> Map.of( + "idx", Integer.toString(e.getKey()), + "flag_idx", Integer.toString(e.getValue().qemuFlagOffset) + )).toList(); + } + /** * Returns the execution class used for backend selection. */ @@ -291,6 +306,8 @@ public Map renderObj() { renderObj.put("is_gvec_capable", isGvecCapable()); renderObj.put("is_tb_state", isTbState()); renderObj.put("tb_state_parts", tbStateParts()); + renderObj.put("sticky_fe_flags", feFlags(FloatFlagAnnotation::stickyFlags)); + renderObj.put("non_sticky_fe_flags", feFlags(FloatFlagAnnotation::nonStickyFlags)); renderObj.put("exec_class", execClass().name()); renderObj.put("constraints", renderConstraints(dims)); renderObj.put("getter_params", renderParamsComma); diff --git a/vadl/main/vadl/iss/passes/tcg/lowering/TcgOpLoweringPass.java b/vadl/main/vadl/iss/passes/tcg/lowering/TcgOpLoweringPass.java index 6715c2da6..7c86179fd 100644 --- a/vadl/main/vadl/iss/passes/tcg/lowering/TcgOpLoweringPass.java +++ b/vadl/main/vadl/iss/passes/tcg/lowering/TcgOpLoweringPass.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import javax.annotation.Nullable; import vadl.configuration.IssConfiguration; import vadl.iss.passes.AbstractIssPass; @@ -64,6 +66,7 @@ import vadl.iss.passes.tcg.lowering.nodes.TcgGenException; import vadl.iss.passes.tcg.lowering.nodes.TcgGottoTb; import vadl.iss.passes.tcg.lowering.nodes.TcgGvecOpNode; +import vadl.iss.passes.tcg.lowering.nodes.TcgHelperCall; import vadl.iss.passes.tcg.lowering.nodes.TcgLoadMemory; import vadl.iss.passes.tcg.lowering.nodes.TcgLookupAndGotoPtr; import vadl.iss.passes.tcg.lowering.nodes.TcgMovCondNode; @@ -94,6 +97,7 @@ import vadl.utils.GraphUtils; import vadl.viam.Constant; import vadl.viam.ExceptionDef; +import vadl.viam.FloatFormat; import vadl.viam.Specification; import vadl.viam.graph.Graph; import vadl.viam.graph.NodeList; @@ -641,7 +645,7 @@ void handle(ReadRegTensorNode toHandle) { /** * Handles the {@link IssLoadNode}, which was created from a {@link ReadMemNode} - * in the {@link vadl.iss.passes.IssMemoryAccessTransformationPass}. + * in the {@link vadl.iss.passes.common.IssMemoryAccessTransformationPass}. */ @Handler void handle(IssLoadNode toHandle) { @@ -694,7 +698,7 @@ void handle(IssRegBitfieldWriteNode toHandle) { /** * Handles the {@link IssStoreNode}, which was created from a {@link WriteMemNode} - * in the {@link vadl.iss.passes.IssMemoryAccessTransformationPass}. + * in the {@link vadl.iss.passes.common.IssMemoryAccessTransformationPass}. */ @Handler void handle(IssStoreNode toHandle) { @@ -949,7 +953,7 @@ void handle(InstructionWidthNode toHandle) { /** * Handles the {@link ReadMemNode}. Should be replaced by a {@link IssLoadNode} in the - * {@link vadl.iss.passes.IssMemoryAccessTransformationPass}. + * {@link vadl.iss.passes.common.IssMemoryAccessTransformationPass}. */ @Handler void handle(ReadMemNode toHandle) { @@ -958,7 +962,7 @@ void handle(ReadMemNode toHandle) { /** * Handles the {@link WriteMemNode}. Should be replaced by a {@link IssStoreNode} in the - * {@link vadl.iss.passes.IssMemoryAccessTransformationPass}. + * {@link vadl.iss.passes.common.IssMemoryAccessTransformationPass}. */ @Handler void handle(WriteMemNode toHandle) { @@ -1026,7 +1030,7 @@ void handle(LabelNode toHandle) { } /** - * The {@link IssGhostCastNode} is removed in the {@link vadl.iss.passes.IssTcgSchedulingPass} + * The {@link IssGhostCastNode} is removed in the {@link vadl.iss.passes.tcg.IssTcgSchedulingPass} * if it had been scheduled. * So it cannot occur during op lowering. */ @@ -1253,6 +1257,43 @@ class BuiltInTcgLoweringExecutor { ); }) + //// Float Arithmetic //// + + .set(BuiltInTable.FSQRT, (ctx) -> floatHelperCall(ctx, 2, "fsqrt")) + .set(BuiltInTable.FADD, (ctx) -> floatHelperCall(ctx, 3, "fadd")) + .set(BuiltInTable.FSUB, (ctx) -> floatHelperCall(ctx, 3, "fsub")) + .set(BuiltInTable.FMUL, (ctx) -> floatHelperCall(ctx, 3, "fmul")) + .set(BuiltInTable.FDIV, (ctx) -> floatHelperCall(ctx, 3, "fdiv")) + .set(BuiltInTable.FMADD, (ctx) -> floatHelperCall(ctx, 4, "fmadd")) + .set(BuiltInTable.FMSUB, (ctx) -> floatHelperCall(ctx, 4, "fmsub")) + .set(BuiltInTable.FNMADD, (ctx) -> floatHelperCall(ctx, 4, "fnmadd")) + .set(BuiltInTable.FNMSUB, (ctx) -> floatHelperCall(ctx, 4, "fnmsub")) + .set(BuiltInTable.FMIN, (ctx) -> floatHelperCall(ctx, 2, "fmin")) + .set(BuiltInTable.FMAX, (ctx) -> floatHelperCall(ctx, 2, "fmax")) + + //// Float Comparison //// + + .set(BuiltInTable.FLT, (ctx) -> floatHelperCall(ctx, 2, "flt")) + .set(BuiltInTable.FLE, (ctx) -> floatHelperCall(ctx, 2, "fle")) + .set(BuiltInTable.FEQ, (ctx) -> floatHelperCall(ctx, 2, "feq")) + + //// Float to Int Conversion //// + + .set(BuiltInTable.FCVT, (ctx) -> floatHelperCall(ctx, 2, "fcvt")) + .set(BuiltInTable.FCVTFS, (ctx) -> floatHelperCall(ctx, 2, "fcvtfs")) + .set(BuiltInTable.FCVTFU, (ctx) -> floatHelperCall(ctx, 2, "fcvtfu")) + .set(BuiltInTable.FCVTSF, (ctx) -> floatHelperCall(ctx, 2, "fcvtsf")) + .set(BuiltInTable.FCVTUF, (ctx) -> floatHelperCall(ctx, 2, "fcvtuf")) + + //// Float Classification //// + + .set(BuiltInTable.FISINF, (ctx) -> floatHelperCall(ctx, 1, "fisinf")) + .set(BuiltInTable.FISZERO, (ctx) -> floatHelperCall(ctx, 1, "fiszero")) + .set(BuiltInTable.FISNEG, (ctx) -> floatHelperCall(ctx, 1, "fisneg")) + .set(BuiltInTable.FISDENORM, (ctx) -> floatHelperCall(ctx, 1, "fisdenorm")) + .set(BuiltInTable.FISSNAN, (ctx) -> floatHelperCall(ctx, 1, "fissnan")) + .set(BuiltInTable.FISQNAN, (ctx) -> floatHelperCall(ctx, 1, "fisqnan")) + .build(); } @@ -1285,6 +1326,25 @@ private static BuiltInResult out(TcgNode... nodes) { return new BuiltInResult(List.of(nodes)); } + /** + * Helper method to create a {@link BuiltInResult} from a helper call for a float built-in. + * + * @param ctx The built-in lowering context. + * @param argc The number of arguments the float built-in takes. + * @param name The name of the helper call to generate. + * @return A {@link BuiltInResult} containing the helper call. + */ + private static BuiltInResult floatHelperCall(BuiltInTcgLoweringExecutor.Context ctx, int argc, + String name) { + return out(new TcgHelperCall( + ctx.dest(), new NodeList<>(IntStream.range(0, argc).mapToObj(ctx::src).toList()), true, + Stream.concat( + ctx.floatFormats().stream().map(FloatFormat::nameLower), + ctx.constIntArgs().stream().map(Object::toString) + ).collect(Collectors.joining("_")) + "_" + name + )); + } + /** * Context for lowering a built-in function call. */ @@ -1315,6 +1375,31 @@ private TcgVRefNode src(int index) { return assignments.singleDestOf(arg); } + /** + * Retrieves the float formats of a float built-in call. + * + * @return A list of the float formats. + */ + private List floatFormats() { + return call.constArgs().stream() + .filter(Constant.FloatType.class::isInstance) + .map(c -> requireNonNull((Constant.FloatType) c).format()) + .toList(); + } + + /** + * Retrieves the constant arguments that are integers. I.e. all {@link Constant.Value} + * passed via the angle brackets `VADL::builtin<...>()`. + * + * @return A list of the constant integer arguments. + */ + private List constIntArgs() { + return call.constArgs().stream() + .filter(Constant.Value.class::isInstance) + .map(c -> requireNonNull((Constant.Value) c).intValue()) + .toList(); + } + /** * Returns a temporary tcgV ref node for the given local index id. * If a temp for {@code i} does not exist yet, it creates one and returns it. diff --git a/vadl/main/vadl/iss/template/IssTemplateRenderingPass.java b/vadl/main/vadl/iss/template/IssTemplateRenderingPass.java index c848ac9d5..0b3e6546e 100644 --- a/vadl/main/vadl/iss/template/IssTemplateRenderingPass.java +++ b/vadl/main/vadl/iss/template/IssTemplateRenderingPass.java @@ -16,26 +16,32 @@ package vadl.iss.template; +import static java.util.Objects.requireNonNull; import static vadl.error.Diagnostic.ensure; import static vadl.error.Diagnostic.error; import static vadl.iss.template.IssRenderUtils.mapRegTensors; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.io.FilenameUtils; import vadl.configuration.IssConfiguration; import vadl.cppCodeGen.formatting.CodeFormatter; import vadl.iss.IssUtils; import vadl.iss.codegen.QemuClangFormatter; +import vadl.iss.passes.common.IssFloatBuiltinCollectionPass; import vadl.iss.passes.extensions.ExceptionInfo; import vadl.iss.passes.extensions.MemoryRegionInfo; import vadl.iss.passes.extensions.RegInfo; import vadl.pass.PassName; import vadl.pass.PassResults; import vadl.template.AbstractTemplateRenderingPass; +import vadl.types.BuiltInTable; +import vadl.viam.Constant; import vadl.viam.Endianness; import vadl.viam.Memory; import vadl.viam.Specification; @@ -130,6 +136,11 @@ protected Map createVariables(PassResults passResults, vars.put("gen_machine_upper", configuration().machineName().toUpperCase()); vars.put("gen_machine_lower", configuration().machineName().toLowerCase()); vars.put("register_tensors", mapRegTensors(specification)); + vars.put("float_builtins", getFloatBuiltins(passResults.lastResultOf( + IssFloatBuiltinCollectionPass.class, + IssFloatBuiltinCollectionPass.Output.class + ))); + vars.put("float_formats", getFloatFormats(specification)); vars.put("pc_info", getPcInfo(specification)); vars.put("target_size", configuration().targetSize().width); vars.put("mem_regions", memRegions(specification)); @@ -148,6 +159,37 @@ private ExceptionInfo getExceptionInfo(Specification viam) { return viam.processor().get().isa().expectExtension(ExceptionInfo.class); } + private Map>> getFloatBuiltins( + IssFloatBuiltinCollectionPass.Output config) { + var conf = config.floatBuiltIns().entrySet().stream().collect(Collectors.toMap( + e -> builtInName(e.getKey()), + e -> getFloatBuiltinConfigs(e.getValue()) + )); + return conf; + } + + private List> getFloatBuiltinConfigs(Set> configs) { + return configs.stream().map(config -> config.stream().map(c -> switch (c) { + case Constant.FloatType ft -> Map.of( + "bit_size", ft.size(), + "name", requireNonNull(ft.format()).nameLower() + ); + case Constant.Value v -> Integer.toString(v.intValue()); + default -> throw new IllegalStateException(); + }).toList()).toList(); + } + + private List> getFloatFormats(Specification viam) { + return viam.isa().get().ownFloatFormats().stream().map(fmt -> Map.of( + "name", fmt.nameLower(), + "bit_size", Integer.toString(requireNonNull(fmt.encoding()).size) + )).toList(); + } + + private String builtInName(BuiltInTable.BuiltIn builtin) { + return builtin.name().substring(builtin.name().lastIndexOf(':') + 1); + } + private Map getPcInfo(Specification viam) { var pc = viam.processor().get().isa().pc(); if (pc == null) { diff --git a/vadl/main/vadl/pass/order/IssPassOrder.java b/vadl/main/vadl/pass/order/IssPassOrder.java index 4ddf6298c..d847c2d1f 100644 --- a/vadl/main/vadl/pass/order/IssPassOrder.java +++ b/vadl/main/vadl/pass/order/IssPassOrder.java @@ -30,6 +30,7 @@ import vadl.iss.passes.common.IssCommonExprSavePass; import vadl.iss.passes.common.IssConfigurationPass; import vadl.iss.passes.common.IssExtractOptimizationPass; +import vadl.iss.passes.common.IssFloatBuiltinCollectionPass; import vadl.iss.passes.common.IssGdbInfoExtractionPass; import vadl.iss.passes.common.IssInfoRetrievalPass; import vadl.iss.passes.common.IssLoopUnrollPass; @@ -140,7 +141,8 @@ private static void addCommonPasses(PassOrder order, IssConfiguration config) { .add(new SideEffectSchedulingPass(config)) .add(new IssSafeResourceReadPass(config)) .add(new IssCommonExprSavePass(config)) - .add(new IssScheduleIndirectJumpsPass(config)); + .add(new IssScheduleIndirectJumpsPass(config)) + .add(new IssFloatBuiltinCollectionPass(config)); } private static void addScalarTcgPasses(PassOrder order, IssConfiguration config) { diff --git a/vadl/main/vadl/types/BuiltInTable.java b/vadl/main/vadl/types/BuiltInTable.java index 34f6e41e5..74077f952 100644 --- a/vadl/main/vadl/types/BuiltInTable.java +++ b/vadl/main/vadl/types/BuiltInTable.java @@ -16,8 +16,11 @@ package vadl.types; +import static java.util.Objects.requireNonNull; import static org.slf4j.LoggerFactory.getLogger; +import static vadl.types.Type.bits; import static vadl.types.Type.constructDataType; +import static vadl.types.Type.signedInt; import com.google.errorprone.annotations.FormatMethod; import java.math.BigInteger; @@ -1039,6 +1042,330 @@ public class BuiltInTable { .build(); + ///// FLOAT ARITHMETIC ////// + + /** + * {@code function fsqrt< t : FloatType >( a : Bits, rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FSQRT = + func("VADL::fsqrt", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(1) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fadd< t : FloatType >( a : Bits, b : Bits, rm : Bits<3> ) -> + * Bits } + */ + public static final BuiltIn FADD = + func("VADL::fadd", + Type.relation(List.of(BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(2) + .returnsFirstBitWidth(BitsType.class) + .returnsFirstFloatType() + .build(); + + // TODO: I think we want a status variant for float built-ins, similar to other built-ins. + // But how to handle these for ISS? + // When doing 64- or even 128-bit ops, returning the status from helpers is afaik not + // possible. So we'd need to store them in a reg or an env variable and load them later. + // But we have to make sure that other float ops are not scheduled in between! + /** + * {@code function fadds< t : FloatType >( a : Bits, b : Bits, rm : Bits<3> ) -> + * ( Bits, FloatStatus ) } + */ + public static final BuiltIn FADDS = + func("VADL::fadds", + Type.relation(List.of(BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), StructType.class)) + .takesFloatArgsAndFrm(2) + // TODO: returns function + .returnsFirstFloatType() + .build(); + + /** + * {@code function fsub< t : FloatType >( a : Bits, b : Bits, rm : Bits<3> ) -> + * Bits } + */ + public static final BuiltIn FSUB = + func("VADL::fsub", + Type.relation(List.of(BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(2) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fmul< t : FloatType >( a : Bits, b : Bits, rm : Bits<3> ) -> + * Bits } + */ + public static final BuiltIn FMUL = + func("VADL::fmul", + Type.relation(List.of(BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(2) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fdiv< t : FloatType >( a : Bits, b : Bits, rm : Bits<3> ) -> + * Bits } + */ + public static final BuiltIn FDIV = + func("VADL::fdiv", + Type.relation(List.of(BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(2) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fmadd< t : FloatType >( a : Bits, b : Bits, c : Bits, + * rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FMADD = + func("VADL::fmadd", + Type.relation(List.of( + BitsType.class, BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(3) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fmsub< t : FloatType >( a : Bits, b : Bits, c : Bits, + * rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FMSUB = + func("VADL::fmsub", + Type.relation(List.of( + BitsType.class, BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(3) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fnmadd< t : FloatType >( a : Bits, b : Bits, c : Bits, + * rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FNMADD = + func("VADL::fnmadd", + Type.relation(List.of( + BitsType.class, BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(3) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fnmsub< t : FloatType >( a : Bits, b : Bits, c : Bits, + * rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FNMSUB = + func("VADL::fnmsub", + Type.relation(List.of( + BitsType.class, BitsType.class, BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(3) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fmin< t : FloatType >( a : Bits, b : Bits ) -> Bits } + */ + public static final BuiltIn FMIN = + func("VADL::fmin", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgs(2) + .returnsFirstFloatType() + .build(); + + /** + * {@code function fmax< t : FloatType >( a : Bits, b : Bits ) -> Bits } + */ + public static final BuiltIn FMAX = + func("VADL::fmax", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFloatArgs(2) + .returnsFirstFloatType() + .build(); + + ///// FLOAT COMPARISON ////// + + /** + * {@code function flt< t : FloatType >( a : Bits, b : Bits ) -> Bool } + */ + public static final BuiltIn FLT = + func("VADL::flt", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(2) + .returns(Type.bool()) + .build(); + + /** + * {@code function fle< t : FloatType >( a : Bits, b : Bits ) -> Bool } + */ + public static final BuiltIn FLE = + func("VADL::fle", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(2) + .returns(Type.bool()) + .build(); + + /** + * {@code function feq< t : FloatType >( a : Bits, b : Bits ) -> Bool } + */ + public static final BuiltIn FEQ = + func("VADL::feq", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(2) + .returns(Type.bool()) + .build(); + + ///// FLOAT CONVERSION ////// + + // TODO: currently the system allows conversion between all float types. We need to restrict + // that to a set of supported conversions (currently ieee 32->64 and 64->32). Should this + // happen during type-checking or in the backend? Different backends might be at different + // stages of implementation and may not support everything other backends do... + + /** + * Float to float conversion. + * {@code function fcvt< t : FloatType, u : FloatType >( a : Bits, rm : Bits<3> ) -> + * Bits } + */ + public static final BuiltIn FCVT = + func("VADL::fcvt", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class, FloatType.class), BitsType.class)) + .takesFloatArgsAndFrm(1) + .returnsSecondFloatType() + .build(); + + // TODO: Integer results have to be truncated by the IssNormalizationPass + + /** + * Float to signed int conversion. + * {@code function fcvtfs< t : FloatType, s : UInt >( a : Bits, rm : Bits<3> ) -> + * SInt } + */ + public static final BuiltIn FCVTFS = + func("VADL::fcvtfs", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class, UIntType.class), SIntType.class)) + .takesFloatArgsAndFrm(1) + .returnsFromSecondConstSize(SIntType.class) + .build(); + + /** + * Float to unsigned int conversion. + * {@code function fcvtfu< t : FloatType, s : UInt >( a : Bits, rm : Bits<3> ) -> + * UInt } + */ + public static final BuiltIn FCVTFU = + func("VADL::fcvtfu", + Type.relation(List.of(BitsType.class, BitsType.class), + List.of(FloatType.class, UIntType.class), UIntType.class)) + .takesFloatArgsAndFrm(1) + .returnsFromSecondConstSize(UIntType.class) + .build(); + + /** + * Signed int to float conversion. + * {@code function fcvtsf< t : FloatType >( a : SInt, rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FCVTSF = + func("VADL::fcvtsf", + Type.relation(List.of(SIntType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFrm(1) + .returnsFirstFloatType() + .build(); + + /** + * Unsigned int to float conversion. + * {@code function fcvtuf< t : FloatType >( a : UInt, rm : Bits<3> ) -> Bits } + */ + public static final BuiltIn FCVTUF = + func("VADL::fcvtuf", + Type.relation(List.of(UIntType.class, BitsType.class), + List.of(FloatType.class), BitsType.class)) + .takesFrm(1) + .returnsFirstFloatType() + .build(); + + ///// FLOAT CLASSIFICATION ////// + + /** + * {@code function fisinf< t : FloatType >( a : Bits ) -> Bool } + */ + public static final BuiltIn FISINF = + func("VADL::fisinf", + Type.relation(List.of(BitsType.class), List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(1) + .returns(Type.bool()) + .build(); + + /** + * {@code function fiszero< t : FloatType >( a : Bits ) -> Bool } + */ + public static final BuiltIn FISZERO = + func("VADL::fiszero", + Type.relation(List.of(BitsType.class), List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(1) + .returns(Type.bool()) + .build(); + + /** + * {@code function fisneg< t : FloatType >( a : Bits ) -> Bool } + */ + public static final BuiltIn FISNEG = + func("VADL::fisneg", + Type.relation(List.of(BitsType.class), List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(1) + .returns(Type.bool()) + .build(); + + /** + * {@code function fisdenorm< t : FloatType >( a : Bits ) -> Bool } + */ + public static final BuiltIn FISDENORM = + func("VADL::fisdenorm", + Type.relation(List.of(BitsType.class), List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(1) + .returns(Type.bool()) + .build(); + + /** + * {@code function fissnan< t : FloatType >( a : Bits ) -> Bool } + */ + public static final BuiltIn FISSNAN = + func("VADL::fissnan", + Type.relation(List.of(BitsType.class), List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(1) + .returns(Type.bool()) + .build(); + + /** + * {@code function fisqnan< t : FloatType >( a : Bits ) -> Bool } + */ + public static final BuiltIn FISQNAN = + func("VADL::fisqnan", + Type.relation(List.of(BitsType.class), List.of(FloatType.class), BoolType.class)) + .takesFloatArgs(1) + .returns(Type.bool()) + .build(); + + ///// FUNCTIONS ////// /** @@ -1409,6 +1736,43 @@ private static BuiltIn instr(String name) { CTO ); + public static final List FLOAT_ARITHMETIC_BUILT_INS = List.of( + FSQRT, + FADD, + FSUB, + FMUL, + FDIV, + FMADD, + FMSUB, + FNMADD, + FNMSUB, + FMIN, + FMAX + ); + + public static final List FLOAT_COMPARISON_BUILT_INS = List.of( + FLT, + FLE, + FEQ + ); + + public static final List FLOAT_CONVERSION_BUILT_INS = List.of( + FCVT, + FCVTFS, + FCVTFU, + FCVTSF, + FCVTUF + ); + + public static final List FLOAT_CLASSIFICATION_BUILT_INS = List.of( + FISINF, + FISZERO, + FISNEG, + FISDENORM, + FISSNAN, + FISQNAN + ); + public static final List FUNCTION_BUILT_INS = List.of( MNEMONIC, CONCATENATE_STRINGS, @@ -1439,12 +1803,20 @@ private static BuiltIn instr(String name) { INSTRUCTION_WRITE ); + public static final List FLOAT_BUILT_INS = Stream.of( + FLOAT_ARITHMETIC_BUILT_INS.stream(), + FLOAT_COMPARISON_BUILT_INS.stream(), + FLOAT_CONVERSION_BUILT_INS.stream(), + FLOAT_CLASSIFICATION_BUILT_INS.stream() + ).flatMap(s -> s).toList(); + public static final List BUILT_INS = Stream.of( ARITHMETIC_BUILT_INS.stream(), LOGICAL_BUILT_INS.stream(), COMPARISON_BUILT_INS.stream(), SHIFTING_BUILT_INS.stream(), BITWISE_COUNTING_BUILT_INS.stream(), + FLOAT_BUILT_INS.stream(), FUNCTION_BUILT_INS.stream(), ASM_PARSER_BUILT_INS_LIST.stream(), MICRO_ARCHITECTURE_BUILT_INS.stream() @@ -1582,7 +1954,7 @@ public boolean hasSameBitWidth() { return hasSameBitWidth; } - public Optional compute(List args) { + public Optional compute(List constArgs, List args) { logger.atWarn().log("Computation of constants for built-in {} is not implemented", this); return Optional.empty(); } @@ -1600,10 +1972,11 @@ public boolean isStatusBuiltin() { * in the built-in definition, and if not, if it is possible to produce a type of the * parameter type class that can be trivially cast from the argument type. * - * @param argTypes of the concrete arguments + * @param constArgs the concrete constant arguments + * @param argTypes of the concrete arguments * @return true if argument types are correct, false otherwise */ - public boolean takes(List argTypes) { + public boolean takes(List constArgs, List argTypes) { if (this.signature().hasVarArgs()) { if (argTypes.size() < argTypeClasses().size()) { @@ -1629,7 +2002,13 @@ public boolean takes(List argTypes) { return false; } - return argsCompatible(argTypes, argTypeClasses()); + if (constArgTypeClasses().size() != constArgs.size()) { + // if the number of constant arguments is not correct, this can't be true + return false; + } + + return argsCompatible(argTypes, argTypeClasses()) + && argsCompatible(constArgs.stream().map(Constant::type).toList(), constArgTypeClasses()); } private boolean argsCompatible(List argTypes, @@ -1666,15 +2045,29 @@ private boolean argsCompatible(List argTypes, return true; } + /** + * Returns the result type of the built-in when called with the given argument types and + * constant arguments. + * It assumes that the argument types are valid, such as a call to + * {@link #takes(List, List)} would return true. + * + * @param constArgs concrete constant arguments. + * @param argTypes concrete types of argument for call. + * @return the concrete type that is return by this built-in + */ + public abstract Type returns(List constArgs, List argTypes); + /** * Returns the result type of the built-in when called with the given argument types. * It assumes that the argument types are valid, such as a call to - * {@link #takes(List)} would return true. + * {@link #takes(List, List)} would return true. * * @param argTypes concrete types of argument for call. * @return the concrete type that is return by this built-in */ - public abstract Type returns(List argTypes); + public Type returns(List argTypes) { + return returns(List.of(), argTypes); + } public final boolean matches(RelationType type) { @@ -1686,6 +2079,10 @@ public String toString() { return name + signature; } + public List> constArgTypeClasses() { + return signature.constArgTypeClass(); + } + public List> argTypeClasses() { return signature.argTypeClasses(); } @@ -1746,11 +2143,11 @@ public static class BuiltInBuilder { private RelationType signature; private BuiltIn.Kind kind; @Nullable - private Function, Optional> computeFunction; + private BiFunction, List, Optional> computeFunction; @Nullable - private Function, Boolean> takesFunction; + private BiFunction, List, Boolean> takesFunction; @Nullable - private Function, Type> returnsFunction; + private BiFunction, List, Type> returnsFunction; private boolean hasSameBitWidth = false; BuiltInBuilder(String name, @Nullable String operator, RelationType signature, @@ -1763,52 +2160,74 @@ public static class BuiltInBuilder { public BuiltInBuilder computeUnary( Function computeFunction) { - this.computeFunction = - (args) -> Optional.of(computeFunction.apply((T) args.get(0))); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply((T) args.get(0))); return this; } public BuiltInBuilder compute( Function, R> computeFunction) { - this.computeFunction = - (args) -> Optional.of(computeFunction.apply(args.stream().map(a -> (T) a).toList())); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply(args.stream().map(a -> (T) a).toList())); return this; } public BuiltInBuilder compute( BiFunction computeFunction) { - this.computeFunction = - (args) -> Optional.of(computeFunction.apply((A) args.get(0), (B) args.get(1))); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply((A) args.get(0), (B) args.get(1))); return this; } @SuppressWarnings("LineLength") public BuiltInBuilder compute( TriFunction computeFunction) { - this.computeFunction = - (args) -> Optional.of( - computeFunction.apply((A) args.get(0), (B) args.get(1), (C) args.get(2))); + this.computeFunction = (constArgs, args) -> + Optional.of(computeFunction.apply((A) args.get(0), (B) args.get(1), (C) args.get(2))); return this; } public BuiltInBuilder noCompute() { - this.computeFunction = (args) -> Optional.empty(); + this.computeFunction = (constArgs, args) -> Optional.empty(); return this; } - public BuiltInBuilder takesData(Function, Boolean> takesFunction) { - this.takesFunction = (args) -> args.stream().allMatch(DataType.class::isInstance) - && takesFunction.apply(args.stream().map(DataType.class::cast).toList()); + public BuiltInBuilder takesData( + BiFunction, List, Boolean> takesFunction) { + this.takesFunction = (constArgs, args) -> args.stream().allMatch(DataType.class::isInstance) + && takesFunction.apply(constArgs, args.stream().map(DataType.class::cast).toList()); return this; } + public BuiltInBuilder takesData(Function, Boolean> takesFunction) { + return takesData((constArgs, args) -> takesFunction.apply(args)); + } + + public BuiltInBuilder takesDataFromFirstFloatSize( + BiFunction, Boolean> takesFunction) { + return takesData((constArgs, args) -> { + ensure(!constArgs.isEmpty(), "Expected at least one constant argument, but found none."); + return takesFunction.apply(floatTypeSize(constArgs.get(0)), args); + }); + } + + public BuiltInBuilder takesDataFromFirstTwoFloatSizes( + TriFunction, Boolean> takesFunction) { + return takesData((constArgs, args) -> { + ensure(constArgs.size() >= 2, "Expected at least two constant argument, but found %d.", + constArgs.size()); + return takesFunction.apply( + floatTypeSize(constArgs.get(0)), floatTypeSize(constArgs.get(1)), args); + }); + } + /** - * This will use the default implementation of {@link BuiltIn#takes(List)}. + * This will use the default implementation of {@link BuiltIn#takes(List, List)}. * So it will compare type classes and checks if an argument is trivially cast * to a parameter's type class. */ public BuiltInBuilder takesDefault() { - this.takesFunction = (args) -> true; + this.takesFunction = (constArgs, args) -> true; return this; } @@ -1826,12 +2245,32 @@ public BuiltInBuilder takesFirstTwoWithSameBitWidths() { return this; } + public BuiltInBuilder takesFloatArgs(int floatArgCount) { + return takesDataFromFirstFloatSize((size, args) -> args.size() == floatArgCount + && args.stream().limit(floatArgCount).allMatch(a -> a.bitWidth() == size)); + } + + public BuiltInBuilder takesFloatArgsAndFrm(int floatArgCount) { + return takesDataFromFirstFloatSize((size, args) -> args.size() == floatArgCount + 1 + && args.stream().limit(floatArgCount).allMatch(a -> a.bitWidth() == size) + && args.get(floatArgCount).bitWidth() == 3); + } + + public BuiltInBuilder takesFrm(int frmArgIdx) { + return takesData(args -> args.size() == frmArgIdx + 1 && args.get(frmArgIdx).bitWidth() == 3); + } + public BuiltInBuilder returns(Type returnType) { returns((args) -> returnType); return this; } public BuiltInBuilder returns(Function, Type> returnsFunction) { + returns((constArgs, args) -> returnsFunction.apply(args)); + return this; + } + + public BuiltInBuilder returns(BiFunction, List, Type> returnsFunction) { this.returnsFunction = returnsFunction; return this; } @@ -1840,17 +2279,25 @@ public BuiltInBuilder returnsFirstBitWidth(Class returnT returnsFromFirstAsDataType( (firstDataType) -> { var result = constructDataType(returnTypeClass, firstDataType.bitWidth()); - Objects.requireNonNull(result); + requireNonNull(result); return result; }); return this; } + public BuiltInBuilder returnsFirstFloatType() { + return returnsFromFirstFloatSize(BitsType::bits); + } + + public BuiltInBuilder returnsSecondFloatType() { + return returnsFromFirstTwoFloatSizes((s0, s1) -> BitsType.bits(s1)); + } + public BuiltInBuilder returnsFirstBitWidthAndStatus( Class returnTypeClass) { returnsFromFirstAsDataType((firstDataType) -> { var valType = constructDataType(returnTypeClass, firstDataType.bitWidth()); - Objects.requireNonNull(valType); + requireNonNull(valType); return Type.struct( BUILTIN_RESULT, valType, BUILTIN_STATUS, Type.status() @@ -1879,6 +2326,42 @@ public BuiltInBuilder returnsFromDataTypes(Function, Type> return return this; } + public BuiltInBuilder returnsFromSecondConstSize( + Class returnTypeClass) { + return returns((constArgs, args) -> { + ensure(constArgs.size() >= 2, "Expected at least two constant argument, but found %d.", + constArgs.size()); + return requireNonNull(constructDataType(returnTypeClass, constInt(constArgs.get(1)))); + }); + } + + public BuiltInBuilder returnsFromFirstFloatSize(Function returnFunction) { + return returns((constArgs, args) -> { + ensure(!constArgs.isEmpty(), "Expected at least one constant argument, but found none."); + return returnFunction.apply(floatTypeSize(constArgs.get(0))); + }); + } + + public BuiltInBuilder returnsFromFirstTwoFloatSizes( + BiFunction returnFunction) { + return returns((constArgs, args) -> { + ensure(constArgs.size() >= 2, "Expected at least two constant argument, but found %d.", + constArgs.size()); + return returnFunction.apply( + floatTypeSize(constArgs.get(0)), floatTypeSize(constArgs.get(1))); + }); + } + + private int floatTypeSize(Constant arg) { + ensure(arg instanceof Constant.FloatType, "Expected a float type, but found %s", arg); + return ((Constant.FloatType) arg).size(); + } + + private int constInt(Constant arg) { + ensure(arg instanceof Constant.Value, "Expected a value type, but found %s", arg); + return ((Constant.Value) arg).intValue(); + } + public BuiltIn build() { @@ -1894,39 +2377,37 @@ public BuiltIn build() { return new BuiltIn(name, operator, signature, kind, hasSameBitWidth) { @Override - public Optional compute(List args) { + public Optional compute(List constArgs, List args) { if (computeFunction == null) { - return super.compute(args); + return super.compute(constArgs, args); } - var argTypes = args.stream() - .map(Constant::type) - .toList(); - if (!takes(argTypes)) { + var argTypes = args.stream().map(Constant::type).toList(); + if (!takes(constArgs, argTypes)) { throw new ViamError("Types of arguments does not match type signature of " + signature) .addContext("built-in", this) - .addContext("constants", List.of(args)); + .addContext("constants", List.of(constArgs, args)); } - return computeFunction.apply(args) + return computeFunction.apply(constArgs, args) .map(result -> result instanceof Constant.Value value - ? value.trivialCastTo(returns(argTypes)) + ? value.trivialCastTo(returns(constArgs, argTypes)) : result); } @Override - public boolean takes(List argTypes) { + public boolean takes(List constArgs, List argTypes) { // always check general case first - var generalConstraintsValid = super.takes(argTypes); + var generalConstraintsValid = super.takes(constArgs, argTypes); if (generalConstraintsValid) { // if general case doesn't fail, then test specific constraints - return takesFunction.apply(argTypes); + return takesFunction.apply(constArgs, argTypes); } return false; } @Override - public Type returns(List argTypes) { - return returnsFunction.apply(argTypes); + public Type returns(List constArgs, List argTypes) { + return returnsFunction.apply(constArgs, argTypes); } }; } diff --git a/vadl/main/vadl/types/FloatStatusType.java b/vadl/main/vadl/types/FloatStatusType.java new file mode 100644 index 000000000..324f75701 --- /dev/null +++ b/vadl/main/vadl/types/FloatStatusType.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.types; + +/** + * A class that represents the VADL float status type. + * + *

It is actually a struct of with five boolean fields. + * These five elements represent status flags: + *

  • nv
  • + *
  • dz
  • + *
  • of
  • + *
  • uf
  • + *
  • nx
  • + * in that order. + */ +public class FloatStatusType extends StructType { + + public static final String INVALID = "nv"; + public static final String DIVISION_BY_ZERO = "dz"; + public static final String OVERFLOW = "of"; + public static final String UNDERFLOW = "uf"; + public static final String INEXACT = "nx"; + + protected FloatStatusType() { + super(Type.struct( + INVALID, Type.bool(), + DIVISION_BY_ZERO, Type.bool(), + OVERFLOW, Type.bool(), + UNDERFLOW, Type.bool(), + INEXACT, Type.bool() + ).fields()); + } + + @Override + public String name() { + return "FloatStatus"; + } + +} diff --git a/vadl/main/vadl/types/FloatType.java b/vadl/main/vadl/types/FloatType.java new file mode 100644 index 000000000..d903ae18b --- /dev/null +++ b/vadl/main/vadl/types/FloatType.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.types; + +/** + * A class that represents the VADL float type. + */ +public class FloatType extends Type { + + protected FloatType() { } + + @Override + public String name() { + return "FloatType"; + } +} diff --git a/vadl/main/vadl/types/RelationType.java b/vadl/main/vadl/types/RelationType.java index 9585d6a3c..d6bac31ed 100644 --- a/vadl/main/vadl/types/RelationType.java +++ b/vadl/main/vadl/types/RelationType.java @@ -28,12 +28,15 @@ public class RelationType extends Type { private final List> argTypeClass; + private final List> constArgTypeClass; private final boolean hasVarArgs; private final Class resultTypeClass; - protected RelationType(List> argTypes, boolean hasVarArgs, + protected RelationType(List> argTypes, + List> constArgTypeClass, boolean hasVarArgs, Class resultType) { this.argTypeClass = argTypes; + this.constArgTypeClass = constArgTypeClass; this.hasVarArgs = hasVarArgs; this.resultTypeClass = resultType; } @@ -42,6 +45,10 @@ public List> argTypeClasses() { return argTypeClass; } + public List> constArgTypeClass() { + return constArgTypeClass; + } + public boolean hasVarArgs() { return hasVarArgs; } @@ -52,10 +59,18 @@ public Class resultTypeClass() { @Override public String name() { - return "(" - + argTypeClass.stream().map(Class::getSimpleName) - .collect(Collectors.joining(", ")) - + ") -> " - + resultTypeClass.getSimpleName(); + var sb = new StringBuilder(); + if (!constArgTypeClass.isEmpty()) { + sb.append("<"); + sb.append(constArgTypeClass.stream().map(Class::getSimpleName) + .collect(Collectors.joining(", "))); + sb.append(">"); + } + sb.append("("); + sb.append(argTypeClass.stream().map(Class::getSimpleName) + .collect(Collectors.joining(", "))); + sb.append(") -> "); + sb.append(resultTypeClass.getSimpleName()); + return sb.toString(); } } diff --git a/vadl/main/vadl/types/Type.java b/vadl/main/vadl/types/Type.java index 225bac78c..3ca3b04cc 100644 --- a/vadl/main/vadl/types/Type.java +++ b/vadl/main/vadl/types/Type.java @@ -120,6 +120,20 @@ public static UIntType unsignedInt(int bitWidth) { .computeIfAbsent(bitWidth, k -> new UIntType(bitWidth)); } + private static @Nullable FloatType floatType = null; + + /** + * Retrieves the instance of FloatType. + * + * @return the FloatType object + */ + public static FloatType floatType() { + if (floatType == null) { + floatType = new FloatType(); + } + return floatType; + } + /** * Returns a DummyType object. * @@ -188,6 +202,25 @@ public static StructType struct(String field1, Type type1, return struct(fields); } + /** + * Retrieves the struct type with the specified subtypes. + * + * @return the struct type with the specified subtypes + */ + public static StructType struct(String field1, Type type1, + String field2, Type type2, + String field3, Type type3, + String field4, Type type4, + String field5, Type type5) { + var fields = new LinkedHashMap(); + fields.put(field1, type1); + fields.put(field2, type2); + fields.put(field3, type3); + fields.put(field4, type4); + fields.put(field5, type5); + return struct(fields); + } + private static @Nullable StatusType statusType = null; /** @@ -202,6 +235,20 @@ public static StatusType status() { return statusType; } + private static @Nullable FloatStatusType floatStatusType = null; + + /** + * Retrieves the float status type instance. + * + * @return the float status type instance + */ + public static FloatStatusType floatStatus() { + if (floatStatusType == null) { + floatStatusType = new FloatStatusType(); + } + return floatStatusType; + } + private static @Nullable VoidType voidType = null; @@ -239,23 +286,53 @@ public static StringType string() { */ public static RelationType relation(List> argTypes, Class returnType) { - return relation(argTypes, false, returnType); + return relation(argTypes, List.of(), false, returnType); } /** * Retrieves the generic relation type. * - * @param argTypes the list of argument type classes - * @param hasVarArgs the flag indicating if the last argument of kind varargs - * @param returnType the return type class + * @param argTypes the list of argument type classes + * @param constArgTypes the list of constant argument type classes + * @param returnType the return type class + * @return the RelationType instance + */ + public static RelationType relation(List> argTypes, + List> constArgTypes, + Class returnType) { + return relation(argTypes, constArgTypes, false, returnType); + } + + /** + * Retrieves the generic relation type. + * + * @param argTypes the list of argument type classes + * @param hasVarArgs the flag indicating if the last argument of kind varargs + * @param returnType the return type class + * @return the RelationType instance + */ + public static RelationType relation(List> argTypes, + boolean hasVarArgs, + Class returnType) { + return relation(argTypes, List.of(), hasVarArgs, returnType); + } + + /** + * Retrieves the generic relation type. + * + * @param argTypes the list of argument type classes + * @param constArgTypes the list of constant argument type classes + * @param hasVarArgs the flag indicating if the last argument of kind varargs + * @param returnType the return type class * @return the RelationType instance */ public static RelationType relation(List> argTypes, + List> constArgTypes, boolean hasVarArgs, Class returnType) { - var hashCode = Objects.hash(argTypes, hasVarArgs, returnType); - return relationTypes - .computeIfAbsent(hashCode, k -> new RelationType(argTypes, hasVarArgs, returnType)); + var hashCode = Objects.hash(argTypes, constArgTypes, hasVarArgs, returnType); + return relationTypes.computeIfAbsent(hashCode, k -> + new RelationType(argTypes, constArgTypes, hasVarArgs, returnType)); } /** @@ -265,7 +342,7 @@ public static RelationType relation(List> argTypes, * @return the RelationType instance */ public static RelationType relation(Class returnType) { - return relation(List.of(), false, returnType); + return relation(List.of(), List.of(), false, returnType); } /** @@ -291,7 +368,7 @@ public static RelationType relation(Class argType, public static RelationType relation(Class firstArg, Class secondArg, Class returnType) { - return relation(List.of(firstArg, secondArg), false, returnType); + return relation(List.of(firstArg, secondArg), List.of(), false, returnType); } private static final HashMap concreteRelationTypes = @@ -403,6 +480,7 @@ public static GroupType group(Type elementType, UIntType lengthType, UIntType bi /// These are all the builtin types that exist in the language. /// Some of them cannot be initialized like them since they require a size, like `SInt<16>` /// which is why they are named bases. - public static final Set builtinTypeBases = - Set.of("Bool", "String", "Bits", "UInt", "SInt", "Instruction", "FetchResult"); + public static final Set builtinTypeBases = Set.of( + "Bool", "String", "Bits", "UInt", "SInt", "Instruction", "FetchResult" + ); } diff --git a/vadl/main/vadl/utils/VadlBuiltInEmptyNoStatusDispatcher.java b/vadl/main/vadl/utils/VadlBuiltInEmptyNoStatusDispatcher.java index 072457474..66d7c125f 100644 --- a/vadl/main/vadl/utils/VadlBuiltInEmptyNoStatusDispatcher.java +++ b/vadl/main/vadl/utils/VadlBuiltInEmptyNoStatusDispatcher.java @@ -16,6 +16,8 @@ package vadl.utils; +import vadl.viam.graph.dependency.BuiltInCall; + /** * A dispatcher that handles all {@code VADL::*} built-ins. * This dispatcher comes with an empty default implementation for each built-in. @@ -190,6 +192,106 @@ default void handleCTZ(T input) { default void handleCTO(T input) { } + @Override + default void handleFSQRT(T input) { + } + + @Override + default void handleFADD(T input) { + } + + @Override + default void handleFSUB(T input) { + } + + @Override + default void handleFMUL(T input) { + } + + @Override + default void handleFDIV(T input) { + } + + @Override + default void handleFMADD(T input) { + } + + @Override + default void handleFMSUB(T input) { + } + + @Override + default void handleFNMADD(T input) { + } + + @Override + default void handleFNMSUB(T input) { + } + + @Override + default void handleFMIN(T input) { + } + + @Override + default void handleFMAX(T input) { + } + + @Override + default void handleFLT(T input) { + } + + @Override + default void handleFLE(T input) { + } + + @Override + default void handleFEQ(T input) { + } + + @Override + default void handleFCVT(T input) { + } + + @Override + default void handleFCVTFS(T input) { + } + + @Override + default void handleFCVTFU(T input) { + } + + @Override + default void handleFCVTSF(T input) { + } + + @Override + default void handleFCVTUF(T input) { + } + + @Override + default void handleFISINF(T input) { + } + + @Override + default void handleFISZERO(T input) { + } + + @Override + default void handleFISNEG(T input) { + } + + @Override + default void handleFISDENORM(T input) { + } + + @Override + default void handleFISSNAN(T input) { + } + + @Override + default void handleFISQNAN(T input) { + } + @Override default void handleConcat(T input) { } diff --git a/vadl/main/vadl/utils/VadlBuiltInFloatStatusOnlyDispatcher.java b/vadl/main/vadl/utils/VadlBuiltInFloatStatusOnlyDispatcher.java new file mode 100644 index 000000000..3f3bba1da --- /dev/null +++ b/vadl/main/vadl/utils/VadlBuiltInFloatStatusOnlyDispatcher.java @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.utils; + +import vadl.types.BuiltInTable; + +/** + * A dispatcher that handles all {@code VADL::F*S} built-ins. + */ +public interface VadlBuiltInFloatStatusOnlyDispatcher { + + /** + * Calls the correct handler for the given built-in and uses the input as argument. + * + * @param input is passed to the handler method. + * @param builtIn to find the correct handler method. + * @return true if the handler was found and called, false otherwise. + */ + default boolean dispatch(T input, BuiltInTable.BuiltIn builtIn) { + if (builtIn == BuiltInTable.FADDS) { + handleFADDS(input); + } else { + return false; + } + return true; + } + + void handleFADDS(T input); + + +} diff --git a/vadl/main/vadl/utils/VadlBuiltInNoStatusDispatcher.java b/vadl/main/vadl/utils/VadlBuiltInNoStatusDispatcher.java index 99be85bee..1f38cc4fb 100644 --- a/vadl/main/vadl/utils/VadlBuiltInNoStatusDispatcher.java +++ b/vadl/main/vadl/utils/VadlBuiltInNoStatusDispatcher.java @@ -113,6 +113,56 @@ default boolean dispatch(T input, BuiltInTable.BuiltIn builtIn) { handleCTZ(input); } else if (builtIn == BuiltInTable.CTO) { handleCTO(input); + } else if (builtIn == BuiltInTable.FSQRT) { + handleFSQRT(input); + } else if (builtIn == BuiltInTable.FADD) { + handleFADD(input); + } else if (builtIn == BuiltInTable.FSUB) { + handleFSUB(input); + } else if (builtIn == BuiltInTable.FMUL) { + handleFMUL(input); + } else if (builtIn == BuiltInTable.FDIV) { + handleFDIV(input); + } else if (builtIn == BuiltInTable.FMADD) { + handleFMADD(input); + } else if (builtIn == BuiltInTable.FMSUB) { + handleFMSUB(input); + } else if (builtIn == BuiltInTable.FNMADD) { + handleFNMADD(input); + } else if (builtIn == BuiltInTable.FNMSUB) { + handleFNMSUB(input); + } else if (builtIn == BuiltInTable.FMIN) { + handleFMIN(input); + } else if (builtIn == BuiltInTable.FMAX) { + handleFMAX(input); + } else if (builtIn == BuiltInTable.FLT) { + handleFLT(input); + } else if (builtIn == BuiltInTable.FLE) { + handleFLE(input); + } else if (builtIn == BuiltInTable.FEQ) { + handleFEQ(input); + } else if (builtIn == BuiltInTable.FCVT) { + handleFCVT(input); + } else if (builtIn == BuiltInTable.FCVTFS) { + handleFCVTFS(input); + } else if (builtIn == BuiltInTable.FCVTFU) { + handleFCVTFU(input); + } else if (builtIn == BuiltInTable.FCVTSF) { + handleFCVTSF(input); + } else if (builtIn == BuiltInTable.FCVTUF) { + handleFCVTUF(input); + } else if (builtIn == BuiltInTable.FISINF) { + handleFISINF(input); + } else if (builtIn == BuiltInTable.FISZERO) { + handleFISZERO(input); + } else if (builtIn == BuiltInTable.FISNEG) { + handleFISNEG(input); + } else if (builtIn == BuiltInTable.FISDENORM) { + handleFISDENORM(input); + } else if (builtIn == BuiltInTable.FISSNAN) { + handleFISSNAN(input); + } else if (builtIn == BuiltInTable.FISQNAN) { + handleFISQNAN(input); } else if (builtIn == BuiltInTable.CONCATENATE_BITS) { handleConcat(input); } else { @@ -205,6 +255,56 @@ default boolean dispatch(T input, BuiltInTable.BuiltIn builtIn) { void handleCTO(T input); + void handleFSQRT(T input); + + void handleFADD(T input); + + void handleFSUB(T input); + + void handleFMUL(T input); + + void handleFDIV(T input); + + void handleFMADD(T input); + + void handleFMSUB(T input); + + void handleFNMADD(T input); + + void handleFNMSUB(T input); + + void handleFMIN(T input); + + void handleFMAX(T input); + + void handleFLT(T input); + + void handleFLE(T input); + + void handleFEQ(T input); + + void handleFCVT(T input); + + void handleFCVTFS(T input); + + void handleFCVTFU(T input); + + void handleFCVTSF(T input); + + void handleFCVTUF(T input); + + void handleFISINF(T input); + + void handleFISZERO(T input); + + void handleFISNEG(T input); + + void handleFISDENORM(T input); + + void handleFISSNAN(T input); + + void handleFISQNAN(T input); + void handleConcat(T input); } diff --git a/vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java b/vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java new file mode 100644 index 000000000..45c674e29 --- /dev/null +++ b/vadl/main/vadl/utils/functionInterfaces/QuadConsumer.java @@ -0,0 +1,51 @@ +package vadl.utils.functionInterfaces; + +import java.util.Objects; + +/** + * Represents an operation that accepts four input arguments and returns no result. + * This is the four-arity specialization of {@code Consumer}. + * Unlike most other functional interfaces, {@code QuadConsumer} is expected + * to operate via side effects. + * + * @param the type of the first argument to the operation + * @param the type of the second argument to the operation + * @param the type of the third argument to the operation + * @param the type of the fourth argument to the operation + * @see java.util.function.Consumer + * @see java.util.function.BiConsumer + */ +@FunctionalInterface +public interface QuadConsumer { + + /** + * Performs this operation on the given arguments. + * + * @param t the first input argument + * @param u the second input argument + * @param v the third input argument + * @param w the fourth input argument + */ + void accept(T t, U u, V v, W w); + + /** + * Returns a composed {@code QuadConsumer} that performs, in sequence, this + * operation followed by the {@code after} operation. If performing either + * operation throws an exception, it is relayed to the caller of the + * composed operation. If performing this operation throws an exception, + * the {@code after} operation will not be performed. + * + * @param after the operation to perform after this operation + * @return a composed {@code QuadConsumer} that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default QuadConsumer andThen( + QuadConsumer after) { + Objects.requireNonNull(after); + return (t, u, v, w) -> { + accept(t, u, v, w); + after.accept(t, u, v, w); + }; + } +} \ No newline at end of file diff --git a/vadl/main/vadl/viam/Constant.java b/vadl/main/vadl/viam/Constant.java index 38a33828f..9058ae58e 100644 --- a/vadl/main/vadl/viam/Constant.java +++ b/vadl/main/vadl/viam/Constant.java @@ -16,6 +16,7 @@ package vadl.viam; +import static java.util.Objects.requireNonNull; import static vadl.error.Diagnostic.warning; import static vadl.types.BuiltInTable.BUILTIN_RESULT; import static vadl.types.BuiltInTable.BUILTIN_STATUS; @@ -41,7 +42,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.jetbrains.annotations.Contract; import vadl.error.DeferredDiagnosticStore; import vadl.types.BitsType; @@ -443,7 +446,7 @@ public Constant.Value multiply(Constant.Value other, boolean longVersion, boolea .multiply(b.integer()); // multiply with other value var newType = Type.constructDataType(divType.getClass(), 2 * divType.bitWidth()); - Objects.requireNonNull(newType); + requireNonNull(newType); return fromInteger(newValue, newType); } else { @@ -467,7 +470,7 @@ public Constant.Value divide(Constant.Value other, boolean signed) { var divType = signed ? Type.signedInt(type().bitWidth()) : Type.unsignedInt(type().bitWidth()); - Objects.requireNonNull(divType); + requireNonNull(divType); var a = this.trivialCastTo(divType); var b = other.trivialCastTo(divType); @@ -510,7 +513,7 @@ public Constant.Value modulo(Constant.Value other, boolean signed) { var divType = signed ? Type.signedInt(type().bitWidth()) : Type.unsignedInt(type().bitWidth()); - Objects.requireNonNull(divType); + requireNonNull(divType); var a = this.trivialCastTo(divType); var b = other.trivialCastTo(divType); @@ -1412,7 +1415,7 @@ public Map values() { * @return The Constant value at the specified name. */ public Constant get(String name) { - return Objects.requireNonNull(values.get(name), + return requireNonNull(values.get(name), "Struct does not contain a value with name %s".formatted(name)); } @@ -1551,6 +1554,82 @@ public Constant.Value overflow() { } } + /** + * Represents a constant float-type. + * + *

    It stores a reference to the {@link FloatFormat}. + */ + public static class FloatType extends Constant { + + @Nullable + private final FloatFormat format; + + private final Integer size; + private final String name; + + /** + * Constructs a float-type constant from a float format definition. + * + * @param format The float format definition. + */ + public FloatType(FloatFormat format) { + super(Type.floatType()); + this.format = format; + // the type-checker checks that float-type definitions have a size, and thus an encoding + this.size = requireNonNull(format.encoding()).size; + this.name = format.simpleName(); + } + + /** + * Constructs a dummy float-type constant from a float format encoding size. This is used by + * the type checker when the float format definition is not yet available. + * + * @param size The float format encoding size, i.e. the bit-size of the float type. + * @param name The name of the float format. + */ + public FloatType(int size, String name) { + super(Type.floatType()); + this.format = null; + this.size = size; + this.name = name; + } + + @Nullable + public FloatFormat format() { + return format; + } + + public Integer size() { + return size; + } + + @Override + public java.lang.String toString() { + return name + ": " + type().toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + FloatType floatType = (FloatType) o; + return Objects.equals(format, floatType.format) && Objects.equals(size, + floatType.size) && Objects.equals(name, floatType.name); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), format, size, name); + } + } + // HELPER FUNCTIONS diff --git a/vadl/main/vadl/viam/DefinitionVisitor.java b/vadl/main/vadl/viam/DefinitionVisitor.java index b5d0a6755..790b74af2 100644 --- a/vadl/main/vadl/viam/DefinitionVisitor.java +++ b/vadl/main/vadl/viam/DefinitionVisitor.java @@ -44,6 +44,8 @@ public interface DefinitionVisitor { void visit(Group group); + void visit(FloatFormat floatFormat); + void visit(Format format); void visit(Format.Field formatField); @@ -138,6 +140,7 @@ public void visit(InstructionSetArchitecture isa) { isa.ownMemories().forEach(e -> e.accept(this)); isa.artificialResources().forEach(e -> e.accept(this)); isa.ownInstructions().forEach(e -> e.accept(this)); + isa.ownFloatFormats().forEach(e -> e.accept(this)); isa.ownPseudoInstructions().forEach(e -> e.accept(this)); var pc = isa.pc(); if (pc != null) { @@ -198,6 +201,12 @@ public void visit(Group group) { afterTraversal(group); } + @Override + public void visit(FloatFormat floatFormat) { + beforeTraversal(floatFormat); + afterTraversal(floatFormat); + } + @Override public void visit(Format format) { beforeTraversal(format); @@ -483,6 +492,11 @@ public void visit(Group group) { } + @Override + public void visit(FloatFormat floatFormat) { + + } + @Override public void visit(Format format) { diff --git a/vadl/main/vadl/viam/FloatExceptionFlag.java b/vadl/main/vadl/viam/FloatExceptionFlag.java new file mode 100644 index 000000000..87e463727 --- /dev/null +++ b/vadl/main/vadl/viam/FloatExceptionFlag.java @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam; + +import java.util.Arrays; +import java.util.Optional; + +/** + * Enum representing float exception flags. + * + *

    The supported flags are: + *

      + *
    • INVALID
    • + *
    • DIV_BY_ZERO
    • + *
    • OVERFLOW
    • + *
    • UNDERFLOW
    • + *
    • INEXACT
    • + *
    + */ +public enum FloatExceptionFlag { + INVALID("invalid", 0), + DIV_BY_ZERO("div_by_zero", 1), + OVERFLOW("overflow", 2), + UNDERFLOW("underflow", 3), + INEXACT("inexact", 4); + + public final String name; + + /** + * See softfloat-types.h + */ + public final int qemuFlagOffset; + + FloatExceptionFlag(String name, int qemuFlagOffset) { + this.name = name; + this.qemuFlagOffset = qemuFlagOffset; + } + + public static Optional from(String name) { + return Arrays.stream(values()).filter(f -> f.name.equals(name)).findFirst(); + } +} diff --git a/vadl/main/vadl/viam/FloatFormat.java b/vadl/main/vadl/viam/FloatFormat.java new file mode 100644 index 000000000..6be249956 --- /dev/null +++ b/vadl/main/vadl/viam/FloatFormat.java @@ -0,0 +1,144 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam; + +import static java.util.Objects.requireNonNull; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import vadl.types.Type; + +/** + * Describes a float format. This covers bit-size, encoding and interpretation. + */ +public class FloatFormat extends Definition implements DefProp.WithType { + + /** + * Represents all supported float encodings. + */ + public enum Encoding { + IEEE32(32, true), + IEEE64(64, true); + + public final int size; + public final boolean ieee; + + Encoding(int size, boolean ieee) { + this.size = size; + this.ieee = ieee; + } + + /** + * Returns the IEEE encoding for the given bit-size. + */ + public static @Nullable Encoding ieee(int size) { + return switch (size) { + case 32 -> IEEE32; + case 64 -> IEEE64; + default -> null; + }; + } + } + + @Nullable + private Encoding encoding = null; + + @Nullable + private Constant canonicalSNaN = null; + @Nullable + private Constant canonicalQNaN = null; + + public FloatFormat(Identifier identifier) { + super(identifier); + } + + public void setEncoding(@CheckForNull Encoding encoding) { + this.encoding = encoding; + } + + public void setCanonicalSNaN(@CheckForNull Constant canonicalSNaN) { + this.canonicalSNaN = canonicalSNaN; + } + + public void setCanonicalQNaN(@CheckForNull Constant canonicalQNaN) { + this.canonicalQNaN = canonicalQNaN; + } + + /** + * The encoding of the float format. + */ + @Nullable + public Encoding encoding() { + return encoding; + } + + /** + * The canonical signaling NaN encoding of the float format. + */ + @Nullable + public Constant canonicalSNaN() { + return canonicalSNaN; + } + + /** + * The canonical quiet NaN encoding of the float format. + */ + @Nullable + public Constant canonicalQNaN() { + return canonicalQNaN; + } + + /** + * The name of the float format in lower case. + */ + public String nameLower() { + return simpleName().toLowerCase(); + } + + @Override + public Type type() { + return Type.floatType(); + } + + @Override + public void accept(DefinitionVisitor visitor) { + visitor.visit(this); + } + + @Override + public void verify() { + super.verify(); + // FIXME: for now this is checked here, but this should create a diagnostic instead of ViamError + ensure(encoding != null, "Encoding not specified"); + checkNaN(canonicalSNaN, "sNaN"); + checkNaN(canonicalQNaN, "qNaN"); + } + + private void checkNaN(@Nullable Constant value, String kind) { + ensure(value != null, "Canonical %s not specified", kind); + var valueBits = value.asVal().integer().bitLength(); + var givenBits = requireNonNull(encoding).size; + ensure(valueBits <= givenBits, + "Canonical %s value cannot require more bits (%d) than the encoding size (%d)", + kind, valueBits, givenBits); + } + + @Override + public String toString() { + return identifier.simpleName(); + } +} diff --git a/vadl/main/vadl/viam/InstructionSetArchitecture.java b/vadl/main/vadl/viam/InstructionSetArchitecture.java index c9fa2130e..58deeb422 100644 --- a/vadl/main/vadl/viam/InstructionSetArchitecture.java +++ b/vadl/main/vadl/viam/InstructionSetArchitecture.java @@ -34,6 +34,7 @@ public class InstructionSetArchitecture extends Definition { @Nullable private final Counter pc; + private final List floatFormats; private final List formats; private final List functions; private final List operations; @@ -72,6 +73,7 @@ public InstructionSetArchitecture(Identifier identifier, @Nullable Counter pc, List memories, List artificialResources, + List floatFormats, @Nullable Group group ) { super(identifier); @@ -87,6 +89,7 @@ public InstructionSetArchitecture(Identifier identifier, this.pc = pc; this.memories = memories; this.artificialResources = artificialResources; + this.floatFormats = floatFormats; this.group = group; // set parent architecture of instructions @@ -171,6 +174,14 @@ public List registerTensors() { } + /** + * Returns the {@link FloatFormat}s owned by this ISA. + * So it might not include definitions accessible through the super ISA. + */ + public List ownFloatFormats() { + return floatFormats; + } + /** * Returns the {@link Format}s owned by this ISA. * So it might not include definitions accessible through the super ISA. diff --git a/vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java b/vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java new file mode 100644 index 000000000..2a3508332 --- /dev/null +++ b/vadl/main/vadl/viam/annotations/FloatFlagAnnotation.java @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.annotations; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import vadl.viam.Annotation; +import vadl.viam.Constant; +import vadl.viam.FloatExceptionFlag; +import vadl.viam.RegisterTensor; + +/** + * Annotation for registers that are saved in the translation block state. Contains a + * {@link Constant.BitSlice} that specifies what bits of the register are saved. + */ +public class FloatFlagAnnotation extends Annotation { + + private final Map sticky = new HashMap<>(); + private final Map nonSticky = new HashMap<>(); + + @Override + public Class parentDefinitionClass() { + return RegisterTensor.class; + } + + public boolean isSticky(int index) { + return sticky.containsKey(index); + } + + @Nullable + public FloatExceptionFlag get(int index) { + return isSticky(index) ? sticky.get(index) : nonSticky.get(index); + } + + public void set(int index, boolean isSticky, FloatExceptionFlag flag) { + (isSticky ? sticky : nonSticky).put(index, flag); + } + + public Map stickyFlags() { + return sticky; + } + + public Map nonStickyFlags() { + return nonSticky; + } + +} diff --git a/vadl/main/vadl/viam/annotations/RegisterSliceAnnotation.java b/vadl/main/vadl/viam/annotations/RegisterSliceAnnotation.java new file mode 100644 index 000000000..eb4d7d5f8 --- /dev/null +++ b/vadl/main/vadl/viam/annotations/RegisterSliceAnnotation.java @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.annotations; + +import java.util.stream.Stream; +import javax.annotation.Nullable; +import vadl.viam.Annotation; +import vadl.viam.Constant; +import vadl.viam.RegisterTensor; + +/** + * Annotation for registers, which contains a {@link Constant.BitSlice} that specifies + * what bits of the register are annotated. + */ +public abstract class RegisterSliceAnnotation extends Annotation { + + private final int registerBitWidth; + + /** + * Determines what bits of the register are annotated. + * If this is null, then the whole register (i.e. all bits) are annotated. + */ + @Nullable + private Constant.BitSlice slice; + + /** + * Constructs a new annotation for a register. If the given bit slice is `null`, then the + * whole register is annotated. Otherwise, only the bits in the slice are annotated. + */ + public RegisterSliceAnnotation(int registerBitWidth, @Nullable Constant.BitSlice slice) { + this.registerBitWidth = registerBitWidth; + this.slice = slice; + normalizeBitSlice(); + } + + @Override + public Class parentDefinitionClass() { + return RegisterTensor.class; + } + + /** + * Marks the bits in the given slice annotated, additional to the already marked bits. + * + * @param slice What bits to add + */ + public void addSlice(@Nullable Constant.BitSlice slice) { + if (slice == null || this.slice == null) { + this.slice = null; + } else { + new Constant.BitSlice(Stream.concat( + this.slice.parts(), slice.parts() + ).toArray(Constant.BitSlice.Part[]::new)); + normalizeBitSlice(); + } + } + + /** + * Returns whether the whole register is annotated. + */ + public boolean wholeRegister() { + return slice == null; + } + + /** + * Returns whether the given slice of bits of the register are annotated. + */ + public boolean covers(Constant.BitSlice slice) { + return this.slice == null || this.slice.covers(slice); + } + + @Nullable + public Constant.BitSlice slice() { + return slice; + } + + /** + * Returns how many bits are annotated. + */ + public int bitSize() { + if (slice == null) { + return registerBitWidth; + } + return slice.bitSize(); + } + + private void normalizeBitSlice() { + if (covers(Constant.BitSlice.of(registerBitWidth - 1, 0))) { + // set to null if the whole register is covered + slice = null; + } + if (slice != null) { + // merge all overlapping parts and ensure that every bit is only covered once + + // Note: calling slice.hasOverlappingParts() does NOT work here, because it does not + // check overlapping parts that are equal + slice = new Constant.BitSlice( + slice.stream().distinct() + .mapToObj(i -> new Constant.BitSlice.Part(i, i)) + .toArray(Constant.BitSlice.Part[]::new) + ); + } + } + +} diff --git a/vadl/main/vadl/viam/annotations/TbStateRegisterAnnotation.java b/vadl/main/vadl/viam/annotations/TbStateRegisterAnnotation.java index 188a48e85..dca2d9d89 100644 --- a/vadl/main/vadl/viam/annotations/TbStateRegisterAnnotation.java +++ b/vadl/main/vadl/viam/annotations/TbStateRegisterAnnotation.java @@ -16,27 +16,14 @@ package vadl.viam.annotations; -import java.util.stream.Stream; import javax.annotation.Nullable; -import vadl.viam.Annotation; import vadl.viam.Constant; -import vadl.viam.RegisterTensor; /** - * Annotation for registers or register aliases that are saved in the - * translation block state. Contains a {@link Constant.BitSlice} that specifies - * what bits of the register are saved. + * Annotation for registers that are saved in the translation block state. Contains a + * {@link Constant.BitSlice} that specifies what bits of the register are saved. */ -public class TbStateRegisterAnnotation extends Annotation { - - private final int registerBitWidth; - - /** - * Determines what bits of the register are saved in the translation block state. - * If this is null, then the whole register is saved. - */ - @Nullable - private Constant.BitSlice slice; +public class TbStateRegisterAnnotation extends RegisterSliceAnnotation { /** * Constructs a new annotation for a register (or register alias) that is saved @@ -45,79 +32,7 @@ public class TbStateRegisterAnnotation extends Annotation { * marked as saved. */ public TbStateRegisterAnnotation(int registerBitWidth, @Nullable Constant.BitSlice slice) { - this.registerBitWidth = registerBitWidth; - this.slice = slice; - normalizeBitSlice(); - } - - @Override - public Class parentDefinitionClass() { - return RegisterTensor.class; - } - - /** - * Marks the bits in the given slice as translation block state saved, additional - * to the already marked bits. - * - * @param slice What bits to add - */ - public void addSlice(@Nullable Constant.BitSlice slice) { - if (slice == null || this.slice == null) { - this.slice = null; - } else { - new Constant.BitSlice(Stream.concat( - this.slice.parts(), slice.parts() - ).toArray(Constant.BitSlice.Part[]::new)); - normalizeBitSlice(); - } - } - - /** - * Returns whether the whole register is saved in the translation block state. - */ - public boolean wholeRegister() { - return slice == null; - } - - /** - * Returns whether the given slice of bits of the register are saved in the - * translation block state. - */ - public boolean covers(Constant.BitSlice slice) { - return this.slice == null || this.slice.covers(slice); - } - - @Nullable - public Constant.BitSlice slice() { - return slice; - } - - /** - * Returns how many bits are marked as saved in the translation block state. - */ - public int bitSize() { - if (slice == null) { - return registerBitWidth; - } - return slice.bitSize(); - } - - private void normalizeBitSlice() { - if (covers(Constant.BitSlice.of(registerBitWidth - 1, 0))) { - // set to null if the whole register is covered - slice = null; - } - if (slice != null) { - // merge all overlapping parts and ensure that every bit is only covered once - - // Note: calling slice.hasOverlappingParts() does NOT work here, because it does not - // check overlapping parts that are equal - slice = new Constant.BitSlice( - slice.stream().distinct() - .mapToObj(i -> new Constant.BitSlice.Part(i, i)) - .toArray(Constant.BitSlice.Part[]::new) - ); - } + super(registerBitWidth, slice); } } diff --git a/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java b/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java index 187f80794..d09542f83 100644 --- a/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java +++ b/vadl/main/vadl/viam/graph/dependency/BuiltInCall.java @@ -23,6 +23,7 @@ import vadl.types.BuiltInTable; import vadl.types.BuiltInTable.BuiltIn; import vadl.types.Type; +import vadl.viam.Constant; import vadl.viam.graph.Canonicalizable; import vadl.viam.graph.GraphNodeVisitor; import vadl.viam.graph.Node; @@ -40,9 +41,19 @@ public class BuiltInCall extends AbstractFunctionCallNode implements Canonicaliz @DataValue protected BuiltIn builtIn; - public BuiltInCall(BuiltIn builtIn, NodeList args, Type type) { + @DataValue + protected List constArgs; + + @SuppressWarnings("checkstyle:MissingJavadocMethod") + public BuiltInCall(BuiltIn builtIn, List constArgs, + NodeList args, Type type) { super(args, type); this.builtIn = builtIn; + this.constArgs = constArgs; + } + + public BuiltInCall(BuiltIn builtIn, NodeList args, Type type) { + this(builtIn, List.of(), args, type); } /** @@ -78,6 +89,17 @@ public BuiltIn builtIn() { return this.builtIn; } + /** + * Gets the constant args, i.e. the args in the pointy brackets {@code <...>}. + */ + public List constArgs() { + return constArgs; + } + + public void setConstArgs(List constArgs) { + this.constArgs = constArgs; + } + public ExpressionNode arg(int index) { return args.get(index); } @@ -96,7 +118,7 @@ public Node canonical() { .toList(); return builtIn - .compute(args) + .compute(constArgs, args) .map(e -> (Node) new ConstantNode(e)) .orElse(this); } @@ -130,11 +152,11 @@ public void verifyState() { "Number of arguments must match, %s vs %s", argTypeClasses.size(), this.arguments().size()); var actualArgTypes = this.arguments().stream().map(ExpressionNode::type).toList(); - ensure(builtIn.takes(actualArgTypes), - "Arguments' types do not match with the type of the builtin. Args: %s", - actualArgTypes); + ensure(builtIn.takes(constArgs, actualArgTypes), + "Arguments' types do not match with the type of the builtin. Const args: %s, Args: %s", + constArgs, actualArgTypes); - var builtInResultType = builtIn.returns(actualArgTypes); + var builtInResultType = builtIn.returns(constArgs, actualArgTypes); ensure(builtInResultType.isTrivialCastTo(this.type()), "BuiltIns' result type does not match node's type. %s vs %s", builtInResultType, this.type() ); @@ -143,13 +165,14 @@ public void verifyState() { @Override public ExpressionNode copy() { return new BuiltInCall(builtIn, + constArgs.stream().toList(), new NodeList<>(this.arguments().stream().map(x -> (ExpressionNode) x.copy()).toList()), this.type()); } @Override public Node shallowCopy() { - return new BuiltInCall(builtIn, args, type()); + return new BuiltInCall(builtIn, constArgs, args, type()); } @@ -157,6 +180,7 @@ public Node shallowCopy() { protected void collectData(List collection) { super.collectData(collection); collection.add(builtIn); + collection.add(constArgs); } @Override @@ -169,6 +193,16 @@ public void prettyPrint(StringBuilder sb) { sb.append(")"); } else { sb.append(builtIn.name()); + if (!constArgs.isEmpty()) { + sb.append("::<"); + for (int i = 0; i < constArgs.size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(constArgs.get(i).toString()); + } + sb.append(">"); + } sb.append("("); for (int i = 0; i < args.size(); i++) { diff --git a/vadl/main/vadl/viam/passes/statusBuiltInInlinePass/StatusBuiltInInlinePass.java b/vadl/main/vadl/viam/passes/statusBuiltInInlinePass/StatusBuiltInInlinePass.java index ef52fcc86..a64ddead3 100644 --- a/vadl/main/vadl/viam/passes/statusBuiltInInlinePass/StatusBuiltInInlinePass.java +++ b/vadl/main/vadl/viam/passes/statusBuiltInInlinePass/StatusBuiltInInlinePass.java @@ -23,6 +23,7 @@ import vadl.pass.Pass; import vadl.pass.PassName; import vadl.pass.PassResults; +import vadl.utils.VadlBuiltInFloatStatusOnlyDispatcher; import vadl.utils.VadlBuiltInStatusOnlyDispatcher; import vadl.viam.Specification; import vadl.viam.graph.Graph; @@ -52,8 +53,10 @@ public PassName getName() { @Override public Object execute(PassResults passResults, Specification viam) throws IOException { return viam.isa().map(isa -> { - isa.ownInstructions().forEach(i -> - new StatusBuiltInInliner(i.behavior()).run()); + isa.ownInstructions().forEach(i -> { + new StatusBuiltInInliner(i.behavior()).run(); + new FloatStatusBuiltInInliner(i.behavior()).run(); + }); return null; }); } @@ -274,3 +277,34 @@ public void handleRRXS(BuiltInCall input) { throwNotImplemented(input); } } + +/** + * Inlines all float status built-ins for the given graph. + * There is a {@link Inliner} for each status built-in. + */ +class FloatStatusBuiltInInliner implements VadlBuiltInFloatStatusOnlyDispatcher { + + private final Graph graph; + + FloatStatusBuiltInInliner(Graph graph) { + this.graph = graph; + } + + void run() { + graph.getNodes(BuiltInCall.class).forEach(n -> { + dispatch(n, n.builtIn()); + }); + } + + private void throwNotImplemented(BuiltInCall input) { + throw Diagnostic.error("Built-In Lowering Not Implemented", input) + .description("OpenVADL does not yet implement the inlining of the %s built-in.", + input.builtIn().name()) + .build(); + } + + @Override + public void handleFADDS(BuiltInCall input) { + throwNotImplemented(input); + } +} \ No newline at end of file diff --git a/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java b/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java index 925c1a31d..4cc898f56 100644 --- a/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java +++ b/vadl/test/vadl/iss/passes/IssTensorAssignmentToForallPassTest.java @@ -176,6 +176,7 @@ private static Fixture createTensorWriteFixture(String name, null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), null )); diff --git a/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java b/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java index a30aa4b09..084e5cf6a 100644 --- a/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java +++ b/vadl/test/vadl/viam/passes/constant_propagation/CanonicalizationPassTest.java @@ -95,6 +95,7 @@ void shouldReplaceAdditionWithConstant() { null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), null );