Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/book/src/reference/compiler_intrinsics.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,13 @@ This number is not guaranteed to be stable on different compiler versions.
`__encoding_mem_id` represents how the type is encoded. It returns 0 when a type does not have encoding representation.

**Constraints:** None

---

```sway
__alloc<T>(count: u64) -> raw_ptr
```

**Description:** Allocate `count` contiguous elements of `T` on the heap and return a pointer to the newly allocated memory.

**Constraints** None
3 changes: 3 additions & 0 deletions sway-ast/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub enum Intrinsic {
ElemAt, // let elem: &T = __elem_at::<T: array or ref_to_slice>(item: T, index)
Transmute, // let dst: B = __transmute::<A, B>(src)
Dbg, // __dbg(value)
Alloc, // __alloc<T>(size: u64) -> raw_ptr
RuntimeMemoryId, // __runtime_mem_id::<T>() -> u64
EncodingMemoryId, // __encoding_mem_id::<T>() -> u64
}
Expand Down Expand Up @@ -96,6 +97,7 @@ impl fmt::Display for Intrinsic {
Intrinsic::ElemAt => "elem_at",
Intrinsic::Transmute => "transmute",
Intrinsic::Dbg => "dbg",
Intrinsic::Alloc => "alloc",
Intrinsic::RuntimeMemoryId => "runtime_mem_id",
Intrinsic::EncodingMemoryId => "encoding_mem_id",
};
Expand Down Expand Up @@ -150,6 +152,7 @@ impl Intrinsic {
"__elem_at" => ElemAt,
"__transmute" => Transmute,
"__dbg" => Dbg,
"__alloc" => Alloc,
"__runtime_mem_id" => RuntimeMemoryId,
"__encoding_mem_id" => EncodingMemoryId,
_ => return None,
Expand Down
5 changes: 5 additions & 0 deletions sway-core/src/asm_generation/evm/evm_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ impl<'ir, 'eng> EvmAsmBuilder<'ir, 'eng> {
}
InstOp::IntToPtr(val, _) => self.compile_int_to_ptr(instr_val, val),
InstOp::Load(src_val) => self.compile_load(handler, instr_val, src_val)?,
InstOp::Alloc { ty, count } => self.compile_alloc(instr_val, ty, count),
InstOp::MemCopyBytes {
dst_val_ptr,
src_val_ptr,
Expand Down Expand Up @@ -501,6 +502,10 @@ impl<'ir, 'eng> EvmAsmBuilder<'ir, 'eng> {
todo!();
}

fn compile_alloc(&mut self, instr_val: &Value, ty: &Type, count: &Value) {
todo!();
}

fn compile_log(&mut self, instr_val: &Value, log_val: &Value, log_ty: &Type, log_id: &Value) {
todo!();
}
Expand Down
73 changes: 73 additions & 0 deletions sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {
}
InstOp::IntToPtr(val, _) => self.compile_no_op_move(instr_val, val),
InstOp::Load(src_val) => self.compile_load(instr_val, src_val),
InstOp::Alloc { ty, count } => self.compile_alloc(instr_val, ty, count),
InstOp::MemCopyBytes {
dst_val_ptr,
src_val_ptr,
Expand Down Expand Up @@ -1467,6 +1468,78 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {
Ok(())
}

fn compile_alloc(
&mut self,
instr_val: &Value,
ty: &Type,
count: &Value,
) -> Result<(), CompileError> {
let owning_span = self.md_mgr.val_to_span(self.context, *instr_val);

let ty_size = ty.size(self.context).in_bytes();

let statically_known_count = count.get_constant(self.context).and_then(|count| {
if let ConstantValue::Uint(count_uint) = count.get_content(self.context).value {
Some(count_uint)
} else {
None
}
});

let size_reg = self.reg_seqr.next();
// If we know the count statically, multiply it by the type size to get the total size.
// Otherwise, just load the type size and multiply later.
if let Some(statically_known_count) = statically_known_count {
self.immediate_to_reg(
ty_size * statically_known_count,
size_reg.clone(),
None,
"get total allocation size in bytes",
owning_span.clone(),
);
} else {
self.immediate_to_reg(
ty_size,
size_reg.clone(),
None,
"get size of allocation element type",
owning_span.clone(),
);
let count_reg = self.value_to_register(count)?;
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::MUL(
size_reg.clone(),
size_reg.clone(),
count_reg,
)),
comment: "get total allocation size in bytes".into(),
owning_span: owning_span.clone(),
});
}

// Actually perform the allocation.
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::ALOC(
VirtualRegister::Constant(ConstantRegister::HeapPointer),
size_reg,
)),
comment: "allocate memory".into(),
owning_span,
});

