use std::cell::Cell;
use std::cell::RefCell;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use std::time::Instant;
use allocative::Allocative;
use dupe::Dupe;
use itertools::Itertools;
use pagable::PagableDeserialize;
use pagable::PagableDeserializer;
use pagable::PagableSerialize;
use pagable::PagableSerializer;
use starlark_derive::StarlarkPagable;
use starlark_syntax::syntax::ast::Visibility;
use crate as starlark;
use crate::collections::Hashed;
use crate::docs::DocModule;
use crate::docs::DocString;
use crate::docs::DocStringKind;
use crate::environment::EnvironmentError;
use crate::environment::Globals;
use crate::environment::names::FrozenNames;
use crate::environment::names::MutableNames;
use crate::environment::slots::FrozenSlots;
use crate::environment::slots::ModuleSlotId;
use crate::environment::slots::MutableSlots;
use crate::errors::did_you_mean::did_you_mean;
use crate::eval::ProfileData;
use crate::eval::runtime::profile::heap::RetainedHeapProfileMode;
use crate::pagable::StarlarkDeserialize;
use crate::pagable::StarlarkDeserializerImpl;
use crate::pagable::StarlarkSerialize;
use crate::pagable::StarlarkSerializerImpl;
use crate::pagable::starlark_deserialize_context::HeapDeserializationState;
use crate::register_starlark_any;
use crate::singleton_heap_name;
use crate::values::Freeze;
use crate::values::FreezeResult;
use crate::values::Freezer;
use crate::values::FrozenHeap;
use crate::values::FrozenHeapRef;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::OwnedFrozenValue;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::Value;
use crate::values::any::FrozenAnyValue;
use crate::values::layout::heap::heap_type::FrozenHeapName;
use crate::values::layout::heap::heap_type::HeapKind;
use crate::values::layout::heap::profile::aggregated::AggregateHeapProfileInfo;
use crate::values::layout::heap::profile::aggregated::RetainedHeapProfile;
#[derive(Debug, thiserror::Error)]
enum ModuleError {
#[error("Retained memory profiling is not enabled")]
RetainedMemoryProfileNotEnabled,
#[error("Extra value already set to a value of type `{}`", .0)]
ExtraValueAlreadySet(&'static str),
}
#[derive(Debug, Clone, Dupe, Allocative)]
pub struct FrozenModule {
heap: FrozenHeapRef,
module: FrozenAnyValue<FrozenModuleData>,
extra_value: Option<FrozenValue>,
pub(crate) eval_duration: Duration,
}
impl PagableSerialize for FrozenModule {
fn pagable_serialize(&self, serializer: &mut dyn PagableSerializer) -> pagable::Result<()> {
self.heap.pagable_serialize(serializer)?;
let state = StarlarkSerializerImpl::get_or_create_state(serializer);
state.ensure_offset_maps_registered(&self.heap);
let mut ctx = StarlarkSerializerImpl::new(serializer, state);
self.module
.starlark_serialize(&mut ctx)
.map_err(|e: crate::Error| e.into_anyhow())?;
self.extra_value
.starlark_serialize(&mut ctx)
.map_err(|e: crate::Error| e.into_anyhow())?;
drop(ctx);
self.eval_duration.pagable_serialize(serializer)?;
Ok(())
}
}
impl<'de> PagableDeserialize<'de> for FrozenModule {
fn pagable_deserialize<D: PagableDeserializer<'de> + ?Sized>(
deserializer: &mut D,
) -> pagable::Result<Self> {
let heap = FrozenHeapRef::pagable_deserialize(deserializer)?;
let state = StarlarkDeserializerImpl::get_or_create_state(deserializer.as_dyn());
let mut ctx = StarlarkDeserializerImpl::new(
deserializer.as_dyn(),
state,
Arc::new(Mutex::new(HeapDeserializationState::empty())),
);
let module = <FrozenAnyValue<FrozenModuleData>>::starlark_deserialize(&mut ctx)
.map_err(|e: crate::Error| e.into_anyhow())?;
let extra_value = <Option<FrozenValue>>::starlark_deserialize(&mut ctx)
.map_err(|e: crate::Error| e.into_anyhow())?;
drop(ctx);
let eval_duration = Duration::pagable_deserialize(deserializer)?;
Ok(Self {
heap,
module,
extra_value,
eval_duration,
})
}
}
#[derive(Debug, Allocative, StarlarkPagable)]
pub(crate) struct FrozenModuleData {
pub(crate) names: FrozenNames,
pub(crate) slots: FrozenSlots,
docstring: Option<String>,
#[starlark_pagable(skip)]
heap_profile: Option<RetainedHeapProfile>,
}
#[derive(Debug)]
pub struct Module<'v> {
heap: Heap<'v>,
frozen_heap: FrozenHeap,
names: MutableNames,
slots: MutableSlots<'v>,
docstring: RefCell<Option<String>>,
eval_duration: Cell<Duration>,
extra_value: Cell<Option<Value<'v>>>,
heap_profile_on_freeze: Cell<Option<RetainedHeapProfileMode>>,
}
impl FrozenModule {
pub fn from_globals(globals: &Globals) -> FreezeResult<FrozenModule> {
Module::with_temp_heap(|module| {
module.frozen_heap.add_reference(globals.heap());
for (name, value) in globals.iter() {
module.set(name, value.to_value());
}
if let Some(docstring) = globals.docstring() {
module.set_docstring(String::from(docstring));
}
module.freeze_named(FrozenHeapName::Singleton(singleton_heap_name!()))
})
}
fn get_any_visibility_option(&self, name: &str) -> Option<(OwnedFrozenValue, Visibility)> {
self.module.names.get_name(name).and_then(|(slot, vis)|
self.module
.slots
.get_slot(slot)
.map(|x| (unsafe { OwnedFrozenValue::new(self.heap.dupe(), x) }, vis)))
}
#[doc(hidden)]
pub fn get_any_visibility(&self, name: &str) -> anyhow::Result<(OwnedFrozenValue, Visibility)> {
self.get_any_visibility_option(name).ok_or_else(|| {
match did_you_mean(name, self.names().map(|s| s.as_str())) {
Some(better) => EnvironmentError::ModuleHasNoSymbolDidYouMean(
name.to_owned(),
better.to_owned(),
)
.into(),
None => EnvironmentError::ModuleHasNoSymbol(name.to_owned()).into(),
}
})
}
pub fn get_option(&self, name: &str) -> anyhow::Result<Option<OwnedFrozenValue>> {
match self.get_any_visibility_option(name) {
None => Ok(None),
Some((_, Visibility::Private)) => {
Err(EnvironmentError::ModuleSymbolIsNotExported(name.to_owned()).into())
}
Some((value, Visibility::Public)) => Ok(Some(value)),
}
}
pub fn get(&self, name: &str) -> anyhow::Result<OwnedFrozenValue> {
self.get_any_visibility(name)
.and_then(|(value, vis)| match vis {
Visibility::Private => {
Err(EnvironmentError::ModuleSymbolIsNotExported(name.to_owned()).into())
}
Visibility::Public => Ok(value),
})
}
pub fn names(&self) -> impl Iterator<Item = FrozenStringValue> + '_ {
self.module.names()
}
pub fn frozen_heap(&self) -> &FrozenHeapRef {
&self.heap
}
pub fn describe(&self) -> String {
self.module.describe()
}
pub(crate) fn all_items(&self) -> impl Iterator<Item = (FrozenStringValue, FrozenValue)> + '_ {
self.module.all_items()
}
pub fn documentation(&self) -> DocModule {
let members = self
.all_items()
.filter(|n| {
self.get_any_visibility_option(n.0.as_str())
.is_some_and(|(_, vis)| vis == Visibility::Public)
})
.map(|(k, v)| (k.as_str().to_owned(), v.to_value().documentation()))
.collect();
DocModule {
docs: self.module.documentation(),
members,
}
}
pub fn heap_profile(&self) -> anyhow::Result<ProfileData> {
match &self.module.heap_profile {
None => Err(ModuleError::RetainedMemoryProfileNotEnabled.into()),
Some(p) => Ok(p.to_profile()),
}
}
pub fn extra_value(&self) -> Option<FrozenValue> {
self.extra_value
}
pub fn owned_extra_value(&self) -> Option<OwnedFrozenValue> {
self.extra_value
.map(|v| unsafe { OwnedFrozenValue::new(self.heap.dupe(), v) })
}
}
impl FrozenModuleData {
fn names(&self) -> impl Iterator<Item = FrozenStringValue> + '_ {
self.names.symbols().map(|x| x.0)
}
fn describe(&self) -> String {
self.items()
.map(|(name, val)| val.to_value().describe(&name))
.join("\n")
}
fn items(&self) -> impl Iterator<Item = (FrozenStringValue, FrozenValue)> + '_ {
self.names
.symbols()
.filter_map(|(name, slot)| Some((name, self.slots.get_slot(slot)?)))
}
fn all_items(&self) -> impl Iterator<Item = (FrozenStringValue, FrozenValue)> + '_ {
self.names
.all_symbols()
.filter_map(|(name, slot)| Some((name, self.slots.get_slot(slot)?)))
}
pub(crate) fn get_slot(&self, slot: ModuleSlotId) -> Option<FrozenValue> {
self.slots.get_slot(slot)
}
pub(crate) fn get_slot_name(&self, slot: ModuleSlotId) -> Option<FrozenStringValue> {
for (s, i) in self.names.symbols() {
if i == slot {
return Some(s);
}
}
None
}
fn documentation(&self) -> Option<DocString> {
self.docstring
.as_ref()
.and_then(|d| DocString::from_docstring(DocStringKind::Starlark, d))
}
}
impl<'v> Module<'v> {
pub fn with_temp_heap<R, F>(f: F) -> R
where
F: for<'v2> FnOnce(Module<'v2>) -> R,
{
Heap::temp(|h| {
unsafe {
h.allow_gc();
}
f(Module::with_heap(h))
})
}
pub async fn with_temp_heap_async<R, F>(f: F) -> R
where
F: for<'v2> AsyncFnOnce(Module<'v2>) -> R,
{
Heap::temp_async(async |h| {
unsafe {
h.allow_gc();
}
f(Module::with_heap(h)).await
})
.await
}
pub(crate) fn with_heap(heap: Heap<'v>) -> Self {
Self {
heap,
frozen_heap: FrozenHeap::new(),
names: MutableNames::new(),
slots: MutableSlots::new(),
docstring: RefCell::new(None),
eval_duration: Cell::new(Duration::ZERO),
extra_value: Cell::new(None),
heap_profile_on_freeze: Cell::new(None),
}
}
pub(crate) fn enable_retained_heap_profile(&self, mode: RetainedHeapProfileMode) {
self.heap_profile_on_freeze.set(Some(mode));
}
pub fn heap(&self) -> Heap<'v> {
self.heap
}
pub fn frozen_heap(&self) -> &FrozenHeap {
&self.frozen_heap
}
pub fn names(&self) -> impl Iterator<Item = FrozenStringValue> + '_ {
self.names
.all_names_and_visibilities()
.into_iter()
.filter_map(|(name, vis)| {
if vis == Visibility::Public {
Some(name)
} else {
None
}
})
}
pub(crate) fn values_by_slot_id(&self) -> Vec<(ModuleSlotId, Value<'v>)> {
self.slots().values_by_slot_id()
}
pub fn names_and_visibilities(
&self,
) -> impl Iterator<Item = (FrozenStringValue, Visibility)> + '_ {
self.names.all_names_and_visibilities().into_iter()
}
pub(crate) fn mutable_names(&self) -> &MutableNames {
&self.names
}
pub(crate) fn slots(&self) -> &MutableSlots<'v> {
&self.slots
}
pub(crate) fn get_any_visibility(&self, name: Hashed<&str>) -> Option<(Value<'v>, Visibility)> {
let (slot, vis) = self.names.get_name(name)?;
let value = self.slots().get_slot(slot)?;
Some((value, vis))
}
pub fn get(&self, name: &str) -> Option<Value<'v>> {
self.get_any_visibility(Hashed::new(name))
.and_then(|(v, vis)| match vis {
Visibility::Private => None,
Visibility::Public => Some(v),
})
}
#[cfg(not(feature = "pagable"))]
pub fn freeze(self) -> FreezeResult<FrozenModule> {
self.freeze_impl(None)
}
pub fn freeze_named(self, name: FrozenHeapName) -> FreezeResult<FrozenModule> {
self.freeze_impl(Some(name))
}
fn freeze_impl(self, name: Option<FrozenHeapName>) -> FreezeResult<FrozenModule> {
let Module {
names,
slots,
frozen_heap,
heap,
docstring,
eval_duration,
extra_value,
heap_profile_on_freeze,
} = self;
#[cfg(not(target_arch = "wasm32"))]
let start = Instant::now();
let freezer = Freezer::new(&frozen_heap);
for r in heap.referenced_heaps() {
frozen_heap.add_reference(&r);
}
let slots = slots.freeze(&freezer)?;
let extra_value = extra_value.into_inner().freeze(&freezer)?;
let stacks = if let Some(mode) = heap_profile_on_freeze.get() {
let heap_profile = AggregateHeapProfileInfo::collect(heap, Some(HeapKind::Frozen));
Some(RetainedHeapProfile {
info: heap_profile,
mode,
})
} else {
None
};
let rest = FrozenModuleData {
names: names.freeze(),
slots,
docstring: docstring.into_inner(),
heap_profile: stacks,
};
let frozen_module_ref = freezer.heap.alloc_any_value(rest);
for frozen_def in freezer.frozen_defs.borrow().as_slice() {
frozen_def.post_freeze(frozen_module_ref, heap, freezer.heap);
}
Ok(FrozenModule {
heap: frozen_heap.into_ref_impl(name, Some(heap.peak_allocated_bytes())),
module: frozen_module_ref,
extra_value,
#[cfg(not(target_arch = "wasm32"))]
eval_duration: start.elapsed() + eval_duration.get(),
#[cfg(target_arch = "wasm32")]
eval_duration: eval_duration.get(),
})
}
pub fn set(&self, name: &str, value: Value<'v>) {
let slot = self.names.add_name(self.frozen_heap.alloc_str_intern(name));
let slots = self.slots();
slots.ensure_slot(slot);
slots.set_slot(slot, value);
}
pub(crate) fn default_visibility(symbol: &str) -> Visibility {
match symbol.starts_with('_') {
true => Visibility::Private,
false => Visibility::Public,
}
}
pub(crate) fn set_private(&self, name: FrozenStringValue, value: Value<'v>) {
let slot = self.names.add_name_visibility(name, Visibility::Private);
let slots = self.slots();
slots.ensure_slot(slot);
slots.set_slot(slot, value);
}
pub fn import_public_symbols(&self, module: &FrozenModule) {
self.frozen_heap.add_reference(&module.heap);
for (k, slot) in module.module.names.symbols() {
if Self::default_visibility(&k) == Visibility::Public {
if let Some(value) = module.module.slots.get_slot(slot) {
self.set_private(k, Value::new_frozen(value))
}
}
}
}
pub(crate) fn load_symbol(
&self,
module: &FrozenModule,
symbol: &str,
) -> crate::Result<Value<'v>> {
if Self::default_visibility(symbol) != Visibility::Public {
return Err(crate::Error::new_other(
EnvironmentError::CannotImportPrivateSymbol(symbol.to_owned()),
));
}
match module.get_any_visibility(symbol)? {
(v, Visibility::Public) => Ok(self.heap().access_owned_frozen_value(&v)),
(_, Visibility::Private) => Err(crate::Error::new_other(
EnvironmentError::ModuleSymbolIsNotExported(symbol.to_owned()),
)),
}
}
pub(crate) fn set_docstring(&self, docstring: String) {
self.docstring.replace(Some(docstring));
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn add_eval_duration(&self, duration: Duration) {
self.eval_duration.set(self.eval_duration.get() + duration);
}
pub(crate) fn trace(&self, tracer: &Tracer<'v>) {
self.slots().get_slots_mut().trace(tracer);
let extra_value = self.extra_value();
if let Some(mut extra_value) = extra_value {
extra_value.trace(tracer);
self.set_extra_value(extra_value);
}
self.heap().trace_interner(tracer);
}
pub fn set_extra_value(&self, v: Value<'v>) {
self.extra_value.set(Some(v));
}
pub fn set_extra_value_no_overwrite(&self, v: Value<'v>) -> anyhow::Result<()> {
if let Some(existing) = self.extra_value() {
return Err(ModuleError::ExtraValueAlreadySet(existing.get_type()).into());
}
self.set_extra_value(v);
Ok(())
}
pub fn extra_value(&self) -> Option<Value<'v>> {
self.extra_value.get()
}
}
#[test]
fn test_send_sync()
where
FrozenModule: Send + Sync,
{
}
#[cfg(test)]
mod tests {
use starlark_derive::starlark_module;
use crate as starlark;
use crate::environment::FrozenModule;
use crate::environment::Globals;
use crate::environment::GlobalsBuilder;
use crate::environment::Module;
use crate::eval::Evaluator;
use crate::eval::runtime::profile::mode::ProfileMode;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::values::layout::heap::heap_type::StarlarkTestHeapName;
use crate::values::list::ListRef;
#[test]
fn test_gen_heap_summary_profile() {
Module::with_temp_heap(|module| {
{
let mut eval = Evaluator::new(&module);
eval.enable_profile(&ProfileMode::HeapSummaryRetained)
.unwrap();
eval.eval_module(
AstModule::parse(
"x.star",
r"
def f(x):
return list([x])
x = f(1)
"
.to_owned(),
&Dialect::AllOptionsInternal,
)
.unwrap(),
&Globals::standard(),
)
.unwrap();
}
let module = module.freeze_named(StarlarkTestHeapName::frozen_heap_name())?;
let heap_summary = module.heap_profile().unwrap().gen_csv().unwrap();
assert!(heap_summary.contains("\"x.star.f\""), "{heap_summary:?}");
crate::Result::Ok(())
})
.unwrap();
}
#[test]
fn test_frozen_module_from_globals() {
#[starlark_module]
fn some_globals(globals: &mut GlobalsBuilder) {
fn foo() -> anyhow::Result<i32> {
Ok(17)
}
const BAR: Vec<String> = Vec::new();
}
let mut globals = GlobalsBuilder::new();
some_globals(&mut globals);
let globals = globals.build();
let module = FrozenModule::from_globals(&globals).unwrap();
assert_eq!("function", module.get("foo").unwrap().value().get_type());
assert_eq!(
0,
ListRef::from_value(module.get("BAR").unwrap().value())
.unwrap()
.len()
);
}
}
register_starlark_any!(FrozenModuleData);