Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9fb57b6

Browse files
committedMar 10, 2025
Added Cmm_helpers.Scalar_type. This provides utilities for converting between integers types of different widths and signedness. This is in preparation for adding unboxed small integer types.
1 parent 93513c0 commit 9fb57b6

File tree

4 files changed

+673
-284
lines changed

4 files changed

+673
-284
lines changed
 

‎backend/cmm_helpers.ml

+267
Original file line numberDiff line numberDiff line change
@@ -4551,3 +4551,270 @@ let reperform ~dbg ~eff ~cont ~last_fiber =
45514551
dbg )
45524552

45534553
let poll ~dbg = return_unit dbg (Cop (Cpoll, [], dbg))
4554+
4555+
module Scalar_type = struct
4556+
module Float_width = struct
4557+
type t = Cmm.float_width =
4558+
| Float64
4559+
| Float32
4560+
4561+
let[@inline] static_cast ~dbg ~src ~dst exp =
4562+
match src, dst with
4563+
| Float64, Float64 -> exp
4564+
| Float32, Float32 -> exp
4565+
| Float32, Float64 -> float_of_float32 ~dbg exp
4566+
| Float64, Float32 -> float32_of_float ~dbg exp
4567+
end
4568+
4569+
module Signedness = struct
4570+
type t =
4571+
| Signed
4572+
| Unsigned
4573+
4574+
let equal (x : t) (y : t) = x = y
4575+
4576+
let print ppf t =
4577+
match t with
4578+
| Signed -> Format.pp_print_string ppf "signed"
4579+
| Unsigned -> Format.pp_print_string ppf "unsigned"
4580+
end
4581+
4582+
module Bit_width_and_signedness : sig
4583+
(** An integer with signedness [signedness t] that fits into a general-purpose
4584+
register. It is canonically stored in twos-complement representation, in the lower
4585+
[bits] bits of its container (whether that be memory or a register), and is sign-
4586+
or zero-extended to fill the entire container. *)
4587+
type t [@@immediate]
4588+
4589+
val create_exn : bit_width:int -> signedness:Signedness.t -> t
4590+
4591+
val bit_width : t -> int
4592+
4593+
val signedness : t -> Signedness.t
4594+
4595+
val equal : t -> t -> bool
4596+
end = struct
4597+
(* [signedness t] is stored in the low bit of [t], and [bit_width t] is
4598+
stored in the remaining high bits of [t]. We use this encoding to fit [t]
4599+
into an immediate value. This is worth trying since we expect to create
4600+
one of these for ~every integer operation, so it should cut down on
4601+
garbage *)
4602+
type t = { bit_width_and_signedness : int } [@@unboxed]
4603+
4604+
let[@inline] equal { bit_width_and_signedness = x }
4605+
{ bit_width_and_signedness = y } =
4606+
Int.equal x y
4607+
4608+
let[@inline] bit_width { bit_width_and_signedness } =
4609+
bit_width_and_signedness lsr 1
4610+
4611+
let[@inline] signedness { bit_width_and_signedness } =
4612+
match bit_width_and_signedness land 1 with
4613+
| 0 -> Signedness.Signed
4614+
| 1 -> Signedness.Unsigned
4615+
| _ -> assert false
4616+
4617+
let[@inline] int_of_signedness : Signedness.t -> int = function
4618+
| Signed -> 0
4619+
| Unsigned -> 1
4620+
4621+
let[@inline] create_exn ~bit_width ~signedness =
4622+
assert (0 < bit_width && bit_width <= arch_bits);
4623+
{ bit_width_and_signedness =
4624+
(bit_width lsl 1) + int_of_signedness signedness
4625+
}
4626+
end
4627+
4628+
module Integral_type = struct
4629+
include Bit_width_and_signedness
4630+
4631+
let[@inline] with_signedness t ~signedness =
4632+
create_exn ~bit_width:(bit_width t) ~signedness
4633+
4634+
let[@inline] signed t = with_signedness t ~signedness:Signed
4635+
4636+
let[@inline] unsigned t = with_signedness t ~signedness:Unsigned
4637+
4638+
(** Determines whether [dst] can represent every value of [src], preserving sign *)
4639+
let[@inline] can_cast_without_losing_information ~src ~dst =
4640+
match signedness src, signedness dst with
4641+
| Signed, Signed | Unsigned, Unsigned -> bit_width src <= bit_width dst
4642+
| Unsigned, Signed -> bit_width src < bit_width dst
4643+
| Signed, Unsigned -> false
4644+
4645+
let[@inline] static_cast ~dbg ~src ~dst exp =
4646+
if can_cast_without_losing_information ~src ~dst
4647+
then
4648+
(* Since [Bit_width_and_signedness] represents sign- or zero-extended
4649+
expressions, this is a no-op *)
4650+
exp
4651+
else
4652+
match signedness dst with
4653+
| Signed -> sign_extend ~bits:(bit_width dst) exp ~dbg
4654+
| Unsigned -> zero_extend ~bits:(bit_width dst) exp ~dbg
4655+
4656+
let[@inline] conjugate ~outer ~inner ~dbg ~f x =
4657+
x
4658+
|> static_cast ~src:outer ~dst:inner ~dbg
4659+
|> f
4660+
|> static_cast ~src:inner ~dst:outer ~dbg
4661+
end
4662+
4663+
module Integer = struct
4664+
include Integral_type
4665+
4666+
let print ppf t =
4667+
Format.fprintf ppf "%a int%d" Signedness.print (signedness t)
4668+
(bit_width t)
4669+
4670+
let nativeint = create_exn ~bit_width:arch_bits ~signedness:Signed
4671+
end
4672+
4673+
(** An {!Integer.t} but with the additional stipulation that its container must
4674+
reserve its lowest bit to be 1. The [bit_width] field includes this bit. *)
4675+
module Tagged_integer = struct
4676+
include Integral_type
4677+
4678+
let[@inline] create_exn ~bit_width_including_tag_bit:bit_width ~signedness =
4679+
assert (bit_width > 1);
4680+
create_exn ~bit_width ~signedness
4681+
4682+
let immediate =
4683+
create_exn ~bit_width_including_tag_bit:arch_bits ~signedness:Signed
4684+
4685+
let[@inline] bit_width_including_tag_bit t = bit_width t
4686+
4687+
let[@inline] bit_width_excluding_tag_bit t = bit_width t - 1
4688+
4689+
let[@inline] untagged t =
4690+
Integer.create_exn
4691+
~bit_width:(bit_width_excluding_tag_bit t)
4692+
~signedness:(signedness t)
4693+
4694+
let[@inline] untag ~dbg t exp =
4695+
match signedness t with
4696+
| Signed -> asr_const exp 1 dbg
4697+
| Unsigned -> lsr_const exp 1 dbg
4698+
4699+
let print ppf t =
4700+
Format.fprintf ppf "tagged %a int%d" Signedness.print (signedness t)
4701+
(bit_width_excluding_tag_bit t)
4702+
end
4703+
4704+
module Integral = struct
4705+
type t =
4706+
| Untagged of Integer.t
4707+
| Tagged of Tagged_integer.t
4708+
4709+
let nativeint = Untagged Integer.nativeint
4710+
4711+
let[@inline] untagged_or_identity = function
4712+
| Untagged t -> t
4713+
| Tagged t -> Tagged_integer.untagged t
4714+
4715+
let signedness = function
4716+
| Untagged t -> Integer.signedness t
4717+
| Tagged t -> Tagged_integer.signedness t
4718+
4719+
let with_signedness t ~signedness =
4720+
match t with
4721+
| Untagged t -> Untagged (Integer.with_signedness t ~signedness)
4722+
| Tagged t -> Tagged (Tagged_integer.with_signedness t ~signedness)
4723+
4724+
let[@inline] signed t = with_signedness t ~signedness:Signed
4725+
4726+
let[@inline] unsigned t = with_signedness t ~signedness:Unsigned
4727+
4728+
let[@inline] equal x y =
4729+
match x, y with
4730+
| Untagged x, Untagged y -> Integer.equal x y
4731+
| Untagged _, _ -> false
4732+
| Tagged x, Tagged y -> Tagged_integer.equal x y
4733+
| Tagged _, _ -> false
4734+
4735+
let print ppf t =
4736+
match t with
4737+
| Untagged untagged -> Integer.print ppf untagged
4738+
| Tagged tagged -> Tagged_integer.print ppf tagged
4739+
4740+
let[@inline] can_cast_without_losing_information ~src ~dst =
4741+
Integer.can_cast_without_losing_information
4742+
~src:(untagged_or_identity src) ~dst:(untagged_or_identity dst)
4743+
4744+
let static_cast ~dbg ~src ~dst exp =
4745+
match src, dst with
4746+
| Untagged src, Untagged dst -> Integer.static_cast ~dbg ~src ~dst exp
4747+
| Tagged src, Tagged dst -> Tagged_integer.static_cast ~dbg ~src ~dst exp
4748+
| Untagged src, Tagged dst ->
4749+
tag_int
4750+
(Integer.static_cast ~dbg ~src ~dst:(Tagged_integer.untagged dst) exp)
4751+
dbg
4752+
| Tagged src, Untagged dst ->
4753+
Integer.static_cast ~dbg
4754+
~src:(Tagged_integer.untagged src)
4755+
~dst
4756+
(Tagged_integer.untag ~dbg src exp)
4757+
4758+
let[@inline] conjugate ~outer ~inner ~dbg ~f x =
4759+
x
4760+
|> static_cast ~src:outer ~dst:inner ~dbg
4761+
|> f
4762+
|> static_cast ~src:inner ~dst:outer ~dbg
4763+
end
4764+
4765+
type t =
4766+
| Integral of Integral.t
4767+
| Float of Float_width.t
4768+
4769+
let static_cast ~dbg ~src ~dst exp =
4770+
match src, dst with
4771+
| Integral src, Integral dst -> Integral.static_cast ~dbg ~src ~dst exp
4772+
| Float src, Float dst -> Float_width.static_cast ~dbg ~src ~dst exp
4773+
| Integral src, Float dst ->
4774+
let float_of_int_arg = Integral.nativeint in
4775+
if not
4776+
(Integral.can_cast_without_losing_information ~src
4777+
~dst:float_of_int_arg)
4778+
then
4779+
Misc.fatal_errorf "static_cast: casting %a to float is not implemented"
4780+
Integral.print src
4781+
else
4782+
unary (Cstatic_cast (Float_of_int dst)) ~dbg
4783+
(Integral.static_cast exp ~dbg ~src ~dst:float_of_int_arg)
4784+
| Float src, Integral dst -> (
4785+
match Integral.signedness dst with
4786+
| Unsigned ->
4787+
Misc.fatal_errorf
4788+
"static_cast: casting floats to unsigned values is not implemented"
4789+
| Signed ->
4790+
(* we can truncate because casting from float -> int is unspecified when
4791+
the rounded value doesn't fit in the integral type. We can't promote
4792+
since nativeint is already the largest integral type supported
4793+
here. *)
4794+
let exp = unary (Cstatic_cast (Int_of_float src)) exp ~dbg in
4795+
let src = Integral.nativeint in
4796+
(* assert that nativeint is indeed the largest integer width *)
4797+
assert (Integral.can_cast_without_losing_information ~src:dst ~dst:src);
4798+
Integral.static_cast exp ~dbg ~src ~dst)
4799+
4800+
let[@inline] conjugate ~outer ~inner ~dbg ~f x =
4801+
x
4802+
|> static_cast ~src:outer ~dst:inner ~dbg
4803+
|> f
4804+
|> static_cast ~src:inner ~dst:outer ~dbg
4805+
4806+
module Untagged = struct
4807+
type numeric = t
4808+
4809+
type t =
4810+
| Untagged of Integer.t
4811+
| Float of float_width
4812+
4813+
let to_numeric : t -> numeric = function
4814+
| Untagged width -> Integral (Untagged width)
4815+
| Float float -> Float float
4816+
4817+
let[@inline] static_cast ~dbg ~src ~dst exp =
4818+
static_cast ~dbg ~src:(to_numeric src) ~dst:(to_numeric dst) exp
4819+
end
4820+
end

0 commit comments

Comments
 (0)
Please sign in to comment.