let instr_reg = self.reg_seqr.next();
// Move the resulting pointer from the heap pointer register to the instruction register.
self.cur_bytecode.push(Op::register_move(
instr_reg.clone(),
VirtualRegister::Constant(ConstantRegister::HeapPointer),
"save allocated memory pointer",
self.md_mgr.val_to_span(self.context, *instr_val),
));

self.reg_map.insert(*instr_val, instr_reg);
Ok(())
}

fn compile_load(&mut self, instr_val: &Value, src_val: &Value) -> Result<(), CompileError> {
let owning_span = self.md_mgr.val_to_span(self.context, *instr_val);
let src_ty = src_val
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,7 @@ fn const_eval_intrinsic(
}
},
Intrinsic::AddrOf
| Intrinsic::Alloc
| Intrinsic::PtrAdd
| Intrinsic::PtrSub
| Intrinsic::IsReferenceType
Expand Down
27 changes: 27 additions & 0 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,33 @@ impl<'a> FnCompiler<'a> {
}
}
}
Intrinsic::Alloc => {
let targ = type_arguments[0].clone();
let ir_type = convert_resolved_type_id(
self.engines,
context,
md_mgr,
self.module,
Some(self),
targ.type_id(),
&targ.span(),
)?;
let count_exp = &arguments[0];
let count_value = return_on_termination_or_extract!(
self.compile_expression_to_register(context, md_mgr, count_exp)?
)
.expect_register();
let span_md_idx = md_mgr.span_to_md(context, &span);
let val = self
.current_block
.append(context)
.alloc(ir_type, count_value)
.add_metadatum(context, span_md_idx);
Ok(TerminatorValue::new(
CompiledValue::InRegister(val),
context,
))
}
Intrinsic::Add
| Intrinsic::Sub
| Intrinsic::Mul
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ impl ty::TyIntrinsicFunctionKind {
Intrinsic::EncodingMemoryId => {
type_check_encoding_memory_id(arguments, handler, kind, type_arguments, span, ctx)
}
Intrinsic::Alloc => {
type_check_alloc(handler, ctx, kind, arguments, type_arguments, span)
}
}
}
}
Expand Down Expand Up @@ -215,6 +218,65 @@ fn type_check_runtime_memory_id(
Ok((intrinsic_function, ctx.engines.te().id_of_u64()))
}

fn type_check_alloc(
handler: &Handler,
mut ctx: TypeCheckContext,
kind: Intrinsic,
arguments: &[Expression],
type_arguments: &[GenericArgument],
span: Span,
) -> 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();

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

let alloc_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));

// type check first argument, ensure that it is u64
let first_argument_typed_expr = {
let ctx = ctx
.by_ref()
.with_help_text("")
.with_type_annotation(engines.te().id_of_u64());
ty::TyExpression::type_check(handler, ctx, &arguments[0])?
};

let mut final_type_arguments = type_arguments.to_vec();
*final_type_arguments[0].type_id_mut() = alloc_type;
Ok((
TyIntrinsicFunctionKind {
kind,
arguments: vec![first_argument_typed_expr],
type_arguments: final_type_arguments,
span,
},
engines.te().id_of_raw_ptr(),
))
}

fn type_check_transmute(
arguments: &[Expression],
handler: &Handler,
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/semantic_analysis/cei_pattern_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ fn effects_of_intrinsic(intr: &sway_ast::Intrinsic) -> HashSet<Effect> {
| ElemAt
| Transmute
| Dbg
| Alloc
| RuntimeMemoryId
| EncodingMemoryId => HashSet::new(),
}
Expand Down
3 changes: 3 additions & 0 deletions sway-ir/src/analysis/memory_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ fn compute_escaped_symbols(context: &Context, function: &Function) -> EscapedSym
InstOp::Store { stored_val, .. } => {
add_from_val(&mut result, stored_val, &mut is_complete)
}
InstOp::Alloc { .. } => (),
}
}

