Skip to content

Commit

Permalink
Transmute intrinsics (#6692)
Browse files Browse the repository at this point in the history
## Description

This PR implements the `__transmute` intrinsic. As Rust this intrinsic
should be a no-op semantically equal to a pointer cast. Although this PR
does not aim that it is 100% no-op yet.

At the moment some trade-offs were taken:

1 - References, pointers etc... are not allowed at the moment;
2 - `u16` and `u32` actually occupy 64bits. To allow transmute to work
in complex aggregates, this PR is accepting them as `u64`. Doing
otherwise would forbid `__transmute` to be no-op. Specially in complex
aggregates;

## Checklist

- [ ] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [ ] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [ ] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: João Matos <[email protected]>
  • Loading branch information
xunilrj and tritao authored Nov 14, 2024
1 parent f7fa086 commit 9951c1d
Show file tree
Hide file tree
Showing 21 changed files with 608 additions and 5 deletions.
3 changes: 3 additions & 0 deletions sway-ast/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub enum Intrinsic {
EncodeBufferAsRawSlice, // let slice: raw_slice = __encode_buffer_as_raw_slice(buffer)
Slice, // let ref_to_slice = __slice::<T: array or ref_to_slice>(item: T, inclusive_start_index, exclusive_end_index)
ElemAt, // let elem: &T = __elem_at::<T: array or ref_to_slice>(item: T, index)
Transmute, // let dst: B = __transmute::<A, B>(src)
}

impl fmt::Display for Intrinsic {
Expand Down Expand Up @@ -89,6 +90,7 @@ impl fmt::Display for Intrinsic {
Intrinsic::EncodeBufferAsRawSlice => "encode_buffer_as_raw_slice",
Intrinsic::Slice => "slice",
Intrinsic::ElemAt => "elem_at",
Intrinsic::Transmute => "transmute",
};
write!(f, "{s}")
}
Expand Down Expand Up @@ -139,6 +141,7 @@ impl Intrinsic {
"__encode_buffer_as_raw_slice" => EncodeBufferAsRawSlice,
"__slice" => Slice,
"__elem_at" => ElemAt,
"__transmute" => Transmute,
_ => return None,
})
}
Expand Down
135 changes: 134 additions & 1 deletion sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::ops::{BitAnd, BitOr, BitXor, Not, Rem};
use std::{
io::Read,
ops::{BitAnd, BitOr, BitXor, Not, Rem},
};

use crate::{
engine_threading::*,
Expand Down Expand Up @@ -1363,6 +1366,136 @@ fn const_eval_intrinsic(
}),
}
}
Intrinsic::Transmute => {
let src_type = &intrinsic.type_arguments[0];
let src_ir_type = convert_resolved_type_id(
lookup.engines.te(),
lookup.engines.de(),
lookup.context,
src_type.type_id,
&src_type.span,
)
.unwrap();

let dst_type = &intrinsic.type_arguments[1];
let dst_ir_type = convert_resolved_type_id(
lookup.engines.te(),
lookup.engines.de(),
lookup.context,
dst_type.type_id,
&dst_type.span,
)
.unwrap();

// check IR sizes match
let src_ir_type_in_bytes = src_ir_type.size(lookup.context).in_bytes();
let dst_ir_type_in_bytes = dst_ir_type.size(lookup.context).in_bytes();
if src_ir_type_in_bytes != dst_ir_type_in_bytes {
return Err(ConstEvalError::CompileError);
}

fn append_bytes(
ctx: &Context<'_>,
bytes: &mut Vec<u8>,
t: &Type,
value: &ConstantValue,
) -> Result<(), ConstEvalError> {
match t.get_content(ctx) {
TypeContent::Array(item_type, size) => match value {
ConstantValue::Array(items) => {
assert!(*size as usize == items.len());
for item in items {
append_bytes(ctx, bytes, item_type, &item.value)?;
}
}
_ => unreachable!(),
},
TypeContent::Uint(8) => match value {
ConstantValue::Uint(v) => {
bytes.extend((*v as u8).to_be_bytes());
}
_ => unreachable!(),
},
TypeContent::Uint(16) => match value {
ConstantValue::Uint(v) => {
bytes.extend([0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
bytes.extend((*v as u16).to_be_bytes());
}
_ => unreachable!(),
},
TypeContent::Uint(32) => match value {
ConstantValue::Uint(v) => {
bytes.extend([0u8, 0u8, 0u8, 0u8]);
bytes.extend((*v as u32).to_be_bytes());
}
_ => unreachable!(),
},
TypeContent::Uint(64) => match value {
ConstantValue::Uint(v) => {
bytes.extend((*v).to_be_bytes());
}
_ => unreachable!(),
},
_ => return Err(ConstEvalError::CompileError),
}
Ok(())
}

fn transmute_bytes(
ctx: &Context<'_>,
bytes: &mut std::io::Cursor<Vec<u8>>,
t: &Type,
) -> Result<Constant, ConstEvalError> {
Ok(match t.get_content(ctx) {
TypeContent::Uint(8) => {
let mut buffer = [0u8];
let _ = bytes.read_exact(&mut buffer);
Constant {
ty: Type::get_uint8(ctx),
value: ConstantValue::Uint(buffer[0] as u64),
}
}
TypeContent::Uint(16) => {
let mut buffer = [0u8; 8]; // u16 = u64 at runtime
let _ = bytes.read_exact(&mut buffer);
let buffer = [buffer[6], buffer[7]];
Constant {
ty: Type::get_uint16(ctx),
value: ConstantValue::Uint(u16::from_be_bytes(buffer) as u64),
}
}
TypeContent::Uint(32) => {
let mut buffer = [0u8; 8]; // u32 = u64 at runtime
let _ = bytes.read_exact(&mut buffer);
let buffer = [buffer[4], buffer[5], buffer[6], buffer[7]];
Constant {
ty: Type::get_uint32(ctx),
value: ConstantValue::Uint(u32::from_be_bytes(buffer) as u64),
}
}
TypeContent::Uint(64) => {
let mut buffer = [0u8; 8];
let _ = bytes.read_exact(&mut buffer);
Constant {
ty: Type::get_uint64(ctx),
value: ConstantValue::Uint(u64::from_be_bytes(buffer)),
}
}
_ => return Err(ConstEvalError::CompileError),
})
}

let mut runtime_bytes = vec![];
append_bytes(
lookup.context,
&mut runtime_bytes,
&src_ir_type,
&args[0].value,
)?;
let mut cursor = std::io::Cursor::new(runtime_bytes);
let c = transmute_bytes(lookup.context, &mut cursor, &dst_ir_type)?;
Ok(Some(c))
}
}
}

