starlark 0.8.0

An implementation of the Starlark language in Rust.
Documentation
/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Address types used in bytecode interpreter.

use std::{
    marker, mem,
    ops::{Add, AddAssign, Sub},
};

use derive_more::Display;
use gazebo::dupe::Dupe;

use crate::eval::bc::{
    if_debug::IfDebug,
    instr::BcInstr,
    opcode::BcOpcode,
    repr::{BcInstrHeader, BcInstrRepr},
};

/// Address relative to bytecode start.
#[derive(
    Eq, PartialEq, Copy, Clone, Dupe, Debug, Ord, PartialOrd, Display, Hash
)]
#[display(fmt = "@{}", _0)]
pub(crate) struct BcAddr(pub(crate) u32);

impl BcAddr {
    pub(crate) fn offset_from(self, start: BcAddr) -> BcAddrOffset {
        debug_assert!(self >= start);
        BcAddrOffset(self.0 - start.0)
    }

    pub(crate) fn offset(self, offset: BcAddrOffset) -> BcAddr {
        BcAddr(self.0 + offset.0)
    }
}

impl Sub<u32> for BcAddr {
    type Output = BcAddr;

    fn sub(self, rhs: u32) -> BcAddr {
        BcAddr(self.0.checked_sub(rhs).unwrap())
    }
}

impl Add<u32> for BcAddr {
    type Output = BcAddr;

    fn add(self, rhs: u32) -> BcAddr {
        BcAddr(self.0 + rhs)
    }
}

impl AddAssign<u32> for BcAddr {
    fn add_assign(&mut self, rhs: u32) {
        self.0 += rhs;
    }
}

/// Valid pointer range of bytecode.
/// Used for debugging assertions. This objects is not created in release mode.
#[derive(Copy, Clone, Dupe, Debug, PartialEq)]
pub(crate) struct BcPtrRange {
    start: *const u8,
    len: usize,
}

impl BcPtrRange {
    pub(crate) fn for_slice(slice: &[usize]) -> BcPtrRange {
        BcPtrRange {
            start: slice.as_ptr() as *const u8,
            len: slice.len() * mem::size_of::<usize>(),
        }
    }

    pub(crate) fn assert_in_range(&self, ptr: *const u8) {
        let offset = unsafe { ptr.offset_from(self.start) };
        assert!(offset >= 0);
        assert!(offset as usize <= self.len);
    }

    fn end(&self) -> *const u8 {
        unsafe { self.start.add(self.len) }
    }
}

/// Pointer to an instruction in memory.
#[derive(Copy, Clone, Dupe, PartialOrd, PartialEq, Debug)]
// Lifetime here is mostly for documentation, it does not guarantee anything.
pub(crate) struct BcPtrAddr<'b> {
    ptr: *const u8,
    /// When assertions enabled, we validate the pointer is in this range.
    range: IfDebug<BcPtrRange>,
    _marker: marker::PhantomData<&'b u8>,
}

impl<'b> BcPtrAddr<'b> {
    /// Constructor.
    unsafe fn new(ptr: *const u8, range: IfDebug<BcPtrRange>) -> BcPtrAddr<'b> {
        range.if_debug(|range| range.assert_in_range(ptr));
        BcPtrAddr {
            ptr,
            range,
            _marker: marker::PhantomData,
        }
    }

    /// Create a pointer for the beginning of the slice.
    pub(crate) fn for_slice_start(slice: &'b [usize]) -> BcPtrAddr<'b> {
        unsafe {
            BcPtrAddr::new(
                slice.as_ptr() as *const u8,
                IfDebug::new(BcPtrRange::for_slice(slice)),
            )
        }
    }

    /// Create a pointer for the beginning of the slice.
    pub(crate) fn for_slice_end(slice: &'b [usize]) -> BcPtrAddr<'b> {
        unsafe {
            BcPtrAddr::new(
                slice.as_ptr().add(slice.len()) as *const u8,
                IfDebug::new(BcPtrRange::for_slice(slice)),
            )
        }
    }

    /// Distance from current ptr to the end of instructions.
    fn remaining_if_debug(self) -> usize {
        unsafe { self.range.get_ref_if_debug().end().offset_from(self.ptr) as usize }
    }

    pub(crate) fn get_instr<I: BcInstr>(self) -> &'b BcInstrRepr<I> {
        debug_assert!(self.remaining_if_debug() >= mem::size_of::<BcInstrRepr<I>>());
        let ptr = self.ptr as *const BcInstrRepr<I>;
        let repr = unsafe { &*ptr };
        debug_assert_eq!(repr.header.opcode, BcOpcode::for_instr::<I>());
        repr
    }

    pub(crate) fn get_instr_mut<I: BcInstr>(self) -> *mut BcInstrRepr<I> {
        debug_assert!(self.remaining_if_debug() >= mem::size_of::<BcInstrRepr<I>>());
        self.ptr as *mut BcInstrRepr<I>
    }

    pub(crate) fn get_opcode(self) -> BcOpcode {
        debug_assert!(self.remaining_if_debug() >= mem::size_of::<BcInstrHeader>());
        let ptr = self.ptr as *const BcInstrHeader;
        unsafe { (*ptr).opcode }
    }

    pub(crate) fn offset_from(self, start: BcPtrAddr) -> BcAddr {
        debug_assert!(self.range.get_ref_if_debug() == start.range.get_ref_if_debug());
        unsafe {
            let offset = self.ptr.offset_from(start.ptr);
            debug_assert!(offset >= 0);
            debug_assert!(offset <= i32::MAX as isize);
            BcAddr(offset as u32)
        }
    }

    pub(crate) fn sub(self, start: BcAddr) -> BcPtrAddr<'b> {
        unsafe { BcPtrAddr::new(self.ptr.sub(start.0 as usize), self.range) }
    }

    pub(crate) fn offset(self, addr: BcAddr) -> BcPtrAddr<'b> {
        self.add(addr.0 as usize)
    }

    pub(crate) fn add_rel(self, rel: BcAddrOffset) -> BcPtrAddr<'b> {
        self.add(rel.0 as usize)
    }

    pub(crate) fn add(self, offset: usize) -> BcPtrAddr<'b> {
        unsafe { BcPtrAddr::new(self.ptr.add(offset), self.range) }
    }

    pub(crate) fn add_instr<I: BcInstr>(self) -> BcPtrAddr<'b> {
        self.add_rel(BcAddrOffset::for_instr::<I>())
    }
}

/// Difference between addresses.
#[derive(Eq, PartialEq, Copy, Clone, Dupe, Debug, Ord, PartialOrd, Display)]
#[display(fmt = "{}", _0)]
pub(crate) struct BcAddrOffset(pub(crate) u32);

impl BcAddrOffset {
    /// Pointer to not yet known address.
    pub(crate) const FORWARD: BcAddrOffset = BcAddrOffset(0xdeadbeef);

    /// Size of an instruction.
    fn for_instr<I: BcInstr>() -> BcAddrOffset {
        <BcInstrRepr<I>>::assert_align();
        BcAddrOffset(mem::size_of::<BcInstrRepr<I>>() as u32)
    }
}