diff --git a/docs/book/src/reference/compiler_intrinsics.md b/docs/book/src/reference/compiler_intrinsics.md index a0dfd0f7c62..c3ba29ce0e0 100644 --- a/docs/book/src/reference/compiler_intrinsics.md +++ b/docs/book/src/reference/compiler_intrinsics.md @@ -2,7 +2,7 @@ The Sway compiler supports a list of intrinsics that perform various low level operations that are useful for building libraries. Compiler intrinsics should rarely be used but are preferred over `asm` blocks because they are type-checked and are safer overall. Below is a list of all available compiler intrinsics: -___ +--- ```sway __size_of_val(val: T) -> u64 @@ -12,7 +12,7 @@ __size_of_val(val: T) -> u64 **Constraints:** None. -___ +--- ```sway __size_of() -> u64 @@ -22,7 +22,7 @@ __size_of() -> u64 **Constraints:** None. -___ +--- ```sway __size_of_str_array() -> u64 @@ -32,7 +32,7 @@ __size_of_str_array() -> u64 **Constraints:** None. -___ +--- ```sway __assert_is_str_array() @@ -42,7 +42,7 @@ __assert_is_str_array() **Constraints:** None. -___ +--- ```sway __to_str_array(s: str) -> str[N] @@ -52,7 +52,7 @@ __to_str_array(s: str) -> str[N] **Constraints:** None. -___ +--- ```sway __is_reference_type() -> bool @@ -62,7 +62,7 @@ __is_reference_type() -> bool **Constraints:** None. -___ +--- ```sway __is_str_array() -> bool @@ -72,7 +72,7 @@ __is_str_array() -> bool **Constraints:** None. -___ +--- ```sway __eq(lhs: T, rhs: T) -> bool @@ -82,7 +82,7 @@ __eq(lhs: T, rhs: T) -> bool **Constraints:** `T` is `bool`, `u8`, `u16`, `u32`, `u64`, `u256`, `b256` or `raw_ptr`. -___ +--- ```sway __gt(lhs: T, rhs: T) -> bool @@ -91,7 +91,8 @@ __gt(lhs: T, rhs: T) -> bool **Description:** Returns whether `lhs` is greater than `rhs`. **Constraints:** `T` is `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ + +--- ```sway __lt(lhs: T, rhs: T) -> bool @@ -100,7 +101,8 @@ __lt(lhs: T, rhs: T) -> bool **Description:** Returns whether `lhs` is less than `rhs`. **Constraints:** `T` is `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ + +--- ```sway __gtf(index: u64, tx_field_id: u64) -> T @@ -110,7 +112,7 @@ __gtf(index: u64, tx_field_id: u64) -> T **Constraints:** None. -___ +--- ```sway __addr_of(val: T) -> raw_ptr @@ -120,7 +122,7 @@ __addr_of(val: T) -> raw_ptr **Constraints:** `T` is a reference type. -___ +--- ```sway __state_load_word(key: b256) -> u64 @@ -130,7 +132,7 @@ __state_load_word(key: b256) -> u64 **Constraints:** None. -___ +--- ```sway __state_load_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool @@ -140,7 +142,7 @@ __state_load_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool **Constraints:** None. -___ +--- ```sway __state_store_word(key: b256, val: u64) -> bool @@ -150,7 +152,7 @@ __state_store_word(key: b256, val: u64) -> bool **Constraints:** None. -___ +--- ```sway __state_store_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool @@ -160,7 +162,7 @@ __state_store_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool **Constraints:** None. -___ +--- ```sway __log(val: T) @@ -170,7 +172,7 @@ __log(val: T) **Constraints:** None. -___ +--- ```sway __add(lhs: T, rhs: T) -> T @@ -180,7 +182,7 @@ __add(lhs: T, rhs: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`. -___ +--- ```sway __sub(lhs: T, rhs: T) -> T @@ -190,7 +192,7 @@ __sub(lhs: T, rhs: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`. -___ +--- ```sway __mul(lhs: T, rhs: T) -> T @@ -200,7 +202,7 @@ __mul(lhs: T, rhs: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`. -___ +--- ```sway __div(lhs: T, rhs: T) -> T @@ -210,7 +212,7 @@ __div(lhs: T, rhs: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`. -___ +--- ```sway __and(lhs: T, rhs: T) -> T @@ -220,7 +222,7 @@ __and(lhs: T, rhs: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ +--- ```sway __or(lhs: T, rhs: T) -> T @@ -230,7 +232,7 @@ __or(lhs: T, rhs: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ +--- ```sway __xor(lhs: T, rhs: T) -> T @@ -239,7 +241,8 @@ __xor(lhs: T, rhs: T) -> T **Description:** Bitwise XOR `lhs` and `rhs`. **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ + +--- ```sway __mod(lhs: T, rhs: T) -> T @@ -248,7 +251,8 @@ __mod(lhs: T, rhs: T) -> T **Description:** Modulo of `lhs` by `rhs`. **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`. -___ + +--- ```sway __rsh(lhs: T, rhs: u64) -> T @@ -257,7 +261,8 @@ __rsh(lhs: T, rhs: u64) -> T **Description:** Logical right shift of `lhs` by `rhs`. **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ + +--- ```sway __lsh(lhs: T, rhs: u64) -> T @@ -266,7 +271,8 @@ __lsh(lhs: T, rhs: u64) -> T **Description:** Logical left shift of `lhs` by `rhs`. **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ + +--- ```sway __revert(code: u64) @@ -276,7 +282,7 @@ __revert(code: u64) **Constraints:** None. -___ +--- ```sway __ptr_add(ptr: raw_ptr, offset: u64) @@ -286,7 +292,7 @@ __ptr_add(ptr: raw_ptr, offset: u64) **Constraints:** None. -___ +--- ```sway __ptr_sub(ptr: raw_ptr, offset: u64) @@ -296,7 +302,7 @@ __ptr_sub(ptr: raw_ptr, offset: u64) **Constraints:** None. -___ +--- ```sway __smo(recipient: b256, data: T, coins: u64) @@ -306,7 +312,7 @@ __smo(recipient: b256, data: T, coins: u64) **Constraints:** None. -___ +--- ```sway __not(op: T) -> T @@ -316,7 +322,7 @@ __not(op: T) -> T **Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`, `u256`, `b256`. -___ +--- ```sway __jmp_mem() @@ -325,3 +331,44 @@ __jmp_mem() **Description:** Jumps to `MEM[$hp]`. **Constraints:** None. + +--- + +```sway +__slice(item: &[T; N], start: u64, end: u64) -> &[T] +__slice(item: &[T], start: u64, end: u64) -> &[T] +__slice(item: &mut [T; N], start: u64, end: u64) -> &mut [T] +__slice(item: &mut [T], start: u64, end: u64) -> &mut [T] +``` + +**Description:** Slices an array or another slice. + +This intrinsic returns a reference to a slice containing the range of elements inside `item`. +The mutability of reference is defined by the first parameter mutability. + +Runtime bound checks are not generated, and must be done manually when and where appropriated. Compile time bound checks are done when possible. + +**Constraints:** + +- `item` is an array or a slice; +- when `start` is a literal, it must be smaller than `item` length; +- when `end` is a literal, it must be smaller than or equal to `item` length; +- `end` must be greater than or equal to `start` + +--- + +```sway +__elem_at(item: &[T; N], index: u64) -> &T +__elem_at(item: &[T], index: u64) -> &T +__elem_at(item: &mut [T; N], index: u64) -> &mut T +__elem_at(item: &mut [T], index: u64) -> &mut T +``` + +**Description:** Returns a reference to the indexed element. The mutability of reference is defined by the first parameter mutability. + +Runtime bound checks are not generated, and must be done manually when and where appropriated. Compile time bound checks are done when possible. + +**Constraints:** + +- `item` is a reference to an array or a reference to a slice; +- when `index` is a literal, it must be smaller than `item` length; diff --git a/docs/reference/src/code/language/built-ins/slices/.gitignore b/docs/reference/src/code/language/built-ins/slices/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/docs/reference/src/code/language/built-ins/slices/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/docs/reference/src/code/language/built-ins/slices/Forc.lock b/docs/reference/src/code/language/built-ins/slices/Forc.lock new file mode 100644 index 00000000000..d064967bf3b --- /dev/null +++ b/docs/reference/src/code/language/built-ins/slices/Forc.lock @@ -0,0 +1,14 @@ +[[package]] +name = 'arrays' +source = 'root' +dependencies = ['std'] + +[[package]] +name = 'core' +source = 'path+from-root-54F92B42A645EA2B' +dependencies = [] + +[[package]] +name = 'std' +source = 'git+https://github.com/fuellabs/sway?tag=v0.24.5#e695606d8884a18664f6231681333a784e623bc9' +dependencies = ['core'] diff --git a/docs/reference/src/code/language/built-ins/slices/Forc.toml b/docs/reference/src/code/language/built-ins/slices/Forc.toml new file mode 100644 index 00000000000..d62b0a85072 --- /dev/null +++ b/docs/reference/src/code/language/built-ins/slices/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "lib.sw" +license = "Apache-2.0" +name = "slices" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/docs/reference/src/code/language/built-ins/slices/src/lib.sw b/docs/reference/src/code/language/built-ins/slices/src/lib.sw new file mode 100644 index 00000000000..5319cec8dac --- /dev/null +++ b/docs/reference/src/code/language/built-ins/slices/src/lib.sw @@ -0,0 +1,7 @@ +library; + +// ANCHOR: syntax +fn syntax(s: &[u64]) -> u64 { + s.len() +} +// ANCHOR_END: syntax diff --git a/docs/reference/src/documentation/language/built-ins/index.md b/docs/reference/src/documentation/language/built-ins/index.md index a3ec2dcf7e0..be937b242b4 100644 --- a/docs/reference/src/documentation/language/built-ins/index.md +++ b/docs/reference/src/documentation/language/built-ins/index.md @@ -22,8 +22,10 @@ Sway has the following primitive types: 1. `str[n]` (fixed-length string of size n) 4. [Bytes](b256.md) 1. `b256` (256 bits / 32 bytes, i.e. a hash) +5. [Slices](slices.md) + The default numeric type is `u64`. The FuelVM's word size is 64 bits, and the cases where using a smaller numeric type to save space are minimal. All other types in Sway are built up of these primitive types, or references to these primitive types. diff --git a/docs/reference/src/documentation/language/built-ins/slices.md b/docs/reference/src/documentation/language/built-ins/slices.md new file mode 100644 index 00000000000..777a7cb1b8a --- /dev/null +++ b/docs/reference/src/documentation/language/built-ins/slices.md @@ -0,0 +1,14 @@ +# Slices + +A slice is similar to an [array](arrays.md), in the sense that it is a contiguous sequence of elements of the same type. + +Unlike arrays, slices cannot be allocated, because its size is unknown at compilation time. The only way to use slices is through references to a slice. + +References to slice are "fat pointers" containing two items: + +- a pointer to the first element of the slice; +- a `u64` with how many elements the slice has. + +```sway +{{#include ../../../code/language/built-ins/slices/src/lib.sw:syntax}} +``` diff --git a/forc-plugins/forc-doc/src/render/item/type_anchor.rs b/forc-plugins/forc-doc/src/render/item/type_anchor.rs index 65b75ad8f39..514d7f6dcd2 100644 --- a/forc-plugins/forc-doc/src/render/item/type_anchor.rs +++ b/forc-plugins/forc-doc/src/render/item/type_anchor.rs @@ -33,6 +33,18 @@ pub(crate) fn render_type_anchor( : format!("; {}]", len.val()); }) } + TypeInfo::Slice(ty_arg) => { + let inner = render_type_anchor( + (*render_plan.engines.te().get(ty_arg.type_id)).clone(), + render_plan, + current_module_info, + )?; + Ok(box_html! { + : "__slice["; + : inner; + : "]"; + }) + } TypeInfo::Tuple(ty_args) => { let mut rendered_args: Vec<_> = Vec::new(); for ty_arg in ty_args { diff --git a/sway-ast/src/intrinsics.rs b/sway-ast/src/intrinsics.rs index 5200da142ae..182d47fa3d9 100644 --- a/sway-ast/src/intrinsics.rs +++ b/sway-ast/src/intrinsics.rs @@ -41,6 +41,8 @@ pub enum Intrinsic { EncodeBufferEmpty, // let buffer: (raw_ptr, u64, u64) = __encode_buffer_empty() EncodeBufferAppend, // let buffer: (raw_ptr, u64, u64) = __encode_buffer_append(buffer, primitive data type) EncodeBufferAsRawSlice, // let slice: raw_slice = __encode_buffer_as_raw_slice(buffer) + Slice, // let ref_to_slice = __slice::(item: T, inclusive_start_index, exclusive_end_index) + ElemAt, // let elem: &T = __elem_at::(item: T, index) } impl fmt::Display for Intrinsic { @@ -85,6 +87,8 @@ impl fmt::Display for Intrinsic { Intrinsic::EncodeBufferEmpty => "encode_buffer_empty", Intrinsic::EncodeBufferAppend => "encode_buffer_append", Intrinsic::EncodeBufferAsRawSlice => "encode_buffer_as_raw_slice", + Intrinsic::Slice => "slice", + Intrinsic::ElemAt => "elem_at", }; write!(f, "{s}") } @@ -133,6 +137,8 @@ impl Intrinsic { "__encode_buffer_empty" => EncodeBufferEmpty, "__encode_buffer_append" => EncodeBufferAppend, "__encode_buffer_as_raw_slice" => EncodeBufferAsRawSlice, + "__slice" => Slice, + "__elem_at" => ElemAt, _ => return None, }) } diff --git a/sway-core/src/abi_generation/abi_str.rs b/sway-core/src/abi_generation/abi_str.rs index b52af834f0c..ed8e9ae3cb0 100644 --- a/sway-core/src/abi_generation/abi_str.rs +++ b/sway-core/src/abi_generation/abi_str.rs @@ -53,6 +53,14 @@ impl TypeId { }; format!("[{}; {}]", inner_type, count.val()) } + (TypeInfo::Slice(type_arg), TypeInfo::Slice(_)) => { + let inner_type = if ctx.abi_with_fully_specified_types { + type_engine.get(type_arg.type_id).abi_str(ctx, engines) + } else { + "_".to_string() + }; + format!("[{}]", inner_type) + } (TypeInfo::Custom { .. }, _) => { format!("generic {}", self_abi_str) } diff --git a/sway-core/src/abi_generation/evm_abi.rs b/sway-core/src/abi_generation/evm_abi.rs index 6cfb1040484..f899e0de209 100644 --- a/sway-core/src/abi_generation/evm_abi.rs +++ b/sway-core/src/abi_generation/evm_abi.rs @@ -49,6 +49,7 @@ fn get_type_str(type_id: &TypeId, engines: &Engines, resolved_type_id: TypeId) - assert_eq!(count.val(), resolved_count.val()); format!("[_; {}]", count.val()) } + (TypeInfo::Slice(_), TypeInfo::Slice(_)) => "__slice[_]".into(), (TypeInfo::Custom { .. }, _) => { format!("generic {}", abi_str(&type_engine.get(*type_id), engines)) } diff --git a/sway-core/src/abi_generation/fuel_abi.rs b/sway-core/src/abi_generation/fuel_abi.rs index f49e8eb6fea..f4d0205d8b0 100644 --- a/sway-core/src/abi_generation/fuel_abi.rs +++ b/sway-core/src/abi_generation/fuel_abi.rs @@ -406,6 +406,47 @@ impl TypeId { unreachable!(); } } + TypeInfo::Slice(..) => { + if let TypeInfo::Slice(elem_ty) = &*type_engine.get(resolved_type_id) { + // The `program_abi::TypeDeclaration`s needed for the slice element type + let elem_abi_ty = program_abi::TypeDeclaration { + type_id: elem_ty.initial_type_id.index(), + type_field: elem_ty.initial_type_id.get_abi_type_str( + &ctx.to_str_context(engines, false), + engines, + elem_ty.type_id, + ), + components: elem_ty.initial_type_id.get_abi_type_components( + ctx, + engines, + types, + elem_ty.type_id, + ), + type_parameters: elem_ty.initial_type_id.get_abi_type_parameters( + ctx, + engines, + types, + elem_ty.type_id, + ), + }; + types.push(elem_abi_ty); + + // Generate the JSON data for the array. This is basically a single + // `program_abi::TypeApplication` for the array element type + Some(vec![program_abi::TypeApplication { + name: "__slice_element".to_string(), + type_id: elem_ty.initial_type_id.index(), + type_arguments: elem_ty.initial_type_id.get_abi_type_arguments( + ctx, + engines, + types, + elem_ty.type_id, + ), + }]) + } else { + unreachable!(); + } + } TypeInfo::Tuple(_) => { if let TypeInfo::Tuple(fields) = &*type_engine.get(resolved_type_id) { // A list of all `program_abi::TypeDeclaration`s needed for the tuple fields diff --git a/sway-core/src/asm_generation/fuel/data_section.rs b/sway-core/src/asm_generation/fuel/data_section.rs index 10dc27f0da8..3db12443f4f 100644 --- a/sway-core/src/asm_generation/fuel/data_section.rs +++ b/sway-core/src/asm_generation/fuel/data_section.rs @@ -137,6 +137,9 @@ impl Entry { ConstantValue::Reference(_) => { todo!("Constant references are currently not supported.") } + ConstantValue::Slice(_) => { + todo!("Constant slices are currently not supported.") + } } } diff --git a/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs b/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs index ff0575219a0..87696ca002a 100644 --- a/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs +++ b/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs @@ -662,6 +662,7 @@ impl<'ir, 'eng> MidenVMAsmBuilder<'ir, 'eng> { B256(_) => todo!(), String(_) => todo!(), Array(_) => todo!(), + Slice(_) => todo!(), Struct(_) => todo!(), Reference(_) => todo!(), RawUntypedSlice(_) => todo!(), diff --git a/sway-core/src/control_flow_analysis/dead_code_analysis.rs b/sway-core/src/control_flow_analysis/dead_code_analysis.rs index 0098313cd58..60843dbf3dd 100644 --- a/sway-core/src/control_flow_analysis/dead_code_analysis.rs +++ b/sway-core/src/control_flow_analysis/dead_code_analysis.rs @@ -970,6 +970,9 @@ fn get_struct_type_info_from_type_id( TypeInfo::Array(type_arg, _) => { get_struct_type_info_from_type_id(type_engine, decl_engine, type_arg.type_id) } + TypeInfo::Slice(type_arg) => { + get_struct_type_info_from_type_id(type_engine, decl_engine, type_arg.type_id) + } _ => Ok(None), } } @@ -1792,35 +1795,28 @@ fn connect_expression<'eng: 'cfg, 'cfg>( elem_type: _, contents, } => { - let mut element_diverge = false; - let nodes = contents - .iter() - .map(|elem| { - if !element_diverge - && type_engine - .get(elem.return_type) - .is_uninhabited(engines.te(), engines.de()) - { - element_diverge = true - } - connect_expression( - engines, - &elem.expression, - graph, - leaves, - exit_node, - "", - tree_type, - elem.span.clone(), - options, - ) - }) - .collect::, _>>()?; - if element_diverge { - Ok(vec![]) - } else { - Ok(nodes.concat()) + let mut last = leaves.to_vec(); + + for elem in contents.iter() { + last = connect_expression( + engines, + &elem.expression, + graph, + last.as_slice(), + None, + "", + tree_type, + elem.span.clone(), + options, + )?; + + // If an element diverges, break the connections and return nothing + if last.is_empty() { + break; + } } + + Ok(last) } ArrayIndex { prefix, index } => { let prefix_idx = connect_expression( diff --git a/sway-core/src/ir_generation/compile.rs b/sway-core/src/ir_generation/compile.rs index 1e8d937b5a2..8d4228de9ef 100644 --- a/sway-core/src/ir_generation/compile.rs +++ b/sway-core/src/ir_generation/compile.rs @@ -11,7 +11,7 @@ use crate::{ use super::{ const_eval::{compile_const_decl, LookupEnv}, - convert::convert_resolved_typeid, + convert::convert_resolved_type_id, function::FnCompiler, CompiledFunctionCache, }; @@ -298,11 +298,11 @@ pub(crate) fn compile_configurables( { let decl = engines.de().get(decl_id); - let ty = convert_resolved_typeid( + let ty = convert_resolved_type_id( engines.te(), engines.de(), context, - &decl.type_ascription.type_id, + decl.type_ascription.type_id, &decl.type_ascription.span, ) .unwrap(); @@ -520,11 +520,11 @@ fn compile_fn( .iter() .map(|param| { // Convert to an IR type. - convert_resolved_typeid( + convert_resolved_type_id( type_engine, decl_engine, context, - ¶m.type_argument.type_id, + param.type_argument.type_id, ¶m.type_argument.span, ) .map(|ty| { @@ -544,11 +544,11 @@ fn compile_fn( .collect::, CompileError>>() .map_err(|err| vec![err])?; - let ret_type = convert_resolved_typeid( + let ret_type = convert_resolved_type_id( type_engine, decl_engine, context, - &return_type.type_id, + return_type.type_id, &return_type.span, ) .map_err(|err| vec![err])?; diff --git a/sway-core/src/ir_generation/const_eval.rs b/sway-core/src/ir_generation/const_eval.rs index 157d103589e..9d6269d9dd8 100644 --- a/sway-core/src/ir_generation/const_eval.rs +++ b/sway-core/src/ir_generation/const_eval.rs @@ -12,7 +12,7 @@ use crate::{ }; use super::{ - convert::{convert_literal_to_constant, convert_resolved_typeid}, + convert::{convert_literal_to_constant, convert_resolved_type_id}, function::FnCompiler, types::*, }; @@ -620,9 +620,26 @@ fn const_eval_typed_expr( } } } - ty::TyExpressionVariant::Ref(_) | ty::TyExpressionVariant::Deref(_) => { + ty::TyExpressionVariant::Ref(_) => { return Err(ConstEvalError::CompileError); } + // We support *__elem_at(...) + ty::TyExpressionVariant::Deref(expr) => { + let value = expr + .as_intrinsic() + .filter(|x| matches!(x.kind, Intrinsic::ElemAt)) + .ok_or(ConstEvalError::CompileError) + .and_then(|kind| const_eval_intrinsic(lookup, known_consts, kind)); + if let Ok(Some(Constant { + value: ConstantValue::Reference(value), + .. + })) = value + { + Some(*value.clone()) + } else { + return Err(ConstEvalError::CompileError); + } + } ty::TyExpressionVariant::EnumTag { exp } => { let value = const_eval_typed_expr(lookup, known_consts, exp)?.map(|x| x.value); if let Some(ConstantValue::Struct(fields)) = value { @@ -1036,11 +1053,11 @@ fn const_eval_intrinsic( } Intrinsic::SizeOfType => { let targ = &intrinsic.type_arguments[0]; - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( lookup.engines.te(), lookup.engines.de(), lookup.context, - &targ.type_id, + targ.type_id, &targ.span, ) .map_err(|_| ConstEvalError::CompileError)?; @@ -1052,11 +1069,11 @@ fn const_eval_intrinsic( Intrinsic::SizeOfVal => { let val = &intrinsic.arguments[0]; let type_id = val.return_type; - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( lookup.engines.te(), lookup.engines.de(), lookup.context, - &type_id, + type_id, &val.span, ) .map_err(|_| ConstEvalError::CompileError)?; @@ -1067,11 +1084,11 @@ fn const_eval_intrinsic( } Intrinsic::SizeOfStr => { let targ = &intrinsic.type_arguments[0]; - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( lookup.engines.te(), lookup.engines.de(), lookup.context, - &targ.type_id, + targ.type_id, &targ.span, ) .map_err(|_| ConstEvalError::CompileError)?; @@ -1084,11 +1101,11 @@ fn const_eval_intrinsic( } Intrinsic::AssertIsStrArray => { let targ = &intrinsic.type_arguments[0]; - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( lookup.engines.te(), lookup.engines.de(), lookup.context, - &targ.type_id, + targ.type_id, &targ.span, ) .map_err(|_| ConstEvalError::CompileError)?; @@ -1284,6 +1301,68 @@ fn const_eval_intrinsic( value: ConstantValue::RawUntypedSlice(bytes[0..(len as usize)].to_vec()), })) } + Intrinsic::Slice => { + let start = args[1].as_uint().expect("Type check allowed non u64") as usize; + let end = args[2].as_uint().expect("Type check allowed non u64") as usize; + + match &args[0].value { + ConstantValue::Array(elements) => { + let slice = elements + .get(start..end) + .ok_or(ConstEvalError::CompileError)?; + let elem_type = args[0] + .ty + .get_array_elem_type(lookup.context) + .expect("unexpected non array"); + Ok(Some(Constant { + ty: Type::get_typed_slice(lookup.context, elem_type), + value: ConstantValue::Slice(slice.to_vec()), + })) + } + ConstantValue::Reference(r) => match &r.value { + ConstantValue::Slice(elements) => { + let slice = elements + .get(start..end) + .ok_or(ConstEvalError::CompileError)?; + let elem_type = args[0] + .ty + .get_typed_slice_elem_type(lookup.context) + .expect("unexpected non slice"); + Ok(Some(Constant { + ty: Type::get_typed_slice(lookup.context, elem_type), + value: ConstantValue::Slice(slice.to_vec()), + })) + } + _ => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), + }, + _ => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), + } + } + Intrinsic::ElemAt => { + let idx = args[1].as_uint().expect("Type check allowed non u64") as usize; + + match &args[0].value { + ConstantValue::Reference(r) => match &r.value { + ConstantValue::Slice(elements) => { + let v = elements[idx].clone(); + Ok(Some(Constant { + ty: Type::new_ptr(lookup.context, v.ty), + value: ConstantValue::Reference(Box::new(v)), + })) + } + _ => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), + }, + _ => Err(ConstEvalError::CannotBeEvaluatedToConst { + span: intrinsic.span.clone(), + }), + } + } } } diff --git a/sway-core/src/ir_generation/convert.rs b/sway-core/src/ir_generation/convert.rs index ddacdeab37a..3aa22401338 100644 --- a/sway-core/src/ir_generation/convert.rs +++ b/sway-core/src/ir_generation/convert.rs @@ -52,38 +52,29 @@ pub(super) fn convert_literal_to_constant( } } -pub(super) fn convert_resolved_typeid( +pub(super) fn convert_resolved_type_id( type_engine: &TypeEngine, decl_engine: &DeclEngine, context: &mut Context, - ast_type: &TypeId, + ast_type: TypeId, span: &Span, ) -> Result { - // There's probably a better way to convert TypeError to String, but... we'll use something - // other than String eventually? IrError? - convert_resolved_type( - type_engine, - decl_engine, - context, - &type_engine - .to_typeinfo(*ast_type, span) - .map_err(|ty_err| CompileError::InternalOwned(format!("{ty_err:?}"), span.clone()))?, - span, - ) + let t = type_engine.get(ast_type); + convert_resolved_type_info(type_engine, decl_engine, context, &t, span) } pub(super) fn convert_resolved_typeid_no_span( type_engine: &TypeEngine, decl_engine: &DeclEngine, context: &mut Context, - ast_type: &TypeId, + ast_type: TypeId, ) -> Result { let msg = "unknown source location"; let span = crate::span::Span::from_string(msg.to_string()); - convert_resolved_typeid(type_engine, decl_engine, context, ast_type, &span) + convert_resolved_type_id(type_engine, decl_engine, context, ast_type, &span) } -fn convert_resolved_type( +fn convert_resolved_type_info( type_engine: &TypeEngine, decl_engine: &DeclEngine, context: &mut Context, @@ -93,10 +84,10 @@ fn convert_resolved_type( // A handy macro for rejecting unsupported types. macro_rules! reject_type { ($name_str:literal) => {{ - return Err(CompileError::Internal( - concat!($name_str, " type cannot be resolved in IR."), - span.clone(), - )); + return Err(CompileError::TypeMustBeKnownAtThisPoint { + span: span.clone(), + internal: $name_str.into(), + }); }}; } @@ -131,15 +122,16 @@ fn convert_resolved_type( &decl_engine.get_enum(decl_ref).variants, )?, TypeInfo::Array(elem_type, length) => { - let elem_type = convert_resolved_typeid( + let elem_type = convert_resolved_type_id( type_engine, decl_engine, context, - &elem_type.type_id, + elem_type.type_id, span, )?; Type::new_array(context, elem_type, length.val() as u64) } + TypeInfo::Tuple(fields) => { if fields.is_empty() { // XXX We've removed Unit from the core compiler, replaced with an empty Tuple. @@ -154,13 +146,32 @@ fn convert_resolved_type( TypeInfo::RawUntypedPtr => Type::get_uint64(context), TypeInfo::RawUntypedSlice => Type::get_slice(context), TypeInfo::Ptr(_) => Type::get_uint64(context), - TypeInfo::Slice(_) => Type::get_slice(context), TypeInfo::Alias { ty, .. } => { - convert_resolved_typeid(type_engine, decl_engine, context, &ty.type_id, span)? + convert_resolved_type_id(type_engine, decl_engine, context, ty.type_id, span)? + } + // refs to slice are actually fat pointers, + // all others refs are thin pointers. + TypeInfo::Ref { + referenced_type, .. + } => { + if let Some(slice_elem) = type_engine.get(referenced_type.type_id).as_slice() { + let elem_ir_type = convert_resolved_type_id( + type_engine, + decl_engine, + context, + slice_elem.type_id, + span, + )?; + Type::get_typed_slice(context, elem_ir_type) + } else { + Type::get_uint64(context) + } } - TypeInfo::Ref { .. } => Type::get_uint64(context), TypeInfo::Never => Type::get_never(context), + // Unsized types + TypeInfo::Slice(_) => reject_type!("unsized"), + // Unsupported types which shouldn't exist in the AST after type checking and // monomorphisation. TypeInfo::Custom { .. } => reject_type!("Custom"), diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index 184a4d88c05..628ea89e903 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -12,8 +12,8 @@ use crate::{ }, language::{ ty::{ - self, ProjectionKind, TyConfigurableDecl, TyConstantDecl, TyExpressionVariant, - TyStorageField, + self, ProjectionKind, TyConfigurableDecl, TyConstantDecl, TyExpression, + TyExpressionVariant, TyStorageField, }, *, }, @@ -104,6 +104,49 @@ pub(crate) struct FnCompiler<'eng> { messages_types_map: HashMap, } +fn to_constant(_s: &mut FnCompiler<'_>, context: &mut Context, value: u64) -> Value { + let needed_size = Constant::new_uint(context, 64, value); + Value::new_constant(context, needed_size) +} + +fn save_to_local_return_ptr( + s: &mut FnCompiler<'_>, + context: &mut Context, + value: Value, +) -> Result { + let temp_arg_name = s.lexical_map.insert_anon(); + + let value_type = value.get_type(context).unwrap(); + let local_var = s + .function + .new_local_var(context, temp_arg_name, value_type, None, false) + .map_err(|ir_error| CompileError::InternalOwned(ir_error.to_string(), Span::dummy()))?; + + let local_var_ptr = s.current_block.append(context).get_local(local_var); + let _ = s.current_block.append(context).store(local_var_ptr, value); + Ok(local_var_ptr) +} + +fn calc_addr_as_ptr( + current_block: &mut Block, + context: &mut Context, + ptr: Value, + len: Value, + ptr_to: Type, +) -> Value { + assert!(ptr.get_type(context).unwrap().is_ptr(context)); + assert!(len.get_type(context).unwrap().is_uint64(context)); + + let uint64 = Type::get_uint64(context); + let ptr = current_block.append(context).ptr_to_int(ptr, uint64); + let addr = current_block + .append(context) + .binary_op(BinaryOpKind::Add, ptr, len); + + let ptr_to = Type::new_ptr(context, ptr_to); + current_block.append(context).int_to_ptr(addr, ptr_to) +} + impl<'eng> FnCompiler<'eng> { #[allow(clippy::too_many_arguments)] pub(super) fn new( @@ -498,7 +541,7 @@ impl<'eng> FnCompiler<'eng> { ty::TyExpressionVariant::Array { elem_type, contents, - } => self.compile_array_expr(context, md_mgr, elem_type, contents, span_md_idx), + } => self.compile_array_expr(context, md_mgr, *elem_type, contents, span_md_idx), ty::TyExpressionVariant::ArrayIndex { prefix, index } => { self.compile_array_index(context, md_mgr, prefix, index, span_md_idx) } @@ -861,11 +904,11 @@ impl<'eng> FnCompiler<'eng> { Intrinsic::SizeOfVal => { let exp = &arguments[0]; // Compile the expression in case of side-effects but ignore its value. - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( engines.te(), engines.de(), context, - &exp.return_type, + exp.return_type, &exp.span, )?; self.compile_expression_to_value(context, md_mgr, exp)?; @@ -874,11 +917,11 @@ impl<'eng> FnCompiler<'eng> { } Intrinsic::SizeOfType => { let targ = type_arguments[0].clone(); - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( engines.te(), engines.de(), context, - &targ.type_id, + targ.type_id, &targ.span, )?; let val = Constant::get_uint(context, 64, ir_type.size(context).in_bytes()); @@ -886,11 +929,11 @@ impl<'eng> FnCompiler<'eng> { } Intrinsic::SizeOfStr => { let targ = type_arguments[0].clone(); - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( engines.te(), engines.de(), context, - &targ.type_id, + targ.type_id, &targ.span, )?; let val = Constant::get_uint( @@ -917,11 +960,11 @@ impl<'eng> FnCompiler<'eng> { } Intrinsic::AssertIsStrArray => { let targ = type_arguments[0].clone(); - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( engines.te(), engines.de(), context, - &targ.type_id, + targ.type_id, &targ.span, )?; match ir_type.get_content(context) { @@ -994,11 +1037,11 @@ impl<'eng> FnCompiler<'eng> { // Get the target type from the type argument provided let target_type = &type_arguments[0]; - let target_ir_type = convert_resolved_typeid( + let target_ir_type = convert_resolved_type_id( engines.te(), engines.de(), context, - &target_type.type_id, + target_type.type_id, &target_type.span, )?; @@ -1279,11 +1322,11 @@ impl<'eng> FnCompiler<'eng> { }; let len = type_arguments[0].clone(); - let ir_type = convert_resolved_typeid( + let ir_type = convert_resolved_type_id( engines.te(), engines.de(), context, - &len.type_id, + len.type_id, &len.span, )?; let len_value = Constant::get_uint(context, 64, ir_type.size(context).in_bytes()); @@ -1579,26 +1622,6 @@ impl<'eng> FnCompiler<'eng> { .binary_op(BinaryOpKind::Add, len, step) } - fn calc_addr_as_ptr( - current_block: &mut Block, - context: &mut Context, - ptr: Value, - len: Value, - ptr_to: Type, - ) -> Value { - assert!(ptr.get_type(context).unwrap().is_ptr(context)); - assert!(len.get_type(context).unwrap().is_uint64(context)); - - let uint64 = Type::get_uint64(context); - let ptr = current_block.append(context).ptr_to_int(ptr, uint64); - let addr = current_block - .append(context) - .binary_op(BinaryOpKind::Add, ptr, len); - - let ptr_to = Type::new_ptr(context, ptr_to); - current_block.append(context).int_to_ptr(addr, ptr_to) - } - fn append_with_store( current_block: &mut Block, context: &mut Context, @@ -1661,27 +1684,6 @@ impl<'eng> FnCompiler<'eng> { .binary_op(BinaryOpKind::Add, len, step) } - fn save_to_local_return_ptr( - s: &mut FnCompiler<'_>, - context: &mut Context, - value: Value, - ) -> Result { - let temp_arg_name = s.lexical_map.insert_anon(); - - let value_type = value.get_type(context).unwrap(); - let local_var = s - .function - .new_local_var(context, temp_arg_name, value_type, None, false) - .map_err(|ir_error| { - CompileError::InternalOwned(ir_error.to_string(), Span::dummy()) - })?; - - let local_var_ptr = s.current_block.append(context).get_local(local_var); - let _ = s.current_block.append(context).store(local_var_ptr, value); - - Ok(local_var_ptr) - } - fn append_with_memcpy( s: &mut FnCompiler<'_>, context: &mut Context, @@ -1847,15 +1849,6 @@ impl<'eng> FnCompiler<'eng> { (merge_block_ptr, merge_block_cap) } - fn to_constant( - _s: &mut FnCompiler<'_>, - context: &mut Context, - needed_size: u64, - ) -> Value { - let needed_size = Constant::new_uint(context, 64, needed_size); - Value::new_constant(context, needed_size) - } - // Grow the buffer if needed let (ptr, cap) = match &*item_type { TypeInfo::Boolean => { @@ -2168,9 +2161,229 @@ impl<'eng> FnCompiler<'eng> { Ok(TerminatorValue::new(buffer, context)) } + Intrinsic::Slice => self.compile_intrinsic_slice(arguments, context, md_mgr), + Intrinsic::ElemAt => self.compile_intrinsic_elem_at(arguments, context, md_mgr), } } + fn ptr_to_first_element( + &mut self, + context: &mut Context, + first_argument_expr: &TyExpression, + first_argument_value: Value, + _md_mgr: &mut MetadataManager, + ) -> Result<(Value, TypeId), CompileError> { + let te = self.engines.te(); + + let err = CompileError::TypeArgumentsNotAllowed { + span: first_argument_expr.span.clone(), + }; + + let first_argument_value = save_to_local_return_ptr(self, context, first_argument_value)?; + + match &*te.get(first_argument_expr.return_type) { + TypeInfo::Array(elem_ty, _) => Ok((first_argument_value, elem_ty.type_id)), + TypeInfo::Ref { + referenced_type, .. + } => match &*te.get(referenced_type.type_id) { + TypeInfo::Slice(elem_ty) => { + let ptr_arg = AsmArg { + name: Ident::new_no_span("ptr".into()), + initializer: Some(first_argument_value), + }; + + let ptr_out_arg = AsmArg { + name: Ident::new_no_span("ptr_out".into()), + initializer: Some(first_argument_value), + }; + + let return_type = Type::get_uint64(context); + let ptr_to_first_element = self.current_block.append(context).asm_block( + vec![ptr_arg, ptr_out_arg], + vec![AsmInstruction::lw_no_span("ptr_out", "ptr", "i0")], + return_type, + Some(Ident::new_no_span("ptr_out".into())), + ); + + Ok((ptr_to_first_element, elem_ty.type_id)) + } + _ => Err(err), + }, + _ => Err(err), + } + } + + fn advance_ptr_n_elements( + &mut self, + context: &mut Context, + first_argument_expr: &TyExpression, + ptr: Value, + elem_type_id: TypeId, + idx: Value, + ) -> Result<(Value, Type), CompileError> { + let te = self.engines.te(); + let de = self.engines.de(); + + let elem_ir_type = convert_resolved_type_id( + te, + de, + context, + elem_type_id, + &first_argument_expr.span.clone(), + )?; + let elem_ir_type_size = elem_ir_type.size(context); + let elem_ir_type_size = to_constant(self, context, elem_ir_type_size.in_bytes()); + let elem_ir_type_size_arg = AsmArg { + name: Ident::new_no_span("elem_ir_type_size".into()), + initializer: Some(elem_ir_type_size), + }; + + let offset_temp_arg = AsmArg { + name: Ident::new_no_span("offset_temp".into()), + initializer: None, + }; + + let idx_arg = AsmArg { + name: Ident::new_no_span("idx".into()), + initializer: Some(idx), + }; + + let ptr_arg = AsmArg { + name: Ident::new_no_span("ptr".into()), + initializer: Some(ptr), + }; + + let ptr_out_arg = AsmArg { + name: Ident::new_no_span("ptr_out".into()), + initializer: Some(ptr), + }; + + let return_type = Type::get_uint64(context); + let ptr = self.current_block.append(context).asm_block( + vec![ + idx_arg, + elem_ir_type_size_arg, + ptr_arg, + offset_temp_arg, + ptr_out_arg, + ], + vec![ + AsmInstruction::mul_no_span("offset_temp", "idx", "elem_ir_type_size"), + AsmInstruction::add_no_span("ptr_out", "ptr", "offset_temp"), + ], + return_type, + Some(Ident::new_no_span("ptr".into())), + ); + + Ok((ptr, elem_ir_type)) + } + + fn compile_intrinsic_elem_at( + &mut self, + arguments: &[ty::TyExpression], + context: &mut Context, + md_mgr: &mut MetadataManager, + ) -> Result { + assert!(arguments.len() == 2); + + let first_argument_expr = &arguments[0]; + let first_argument_value = return_on_termination_or_extract!( + self.compile_expression_to_value(context, md_mgr, first_argument_expr)? + ); + let (ptr_to_first_elem, elem_type_id) = + self.ptr_to_first_element(context, first_argument_expr, first_argument_value, md_mgr)?; + + let idx = &arguments[1]; + let idx = return_on_termination_or_extract!( + self.compile_expression_to_value(context, md_mgr, idx)? + ); + let (ptr_to_elem, _) = self.advance_ptr_n_elements( + context, + first_argument_expr, + ptr_to_first_elem, + elem_type_id, + idx, + )?; + + Ok(TerminatorValue::new(ptr_to_elem, context)) + } + + fn compile_intrinsic_slice( + &mut self, + arguments: &[ty::TyExpression], + context: &mut Context, + md_mgr: &mut MetadataManager, + ) -> Result { + assert!(arguments.len() == 3); + + let first_argument_expr = &arguments[0]; + let first_argument_value = return_on_termination_or_extract!( + self.compile_expression_to_value(context, md_mgr, first_argument_expr)? + ); + let (ptr_to_first_elem, elem_type_id) = + self.ptr_to_first_element(context, first_argument_expr, first_argument_value, md_mgr)?; + + let start = &arguments[1]; + let start = return_on_termination_or_extract!( + self.compile_expression_to_value(context, md_mgr, start)? + ); + let (ptr_to_elem, elem_ir_type) = self.advance_ptr_n_elements( + context, + first_argument_expr, + ptr_to_first_elem, + elem_type_id, + start, + )?; + + let end = &arguments[2]; + let end = return_on_termination_or_extract!( + self.compile_expression_to_value(context, md_mgr, end)? + ); + + let return_type = Type::get_unit(context); + self.current_block.append(context).asm_block( + vec![ + AsmArg { + name: Ident::new_no_span("end".into()), + initializer: Some(end), + }, + AsmArg { + name: Ident::new_no_span("start".into()), + initializer: Some(start), + }, + ], + vec![AsmInstruction::log_no_span("end", "start", "end", "start")], + return_type, + None, + ); + + let slice_len = self + .current_block + .append(context) + .binary_op(BinaryOpKind::Sub, end, start); + + // compile the slice together + let uint64 = Type::get_uint64(context); + let return_type = Type::get_typed_slice(context, elem_ir_type); + let slice_as_tuple = self.compile_tuple_from_values( + context, + vec![ptr_to_elem, slice_len], + vec![uint64, uint64], + None, + )?; + let slice = self.current_block.append(context).asm_block( + vec![AsmArg { + name: Ident::new_no_span("s".into()), + initializer: Some(slice_as_tuple), + }], + vec![], + return_type, + Some(Ident::new_no_span("s".into())), + ); + + Ok(TerminatorValue::new(slice, context)) + } + fn compile_return( &mut self, context: &mut Context, @@ -2299,11 +2512,11 @@ impl<'eng> FnCompiler<'eng> { )), }?; - let referenced_ir_type = convert_resolved_typeid( + let referenced_ir_type = convert_resolved_type_id( self.engines.te(), self.engines.de(), context, - &referenced_ast_type, + referenced_ast_type, &ast_expr.span.clone(), )?; @@ -2629,7 +2842,7 @@ impl<'eng> FnCompiler<'eng> { self.engines.te(), self.engines.de(), context, - &ast_return_type, + ast_return_type, )?; let ret_is_copy_type = self .engines @@ -2778,7 +2991,7 @@ impl<'eng> FnCompiler<'eng> { self.engines.te(), self.engines.de(), context, - &return_type, + return_type, ) .unwrap_or_else(|_| Type::get_unit(context)); let merge_val_arg_idx = merge_block.new_arg(context, return_type); @@ -2806,11 +3019,11 @@ impl<'eng> FnCompiler<'eng> { variant: &ty::TyEnumVariant, ) -> Result { // Retrieve the type info for the enum. - let enum_type = match convert_resolved_typeid( + let enum_type = match convert_resolved_type_id( self.engines.te(), self.engines.de(), context, - &exp.return_type, + exp.return_type, &exp.span, )? { ty if ty.is_struct(context) => ty, @@ -3087,11 +3300,11 @@ impl<'eng> FnCompiler<'eng> { // accessed and isn't present in the environment. let init_val = self.compile_expression_to_value(context, md_mgr, body); - let return_type = convert_resolved_typeid( + let return_type = convert_resolved_type_id( self.engines.te(), self.engines.de(), context, - &body.return_type, + body.return_type, &body.span, )?; @@ -3166,11 +3379,11 @@ impl<'eng> FnCompiler<'eng> { .lexical_map .insert(call_path.suffix.as_str().to_owned()); - let return_type = convert_resolved_typeid( + let return_type = convert_resolved_type_id( self.engines.te(), self.engines.de(), context, - &value.return_type, + value.return_type, &value.span, )?; @@ -3371,7 +3584,7 @@ impl<'eng> FnCompiler<'eng> { &mut self, context: &mut Context, md_mgr: &mut MetadataManager, - elem_type: &TypeId, + elem_type: TypeId, contents: &[ty::TyExpression], span_md_idx: Option, ) -> Result { @@ -3617,7 +3830,7 @@ impl<'eng> FnCompiler<'eng> { self.engines.te(), self.engines.de(), context, - &struct_field.value.return_type, + struct_field.value.return_type, )?; field_types.push(field_type); } @@ -3696,11 +3909,11 @@ impl<'eng> FnCompiler<'eng> { ) })?; - let field_type = convert_resolved_typeid( + let field_type = convert_resolved_type_id( self.engines.te(), self.engines.de(), context, - &field_type_id, + field_type_id, &ast_field.span, )?; @@ -3855,7 +4068,7 @@ impl<'eng> FnCompiler<'eng> { self.engines.te(), self.engines.de(), context, - &field_expr.return_type, + field_expr.return_type, )?; init_values.push(init_value); init_types.push(init_type); @@ -3879,11 +4092,11 @@ impl<'eng> FnCompiler<'eng> { let tuple_value = return_on_termination_or_extract!( self.compile_expression_to_ptr(context, md_mgr, tuple)? ); - let tuple_type = convert_resolved_typeid( + let tuple_type = convert_resolved_type_id( self.engines.te(), self.engines.de(), context, - &tuple_type, + tuple_type, &span, )?; @@ -3930,7 +4143,7 @@ impl<'eng> FnCompiler<'eng> { self.engines.te(), self.engines.de(), context, - &base_type, + base_type, )?; // Do the actual work. This is a recursive function because we want to drill down @@ -4035,7 +4248,7 @@ impl<'eng> FnCompiler<'eng> { self.engines.te(), self.engines.de(), context, - &return_type, + return_type, )?; let val = self .current_block diff --git a/sway-core/src/ir_generation/types.rs b/sway-core/src/ir_generation/types.rs index 6d549c7f890..63487dd6b78 100644 --- a/sway-core/src/ir_generation/types.rs +++ b/sway-core/src/ir_generation/types.rs @@ -26,7 +26,7 @@ pub(super) fn create_tagged_union_type( type_engine, decl_engine, context, - &tev.type_argument.type_id, + tev.type_argument.type_id, ) }) .collect::, CompileError>>()?; @@ -51,7 +51,7 @@ pub(super) fn create_tuple_aggregate( ) -> Result { let field_types = fields .iter() - .map(|ty_id| convert_resolved_typeid_no_span(type_engine, decl_engine, context, ty_id)) + .map(|ty_id| convert_resolved_typeid_no_span(type_engine, decl_engine, context, *ty_id)) .collect::, CompileError>>()?; Ok(Type::new_struct(context, field_types)) @@ -65,7 +65,7 @@ pub(super) fn create_array_aggregate( count: u64, ) -> Result { let element_type = - convert_resolved_typeid_no_span(type_engine, decl_engine, context, &element_type_id)?; + convert_resolved_typeid_no_span(type_engine, decl_engine, context, element_type_id)?; Ok(Type::new_array(context, element_type, count)) } @@ -77,7 +77,7 @@ pub(super) fn get_struct_for_types( ) -> Result { let types = type_ids .iter() - .map(|ty_id| convert_resolved_typeid_no_span(type_engine, decl_engine, context, ty_id)) + .map(|ty_id| convert_resolved_typeid_no_span(type_engine, decl_engine, context, *ty_id)) .collect::, CompileError>>()?; Ok(Type::new_struct(context, types)) } diff --git a/sway-core/src/language/literal.rs b/sway-core/src/language/literal.rs index a50712d04ad..afedaad12d0 100644 --- a/sway-core/src/language/literal.rs +++ b/sway-core/src/language/literal.rs @@ -22,6 +22,19 @@ pub enum Literal { B256([u8; 32]), } +impl Literal { + pub fn cast_value_to_u64(&self) -> Option { + match self { + Literal::U8(v) => Some(*v as u64), + Literal::U16(v) => Some(*v as u64), + Literal::U32(v) => Some(*v as u64), + Literal::U64(v) => Some(*v), + Literal::Numeric(v) => Some(*v), + _ => None, + } + } +} + impl Hash for Literal { fn hash(&self, state: &mut H) { use Literal::*; diff --git a/sway-core/src/language/ty/expression/expression.rs b/sway-core/src/language/ty/expression/expression.rs index fc1b1b3a0f5..762b3fd2384 100644 --- a/sway-core/src/language/ty/expression/expression.rs +++ b/sway-core/src/language/ty/expression/expression.rs @@ -423,4 +423,11 @@ impl TyExpression { _ => {} } } + + pub fn as_intrinsic(&self) -> Option<&TyIntrinsicFunctionKind> { + match &self.expression { + TyExpressionVariant::IntrinsicFunction(v) => Some(v), + _ => None, + } + } } diff --git a/sway-core/src/language/ty/expression/expression_variant.rs b/sway-core/src/language/ty/expression/expression_variant.rs index 12f68716031..4f5643a5020 100644 --- a/sway-core/src/language/ty/expression/expression_variant.rs +++ b/sway-core/src/language/ty/expression/expression_variant.rs @@ -172,6 +172,15 @@ pub enum TyExpressionVariant { Deref(Box), } +impl TyExpressionVariant { + pub fn as_literal(&self) -> Option<&Literal> { + match self { + TyExpressionVariant::Literal(v) => Some(v), + _ => None, + } + } +} + impl EqWithEngines for TyExpressionVariant {} impl PartialEqWithEngines for TyExpressionVariant { fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool { diff --git a/sway-core/src/language/ty/module.rs b/sway-core/src/language/ty/module.rs index 21bc45eb6cf..33078fb2bb6 100644 --- a/sway-core/src/language/ty/module.rs +++ b/sway-core/src/language/ty/module.rs @@ -4,9 +4,8 @@ use sway_error::handler::{ErrorEmitted, Handler}; use sway_types::Span; use crate::{ - decl_engine::{DeclEngine, DeclRef, DeclRefFunction}, - language::ModName, - language::{ty::*, HasModule, HasSubmodules}, + decl_engine::{DeclEngine, DeclEngineGet, DeclRef, DeclRefFunction}, + language::{ty::*, HasModule, HasSubmodules, ModName}, semantic_analysis::namespace, transform::{self, AllowDeprecatedState}, Engines, @@ -21,6 +20,59 @@ pub struct TyModule { pub attributes: transform::AttributesMap, } +impl TyModule { + /// Iter on all constants in this module, which means, globals constants and + /// local constants, but it does not enter into submodules. + pub fn iter_constants(&self, de: &DeclEngine) -> Vec { + fn inside_code_block(de: &DeclEngine, block: &TyCodeBlock) -> Vec { + block + .contents + .iter() + .flat_map(|node| inside_ast_node(de, node)) + .collect::>() + } + + fn inside_ast_node(de: &DeclEngine, node: &TyAstNode) -> Vec { + match &node.content { + TyAstNodeContent::Declaration(decl) => match decl { + TyDecl::ConstantDecl(decl) => { + vec![decl.clone()] + } + TyDecl::FunctionDecl(decl) => { + let decl = de.get(&decl.decl_id); + inside_code_block(de, &decl.body) + } + TyDecl::ImplSelfOrTrait(decl) => { + let decl = de.get(&decl.decl_id); + decl.items + .iter() + .flat_map(|item| match item { + TyTraitItem::Fn(decl) => { + let decl = de.get(decl.id()); + inside_code_block(de, &decl.body) + } + TyTraitItem::Constant(decl) => { + vec![ConstantDecl { + decl_id: *decl.id(), + }] + } + _ => vec![], + }) + .collect() + } + _ => vec![], + }, + _ => vec![], + } + } + + self.all_nodes + .iter() + .flat_map(|node| inside_ast_node(de, node)) + .collect::>() + } +} + #[derive(Clone, Debug)] pub struct TySubmodule { pub module: TyModule, diff --git a/sway-core/src/language/ty/program.rs b/sway-core/src/language/ty/program.rs index 7d585533f18..92d273f1526 100644 --- a/sway-core/src/language/ty/program.rs +++ b/sway-core/src/language/ty/program.rs @@ -72,7 +72,6 @@ impl TyProgram { let decl_engine = engines.de(); // Validate all submodules - let mut non_configurables_constants = vec![]; let mut configurables = vec![]; for (_, submodule) in &root.submodules { match Self::validate_root( @@ -114,13 +113,6 @@ impl TyProgram { declarations.push(TyDecl::FunctionDecl(FunctionDecl { decl_id: *decl_id })); } - TyAstNodeContent::Declaration(TyDecl::ConstantDecl(ConstantDecl { - decl_id, - .. - })) => { - let decl = (*decl_engine.get_constant(decl_id)).clone(); - non_configurables_constants.push(decl); - } TyAstNodeContent::Declaration(TyDecl::ConfigurableDecl(ConfigurableDecl { decl_id, .. @@ -404,6 +396,7 @@ impl TyProgram { &c.type_ascription, |t| match t { TypeInfo::StringSlice => Some(TypeNotAllowedReason::StringSliceInConfigurables), + TypeInfo::Slice(_) => Some(TypeNotAllowedReason::SliceInConst), _ => None, }, ) { @@ -411,16 +404,18 @@ impl TyProgram { } } - for c in non_configurables_constants.iter() { - if let Some(error) = get_type_not_allowed_error( - engines, - c.return_type, - &c.type_ascription, - |t| match t { - TypeInfo::StringSlice => Some(TypeNotAllowedReason::StringSliceInConst), - _ => None, - }, - ) { + // verify all constants + for decl in root.iter_constants(decl_engine).iter() { + let decl = decl_engine.get_constant(&decl.decl_id); + let e = + get_type_not_allowed_error(engines, decl.return_type, &decl.type_ascription, |t| { + match t { + TypeInfo::StringSlice => Some(TypeNotAllowedReason::StringSliceInConst), + TypeInfo::Slice(_) => Some(TypeNotAllowedReason::SliceInConst), + _ => None, + } + }); + if let Some(error) = e { handler.emit_err(error); } } diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index bdc7e683ceb..7508faab6ea 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -573,13 +573,17 @@ pub fn parsed_to_ast( } }; - // Skip collecting metadata if we triggered an optimised build from LSP. - let types_metadata = if !lsp_config.as_ref().is_some_and(|lsp| lsp.optimized_build) { + // Skip collecting type metadata and control-flow analysis + // if we triggered an optimised build from LSP. + + let is_lsp_optimized_build = lsp_config.as_ref().is_some_and(|lsp| lsp.optimized_build); + let types_metadata = if !is_lsp_optimized_build { // Collect information about the types used in this program let types_metadata_result = typed_program.collect_types_metadata( handler, &mut CollectTypesMetadataContext::new(engines, experimental, package_name.to_string()), ); + let types_metadata = match types_metadata_result { Ok(types_metadata) => types_metadata, Err(e) => { @@ -588,19 +592,17 @@ pub fn parsed_to_ast( } }; - typed_program - .logged_types - .extend(types_metadata.iter().filter_map(|m| match m { - TypeMetadata::LoggedType(log_id, type_id) => Some((*log_id, *type_id)), - _ => None, - })); + let logged_types = types_metadata.iter().filter_map(|m| match m { + TypeMetadata::LoggedType(log_id, type_id) => Some((*log_id, *type_id)), + _ => None, + }); + typed_program.logged_types.extend(logged_types); - typed_program - .messages_types - .extend(types_metadata.iter().filter_map(|m| match m { - TypeMetadata::MessageType(message_id, type_id) => Some((*message_id, *type_id)), - _ => None, - })); + let message_types = types_metadata.iter().filter_map(|m| match m { + TypeMetadata::MessageType(message_id, type_id) => Some((*message_id, *type_id)), + _ => None, + }); + typed_program.messages_types.extend(message_types); let (print_graph, print_graph_url_format) = match build_config { Some(cfg) => ( @@ -828,10 +830,13 @@ pub(crate) fn compile_ast_to_ir_to_asm( program: &ty::TyProgram, build_config: &BuildConfig, ) -> Result { - // The IR pipeline relies on type information being fully resolved. - // If type information is found to still be generic or unresolved inside of - // IR, this is considered an internal compiler error. To resolve this situation, - // we need to explicitly ensure all types are resolved before going into IR. + // IR generaterion requires type information to be fully resolved. + // + // If type information is found to still be generic inside of + // IR, this is considered an internal compiler error. + // + // But, there are genuine cases for types be unknown here, like `let a = []`. These should + // have friendly errors. // // We _could_ introduce a new type here that uses TypeInfo instead of TypeId and throw away // the engine, since we don't need inference for IR. That'd be a _lot_ of copy-pasted code, diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs index 2731811f6a7..d0629249f51 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs @@ -221,7 +221,7 @@ where }, _ => { let variant_type_name = Self::generate_type(engines, x.type_argument.type_id)?; - format!("{tag_value} => {enum_name}::{variant_name}(buffer.decode::<{variant_type}>()), \n", + format!("{tag_value} => {enum_name}::{variant_name}(buffer.decode::<{variant_type}>()), \n", tag_value = x.tag, enum_name = enum_name, variant_name = name, @@ -551,6 +551,12 @@ where count.val() ) } + TypeInfo::Slice(elem_ty) => { + format!( + "__slice[{}]", + Self::generate_type(engines, elem_ty.type_id)? + ) + } TypeInfo::RawUntypedPtr => "raw_ptr".into(), TypeInfo::RawUntypedSlice => "raw_slice".into(), TypeInfo::Alias { name, .. } => name.to_string(), @@ -847,7 +853,7 @@ where let code = if args_types == "()" { format!( "pub fn __entry() -> raw_slice {{ - let result: {return_type} = main(); + let result: {return_type} = main(); encode::<{return_type}>(result) }}" ) @@ -855,7 +861,7 @@ where format!( "pub fn __entry() -> raw_slice {{ let args: {args_types} = decode_script_data::<{args_types}>(); - let result: {return_type} = main({expanded_args}); + let result: {return_type} = main({expanded_args}); encode::<{return_type}>(result) }}" ) diff --git a/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs b/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs index 9c324cf1289..e041de4bf45 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs @@ -10,7 +10,8 @@ use crate::{ engine_threading::*, language::{ parsed::{Expression, ExpressionKind}, - ty, Literal, + ty::{self, TyIntrinsicFunctionKind}, + Literal, }, semantic_analysis::{type_check_context::EnforceTypeArguments, TypeCheckContext}, type_system::*, @@ -101,10 +102,263 @@ impl ty::TyIntrinsicFunctionKind { Intrinsic::EncodeBufferAsRawSlice => { type_check_encode_as_raw_slice(handler, ctx, kind, arguments, type_arguments, span) } + Intrinsic::Slice => { + type_check_slice(handler, ctx, kind, arguments, type_arguments, span) + } + Intrinsic::ElemAt => type_check_elem_at(arguments, handler, kind, span, ctx), } } } +fn type_check_elem_at( + arguments: &[Expression], + handler: &Handler, + kind: Intrinsic, + span: Span, + ctx: TypeCheckContext, +) -> Result<(TyIntrinsicFunctionKind, TypeId), ErrorEmitted> { + if arguments.len() != 2 { + return Err(handler.emit_err(CompileError::IntrinsicIncorrectNumArgs { + name: kind.to_string(), + expected: 2, + span, + })); + } + + let type_engine = ctx.engines.te(); + let engines = ctx.engines(); + + let mut ctx = ctx; + + // check first argument + let first_argument_span = arguments[0].span.clone(); + let first_argument_type = type_engine.insert(engines, TypeInfo::Unknown, None); + let first_argument_typed_expr = { + let ctx = ctx + .by_ref() + .with_help_text("") + .with_type_annotation(first_argument_type); + ty::TyExpression::type_check(handler, ctx, &arguments[0])? + }; + + // first argument can be array or ref to slice + let elem_type = match &*type_engine.get(first_argument_type) { + TypeInfo::Array(elem_ty, _) => Some(elem_ty.type_id), + TypeInfo::Ref { + referenced_type, .. + } => match &*type_engine.get(referenced_type.type_id) { + TypeInfo::Slice(elem_ty) => Some(elem_ty.type_id), + _ => None, + }, + _ => None, + }; + let Some(elem_type_type_id) = elem_type else { + return Err(handler.emit_err(CompileError::IntrinsicUnsupportedArgType { + name: kind.to_string(), + span: first_argument_span, + hint: "".to_string(), + })); + }; + + // index argument + let index_type = type_engine.insert( + engines, + TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), + None, + ); + let index_typed_expr = { + let ctx = ctx + .by_ref() + .with_help_text("") + .with_type_annotation(index_type); + ty::TyExpression::type_check(handler, ctx, &arguments[1])? + }; + + let return_type = type_engine.insert( + engines, + TypeInfo::Ref { + to_mutable_value: true, + referenced_type: TypeArgument { + type_id: elem_type_type_id, + initial_type_id: elem_type_type_id, + span: Span::dummy(), + call_path_tree: None, + }, + }, + None, + ); + + Ok(( + TyIntrinsicFunctionKind { + kind, + arguments: vec![first_argument_typed_expr, index_typed_expr], + type_arguments: vec![], + span, + }, + return_type, + )) +} + +fn type_check_slice( + handler: &Handler, + mut ctx: TypeCheckContext, + kind: sway_ast::Intrinsic, + arguments: &[Expression], + _type_arguments: &[TypeArgument], + span: Span, +) -> Result<(ty::TyIntrinsicFunctionKind, TypeId), ErrorEmitted> { + if arguments.len() != 3 { + return Err(handler.emit_err(CompileError::IntrinsicIncorrectNumArgs { + name: kind.to_string(), + expected: 3, + span, + })); + } + + let type_engine = ctx.engines.te(); + let engines = ctx.engines(); + + // start index argument + let start_type = type_engine.insert( + engines, + TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), + None, + ); + let start_ty_expr = { + let ctx = ctx + .by_ref() + .with_help_text("") + .with_type_annotation(start_type); + ty::TyExpression::type_check(handler, ctx, &arguments[1])? + }; + + // end index argument + let end_type = type_engine.insert( + engines, + TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), + None, + ); + let end_ty_expr = { + let ctx = ctx + .by_ref() + .with_help_text("") + .with_type_annotation(end_type); + ty::TyExpression::type_check(handler, ctx, &arguments[2])? + }; + + // check first argument + let first_argument_span = arguments[0].span.clone(); + let first_argument_type = type_engine.insert(engines, TypeInfo::Unknown, None); + let first_argument_ty_expr = { + let ctx = ctx + .by_ref() + .with_help_text("") + .with_type_annotation(first_argument_type); + ty::TyExpression::type_check(handler, ctx, &arguments[0])? + }; + + // statically check start and end, if possible + let start_literal = start_ty_expr + .expression + .as_literal() + .and_then(|x| x.cast_value_to_u64()); + + let end_literal = end_ty_expr + .expression + .as_literal() + .and_then(|x| x.cast_value_to_u64()); + + if let (Some(start), Some(end)) = (start_literal, end_literal) { + if start > end { + return Err( + handler.emit_err(CompileError::InvalidRangeEndGreaterThanStart { + start, + end, + span, + }), + ); + } + } + + fn create_ref_to_slice(engines: &Engines, elem_type_arg: TypeArgument) -> TypeId { + let type_engine = engines.te(); + let slice_type_id = + type_engine.insert(engines, TypeInfo::Slice(elem_type_arg.clone()), None); + let ref_to_slice_type = TypeInfo::Ref { + to_mutable_value: false, + referenced_type: TypeArgument { + type_id: slice_type_id, + initial_type_id: slice_type_id, + span: Span::dummy(), + call_path_tree: None, + }, + }; + type_engine.insert(engines, ref_to_slice_type, None) + } + + // We can slice arrays or other slices + let err = CompileError::IntrinsicUnsupportedArgType { + name: kind.to_string(), + span: first_argument_span, + hint: "".to_string(), + }; + let r = match &*type_engine.get(first_argument_type) { + TypeInfo::Array(elem_type_arg, array_len) => { + let array_len = array_len.val() as u64; + + if let Some(v) = start_literal { + if v > array_len { + return Err(handler.emit_err(CompileError::ArrayOutOfBounds { + index: v, + count: array_len, + span, + })); + } + } + + if let Some(v) = end_literal { + if v > array_len { + return Err(handler.emit_err(CompileError::ArrayOutOfBounds { + index: v, + count: array_len, + span, + })); + } + } + + Some(( + TyIntrinsicFunctionKind { + kind, + arguments: vec![first_argument_ty_expr, start_ty_expr, end_ty_expr], + type_arguments: vec![], + span, + }, + create_ref_to_slice(engines, elem_type_arg.clone()), + )) + } + TypeInfo::Ref { + referenced_type, .. + } => match &*type_engine.get(referenced_type.type_id) { + TypeInfo::Slice(elem_type_arg) => Some(( + TyIntrinsicFunctionKind { + kind, + arguments: vec![first_argument_ty_expr, start_ty_expr, end_ty_expr], + type_arguments: vec![], + span, + }, + create_ref_to_slice(engines, elem_type_arg.clone()), + )), + _ => None, + }, + _ => None, + }; + + match r { + Some(r) => Ok(r), + None => Err(handler.emit_err(err)), + } +} + fn type_check_encode_as_raw_slice( handler: &Handler, mut ctx: TypeCheckContext, diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index e5f5ca57586..8a0c4f671c9 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -1805,20 +1805,20 @@ impl ty::TyExpression { let engines = ctx.engines(); if contents.is_empty() { - let never_type = type_engine.insert(engines, TypeInfo::Never, None); + let elem_type = type_engine.insert(engines, TypeInfo::Unknown, None); return Ok(ty::TyExpression { expression: ty::TyExpressionVariant::Array { - elem_type: never_type, + elem_type, contents: Vec::new(), }, return_type: type_engine.insert( engines, TypeInfo::Array( TypeArgument { - type_id: never_type, + type_id: elem_type, span: Span::dummy(), call_path_tree: None, - initial_type_id: never_type, + initial_type_id: elem_type, }, Length::new(0, Span::dummy()), ), diff --git a/sway-core/src/semantic_analysis/cei_pattern_analysis.rs b/sway-core/src/semantic_analysis/cei_pattern_analysis.rs index 808d114c23a..1dbad596ed4 100644 --- a/sway-core/src/semantic_analysis/cei_pattern_analysis.rs +++ b/sway-core/src/semantic_analysis/cei_pattern_analysis.rs @@ -647,7 +647,9 @@ fn effects_of_intrinsic(intr: &sway_ast::Intrinsic) -> HashSet { | Not | EncodeBufferEmpty | EncodeBufferAppend - | EncodeBufferAsRawSlice => HashSet::new(), + | EncodeBufferAsRawSlice + | Slice + | ElemAt => HashSet::new(), } } diff --git a/sway-core/src/semantic_analysis/node_dependencies.rs b/sway-core/src/semantic_analysis/node_dependencies.rs index 45d2858a1b8..9fe1e98b670 100644 --- a/sway-core/src/semantic_analysis/node_dependencies.rs +++ b/sway-core/src/semantic_analysis/node_dependencies.rs @@ -794,6 +794,7 @@ impl Dependencies { deps.gather_from_type_argument(engines, elem) }), TypeInfo::Array(elem_type, _) => self.gather_from_type_argument(engines, elem_type), + TypeInfo::Slice(elem_type) => self.gather_from_type_argument(engines, elem_type), TypeInfo::Struct(decl_ref) => self.gather_from_iter( decl_engine.get_struct(decl_ref).fields.iter(), |deps, field| deps.gather_from_type_argument(engines, &field.type_argument), diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index 621c8e8d838..dc1d4661e98 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -593,6 +593,28 @@ impl<'a> TypeCheckContext<'a> { elem_ty.span.source_id(), ) } + TypeInfo::Slice(mut elem_ty) => { + elem_ty.type_id = self + .resolve( + handler, + elem_ty.type_id, + span, + enforce_type_arguments, + None, + mod_path, + ) + .unwrap_or_else(|err| { + self.engines + .te() + .insert(self.engines, TypeInfo::ErrorRecovery(err), None) + }); + + self.engines.te().insert( + self.engines, + TypeInfo::Slice(elem_ty.clone()), + elem_ty.span.source_id(), + ) + } TypeInfo::Tuple(mut type_arguments) => { for type_argument in type_arguments.iter_mut() { type_argument.type_id = self diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index 793be444b13..eeb22e6a734 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -1049,7 +1049,7 @@ impl TypeInfo { TypeInfo::Tuple(fields) => fields .iter() .any(|field_type| id_uninhabited(field_type.type_id)), - TypeInfo::Array(elem_ty, length) => length.val() > 0 && id_uninhabited(elem_ty.type_id), + TypeInfo::Array(elem_ty, _) => id_uninhabited(elem_ty.type_id), TypeInfo::Ptr(ty) => id_uninhabited(ty.type_id), TypeInfo::Alias { name: _, ty } => id_uninhabited(ty.type_id), TypeInfo::Slice(ty) => id_uninhabited(ty.type_id), @@ -1175,6 +1175,16 @@ impl TypeInfo { matches!(self, TypeInfo::Ref { .. }) } + pub fn as_reference(&self) -> Option<(&bool, &TypeArgument)> { + match self { + TypeInfo::Ref { + to_mutable_value, + referenced_type, + } => Some((to_mutable_value, referenced_type)), + _ => None, + } + } + pub fn is_array(&self) -> bool { matches!(self, TypeInfo::Array(_, _)) } @@ -1191,6 +1201,18 @@ impl TypeInfo { matches!(self, TypeInfo::Tuple(_)) } + pub fn is_slice(&self) -> bool { + matches!(self, TypeInfo::Slice(_)) + } + + pub fn as_slice(&self) -> Option<&TypeArgument> { + if let TypeInfo::Slice(t) = self { + Some(t) + } else { + None + } + } + pub(crate) fn apply_type_arguments( self, handler: &Handler, @@ -1413,7 +1435,6 @@ impl TypeInfo { | TypeInfo::RawUntypedPtr | TypeInfo::RawUntypedSlice | TypeInfo::Ptr(_) - | TypeInfo::Slice(_) | TypeInfo::ErrorRecovery(_) | TypeInfo::TraitType { .. } | TypeInfo::Never => false, @@ -1423,6 +1444,7 @@ impl TypeInfo { | TypeInfo::Custom { .. } | TypeInfo::Tuple(_) | TypeInfo::Array(_, _) + | TypeInfo::Slice(_) | TypeInfo::Contract | TypeInfo::Storage { .. } | TypeInfo::Numeric diff --git a/sway-core/src/type_system/substitute/subst_map.rs b/sway-core/src/type_system/substitute/subst_map.rs index 3d917bc5f43..9eb13a8da3d 100644 --- a/sway-core/src/type_system/substitute/subst_map.rs +++ b/sway-core/src/type_system/substitute/subst_map.rs @@ -233,6 +233,14 @@ impl TypeSubstMap { vec![type_argument.type_id], ) } + (TypeInfo::Slice(type_parameter), TypeInfo::Slice(type_argument)) => { + TypeSubstMap::from_superset_and_subset_helper( + type_engine, + decl_engine, + vec![type_parameter.type_id], + vec![type_argument.type_id], + ) + } ( TypeInfo::Storage { fields: type_parameters, @@ -408,6 +416,15 @@ impl TypeSubstMap { ) }) } + TypeInfo::Slice(mut elem_ty) => { + let type_id = self.find_match(elem_ty.type_id, engines)?; + elem_ty.type_id = type_id; + Some(type_engine.insert( + engines, + TypeInfo::Slice(elem_ty.clone()), + elem_ty.span.source_id(), + )) + } TypeInfo::Tuple(fields) => { let mut need_to_create_new = false; let mut source_id = None; @@ -470,10 +487,6 @@ impl TypeSubstMap { ty.type_id = type_id; type_engine.insert(engines, TypeInfo::Ptr(ty.clone()), ty.span.source_id()) }), - TypeInfo::Slice(mut ty) => self.find_match(ty.type_id, engines).map(|type_id| { - ty.type_id = type_id; - type_engine.insert(engines, TypeInfo::Slice(ty.clone()), ty.span.source_id()) - }), TypeInfo::TraitType { .. } => iter_for_match(engines, self, &type_info), TypeInfo::Ref { to_mutable_value, diff --git a/sway-core/src/type_system/unify/unifier.rs b/sway-core/src/type_system/unify/unifier.rs index 0efc7883153..e94cb3b7384 100644 --- a/sway-core/src/type_system/unify/unifier.rs +++ b/sway-core/src/type_system/unify/unifier.rs @@ -93,8 +93,8 @@ impl<'a> Unifier<'a> { pub(crate) fn unify(&self, handler: &Handler, received: TypeId, expected: TypeId, span: &Span) { use TypeInfo::{ Alias, Array, Boolean, Contract, Enum, Never, Numeric, Placeholder, RawUntypedPtr, - RawUntypedSlice, Ref, StringArray, StringSlice, Struct, Tuple, Unknown, UnknownGeneric, - UnsignedInteger, B256, + RawUntypedSlice, Ref, Slice, StringArray, StringSlice, Struct, Tuple, Unknown, + UnknownGeneric, UnsignedInteger, B256, }; if received == expected { @@ -123,6 +123,9 @@ impl<'a> Unifier<'a> { (Array(re, rc), Array(ee, ec)) if rc.val() == ec.val() => { self.unify_type_arguments_in_parents(handler, received, expected, span, re, ee); } + (Slice(re), Slice(ee)) => { + self.unify_type_arguments_in_parents(handler, received, expected, span, re, ee); + } (Struct(r_decl_ref), Struct(e_decl_ref)) => { let r_decl = self.engines.de().get_struct(r_decl_ref); let e_decl = self.engines.de().get_struct(e_decl_ref); diff --git a/sway-core/src/type_system/unify/unify_check.rs b/sway-core/src/type_system/unify/unify_check.rs index 24f73a077c1..26a0777bdee 100644 --- a/sway-core/src/type_system/unify/unify_check.rs +++ b/sway-core/src/type_system/unify/unify_check.rs @@ -224,7 +224,8 @@ impl<'a> UnifyCheck<'a> { fn check_inner(&self, left: TypeId, right: TypeId) -> bool { use TypeInfo::{ Alias, Array, ContractCaller, Custom, Enum, ErrorRecovery, Never, Numeric, Placeholder, - Ref, StringArray, StringSlice, Struct, Tuple, Unknown, UnknownGeneric, UnsignedInteger, + Ref, Slice, StringArray, StringSlice, Struct, Tuple, Unknown, UnknownGeneric, + UnsignedInteger, }; use UnifyCheckMode::{ Coercion, ConstraintSubset, NonDynamicEquality, NonGenericConstraintSubset, @@ -242,10 +243,15 @@ impl<'a> UnifyCheck<'a> { (Never, Never) => { return true; } + (Array(l0, l1), Array(r0, r1)) => { return self.check_inner(l0.type_id, r0.type_id) && l1.val() == r1.val(); } + (Slice(l0), Slice(r0)) => { + return self.check_inner(l0.type_id, r0.type_id); + } + (Tuple(l_types), Tuple(r_types)) => { let l_types = l_types.iter().map(|x| x.type_id).collect::>(); let r_types = r_types.iter().map(|x| x.type_id).collect::>(); diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 45c42c222dd..f0bb9c26a84 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -655,8 +655,12 @@ pub enum CompileError { ContractStorageFromExternalContext { span: Span }, #[error("The {opcode} opcode cannot be used in a predicate.")] InvalidOpcodeFromPredicate { opcode: String, span: Span }, - #[error("Array index out of bounds; the length is {count} but the index is {index}.")] + #[error("Index out of bounds; the length is {count} but the index is {index}.")] ArrayOutOfBounds { index: u64, count: u64, span: Span }, + #[error( + "Invalid range; the range end at index {end} is smaller than its start at index {start}" + )] + InvalidRangeEndGreaterThanStart { start: u64, end: u64, span: Span }, #[error("Tuple index {index} is out of bounds. The tuple has {count} element{}.", plural_s(*count))] TupleIndexOutOfBounds { index: usize, @@ -1005,6 +1009,8 @@ pub enum CompileError { EncodingUnsupportedType { span: Span }, #[error("Configurables need a function named \"abi_decode_in_place\" to be in scope.")] ConfigurableMissingAbiDecodeInPlace { span: Span }, + #[error("Type must be known at this point")] + TypeMustBeKnownAtThisPoint { span: Span, internal: String }, } impl std::convert::From for CompileError { @@ -1220,6 +1226,8 @@ impl Spanned for CompileError { CannotBeEvaluatedToConfigurableSizeUnknown { span } => span.clone(), EncodingUnsupportedType { span } => span.clone(), ConfigurableMissingAbiDecodeInPlace { span } => span.clone(), + InvalidRangeEndGreaterThanStart { span, .. } => span.clone(), + TypeMustBeKnownAtThisPoint { span, .. } => span.clone(), } } } @@ -2668,6 +2676,9 @@ pub enum TypeNotAllowedReason { #[error("`str` or a type containing `str` on `const` is not allowed.")] StringSliceInConst, + + #[error("slices or types containing slices on `const` are not allowed.")] + SliceInConst, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/sway-ir/src/asm.rs b/sway-ir/src/asm.rs index 8a74df16ecf..b6f4cd657c9 100644 --- a/sway-ir/src/asm.rs +++ b/sway-ir/src/asm.rs @@ -43,6 +43,82 @@ pub struct AsmInstruction { pub metadata: Option, } +impl AsmInstruction { + pub fn log_no_span( + ra: impl Into, + rb: impl Into, + rc: impl Into, + rd: impl Into, + ) -> Self { + AsmInstruction { + op_name: Ident::new(sway_types::Span::from_string("log".into())), + args: vec![ + Ident::new_no_span(ra.into()), + Ident::new_no_span(rb.into()), + Ident::new_no_span(rc.into()), + Ident::new_no_span(rd.into()), + ], + immediate: None, + metadata: None, + } + } + + pub fn lw_no_span( + dst: impl Into, + src: impl Into, + offset: impl Into, + ) -> Self { + AsmInstruction { + op_name: Ident::new(sway_types::Span::from_string("lw".into())), + args: vec![ + Ident::new_no_span(dst.into()), + Ident::new_no_span(src.into()), + ], + immediate: Some(Ident::new_no_span(offset.into())), + metadata: None, + } + } + + pub fn mul_no_span(dst: impl Into, a: impl Into, b: impl Into) -> Self { + AsmInstruction { + op_name: Ident::new(sway_types::Span::from_string("mul".into())), + args: vec![ + Ident::new_no_span(dst.into()), + Ident::new_no_span(a.into()), + Ident::new_no_span(b.into()), + ], + immediate: None, + metadata: None, + } + } + + pub fn add_no_span(dst: impl Into, a: impl Into, b: impl Into) -> Self { + AsmInstruction { + op_name: Ident::new(sway_types::Span::from_string("add".into())), + args: vec![ + Ident::new_no_span(dst.into()), + Ident::new_no_span(a.into()), + Ident::new_no_span(b.into()), + ], + immediate: None, + metadata: None, + } + } + + pub fn sub_no_span(dst: impl Into, a: impl Into, b: impl Into) -> Self { + AsmInstruction { + op_name: Ident::new(sway_types::Span::from_string("sub".into())), + args: vec![ + Ident::new_no_span(dst.into()), + Ident::new_no_span(a.into()), + Ident::new_no_span(b.into()), + ], + immediate: None, + metadata: None, + } + } +} + impl AsmBlock { /// Create a new [`AsmBlock`] in the passed context and return its handle. pub fn new( diff --git a/sway-ir/src/constant.rs b/sway-ir/src/constant.rs index 9aaae3c4218..b3715b16803 100644 --- a/sway-ir/src/constant.rs +++ b/sway-ir/src/constant.rs @@ -23,6 +23,7 @@ pub enum ConstantValue { B256(B256), String(Vec), Array(Vec), + Slice(Vec), Struct(Vec), Reference(Box), RawUntypedSlice(Vec), @@ -269,4 +270,11 @@ impl Constant { _ => false, } } + + pub fn as_uint(&self) -> Option { + match &self.value { + ConstantValue::Uint(v) => Some(*v), + _ => None, + } + } } diff --git a/sway-ir/src/irtype.rs b/sway-ir/src/irtype.rs index 3bca2ace003..1211c1da6fe 100644 --- a/sway-ir/src/irtype.rs +++ b/sway-ir/src/irtype.rs @@ -39,6 +39,7 @@ pub enum TypeContent { Struct(Vec), Slice, Pointer(Type), + TypedSlice(Type), } impl Type { @@ -162,6 +163,11 @@ impl Type { Self::get_type(context, &TypeContent::Slice).expect("create_basic_types not called") } + /// Get typed slice type + pub fn get_typed_slice(context: &mut Context, item_ty: Type) -> Type { + Self::get_or_create_unique_type(context, TypeContent::TypedSlice(item_ty)) + } + /// Return a string representation of type, used for printing. pub fn as_string(&self, context: &Context) -> String { let sep_types_str = |agg_content: &Vec, sep: &str| { @@ -190,6 +196,7 @@ impl Type { format!("{{ {} }}", sep_types_str(agg, ", ")) } TypeContent::Slice => "slice".into(), + TypeContent::TypedSlice(ty) => format!("__slice[{}]", ty.as_string(context)), TypeContent::Pointer(ty) => format!("ptr {}", ty.as_string(context)), } } @@ -209,6 +216,9 @@ impl Type { (TypeContent::Array(l, llen), TypeContent::Array(r, rlen)) => { llen == rlen && l.eq(context, r) } + + (TypeContent::TypedSlice(l), TypeContent::TypedSlice(r)) => l.eq(context, r), + (TypeContent::Struct(l), TypeContent::Struct(r)) | (TypeContent::Union(l), TypeContent::Union(r)) => { l.len() == r.len() && l.iter().zip(r.iter()).all(|(l, r)| l.eq(context, r)) @@ -451,6 +461,15 @@ impl Type { } } + /// Get the type of the array element, if applicable. + pub fn get_typed_slice_elem_type(&self, context: &Context) -> Option { + if let TypeContent::TypedSlice(ty) = *self.get_content(context) { + Some(ty) + } else { + None + } + } + /// Get the length of the array , if applicable. pub fn get_array_len(&self, context: &Context) -> Option { if let TypeContent::Array(_, n) = *self.get_content(context) { @@ -542,6 +561,7 @@ impl Type { TypeContent::Uint(256) => TypeSize::new(32), TypeContent::Uint(_) => unreachable!(), TypeContent::Slice => TypeSize::new(16), + TypeContent::TypedSlice(..) => TypeSize::new(16), TypeContent::B256 => TypeSize::new(32), TypeContent::StringSlice => TypeSize::new(16), TypeContent::StringArray(n) => { diff --git a/sway-ir/src/optimize/sroa.rs b/sway-ir/src/optimize/sroa.rs index 31550709dfb..70c8160362c 100644 --- a/sway-ir/src/optimize/sroa.rs +++ b/sway-ir/src/optimize/sroa.rs @@ -404,6 +404,7 @@ fn is_processable_aggregate(context: &Context, ty: Type) -> bool { fields.iter().all(|ty| check_sub_types(context, *ty)) } crate::TypeContent::Slice => false, + crate::TypeContent::TypedSlice(..) => false, crate::TypeContent::Pointer(_) => true, crate::TypeContent::StringSlice => false, crate::TypeContent::StringArray(_) => false, diff --git a/sway-ir/src/printer.rs b/sway-ir/src/printer.rs index 19b90c92861..bcb957b2999 100644 --- a/sway-ir/src/printer.rs +++ b/sway-ir/src/printer.rs @@ -1188,6 +1188,15 @@ impl Constant { .collect::>() .join(", ") ), + ConstantValue::Slice(elems) => format!( + "__slice[{}] [{}]", + self.ty.as_string(context), + elems + .iter() + .map(|elem| elem.as_lit_string(context)) + .collect::>() + .join(", ") + ), ConstantValue::Struct(fields) => format!( "{} {{ {} }}", self.ty.as_string(context), diff --git a/sway-lib-core/src/codec.sw b/sway-lib-core/src/codec.sw index 9ab99efc188..2b2489b16e8 100644 --- a/sway-lib-core/src/codec.sw +++ b/sway-lib-core/src/codec.sw @@ -2533,7 +2533,7 @@ where } } -// Decode +// Decode pub trait AbiDecode { fn abi_decode(ref mut buffer: BufferReader) -> Self; diff --git a/sway-lib-core/src/lib.sw b/sway-lib-core/src/lib.sw index 9bec5cc9bd9..4437bdd85cb 100644 --- a/sway-lib-core/src/lib.sw +++ b/sway-lib-core/src/lib.sw @@ -3,6 +3,7 @@ library; pub mod primitives; pub mod raw_ptr; pub mod raw_slice; +pub mod slice; pub mod r#str; pub mod ops; pub mod primitive_conversions; diff --git a/sway-lib-core/src/ops.sw b/sway-lib-core/src/ops.sw index 1229ca8e649..7dd28960cb3 100644 --- a/sway-lib-core/src/ops.sw +++ b/sway-lib-core/src/ops.sw @@ -1,6 +1,7 @@ library; use ::primitives::*; +use ::slice::*; /// Trait for the addition of two values. pub trait Add { diff --git a/sway-lib-core/src/slice.sw b/sway-lib-core/src/slice.sw new file mode 100644 index 00000000000..c2a19246696 --- /dev/null +++ b/sway-lib-core/src/slice.sw @@ -0,0 +1,19 @@ +library; + +use ::raw_ptr::*; + +impl &__slice[T] { + pub fn ptr(self) -> raw_ptr { + let (ptr, _) = asm(s: self) { + s: (raw_ptr, u64) + }; + ptr + } + + pub fn len(self) -> u64 { + let (_, len) = asm(s: self) { + s: (raw_ptr, u64) + }; + len + } +} diff --git a/sway-lsp/src/core/token.rs b/sway-lsp/src/core/token.rs index 51a84347d83..3e657a0cff9 100644 --- a/sway-lsp/src/core/token.rs +++ b/sway-lsp/src/core/token.rs @@ -285,6 +285,10 @@ pub fn type_info_to_symbol_kind( let type_info = type_engine.get(elem_ty.type_id); type_info_to_symbol_kind(type_engine, &type_info, Some(&elem_ty.span())) } + TypeInfo::Slice(elem_ty) => { + let type_info = type_engine.get(elem_ty.type_id); + type_info_to_symbol_kind(type_engine, &type_info, Some(&elem_ty.span())) + } _ => SymbolKind::Unknown, } } diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index 39ba771f879..869afce1925 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -1041,6 +1041,9 @@ impl Parse for TypeArgument { ); type_arg.parse(ctx); } + TypeInfo::Slice(type_arg) => { + type_arg.parse(ctx); + } TypeInfo::Tuple(type_arguments) => { adaptive_iter(type_arguments, |type_arg| type_arg.parse(ctx)); } diff --git a/sway-lsp/src/traverse/typed_tree.rs b/sway-lsp/src/traverse/typed_tree.rs index acfa4bf9de2..3c6098cc43d 100644 --- a/sway-lsp/src/traverse/typed_tree.rs +++ b/sway-lsp/src/traverse/typed_tree.rs @@ -1270,6 +1270,9 @@ fn collect_type_id( TypeInfo::Array(type_arg, ..) => { collect_type_argument(ctx, type_arg); } + TypeInfo::Slice(type_arg, ..) => { + collect_type_argument(ctx, type_arg); + } TypeInfo::Tuple(type_arguments) => { adaptive_iter(type_arguments, |type_arg| { collect_type_argument(ctx, type_arg); diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index 62993a04149..41897a180ba 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -13,6 +13,7 @@ use forc_pkg::manifest::{GenericManifestFile, ManifestFile}; use forc_pkg::BuildProfile; use forc_test::decode_log_data; use fuel_vm::fuel_tx; +use fuel_vm::fuel_types::canonical::Input; use fuel_vm::prelude::*; use regex::Regex; use std::collections::HashSet; @@ -114,6 +115,8 @@ struct TestContext { } fn print_receipts(output: &mut String, receipts: &[Receipt]) { + let mut text_log = String::new(); + use std::fmt::Write; let _ = writeln!(output, " {}", "Receipts".green().bold()); for (i, receipt) in receipts.iter().enumerate() { @@ -130,6 +133,28 @@ fn print_receipts(output: &mut String, receipts: &[Receipt]) { is, data, } => { + // Small hack to allow log from tests. + if *ra == u64::MAX { + match rb { + 0 => { + let mut data = data.as_deref().unwrap(); + data.skip(8).unwrap(); + let s = std::str::from_utf8(data).unwrap(); + + text_log.push_str(s); + } + 1 => { + let data = data.as_deref().unwrap(); + let s = u64::from_be_bytes(data.try_into().unwrap()); + + text_log.push_str(&format!("{}", s)); + } + 2 => { + text_log.push('\n'); + } + _ => {} + } + } let _ = write!(output, " LogData\n ID: {id:?}\n RA: {ra:?}\n RB: {rb:?}\n Ptr: {ptr:?}\n Len: {len:?}\n Digest: {digest:?}\n PC: {pc:?}\n IS: {is:?}\n Data: {data:?}\n"); } Receipt::ReturnData { @@ -239,6 +264,14 @@ fn print_receipts(output: &mut String, receipts: &[Receipt]) { } } } + + if !text_log.is_empty() { + let _ = writeln!(output, " {}", "Text Logs".green().bold()); + + for l in text_log.lines() { + let _ = writeln!(output, "{l}"); + } + } } impl TestContext { diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob/test.toml index f4fb799b640..059ddb65458 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob/test.toml @@ -1,3 +1,3 @@ category = "fail" -# check: $()Array index out of bounds; the length is 3 but the index is 4. +# check: $()Index out of bounds; the length is 3 but the index is 4. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_global_const_index/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_global_const_index/test.toml index f4fb799b640..059ddb65458 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_global_const_index/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_global_const_index/test.toml @@ -1,3 +1,3 @@ category = "fail" -# check: $()Array index out of bounds; the length is 3 but the index is 4. +# check: $()Index out of bounds; the length is 3 but the index is 4. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_variable_index/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_variable_index/test.toml index f4fb799b640..059ddb65458 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_variable_index/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_variable_index/test.toml @@ -1,3 +1,3 @@ category = "fail" -# check: $()Array index out of bounds; the length is 3 but the index is 4. +# check: $()Index out of bounds; the length is 3 but the index is 4. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/Forc.lock new file mode 100644 index 00000000000..c5bc5415952 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "core" +source = "path+from-root-53D6DC514F06A250" + +[[package]] +name = "slice_intrinsics" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/Forc.toml new file mode 100644 index 00000000000..011fe1ab735 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "slice_intrinsics" + +[dependencies] +core = { path = "../../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/json_abi_oracle.json new file mode 100644 index 00000000000..ad50b55d54c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "u64", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/json_abi_oracle_new_encoding.json new file mode 100644 index 00000000000..e60dda965d4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/json_abi_oracle_new_encoding.json @@ -0,0 +1,26 @@ +{ + "configurables": [], + "encoding": "1", + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": [], + "type": "()", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/src/main.sw new file mode 100644 index 00000000000..919047ccbf1 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/src/main.sw @@ -0,0 +1,40 @@ +script; + +// slice cannot be consts +const GLOBAL_ARRAY: [u64; 5] = [1, 2, 3, 4, 5]; +const GLOBAL_SLICE: &__slice[u64] = __slice(GLOBAL_ARRAY, 0, 5); + +fn main() { + // slice cannot be consts + const LOCAL_ARRAY: [u64; 5] = [1, 2, 3, 4, 5]; + const LOCAL_SLICE: &__slice[u64] = __slice(LOCAL_ARRAY, 0, 5); + + // Wrong start index + let a: [u64; 5] = [1, 2, 3, 4, 5]; + let _ = __slice(a, 6, 7); + + // Wrong end index + let a: [u64; 5] = [1, 2, 3, 4, 5]; + let _ = __slice(a, 0, 6); + + // Wrong first argument + __slice(0, 0, 0); + + // Wrong start index + __slice(a, "", 0); + + // Wrong end index + __slice(a, 0, ""); + + let a: [u64; 5] = [1, 2, 3, 4, 5]; + let s: &__slice[u64] = __slice(LOCAL_ARRAY, 0, 5); + + // Wrong first argument + __elem_at(0, 0); + + // Wrong index type + __elem_at(a, ""); + + // Wrong index type + __elem_at(s, ""); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/test.toml new file mode 100644 index 00000000000..e9cb4879e6c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/slice/slice_intrinsics/test.toml @@ -0,0 +1,39 @@ +category = "fail" + +# check: let _ = __slice(a, 6, 7) +# nextln: $()Index out of bounds; the length is 5 but the index is 6. + +# check: let _ = __slice(a, 0, 6) +# nextln: $()Index out of bounds; the length is 5 but the index is 6. + +# check: __slice(0, 0, 0) +# nextln: $()Unsupported argument type to intrinsic "slice" + +# check: __slice(a, "", 0) +# nextln: $()Mismatched types +# nextln: $()expected: u64 +# nextln: $()found: str + +# check: __slice(a, 0, "") +# nextln: $()Mismatched types +# nextln: $()expected: u64 +# nextln: $()found: str +# +# check: __elem_at(0, 0); +# nextln: $()Unsupported argument type to intrinsic "elem_at" + +# check: __elem_at(a, ""); +# nextln: $()Mismatched types. +# nextln: $()expected: u64 +# nextln: $()found: str. + +# check: __elem_at(s, ""); +# nextln: $()Mismatched types. +# nextln: $()expected: u64 +# nextln: $()found: str. + +# check: &__slice[u64] = __slice(GLOBAL_ARRAY, 0, 5) +# nextln: $()slices or types containing slices on `const` are not allowed + +# check: &__slice[u64] = __slice(LOCAL_ARRAY, 0, 5) +# nextln: $()slices or types containing slices on `const` are not allowed diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/Forc.lock new file mode 100644 index 00000000000..a7de4b3565f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/Forc.lock @@ -0,0 +1,16 @@ +[[package]] +name = "core" +source = "path+from-root-53D6DC514F06A250" + +[[package]] +name = "slice_intrinsics" +source = "member" +dependencies = [ + "core", + "utils", +] + +[[package]] +name = "utils" +source = "path+from-root-53D6DC514F06A250" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/Forc.toml new file mode 100644 index 00000000000..fe47736bbcf --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "slice_intrinsics" + +[dependencies] +core = { path = "../../../../../../../../sway-lib-core" } +utils = { path = "../../../../../../../../test/src/e2e_vm_tests/utils" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/json_abi_oracle.json new file mode 100644 index 00000000000..ad50b55d54c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "u64", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/json_abi_oracle_new_encoding.json new file mode 100644 index 00000000000..17f3d600e0d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/json_abi_oracle_new_encoding.json @@ -0,0 +1,41 @@ +{ + "configurables": [], + "encoding": "1", + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [ + { + "logId": "1515152261580153489", + "loggedType": { + "name": "", + "type": 1, + "typeArguments": null + } + } + ], + "messagesTypes": [], + "types": [ + { + "components": [], + "type": "()", + "typeId": 0, + "typeParameters": null + }, + { + "components": null, + "type": "u64", + "typeId": 1, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/src/main.sw new file mode 100644 index 00000000000..6364100f77c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/src/main.sw @@ -0,0 +1,164 @@ +script; + +use utils::*; + +fn alloc_slice(len: u64) -> &__slice[T] { + let size_in_bytes = len * __size_of::(); + let ptr = asm(size_in_bytes: size_in_bytes) { + aloc size_in_bytes; + hp: raw_ptr + }; + asm(buf: (ptr, len)) { + buf: &__slice[T] + } +} + +fn realloc_slice(old: &__slice[T], len: u64) -> &__slice[T] { + let old_ptr = old.ptr(); + let old_len_in_bytes = old.len() * __size_of::(); + + let new_len_in_bytes = len * __size_of::(); + let new_ptr = asm(new_len_in_bytes: new_len_in_bytes, old_ptr: old_ptr, old_len_in_bytes: old_len_in_bytes) { + aloc new_len_in_bytes; + mcp hp old_ptr old_len_in_bytes; + hp: raw_ptr + }; + + asm(buf: (new_ptr, len)) { + buf: &__slice[T] + } +} + +pub struct Vec { + buf: &__slice[T], + len: u64, +} + +impl Dbg for Vec { + fn dbg(self) { + ( + "Vec { buf: (ptr: ", + asm(v: self.buf.ptr()) { v: u64 }, + ", len: ", + self.buf.len(), + "), len: ", + self.len, + ")" + ).dbg(); + } +} + + +impl Vec { + pub fn new() -> Self { + Self { + buf: alloc_slice::(0), + len: 0 + } + } + + pub fn push(ref mut self, item: T) { + "Vec::push(...)".dbgln(); + (" ", self).dbgln(); + + let new_item_idx = self.len; + let current_cap = self.buf.len(); + if new_item_idx >= current_cap { + let new_cap = if current_cap == 0 { + 1 + } else { + current_cap * 2 + }; + self.buf = realloc_slice(self.buf, new_cap); + (" After realloc: ", self).dbgln(); + } + + let v: &mut T = __elem_at(self.buf, new_item_idx); + + let buffer_addr = asm(v: self.buf.ptr()) { v: u64 }; + let elem_addr = asm(v: v) { v: u64 }; + (" elem ", new_item_idx, " at ", elem_addr, " buffer offset (in bytes): ", elem_addr - buffer_addr).dbgln(); + *v = item; + + self.len += 1; + + (" ", self).dbgln(); + } + + pub fn get(self, index: u64) -> T { + ("Vec::get(", index, ")").dbgln(); + (" ", self).dbgln(); + + let item: &mut T = __elem_at(self.buf, index); + + let buffer_addr = asm(v: self.buf.ptr()) { v: u64 }; + let elem_addr = asm(v: item) { v: u64 }; + (" element ", index, " at ", elem_addr, " buffer offset (in bytes): ", elem_addr - buffer_addr).dbgln(); + + *item + } +} + +fn assert(l: T, r: T) +where + T: Eq + AbiEncode +{ + if l != r { + __log(l); + __log(r); + __revert(1) + } +} + +fn main() { + // index arrays + let some_array: [u64; 5] = [1, 2, 3, 4, 5]; + assert(1, *__elem_at(some_array, 0)); + assert(2, *__elem_at(some_array, 1)); + assert(3, *__elem_at(some_array, 2)); + assert(4, *__elem_at(some_array, 3)); + assert(5, *__elem_at(some_array, 4)); + + // slice arrays + let some_slice: &__slice[u64] = __slice(some_array, 0, 5); + assert(1, *__elem_at(some_slice, 0)); + assert(2, *__elem_at(some_slice, 1)); + assert(3, *__elem_at(some_slice, 2)); + assert(4, *__elem_at(some_slice, 3)); + assert(5, *__elem_at(some_slice, 4)); + + // slice another slice + let another_slice: &__slice[u64] = __slice(some_slice, 1, 4); + assert(2, *__elem_at(another_slice, 0)); + assert(3, *__elem_at(another_slice, 1)); + assert(4, *__elem_at(another_slice, 2)); + + // Vec impl using slices + let mut v: Vec = Vec::new(); + v.push(1); + assert(v.get(0), 1); + + v.push(2); + v.push(3); + assert(v.get(0), 1); + assert(v.get(1), 2); + assert(v.get(2), 3); + + v.push(4); + v.push(5); + v.push(6); + v.push(7); + assert(v.get(0), 1); + assert(v.get(1), 2); + assert(v.get(2), 3); + assert(v.get(3), 4); + assert(v.get(4), 5); + assert(v.get(5), 6); + assert(v.get(6), 7); + + //indices as expressions + assert(2, *__elem_at(some_array, v.get(0))); + + let _some_slice: &__slice[u64] = __slice(some_array, v.get(0), v.get(4)); + assert(2, *__elem_at(some_slice, v.get(0))); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/test.toml new file mode 100644 index 00000000000..dae3eb53b08 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/slice/slice_intrinsics/test.toml @@ -0,0 +1,7 @@ +category = "run" + +category_new_encoding = "run" +script_data_new_encoding = "" +expected_result_new_encoding = { action = "return_data", value = "" } + +validate_abi = true diff --git a/test/src/e2e_vm_tests/utils/.gitignore b/test/src/e2e_vm_tests/utils/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/test/src/e2e_vm_tests/utils/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/test/src/e2e_vm_tests/utils/Forc.toml b/test/src/e2e_vm_tests/utils/Forc.toml new file mode 100644 index 00000000000..10a6cda5587 --- /dev/null +++ b/test/src/e2e_vm_tests/utils/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "utils" + +[dependencies] +core = { path = "../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/utils/src/main.sw b/test/src/e2e_vm_tests/utils/src/main.sw new file mode 100644 index 00000000000..4dcfbf52f28 --- /dev/null +++ b/test/src/e2e_vm_tests/utils/src/main.sw @@ -0,0 +1,146 @@ +library; + +/// This is only useful for the e2e harness setup, because +/// no one else knows how to "decode" this into meaningful +// textual logs. +pub trait Dbg { + fn dbg(self); +} { + fn dbgln(self) { + self.dbg(); + asm(ra: u64::max(), rb: 2) { + logd ra rb zero zero; + } + } +} + +impl Dbg for str { + fn dbg(self) { + let encoded = encode(self); + asm(ra: u64::max(), ptr: encoded.ptr(), len: encoded.len::()) { + logd ra zero ptr len; + } + } +} + + +impl Dbg for u64 { + fn dbg(self) { + let encoded = encode(self); + asm(ra: u64::max(), ptr: encoded.ptr(), len: encoded.len::()) { + logd ra one ptr len; + } + } +} + +pub struct NewLine {} + +pub fn new_line() -> NewLine { + NewLine { } +} + +impl Dbg for NewLine { + fn dbg(self) { + asm(ra: u64::max(), rb: 2) { + logd ra rb zero zero; + } + } +} + +impl Dbg for (A, B) +where + A: Dbg, + B: Dbg, +{ + fn dbg(self) { + self.0.dbg(); + self.1.dbg(); + } +} + +impl Dbg for (A, B, C) +where + A: Dbg, + B: Dbg, + C: Dbg, +{ + #[allow(dead_code)] + fn dbg(self) { + self.0.dbg(); + self.1.dbg(); + self.2.dbg(); + } +} + +impl Dbg for (A, B, C, D) +where + A: Dbg, + B: Dbg, + C: Dbg, + D: Dbg +{ + fn dbg(self) { + self.0.dbg(); + self.1.dbg(); + self.2.dbg(); + self.3.dbg(); + } +} + + +impl Dbg for (A, B, C, D, E) +where + A: Dbg, + B: Dbg, + C: Dbg, + D: Dbg, + E: Dbg, +{ + fn dbg(self) { + self.0.dbg(); + self.1.dbg(); + self.2.dbg(); + self.3.dbg(); + self.4.dbg(); + } +} + +impl Dbg for (A, B, C, D, E, F) +where + A: Dbg, + B: Dbg, + C: Dbg, + D: Dbg, + E: Dbg, + F: Dbg, +{ + fn dbg(self) { + self.0.dbg(); + self.1.dbg(); + self.2.dbg(); + self.3.dbg(); + self.4.dbg(); + self.5.dbg(); + } +} + +impl Dbg for (A, B, C, D, E, F, G) +where + A: Dbg, + B: Dbg, + C: Dbg, + D: Dbg, + E: Dbg, + F: Dbg, + G: Dbg +{ + fn dbg(self) { + self.0.dbg(); + self.1.dbg(); + self.2.dbg(); + self.3.dbg(); + self.4.dbg(); + self.5.dbg(); + self.6.dbg(); + } +}