Expand Down
63 changes: 60 additions & 3 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,9 +660,13 @@ impl<'eng> FnCompiler<'eng> {
span_md_idx,
)
}
ty::TyExpressionVariant::IntrinsicFunction(kind) => {
self.compile_intrinsic_function(context, md_mgr, kind, ast_expr.span.clone())
}
ty::TyExpressionVariant::IntrinsicFunction(kind) => self.compile_intrinsic_function(
context,
md_mgr,
kind,
ast_expr.span.clone(),
ast_expr.return_type,
),
ty::TyExpressionVariant::AbiName(_) => {
let val = Value::new_constant(context, Constant::new_unit(context));
Ok(TerminatorValue::new(val, context))
Expand Down Expand Up @@ -869,6 +873,7 @@ impl<'eng> FnCompiler<'eng> {
span: _,
}: &ty::TyIntrinsicFunctionKind,
span: Span,
return_type: TypeId,
) -> Result<TerminatorValue, CompileError> {
fn store_key_in_local_mem(
compiler: &mut FnCompiler,
Expand Down Expand Up @@ -2174,9 +2179,61 @@ impl<'eng> FnCompiler<'eng> {
}
Intrinsic::Slice => self.compile_intrinsic_slice(arguments, context, md_mgr),
Intrinsic::ElemAt => self.compile_intrinsic_elem_at(arguments, context, md_mgr),
Intrinsic::Transmute => {
self.compile_intrinsic_transmute(arguments, return_type, context, md_mgr, &span)
}
}
}

fn compile_intrinsic_transmute(
&mut self,
arguments: &[ty::TyExpression],
return_type: TypeId,
context: &mut Context,
md_mgr: &mut MetadataManager,
span: &Span,
) -> Result<TerminatorValue, CompileError> {
assert!(arguments.len() == 1);

let te = self.engines.te();
let de = self.engines.de();

let return_type_ir_type = convert_resolved_type_id(te, de, context, return_type, span)?;
let return_type_ir_type_ptr = Type::new_ptr(context, return_type_ir_type);

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 first_argument_type = first_argument_value
.get_type(context)
.expect("transmute first argument type not found");
let first_argument_ptr = save_to_local_return_ptr(self, context, first_argument_value)?;

// check IR sizes match
let first_arg_size = first_argument_type.size(context).in_bytes();
let return_type_size = return_type_ir_type.size(context).in_bytes();
if first_arg_size != return_type_size {
return Err(CompileError::Internal(
"Types size do not match",
span.clone(),
));
}

let u64 = Type::get_uint64(context);
let first_argument_ptr = self
.current_block
.append(context)
.ptr_to_int(first_argument_ptr, u64);
let first_argument_ptr = self
.current_block
.append(context)
.int_to_ptr(first_argument_ptr, return_type_ir_type_ptr);

let final_value = self.current_block.append(context).load(first_argument_ptr);
Ok(TerminatorValue::new(final_value, context))
}

