Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 55 additions & 41 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ impl<'a> FnCompiler<'a> {
self.compile_storage_access(
context,
md_mgr,
access.storage_field_names.clone(),
access.storage_field_path.clone(),
access.struct_field_names.clone(),
key,
&access.fields,
Expand Down Expand Up @@ -5494,7 +5494,7 @@ impl<'a> FnCompiler<'a> {
&mut self,
context: &mut Context,
md_mgr: &mut MetadataManager,
storage_field_names: Vec<String>,
storage_field_path: Vec<String>,
struct_field_names: Vec<String>,
key: Option<U256>,
fields: &[ty::TyStorageAccessDescriptor],
Expand All @@ -5509,7 +5509,7 @@ impl<'a> FnCompiler<'a> {
&fields[1..],
)?;

// Get the IR type of the storage variable.
// Get the IR type of the storage field.
let base_type = convert_resolved_typeid_no_span(
self.engines,
context,
Expand All @@ -5522,7 +5522,7 @@ impl<'a> FnCompiler<'a> {
self.compile_get_storage_key(
context,
md_mgr,
storage_field_names,
storage_field_path,
struct_field_names,
key,
&field_indices,
Expand Down Expand Up @@ -5616,72 +5616,86 @@ impl<'a> FnCompiler<'a> {
&mut self,
context: &mut Context,
md_mgr: &mut MetadataManager,
storage_field_names: Vec<String>,
storage_field_path: Vec<String>,
struct_field_names: Vec<String>,
key: Option<U256>,
indices: &[u64],
base_type: &Type,
span_md_idx: Option<MetadataIndex>,
) -> Result<TerminatorValue, CompileError> {
let storage_field_key = get_storage_key(&storage_field_path, key);
let (path, field_id) =
get_storage_field_path_and_field_id(&storage_field_names, &struct_field_names);
get_storage_field_path_and_field_id(&storage_field_path, &struct_field_names);

let storage_key = match self.module.get_storage_key(context, &path) {
Some(storage_key) => *storage_key,
None => {
// Get the actual storage key as a `Bytes32` as well as the offset, in words,
// within the slot. The offset depends on what field of the top level storage
// variable is being accessed.
let (slot, offset_within_slot) = {
let offset_in_words = match base_type.get_indexed_offset(context, indices) {
Some(offset_in_bytes) => {
// TODO-MEMLAY: Warning! Here we make an assumption about the memory layout of structs.
// The memory layout of structs can be changed in the future.
// We will not refactor the Storage API at the moment to remove this
// assumption. It is a questionable effort because we anyhow
// want to improve and refactor Storage API in the future.
assert!(
offset_in_bytes % 8 == 0,
"Expected struct fields to be aligned to word boundary. The field offset in bytes was {offset_in_bytes}.",
);
offset_in_bytes / 8
}
None => return Err(CompileError::Internal(
"Cannot get the offset within the slot while compiling storage read.",
let offset_in_bytes = match base_type.get_indexed_offset(context, indices) {
Some(offset_in_bytes) => {
// TODO-MEMLAY: Warning! Here we make an assumption about the memory layout of structs.
// The memory layout of structs can be changed in the future.
// We will not refactor the Storage API at the moment to remove this
// assumption. It is a questionable effort because we anyhow
// want to improve and refactor Storage API in the future.
assert!(
offset_in_bytes % 8 == 0,
"Expected struct fields to be aligned to word boundary. The field offset in bytes was {offset_in_bytes}.",
);
offset_in_bytes
}
None => {
return Err(CompileError::Internal(
"Cannot get the offset within the slot while compiling getting of storage key.",
md_mgr
.md_to_span(context, span_md_idx)
.unwrap_or_else(Span::dummy),
)),
};
let offset_in_slots = offset_in_words / 4;
let offset_remaining = offset_in_words % 4;

// The storage key we need is the storage key of the top level storage variable
// plus the offset, in number of slots, computed above. The offset within this
// particular slot is the remaining offset, in words.
(
add_to_b256(get_storage_key(storage_field_names, key), offset_in_slots),
offset_remaining,
)
))
}
};

let storage_key =
StorageKey::new(context, slot.into(), offset_within_slot, field_id.into());
let storage_key = if context.experimental.dynamic_storage {
StorageKey::new(
context,
storage_field_key.into(),
offset_in_bytes,
storage_field_key.into(),
)
} else {
// Get the actual storage key of the slot, as a `Bytes32`, as well as the offset, in words,
// within that slot. The offset depends on what field of the top level storage
// variable is being accessed.
let offset_in_words = offset_in_bytes / 8;

let (slot, offset_within_slot) = {
let offset_in_slots = offset_in_words / 4;
let offset_remaining = offset_in_words % 4;

// The storage key we need is the storage key of the top level storage variable
// plus the offset, in number of slots, computed above. The offset within this
// particular slot is the remaining offset, in words.
(
add_to_b256(storage_field_key, offset_in_slots),
offset_remaining,
)
};

StorageKey::new(context, slot.into(), offset_within_slot, field_id.into())
};

self.module.add_storage_key(context, path, storage_key);

storage_key
}
};

let storage_key = self
let get_storage_key_inst = self
.current_block
.append(context)
.get_storage_key(storage_key)
.add_metadatum(context, span_md_idx);

Ok(TerminatorValue::new(
CompiledValue::InMemory(storage_key),
CompiledValue::InMemory(get_storage_key_inst),
context,
))
}
Expand Down
6 changes: 3 additions & 3 deletions sway-core/src/ir_generation/purity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub(crate) fn check_function_purity(
| FuelVmInstruction::StateStoreQuadWord { .. }
| FuelVmInstruction::StateWriteSlot { .. }
| FuelVmInstruction::StateStoreWord { .. } => (reads, true),
FuelVmInstruction::StateUpdateSlot { .. } => (true, true),
FuelVmInstruction::StateUpdateSlot { .. } => (reads, true),
_ => unreachable!("The FuelVM instruction is checked to be a store access instruction."),
}
}
Expand All @@ -89,7 +89,7 @@ pub(crate) fn check_function_purity(
match inst {
"srw" | "srwq" | "srdd" | "srdi" | "spld" => (true, writes),
"scwq" | "sclr" | "sww" | "swwq" | "swrd" | "swri" => (reads, true),
"supd" | "supi" => (true, true),
"supd" | "supi" => (reads, true),
_ => unreachable!("The ASM instruction is checked to be a store access instruction."),
}
} else {
Expand Down Expand Up @@ -129,7 +129,7 @@ pub(crate) fn check_function_purity(
// Simple closures for each of the error types.
let error = |span: Span, needed| {
// We don't emit errors on the generated `__entry` function
// but do on the original entry functions and all other functions.
// but do emit on the original entry functions and all other functions.
if !function.is_entry(context) || function.is_original_entry(context) {
handler.emit_err(CompileError::StorageAccessMismatched {
span,
Expand Down
54 changes: 30 additions & 24 deletions sway-core/src/ir_generation/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,50 +20,55 @@ enum InByte8Padding {
Left,
}

/// Hands out storage keys using storage field names or an existing key.
/// Basically returns sha256((0u8, "storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>"))
/// or key if defined.
pub(super) fn get_storage_key(storage_field_names: Vec<String>, key: Option<U256>) -> Bytes32 {
/// Returns storage key of the `storage` declaration field given by the `storage_field_path`, or the `key`, if `Some`.
///
/// If the `key` is `None`, the returned storage key is generated as:
/// `sha256((0u8, "storage::<namespace_1>::<namespace_2>.<storage_field_name>"))`
pub(super) fn get_storage_key(storage_field_path: &[String], key: Option<U256>) -> Bytes32 {
match key {
Some(key) => key.to_be_bytes().into(),
None => hash_storage_key_string(&get_storage_key_string(&storage_field_names)),
None => hash_storage_key_string(&get_storage_key_string(storage_field_path)),
}
}

pub fn get_storage_key_string(storage_field_names: &[String]) -> String {
if storage_field_names.len() == 1 {
/// Returns the string representation of a `storage` declaration field,
/// given by the `storage_field_path`. The string representation consists
/// of the field name preceded by its full namespace path.
/// E.g., "storage::<namespace_1>::<namespace_2>.<storage_field_name>".
pub fn get_storage_key_string(storage_field_path: &[String]) -> String {
if storage_field_path.len() == 1 {
format!(
"{}{}{}",
sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
sway_utils::constants::STORAGE_FIELD_SEPARATOR,
storage_field_names.last().unwrap(),
storage_field_path.last().unwrap(),
)
} else {
format!(
"{}{}{}{}{}",
sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR,
storage_field_names
storage_field_path
.iter()
.take(storage_field_names.len() - 1)
.take(storage_field_path.len() - 1)
.cloned()
.collect::<Vec<_>>()
.join(sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR),
sway_utils::constants::STORAGE_FIELD_SEPARATOR,
storage_field_names.last().unwrap(),
storage_field_path.last().unwrap(),
)
}
}

/// Hands out unique storage field ids using storage field names and struct field names.
/// Basically returns sha256((0u8, "storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>.<struct_field_name1>.<struct_field_name2>")).
pub(super) fn get_storage_field_path_and_field_id(
storage_field_names: &[String],
storage_field_path: &[String],
struct_field_names: &[String],
) -> (String, Bytes32) {
let path = format!(
"{}{}",
get_storage_key_string(storage_field_names),
get_storage_key_string(storage_field_path),
if struct_field_names.is_empty() {
"".to_string()
} else {
Expand Down Expand Up @@ -125,23 +130,24 @@ pub(super) fn add_to_b256(x: Bytes32, y: u64) -> Bytes32 {
/// This behavior matches the behavior of how storage slots are assigned for storage reads and
/// writes (i.e. how `state_read_*` and `state_write_*` instructions are generated).
pub fn serialize_to_storage_slots(
constant: &Constant,
context: &Context,
storage_field_names: Vec<String>,
constant: &Constant,
storage_field_path: &[String],
key: Option<U256>,
ty: &Type,
) -> Vec<StorageSlot> {
let ty = constant.get_content(context).ty;

match &constant.get_content(context).value {
ConstantValue::Undef => vec![],
// If not being a part of an aggregate, single byte values like `bool`, `u8`, and unit
// are stored as a byte at the beginning of the storage slot.
ConstantValue::Unit if ty.is_unit(context) => vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_path, key),
Bytes32::new([0; 32]),
)],
ConstantValue::Bool(b) if ty.is_bool(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_path, key),
Bytes32::new([
if *b { 1 } else { 0 },
0,
Expand Down Expand Up @@ -180,7 +186,7 @@ pub fn serialize_to_storage_slots(
}
ConstantValue::Uint(b) if ty.is_uint8(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_path, key),
Bytes32::new([
*b as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
Expand All @@ -190,7 +196,7 @@ pub fn serialize_to_storage_slots(
// Similarly, other uint values are stored at the beginning of the storage slot.
ConstantValue::Uint(n) if ty.is_uint(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_path, key),
Bytes32::new(
n.to_be_bytes()
.iter()
Expand All @@ -204,13 +210,13 @@ pub fn serialize_to_storage_slots(
}
ConstantValue::U256(b) if ty.is_uint_of(context, 256) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_path, key),
Bytes32::new(b.to_be_bytes()),
)]
}
ConstantValue::B256(b) if ty.is_b256(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_path, key),
Bytes32::new(b.to_be_bytes()),
)]
}
Expand All @@ -225,7 +231,7 @@ pub fn serialize_to_storage_slots(
let mut packed = serialize_to_words(
constant.get_content(context),
context,
ty,
&ty,
InByte8Padding::default(),
);
packed.extend(vec![
Expand Down Expand Up @@ -255,7 +261,7 @@ pub fn serialize_to_storage_slots(
);
}

let storage_key = get_storage_key(storage_field_names, key);
let storage_key = get_storage_key(storage_field_path, key);
(0..type_size_in_bytes.div_ceil(32))
.map(|i| add_to_b256(storage_key, i))
.zip((0..packed.len() / 4).map(|i| {
Expand Down
10 changes: 10 additions & 0 deletions sway-core/src/language/parsed/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,19 @@ impl PartialEqWithEngines for ArrayIndexExpression {
}
}

/// A storage access expression of the form `storage[::<namespace_name>]*<.field_name>*`.
/// E.g.:
/// - `storage.field`
/// - `storage::ns1.field`
/// - `storage::ns1::ns2.field.subfield`
#[derive(Debug, Clone)]
pub struct StorageAccessExpression {
/// The list of namespace names in the storage access expression, if any.
/// E.g.: for `storage::ns1::ns2.field.subfield`, this would be `["ns1", "ns2"]`.
pub namespace_names: Vec<Ident>,
/// The list of field names in the storage access expression.
/// There must be at least one field name.
/// E.g.: for `storage::ns1::ns2.field.subfield`, this would be `["field", "subfield"]`.
pub field_names: Vec<Ident>,
pub storage_keyword_span: Span,
}
Expand Down
Loading
Loading