Безопасно ли определенное поведение для трансмутации между T и UnsafeCell <T>? - PullRequest
0 голосов
/ 20 мая 2018

A недавний вопрос искал способность строить само-ссылочные структуры.При обсуждении возможных ответов на вопрос один потенциальный ответ включал использование UnsafeCell для внутренней изменчивости, а затем "отбрасывание" изменчивости через transmute.

* 1010.* Вот небольшой пример такой идеи в действии.Меня не очень интересует сам пример, но достаточно сложностей, чтобы потребовать больший молот, такой как transmute, а не просто использовать UnsafeCell::new и / или UnsafeCell::into_inner:
use std::{
    cell::UnsafeCell, mem, rc::{Rc, Weak},
};

// This is our real type.
struct ReallyImmutable {
    value: i32,
    myself: Weak<ReallyImmutable>,
}

fn initialize() -> Rc<ReallyImmutable> {
    // This mirrors ReallyImmutable but we use `UnsafeCell` 
    // to perform some initial interior mutation.
    struct NotReallyImmutable {
        value: i32,
        myself: Weak<UnsafeCell<NotReallyImmutable>>,
    }

    let initial = NotReallyImmutable {
        value: 42,
        myself: Weak::new(),
    };

    // Without interior mutability, we couldn't update the `myself` field
    // after we've created the `Rc`.
    let second = Rc::new(UnsafeCell::new(initial));

    // Tie the recursive knot 
    let new_myself = Rc::downgrade(&second);

    unsafe {
        // Should be safe as there can be no other accesses to this field
        (&mut *second.get()).myself = new_myself;

        // No one outside of this function needs the interior mutability
        // TODO: Is this call safe?
        mem::transmute(second)
    }
}

fn main() {
    let v = initialize();
    println!("{} -> {:?}", v.value, v.myself.upgrade().map(|v| v.value))
}

Этот код выводит на печать то, что я ожидал, но это не значит, что он безопасен или использует определенную семантику.

Передает из UnsafeCell<T>к T памяти безопасно?Это вызывает неопределенное поведение?Как насчет трансмутации в обратном направлении, от T до UnsafeCell<T>?

1 Ответ

0 голосов
/ 22 мая 2018

(я все еще новичок в SO и не уверен, что «ну, может быть» квалифицируется как ответ, но вот, пожалуйста.)

Отказ от ответственности: правил для такого рода вещей нет (пока) высечены в камне.Итак, окончательного ответа пока нет.Я собираюсь сделать некоторые предположения, основанные на (а), какие виды преобразований компилятора LLVM делает / мы в конечном итоге захотим сделать, и (б) какие модели у меня в голове, которые будут определять ответ на этот вопрос.

Кроме того, я вижу две части этого: перспектива компоновки данных и перспектива наложения имен.Проблема с компоновкой заключается в том, что NotReallyImmutable может в принципе иметь совершенно иную компоновку, чем ReallyImmutable.Я не знаю много о разметке данных, но с UnsafeCell становится repr(transparent) и это единственное различие между этими двумя типами, я думаю, намерение это для того, чтобы это работало.Однако вы полагаетесь на то, что repr(transparent) является «структурным» в том смысле, что это должно позволить вам заменять вещи более крупными типами, что, я не уверен, было записано где-либо явно.Похоже на предложение о последующем RFC, который соответствующим образом расширяет гарантии repr(transparent)?

Что касается псевдонимов, проблема заключается в нарушении правил вокруг &T.Я бы сказал, что до тех пор, пока у вас нигде нет живого &T, когда вы пишете через &UnsafeCell<T>, вы хороши - но я не думаю, что мы можем гарантировать это еще.Давайте рассмотрим более подробно.

Перспектива компилятора

Соответствующие оптимизации - это те, которые используют &T только для чтения.Поэтому, если вы переупорядочите последние две строки (transmute и присвоение), этот код, скорее всего, будет UB, так как мы можем захотеть, чтобы компилятор мог «предварительно извлекать» значение за общей ссылкой и повторно использовать это значениепозже (т.е. после встраивания этого).

Но в вашем коде мы будем генерировать аннотации "только для чтения" (noalias в LLVM) после того, как transmute вернется, и данные действительно будут прочитанытолько начиная там.Итак, это должно быть хорошо.

Модели памяти

"Наиболее агрессивные" из моих моделей памяти по сути утверждают, что все значения всегда действительны , и я думаю, что даже этомодель должна быть в порядке с вашим кодом.&UnsafeCell - это особый случай в этой модели, где валидность просто прекращается, и ничего не сказано о том, что стоит за этой ссылкой.В тот момент, когда transmute возвращается, мы берем память, на которую он указывает, и делаем все это только для чтения, и даже если мы сделали это "рекурсивно" через Rc (чего не делает моя модель, но только потому, что я не могне поймете, как это сделать), все будет хорошо, так как вы больше не будете мутировать после transmute.(Как вы могли заметить, это то же ограничение, что и в перспективе компилятора. Смысл этих моделей, в конце концов, заключается в том, чтобы разрешить оптимизацию компилятора.;)

(Как примечание, я действительно хочуСейчас miri была в лучшей форме. Кажется, мне нужно попытаться заставить проверку работать снова там, потому что тогда я могу сказать вам, что нужно просто запустить ваш код в miri, и он скажет вам, подходит ли эта версия моей моделичто вы делаете: D)

Я думаю о других моделях, которые в настоящее время проверяют только «при доступе», но еще не разработали историю UnsafeCell для этой модели.Этот пример показывает, что модель может содержать пути «фазового перехода» памяти: сначала UnsafeCell, но затем с нормальным совместным использованием с гарантиями только для чтения.Спасибо за то, что подняли этот вопрос, и это послужит хорошим примером для размышлений!

Итак, я думаю, что могу сказать, что (по крайней мере, с моей стороны) есть намерение 1047 *, чтобы позволитьэтот вид кода, и это, похоже, не препятствует какой-либо оптимизации.Удастся ли нам найти модель, с которой все могут согласиться и которая все еще позволяет это, я не могу предсказать.

Противоположное направление: T -> UnsafeCell<T>

Теперь это более интересно.Проблема в том, что, как я сказал выше, вы не должны иметь &T live при записи через UnsafeCell<T>.Но что здесь означает «жить»?Это сложный вопрос!В некоторых моих моделях это может быть настолько слабым, что «ссылка такого типа существует где-то, а время жизни все еще активно», т. Е. Не может иметь никакого отношения к тому, действительно ли ссылка используется используется .(Это полезно, потому что это позволяет нам проводить больше оптимизаций, например, перемещать нагрузку из цикла, даже если мы не можем доказать, что цикл когда-либо выполняется - что привело бы к использованию неиспользуемой ссылки в противном случае.) И так как &T - это Copy, вы даже не можете избавиться от такой ссылки.Итак, если у вас есть x: &T, то после let y: &UnsafeCell<T> = transmute(x) старый x все еще существует и его время жизни все еще активно, поэтому запись через y вполне может быть UB.

Я думаю, что вы 'Я должен каким-то образом ограничить псевдонимы, которые &T позволяет, очень тщательно следя за тем, чтобы никто не сохранил такую ​​ссылку.Я не собираюсь говорить «это невозможно», потому что люди продолжают удивлять меня (особенно в этом сообществе;), но я не могу придумать, как сделать эту работу.Мне было бы любопытно, если у вас есть пример, хотя вы думаете, что это разумно.

...