Expand Down Expand Up @@ -519,6 +520,7 @@ pub fn get_loaded_ptr_values(context: &Context, inst: Value) -> Vec<Value> {
..
}) => vec![*memopd1, *memopd2],
InstOp::Store { dst_val_ptr: _, .. } => vec![],
InstOp::Alloc { .. } => vec![],
InstOp::FuelVm(FuelVmInstruction::Gtf { .. })
| InstOp::FuelVm(FuelVmInstruction::ReadRegister(_))
| InstOp::FuelVm(FuelVmInstruction::Revert(_) | FuelVmInstruction::JmpMem) => vec![],
Expand Down Expand Up @@ -578,6 +580,7 @@ pub fn get_stored_ptr_values(context: &Context, inst: Value) -> Vec<Value> {
| InstOp::MemClearVal { dst_val_ptr }
| InstOp::Store { dst_val_ptr, .. } => vec![*dst_val_ptr],
InstOp::Load(_) => vec![],
InstOp::Alloc { .. } => vec![],
InstOp::FuelVm(vmop) => match vmop {
FuelVmInstruction::Gtf { .. }
| FuelVmInstruction::Log { .. }
Expand Down
7 changes: 7 additions & 0 deletions sway-ir/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub enum IrError {
VerifyIntToPtrFromNonIntegerType(String),
VerifyIntToPtrToNonPointer(String),
VerifyIntToPtrUnknownSourceType,
VerifyAllocCountNotUint64,
VerifyInvalidGtfIndexType,
VerifyLoadFromNonPointer(String),
VerifyLocalMissingInitializer(String, String),
Expand Down Expand Up @@ -280,6 +281,12 @@ impl fmt::Display for IrError {
f,
"Verification failed: int_to_ptr unable to determine source type."
),
IrError::VerifyAllocCountNotUint64 => {
write!(
f,
"Verification failed: alloc instruction count must be a u64 integer."
)
}
IrError::VerifyLoadFromNonPointer(ty) => {
write!(
f,
Expand Down
25 changes: 25 additions & 0 deletions sway-ir/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ pub enum InstOp {
dst_val_ptr: Value,
stored_val: Value,
},
/// Allocate `count` objects of type `ty` on the heap.
Alloc {
ty: Type,
count: Value,
},
}

/// Metadata describing a logged event.
Expand Down Expand Up @@ -422,6 +427,7 @@ impl InstOp {
crate::ConfigContent::V1 { ptr_ty, .. } => *ptr_ty,
}),
InstOp::GetStorageKey(storage_key) => Some(storage_key.get_type(context)),
InstOp::Alloc { ty: _, count: _ } => Some(Type::get_ptr(context)),

// Use for casting between pointers and pointer-width integers.
InstOp::IntToPtr(_, ptr_ty) => Some(*ptr_ty),
Expand Down Expand Up @@ -522,6 +528,7 @@ impl InstOp {
// `GetStorageKey` returns an SSA `Value` but does not take any as an operand.
vec![]
}
InstOp::Alloc { ty: _, count } => vec![*count],
InstOp::IntToPtr(v, _) => vec![*v],
InstOp::Load(v) => vec![*v],
InstOp::MemCopyBytes {
Expand Down Expand Up @@ -738,6 +745,13 @@ impl InstOp {
// `GetStorageKey` returns an SSA `Value` but does not take any as an operand.
panic!("Invalid index for GetStorageKey");
}
InstOp::Alloc { ty: _, count } => {
if idx == 0 {
*count = replacement;
} else {
panic!("Invalid index for Alloc");
}
}
InstOp::IntToPtr(v, _) => {
if idx == 0 {
*v = replacement;
Expand Down Expand Up @@ -1049,6 +1063,7 @@ impl InstOp {
replace(base);
indices.iter_mut().for_each(replace);
}
InstOp::Alloc { ty: _, count } => replace(count),
InstOp::IntToPtr(value, _) => replace(value),
InstOp::Load(ptr) => replace(ptr),
InstOp::MemCopyBytes {
Expand Down Expand Up @@ -1212,6 +1227,12 @@ impl InstOp {
| InstOp::Load(_)
| InstOp::Nop
| InstOp::PtrToInt(..) => false,

// Although `Alloc` is a side-effecting operation in the general sense (it changes
// program state by allocating memory), it does not have observable side effects that
// would affect program behavior in a way that matters for optimizations like
// dead code elimination. Therefore, we treat it as not having side effects here.
InstOp::Alloc { .. } => false,
}
}

Expand Down Expand Up @@ -1573,6 +1594,10 @@ impl<'a, 'eng> InstructionInserter<'a, 'eng> {
insert_instruction!(self, InstOp::GetConfig(module, name))
}

pub fn alloc(self, ty: Type, count: Value) -> Value {
insert_instruction!(self, InstOp::Alloc { ty, count })
}

pub fn get_storage_key(self, storage_key: StorageKey) -> Value {
insert_instruction!(self, InstOp::GetStorageKey(storage_key))
}
Expand Down
1 change: 1 addition & 0 deletions sway-ir/src/optimize/arg_mutability_tagger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ fn analyse_fn(
| InstOp::GetConfig(_, _)
| InstOp::GetStorageKey(_)
| InstOp::IntToPtr(_, _)
| InstOp::Alloc { .. }
| InstOp::Nop => {
panic!("Pointers shouldn't be used in these instructions");
}
Expand Down
Loading
Loading