A narrow shift masks its own count, since the 64-bit instruction masks to 63
This commit is contained in:
parent
47cb46aa00
commit
8c9e8a314c
34
lib/x86.ml
34
lib/x86.ml
@ -351,6 +351,7 @@ let grp1_imm b ~ext ~dst n =
|
||||
rex b ~w:true ~r:0 ~x:0 ~m:dst; u8 b 0x81; modrm_r b ~r:ext ~m:dst; i32 b n
|
||||
|
||||
let add_imm b ~dst n = grp1_imm b ~ext:0 ~dst n
|
||||
let and_imm b ~dst n = grp1_imm b ~ext:4 ~dst n
|
||||
let sub_imm b ~dst n = grp1_imm b ~ext:5 ~dst n
|
||||
let cmp_imm b ~dst n = grp1_imm b ~ext:7 ~dst n
|
||||
|
||||
@ -364,8 +365,14 @@ let cqo b = u8 b 0x48; u8 b 0x99
|
||||
let idiv_r b ~src = rex b ~w:true ~r:0 ~x:0 ~m:src; u8 b 0xf7; modrm_r b ~r:7 ~m:src
|
||||
let div_r b ~src = rex b ~w:true ~r:0 ~x:0 ~m:src; u8 b 0xf7; modrm_r b ~r:6 ~m:src
|
||||
|
||||
(* Shifts by cl. The count is masked to the operand width by the hardware,
|
||||
which is the rule the language already defines (item 15's audit). *)
|
||||
(* Shifts by cl, always 64 bits wide, because every integer in this backend is
|
||||
computed in a full register and narrowed only on the way to memory. The
|
||||
hardware masks the count to 63 for a 64-bit shift and to 31 for a 32-bit
|
||||
one, and to nothing narrower than that — there is no 8- or 16-bit masking
|
||||
rule to inherit. So the width the count is masked to is a property of the
|
||||
*instruction* emitted, not of the language's type, and since the instruction
|
||||
is always the 64-bit one the caller has to mask the count itself for every
|
||||
width below 64. See the shift arm in [prim_to]. *)
|
||||
let shift_cl b ~ext ~dst = rex b ~w:true ~r:0 ~x:0 ~m:dst; u8 b 0xd3; modrm_r b ~r:ext ~m:dst
|
||||
let shl_cl b ~dst = shift_cl b ~ext:4 ~dst
|
||||
let shr_cl b ~dst = shift_cl b ~ext:5 ~dst
|
||||
@ -2530,10 +2537,25 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) dst =
|
||||
| Tast.BitAnd -> and_rr f.b ~dst:rax ~src:rcx
|
||||
| Tast.BitOr -> or_rr f.b ~dst:rax ~src:rcx
|
||||
| Tast.BitXor -> xor_rr f.b ~dst:rax ~src:rcx
|
||||
(* The count is masked to the operand width by the hardware, which is
|
||||
the rule the language already defines. *)
|
||||
| Tast.Shl -> shl_cl f.b ~dst:rax
|
||||
| Tast.Shr -> if signed then sar_cl f.b ~dst:rax else shr_cl f.b ~dst:rax
|
||||
(* The language masks a computed count to the operand's width minus one
|
||||
and says so out loud (NEXT.md's sharp edges); [emit] writes that mask
|
||||
into the IR at 2041. The hardware would do it too if the shift were
|
||||
the operand's width, but [shift_cl] is 64 bits wide whatever the type
|
||||
says, so what the silicon masks to is 63 and not 31, 15 or 7. Without
|
||||
the [and] a count of 32 on an i32 shifts the sign-extended value out
|
||||
of the low half and stores zero, where the rule — and LLVM — say the
|
||||
count is 0 and the value comes back unchanged. One extra [and] on the
|
||||
count register buys the agreement; 64-bit shifts skip it because
|
||||
there the hardware's mask is already the rule's. A literal out of
|
||||
range never reaches here: [check] rejects it as a typo. *)
|
||||
| Tast.Shl | Tast.Shr ->
|
||||
(match t with
|
||||
| Types.Int k when Types.bits k < 64 ->
|
||||
and_imm f.b ~dst:rcx (Types.bits k - 1)
|
||||
| _ -> ());
|
||||
(match p with
|
||||
| Tast.Shl -> shl_cl f.b ~dst:rax
|
||||
| _ -> if signed then sar_cl f.b ~dst:rax else shr_cl f.b ~dst:rax)
|
||||
| Tast.Div | Tast.Rem ->
|
||||
(* The guard goes *before* the instruction, which is the whole of why
|
||||
it has to be emitted at all: `idiv` raises SIGFPE on both the zero
|
||||
|
||||
85
spike/x86/p11-shift-edges.flan
Normal file
85
spike/x86/p11-shift-edges.flan
Normal file
@ -0,0 +1,85 @@
|
||||
;;;; A shift whose count reaches the operand's own width, at every width.
|
||||
;;;;
|
||||
;;;; This backend computes every integer in a full 64-bit register and narrows
|
||||
;;;; only on the way to memory, so the shift instruction it emits is the 64-bit
|
||||
;;;; one whatever the type says. The hardware masks a 64-bit shift's count to
|
||||
;;;; 63 — not to 31, 15 or 7 — so for every width below 64 the mask the
|
||||
;;;; language promises (count masked to width−1, which is what emit writes into
|
||||
;;;; the IR) has to be emitted by hand. Without it a count of 32 on an i32
|
||||
;;;; shifts the value clean out of the low half and stores zero, where the rule
|
||||
;;;; says the count is 0 and the value comes back unchanged.
|
||||
;;;;
|
||||
;;;; Every count is a global and never a literal: the checker rejects a literal
|
||||
;;;; count that is out of range — that is a typo, not a program — so the only
|
||||
;;;; shift that can reach either backend out of range is a computed one, and a
|
||||
;;;; literal would be folded away before the code under test ran anyway.
|
||||
|
||||
(defvar i8min i8 -128)
|
||||
(defvar i16min i16 -32768)
|
||||
(defvar i32min i32 -2147483648)
|
||||
(defvar i64min i64 -9223372036854775808)
|
||||
(defvar u8hi u8 255)
|
||||
(defvar u16hi u16 65535)
|
||||
(defvar u32hi u32 4294967295)
|
||||
(defvar one8 i8 1)
|
||||
(defvar one16 i16 1)
|
||||
(defvar one32 i32 1)
|
||||
(defvar oneu8 u8 1)
|
||||
(defvar oneu16 u16 1)
|
||||
(defvar oneu32 u32 1)
|
||||
(defvar one64 i64 1)
|
||||
|
||||
(defvar c8 i8 8)
|
||||
(defvar c9 i8 9)
|
||||
(defvar cu8 u8 8)
|
||||
(defvar c16 i16 16)
|
||||
(defvar cu16 u16 17)
|
||||
(defvar c32 i32 32)
|
||||
(defvar c33 i32 33)
|
||||
(defvar cu32 u32 32)
|
||||
(defvar cu33 u32 33)
|
||||
(defvar c64 i64 64)
|
||||
(defvar c65 i64 65)
|
||||
|
||||
(defn main [] i32
|
||||
;; ── The count exactly at the width: masks to 0, so nothing moves ──
|
||||
(println (<< one8 c8))
|
||||
(println (<< one16 c16))
|
||||
(println (<< one32 c32))
|
||||
(println (<< oneu8 cu8))
|
||||
(println (<< oneu32 cu32))
|
||||
(println (<< one64 c64))
|
||||
|
||||
;; ── The sign bit, shifted by the width ────────────────────────────
|
||||
;; An arithmetic shift of a negative number by a count the hardware would
|
||||
;; read as the full width answers all-ones; masked, it answers the value.
|
||||
(println (<< i8min c8))
|
||||
(println (>> i8min c8))
|
||||
(println (<< i16min c16))
|
||||
(println (>> i16min c16))
|
||||
(println (<< i32min c32))
|
||||
(println (>> i32min c32))
|
||||
(println (<< i64min c64))
|
||||
(println (>> i64min c64))
|
||||
|
||||
;; ── The same edge unsigned, where the shift is logical ────────────
|
||||
(println (>> u8hi cu8))
|
||||
(println (>> u16hi cu16)) ; 17, one past the width: masks to 1
|
||||
(println (>> u32hi cu32))
|
||||
(println (<< u32hi cu33)) ; 33 masks to 1: the top of u32 doubled, wrapped
|
||||
|
||||
;; ── One past the width, where the masked count is 1 ───────────────
|
||||
(println (<< one8 c9))
|
||||
(println (<< one32 c33))
|
||||
(println (<< one64 c65))
|
||||
|
||||
;; ── And the ordinary counts, which never needed a mask ────────────
|
||||
(println (<< one32 one32))
|
||||
(println (>> i32min one32))
|
||||
(println (>> u32hi oneu32))
|
||||
(println (>> i8min one8))
|
||||
(println (>> u8hi oneu8))
|
||||
(println (<< one16 one16))
|
||||
(println (>> i16min one16))
|
||||
(println (>> u16hi oneu16))
|
||||
0)
|
||||
Loading…
x
Reference in New Issue
Block a user