fn ptr_to_first_element(
&mut self,
context: &mut Context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,118 @@ impl ty::TyIntrinsicFunctionKind {
type_check_slice(handler, ctx, kind, arguments, type_arguments, span)
}
Intrinsic::ElemAt => type_check_elem_at(arguments, handler, kind, span, ctx),
Intrinsic::Transmute => {
type_check_transmute(arguments, handler, kind, type_arguments, span, ctx)
}
}
}
}

fn type_check_transmute(
arguments: &[Expression],
handler: &Handler,
kind: Intrinsic,
type_arguments: &[TypeArgument],
span: Span,
mut ctx: TypeCheckContext,
) -> Result<(TyIntrinsicFunctionKind, TypeId), ErrorEmitted> {
if arguments.len() != 1 {
return Err(handler.emit_err(CompileError::IntrinsicIncorrectNumArgs {
name: kind.to_string(),
expected: 1,
span,
}));
}

let engines = ctx.engines();

// Both type arguments needs to be explicitly defined
if type_arguments.len() != 2 {
return Err(handler.emit_err(CompileError::IntrinsicIncorrectNumTArgs {
name: kind.to_string(),
expected: 2,
span,
}));
}

let src_type = ctx
.resolve_type(
handler,
type_arguments[0].type_id,
&type_arguments[0].span,
EnforceTypeArguments::Yes,
None,
)
.unwrap_or_else(|err| engines.te().id_of_error_recovery(err));
let return_type = ctx
.resolve_type(
handler,
type_arguments[1].type_id,
&type_arguments[1].span,
EnforceTypeArguments::Yes,
None,
)
.unwrap_or_else(|err| engines.te().id_of_error_recovery(err));

// Forbid ref and ptr types
fn forbid_ref_ptr_types(
engines: &Engines,
handler: &Handler,
t: TypeId,
span: &Span,
) -> Result<(), ErrorEmitted> {
let types = t.extract_any_including_self(
engines,
&|t| {
matches!(
t,
TypeInfo::StringSlice
| TypeInfo::RawUntypedPtr
| TypeInfo::RawUntypedSlice
| TypeInfo::Ptr(_)
| TypeInfo::Slice(_)
| TypeInfo::Ref { .. }
)
},
vec![],
0,
);
if !types.is_empty() {
Err(handler.emit_err(CompileError::TypeNotAllowed {
reason: sway_error::error::TypeNotAllowedReason::NotAllowedInTransmute,
span: span.clone(),
}))
} else {
Ok(())
}
}

forbid_ref_ptr_types(engines, handler, src_type, &type_arguments[0].span)?;
forbid_ref_ptr_types(engines, handler, return_type, &type_arguments[1].span)?;

// check first argument
let arg_type = engines.te().get(src_type);
let first_argument_typed_expr = {
let ctx = ctx
.by_ref()
.with_help_text("")
.with_type_annotation(engines.te().insert(
engines,
(*arg_type).clone(),
type_arguments[0].span.source_id(),
));
ty::TyExpression::type_check(handler, ctx, &arguments[0]).unwrap()
};

Ok((
TyIntrinsicFunctionKind {
kind,
arguments: vec![first_argument_typed_expr],
type_arguments: type_arguments.to_vec(),
span,
},
return_type,
))
}

fn type_check_elem_at(
Expand Down
3 changes: 2 additions & 1 deletion sway-core/src/semantic_analysis/cei_pattern_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,8 @@ fn effects_of_intrinsic(intr: &sway_ast::Intrinsic) -> HashSet<Effect> {
| EncodeBufferAppend
| EncodeBufferAsRawSlice
| Slice
| ElemAt => HashSet::new(),
| ElemAt
| Transmute => HashSet::new(),
}
}

Expand Down
3 changes: 3 additions & 0 deletions sway-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2812,6 +2812,9 @@ pub enum TypeNotAllowedReason {

#[error("slices or types containing slices on `const` are not allowed.")]
SliceInConst,

#[error("references, pointers, slices, string slices or types containing any of these are not allowed.")]
NotAllowedInTransmute,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Loading

0 comments on commit 9951c1d

Please sign in to comment.