Skip to content

Commit fcc1e4b

Browse files
Soroban: support dynamic memory arrays (#1838)
This PR adds support for dynamic memory arrays is Soroban's Solidity. Namely, this example: http://github.com/stellar/soroban-examples/blob/main/alloc/src/lib.rs Under the hood, the example above uses a custom no-free bump allocator, written in Rust and linked with Soroban contracts: https://github.com/stellar/rs-soroban-sdk/blob/main/soroban-sdk/src/alloc.rs#L5 We followed the same approach, but wrote it in C instead. The reason for opting for C instead of Rust is that it fits well with Solang's compiling and linking pipeline. --------- Signed-off-by: salaheldinsoliman <[email protected]>
1 parent af31424 commit fcc1e4b

File tree

14 files changed

+419
-125
lines changed

14 files changed

+419
-125
lines changed

src/codegen/encoding/soroban_encoding.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub fn soroban_encode(
3131

3232
let size_expr = Expression::NumberLiteral {
3333
loc: *loc,
34-
ty: Uint(64),
34+
ty: Uint(32),
3535
value: size.into(),
3636
};
3737
let encoded_bytes = vartab.temp_name("abi_encoded", &Type::Bytes(size as u8));
@@ -40,7 +40,7 @@ pub fn soroban_encode(
4040
loc: *loc,
4141
ty: Type::Bytes(size as u8),
4242
size: size_expr.clone().into(),
43-
initializer: Some(vec![]),
43+
initializer: None,
4444
};
4545

4646
cfg.add(
@@ -323,6 +323,8 @@ pub fn soroban_encode_arg(
323323
_ => unreachable!(),
324324
};
325325

326+
println!("encoded: {:?}, len: {:?}", encoded, len);
327+
326328
Instr::Call {
327329
res: vec![obj],
328330
return_tys: vec![Type::Uint(64)],

src/emit/binary.rs

Lines changed: 39 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,27 @@ impl<'a> Binary<'a> {
292292
export_list.push("__lshrti3");
293293
export_list.push("__ashrti3");
294294

295+
if self.ns.target == crate::Target::Soroban {
296+
let mut f = self.module.get_first_function();
297+
while let Some(func) = f {
298+
let name = func.get_name().to_str().unwrap();
299+
// leave LLVM intrinsics alone
300+
if name.starts_with("llvm.") {
301+
f = func.get_next_function();
302+
continue;
303+
}
304+
305+
if export_list.contains(&name) {
306+
func.set_linkage(inkwell::module::Linkage::External);
307+
} else {
308+
func.set_linkage(inkwell::module::Linkage::Internal);
309+
}
310+
311+
f = func.get_next_function();
312+
}
313+
return;
314+
}
315+
295316
while let Some(f) = func {
296317
let name = f.get_name().to_str().unwrap();
297318

@@ -1036,71 +1057,7 @@ impl<'a> Binary<'a> {
10361057
size: IntValue<'a>,
10371058
elem_size: IntValue<'a>,
10381059
init: Option<&Vec<u8>>,
1039-
ty: &Type,
10401060
) -> BasicValueEnum<'a> {
1041-
if self.ns.target == Target::Soroban {
1042-
if matches!(ty, Type::Bytes(_)) {
1043-
let n = if let Type::Bytes(n) = ty {
1044-
n
1045-
} else {
1046-
unreachable!()
1047-
};
1048-
1049-
let data = self
1050-
.builder
1051-
.build_alloca(self.context.i64_type().array_type((*n / 8) as u32), "data")
1052-
.unwrap();
1053-
1054-
let ty = self.context.struct_type(
1055-
&[data.get_type().into(), self.context.i64_type().into()],
1056-
false,
1057-
);
1058-
1059-
// Start with an undefined struct value
1060-
let mut struct_value = ty.get_undef();
1061-
1062-
// Insert `data` into the first field of the struct
1063-
struct_value = self
1064-
.builder
1065-
.build_insert_value(struct_value, data, 0, "insert_data")
1066-
.unwrap()
1067-
.into_struct_value();
1068-
1069-
// Insert `size` into the second field of the struct
1070-
struct_value = self
1071-
.builder
1072-
.build_insert_value(struct_value, size, 1, "insert_size")
1073-
.unwrap()
1074-
.into_struct_value();
1075-
1076-
// Return the constructed struct value
1077-
return struct_value.into();
1078-
} else if matches!(ty, Type::String) {
1079-
let default = " ".as_bytes().to_vec();
1080-
let bs = init.unwrap_or(&default);
1081-
1082-
let data = self.emit_global_string("const_string", bs, true);
1083-
1084-
// A constant string, or array, is represented by a struct with two fields: a pointer to the data, and its length.
1085-
let ty = self.context.struct_type(
1086-
&[
1087-
self.context.ptr_type(AddressSpace::default()).into(),
1088-
self.context.i64_type().into(),
1089-
],
1090-
false,
1091-
);
1092-
1093-
return ty
1094-
.const_named_struct(&[
1095-
data.into(),
1096-
self.context
1097-
.i64_type()
1098-
.const_int(bs.len() as u64, false)
1099-
.into(),
1100-
])
1101-
.as_basic_value_enum();
1102-
}
1103-
}
11041061
if let Some(init) = init {
11051062
if init.is_empty() {
11061063
return self
@@ -1116,16 +1073,22 @@ impl<'a> Binary<'a> {
11161073
Some(s) => self.emit_global_string("const_string", s, true),
11171074
};
11181075

1119-
self.builder
1120-
.build_call(
1076+
println!("calling soroban alloc : {:?}", size);
1077+
let allocator = if self.ns.target == Target::Soroban {
1078+
self.builder.build_call(
1079+
self.module.get_function("soroban_alloc_init").unwrap(),
1080+
&[size.into(), init.into()],
1081+
"soroban_alloc",
1082+
)
1083+
} else {
1084+
self.builder.build_call(
11211085
self.module.get_function("vector_new").unwrap(),
11221086
&[size.into(), elem_size.into(), init.into()],
1123-
"",
1087+
"vector_new",
11241088
)
1125-
.unwrap()
1126-
.try_as_basic_value()
1127-
.left()
1128-
.unwrap()
1089+
};
1090+
1091+
allocator.unwrap().try_as_basic_value().left().unwrap()
11291092
}
11301093

11311094
/// Number of element in a vector
@@ -1134,19 +1097,19 @@ impl<'a> Binary<'a> {
11341097
// slice
11351098
let slice = vector.into_struct_value();
11361099

1137-
let len_type = if self.ns.target == Target::Soroban {
1100+
/*let len_type = if self.ns.target == Target::Soroban {
11381101
self.context.i64_type()
11391102
} else {
11401103
self.context.i32_type()
1141-
};
1104+
};*/
11421105

11431106
self.builder
11441107
.build_int_truncate(
11451108
self.builder
11461109
.build_extract_value(slice, 1, "slice_len")
11471110
.unwrap()
11481111
.into_int_value(),
1149-
len_type,
1112+
self.context.i32_type(),
11501113
"len",
11511114
)
11521115
.unwrap()
@@ -1376,11 +1339,12 @@ static BPF_IR: [&[u8]; 6] = [
13761339
include_bytes!("../../target/bpf/heap.bc"),
13771340
];
13781341

1379-
static WASM_IR: [&[u8]; 4] = [
1342+
static WASM_IR: [&[u8]; 5] = [
13801343
include_bytes!("../../target/wasm/stdlib.bc"),
13811344
include_bytes!("../../target/wasm/heap.bc"),
13821345
include_bytes!("../../target/wasm/bigint.bc"),
13831346
include_bytes!("../../target/wasm/format.bc"),
1347+
include_bytes!("../../target/wasm/soroban.bc"),
13841348
];
13851349

13861350
static RIPEMD160_IR: &[u8] = include_bytes!("../../target/wasm/ripemd160.bc");

src/emit/expression.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
128128
s.into()
129129
}
130130
Expression::BytesLiteral { value: bs, ty, .. } => {
131+
println!("BytesLiteral: {:?} and ty {:?}", bs, ty);
131132
// If the type of a BytesLiteral is a String, embedd the bytes in the binary.
132133
if ty == &Type::String || ty == &Type::Address(true) {
133134
let data = bin.emit_global_string("const_string", bs, true);
@@ -1580,7 +1581,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
15801581
.unwrap()
15811582
.const_cast(bin.context.i32_type(), false);
15821583

1583-
bin.vector_new(size, elem_size, initializer.as_ref(), ty)
1584+
bin.vector_new(size, elem_size, initializer.as_ref())
15841585
}
15851586
}
15861587
Expression::Builtin {

src/emit/instructions.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,16 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
211211
let size = bin.builder.build_int_mul(elem_size, new_len, "").unwrap();
212212
let size = bin.builder.build_int_add(size, vec_size, "").unwrap();
213213

214+
let allocator = if bin.ns.target == Target::Soroban {
215+
"soroban_realloc"
216+
} else {
217+
"__realloc"
218+
};
214219
// Reallocate and reassign the array pointer
215220
let new = bin
216221
.builder
217222
.build_call(
218-
bin.module.get_function("__realloc").unwrap(),
223+
bin.module.get_function(allocator).unwrap(),
219224
&[arr.into(), size.into()],
220225
"",
221226
)
@@ -382,11 +387,16 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
382387
w.vars.get_mut(res).unwrap().value = ret_val;
383388
}
384389

390+
let allocator = if bin.ns.target == Target::Soroban {
391+
"soroban_realloc"
392+
} else {
393+
"__realloc"
394+
};
385395
// Reallocate and reassign the array pointer
386396
let new = bin
387397
.builder
388398
.build_call(
389-
bin.module.get_function("__realloc").unwrap(),
399+
bin.module.get_function(allocator).unwrap(),
390400
&[a.into(), size.into()],
391401
"",
392402
)

src/emit/solana/target.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
749749
.unwrap()
750750
.into_int_value();
751751

752-
dest = bin
753-
.vector_new(length, elem_size, None, elem_ty)
754-
.into_pointer_value();
752+
dest = bin.vector_new(length, elem_size, None).into_pointer_value();
755753
};
756754

757755
let elem_size = elem_ty.solana_storage_size(bin.ns).to_u64().unwrap();

src/emit/soroban/target.rs

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -345,33 +345,28 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
345345
/// Prints a string
346346
/// TODO: Implement this function, with a call to the `log` function in the Soroban runtime.
347347
fn print(&self, bin: &Binary, string: PointerValue, length: IntValue) {
348-
if string.is_const() && length.is_const() {
349-
let msg_pos = bin
350-
.builder
351-
.build_ptr_to_int(string, bin.context.i64_type(), "msg_pos")
352-
.unwrap();
353-
let length = length.const_cast(bin.context.i64_type(), false);
348+
let msg_pos = bin
349+
.builder
350+
.build_ptr_to_int(string, bin.context.i64_type(), "msg_pos")
351+
.unwrap();
354352

355-
let msg_pos_encoded = encode_value(msg_pos, 32, 4, bin);
356-
let length_encoded = encode_value(length, 32, 4, bin);
353+
let msg_pos_encoded = encode_value(msg_pos, 32, 4, bin);
354+
let length_encoded = encode_value(length, 32, 4, bin);
357355

358-
bin.builder
359-
.build_call(
360-
bin.module
361-
.get_function(HostFunctions::LogFromLinearMemory.name())
362-
.unwrap(),
363-
&[
364-
msg_pos_encoded.into(),
365-
length_encoded.into(),
366-
msg_pos_encoded.into(),
367-
encode_value(bin.context.i64_type().const_zero(), 32, 4, bin).into(),
368-
],
369-
"log",
370-
)
371-
.unwrap();
372-
} else {
373-
todo!("Dynamic String printing is not yet supported")
374-
}
356+
bin.builder
357+
.build_call(
358+
bin.module
359+
.get_function(HostFunctions::LogFromLinearMemory.name())
360+
.unwrap(),
361+
&[
362+
msg_pos_encoded.into(),
363+
length_encoded.into(),
364+
msg_pos_encoded.into(),
365+
encode_value(bin.context.i64_type().const_zero(), 32, 4, bin).into(),
366+
],
367+
"log",
368+
)
369+
.unwrap();
375370
}
376371

377372
/// Return success without any result
@@ -452,7 +447,7 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
452447
.builder
453448
.build_int_unsigned_div(
454449
payload_len,
455-
bin.context.i64_type().const_int(8, false),
450+
payload_len.get_type().const_int(8, false),
456451
"args_len",
457452
)
458453
.unwrap();
@@ -461,7 +456,7 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
461456
.builder
462457
.build_int_sub(
463458
args_len,
464-
bin.context.i64_type().const_int(1, false),
459+
args_len.get_type().const_int(1, false),
465460
"args_len",
466461
)
467462
.unwrap();
@@ -756,7 +751,25 @@ fn storage_type_to_int(storage_type: &Option<StorageType>) -> u64 {
756751
}
757752
}
758753

759-
fn encode_value<'a>(value: IntValue<'a>, shift: u64, add: u64, bin: &'a Binary) -> IntValue<'a> {
754+
fn encode_value<'a>(
755+
mut value: IntValue<'a>,
756+
shift: u64,
757+
add: u64,
758+
bin: &'a Binary,
759+
) -> IntValue<'a> {
760+
match value.get_type().get_bit_width() {
761+
32 =>
762+
// extend to 64 bits
763+
{
764+
value = bin
765+
.builder
766+
.build_int_z_extend(value, bin.context.i64_type(), "temp")
767+
.unwrap();
768+
}
769+
64 => (),
770+
_ => unreachable!(),
771+
}
772+
760773
let shifted = bin
761774
.builder
762775
.build_left_shift(
@@ -765,6 +778,7 @@ fn encode_value<'a>(value: IntValue<'a>, shift: u64, add: u64, bin: &'a Binary)
765778
"temp",
766779
)
767780
.unwrap();
781+
768782
bin.builder
769783
.build_int_add(
770784
shifted,

src/emit/strings.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,7 @@ pub(super) fn format_string<'a, T: TargetRuntime<'a> + ?Sized>(
9595

9696
// allocate the string and
9797
let vector = bin
98-
.vector_new(
99-
length,
100-
bin.context.i32_type().const_int(1, false),
101-
None,
102-
&Type::String,
103-
)
98+
.vector_new(length, bin.context.i32_type().const_int(1, false), None)
10499
.into_pointer_value();
105100

106101
let output_start = bin.vector_bytes(vector.into());

0 commit comments

Comments
 (0)