11use rustc_hir:: def:: DefKind ;
22use rustc_middle:: mir;
33use rustc_middle:: mir:: interpret:: PointerArithmetic ;
4- use rustc_middle:: ty:: layout:: FnAbiOf ;
4+ use rustc_middle:: ty:: layout:: LayoutOf ;
55use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
66use std:: borrow:: Borrow ;
77use std:: hash:: Hash ;
8- use std:: ops:: ControlFlow ;
98
109use rustc_data_structures:: fx:: FxIndexMap ;
1110use rustc_data_structures:: fx:: IndexEntry ;
@@ -20,8 +19,8 @@ use rustc_target::abi::{Align, Size};
2019use rustc_target:: spec:: abi:: Abi as CallAbi ;
2120
2221use crate :: interpret:: {
23- self , compile_time_machine, AllocId , ConstAllocation , FnVal , Frame , ImmTy , InterpCx ,
24- InterpResult , OpTy , PlaceTy , Pointer , Scalar , StackPopUnwind ,
22+ self , compile_time_machine, AllocId , ConstAllocation , Frame , ImmTy , InterpCx , InterpResult ,
23+ OpTy , PlaceTy , Pointer , Scalar , StackPopUnwind ,
2524} ;
2625
2726use super :: error:: * ;
@@ -156,7 +155,6 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
156155 fn hook_special_const_fn (
157156 & mut self ,
158157 instance : ty:: Instance < ' tcx > ,
159- _abi : CallAbi ,
160158 args : & [ OpTy < ' tcx > ] ,
161159 dest : & PlaceTy < ' tcx > ,
162160 ret : Option < mir:: BasicBlock > ,
@@ -194,24 +192,21 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
194192
195193 return Ok ( Some ( new_instance) ) ;
196194 } else if Some ( def_id) == self . tcx . lang_items ( ) . align_offset_fn ( ) {
197- // For align_offset, we replace the function call if the pointer has no address.
198- match self . align_offset ( instance, args, dest, ret) ? {
199- ControlFlow :: Continue ( ( ) ) => return Ok ( Some ( instance) ) ,
200- ControlFlow :: Break ( ( ) ) => return Ok ( None ) ,
201- }
195+ // For align_offset, we replace the function call entirely.
196+ self . align_offset ( instance, args, dest, ret) ?;
197+ return Ok ( None ) ;
202198 }
203199 Ok ( Some ( instance) )
204200 }
205201
206- /// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
207- /// may not have an address.
202+ /// This function replaces `align_offset(ptr, target_align)` in const eval, because the
203+ /// pointer may not have an address.
208204 ///
209- /// If `ptr` does have a known address, then we return `CONTINUE` and the function call should
210- /// proceed as normal.
205+ /// If `ptr` does have a known address, we forward it to [`Self::align_offset_impl`].
211206 ///
212207 /// If `ptr` doesn't have an address, but its underlying allocation's alignment is at most
213- /// `target_align`, then we call the function again with an dummy address relative to the
214- /// allocation.
208+ /// `target_align`, then we call [`Self::align_offset_impl`] with an dummy address relative
209+ /// to the allocation.
215210 ///
216211 /// If `ptr` doesn't have an address and `target_align` is stricter than the underlying
217212 /// allocation's alignment, then we return `usize::MAX` immediately.
@@ -221,50 +216,100 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
221216 args : & [ OpTy < ' tcx > ] ,
222217 dest : & PlaceTy < ' tcx > ,
223218 ret : Option < mir:: BasicBlock > ,
224- ) -> InterpResult < ' tcx , ControlFlow < ( ) > > {
219+ ) -> InterpResult < ' tcx > {
225220 assert_eq ! ( args. len( ) , 2 ) ;
226221
227222 let ptr = self . read_pointer ( & args[ 0 ] ) ?;
228223 let target_align = self . read_scalar ( & args[ 1 ] ) ?. to_machine_usize ( self ) ?;
229224
225+ let pointee_ty = instance. substs . type_at ( 0 ) ;
226+ let stride = self . layout_of ( pointee_ty) ?. size . bytes ( ) ;
227+
230228 if !target_align. is_power_of_two ( ) {
231229 throw_ub_format ! ( "`align_offset` called with non-power-of-two align: {}" , target_align) ;
232230 }
233231
234- match self . ptr_try_get_alloc_id ( ptr) {
232+ let mut align_offset = match self . ptr_try_get_alloc_id ( ptr) {
235233 Ok ( ( alloc_id, offset, _extra) ) => {
234+ // Extract the address relative to a base that is definitely sufficiently aligned.
236235 let ( _size, alloc_align, _kind) = self . get_alloc_info ( alloc_id) ;
237236
238237 if target_align <= alloc_align. bytes ( ) {
239- // Extract the address relative to the allocation base that is definitely
240- // sufficiently aligned and call `align_offset` again.
241- let addr = ImmTy :: from_uint ( offset. bytes ( ) , args[ 0 ] . layout ) . into ( ) ;
242- let align = ImmTy :: from_uint ( target_align, args[ 1 ] . layout ) . into ( ) ;
243-
244- let fn_abi = self . fn_abi_of_instance ( instance, ty:: List :: empty ( ) ) ?;
245- self . eval_fn_call (
246- FnVal :: Instance ( instance) ,
247- ( CallAbi :: Rust , fn_abi) ,
248- & [ addr, align] ,
249- false ,
250- dest,
251- ret,
252- StackPopUnwind :: NotAllowed ,
253- ) ?;
254- Ok ( ControlFlow :: BREAK )
238+ // The pointer *is* alignable in const. We use an address relative to the
239+ // allocation base that is definitely sufficiently aligned.
240+ let addr = offset. bytes ( ) ;
241+ Self :: align_offset_impl ( addr, stride, target_align)
255242 } else {
256- // Not alignable in const, return `usize::MAX`.
257- let usize_max = Scalar :: from_machine_usize ( self . machine_usize_max ( ) , self ) ;
258- self . write_scalar ( usize_max, dest) ?;
259- self . return_to_block ( ret) ?;
260- Ok ( ControlFlow :: BREAK )
243+ // The pointer *is not* alignable in const, return `usize::MAX`.
244+ // (We clamp this to machine `usize` below.)
245+ u64:: MAX
261246 }
262247 }
263- Err ( _addr ) => {
264- // The pointer has an address, continue with function call .
265- Ok ( ControlFlow :: CONTINUE )
248+ Err ( addr ) => {
249+ // The pointer has a known address .
250+ Self :: align_offset_impl ( addr , stride , target_align )
266251 }
252+ } ;
253+
254+ let usize_max = self . machine_usize_max ( ) ;
255+ if align_offset > usize_max {
256+ align_offset = usize_max;
257+ }
258+
259+ self . write_scalar ( Scalar :: from_machine_usize ( align_offset, self ) , dest) ?;
260+ self . return_to_block ( ret) ?;
261+
262+ Ok ( ( ) )
263+ }
264+
265+ /// Const eval implementation of `#[lang = "align_offset"]`.
266+ /// See the runtime version for a detailed explanation how this works.
267+ fn align_offset_impl ( addr : u64 , stride : u64 , align : u64 ) -> u64 {
268+ assert ! ( align. is_power_of_two( ) ) ;
269+
270+ let addr_mod_align = addr % align;
271+
272+ if addr_mod_align == 0 {
273+ // The address is already sufficiently aligned.
274+ return 0 ;
267275 }
276+
277+ if stride == 0 {
278+ // The address cannot be aligned.
279+ return u64:: MAX ;
280+ }
281+
282+ let byte_offset = align - addr_mod_align;
283+
284+ if align % stride == 0 {
285+ if byte_offset % stride == 0 {
286+ return byte_offset / stride;
287+ } else {
288+ return u64:: MAX ;
289+ }
290+ }
291+
292+ // This only works, because `align` is a power of two.
293+ let gcd = 1u64 << ( stride | align) . trailing_zeros ( ) ;
294+
295+ if addr % gcd != 0 {
296+ // The address cannot be aligned.
297+ return u64:: MAX ;
298+ }
299+
300+ let align2 = align / gcd;
301+ let stride2 = ( stride / gcd) % align2;
302+
303+ let mut stride_inv = 1u64 ;
304+ let mut mod_gate = 2u64 ;
305+ let mut overflow = false ;
306+ while !overflow && mod_gate < align2 {
307+ stride_inv =
308+ stride_inv. wrapping_mul ( 2u64 . wrapping_sub ( stride2. wrapping_mul ( stride_inv) ) ) ;
309+ ( mod_gate, overflow) = mod_gate. overflowing_mul ( mod_gate) ;
310+ }
311+
312+ byte_offset. wrapping_mul ( stride_inv) % align2
268313 }
269314
270315 /// See documentation on the `ptr_guaranteed_cmp` intrinsic.
@@ -367,7 +412,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
367412 }
368413 }
369414
370- let Some ( new_instance) = ecx. hook_special_const_fn ( instance, _abi , args, dest, ret) ? else {
415+ let Some ( new_instance) = ecx. hook_special_const_fn ( instance, args, dest, ret) ? else {
371416 return Ok ( None ) ;
372417 } ;
373418
0 commit comments