Skip to content

Commit f0058db

Browse files
feat(EXC-1847): Support for ic0.subnet_self() (dfinity#3637)
This PR implements a new system API that enables canisters to learn which subnet they run on. The new functionality is composed of two functions: `ic0.subnet_self_size()` and `ic0.subnet_self_copy()`, described in this [Spec PR](dfinity/portal#3790).
1 parent 35f3948 commit f0058db

File tree

11 files changed

+359
-2
lines changed

11 files changed

+359
-2
lines changed

rs/embedders/src/wasm_utils/validation.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,26 @@ fn get_valid_system_apis_common(I: ValType) -> HashMap<String, HashMap<String, F
637637
},
638638
)],
639639
),
640+
(
641+
"subnet_self_size",
642+
vec![(
643+
API_VERSION_IC0,
644+
FunctionSignature {
645+
param_types: vec![],
646+
return_type: vec![I],
647+
},
648+
)],
649+
),
650+
(
651+
"subnet_self_copy",
652+
vec![(
653+
API_VERSION_IC0,
654+
FunctionSignature {
655+
param_types: vec![I, I, I],
656+
return_type: vec![],
657+
},
658+
)],
659+
),
640660
];
641661

642662
valid_system_apis

rs/embedders/src/wasmtime_embedder/system_api.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ pub fn syscalls<
459459
charge_for_cpu(&mut caller, overhead::MSG_METHOD_NAME_SIZE)?;
460460
with_system_api(&mut caller, |s| s.ic0_msg_method_name_size()).and_then(|s| {
461461
I::try_from(s).map_err(|e| {
462-
anyhow::Error::msg(format!("ic0::msg_metohd_name_size failed: {}", e))
462+
anyhow::Error::msg(format!("ic0::msg_method_name_size failed: {}", e))
463463
})
464464
})
465465
}
@@ -1037,6 +1037,38 @@ pub fn syscalls<
10371037
})
10381038
.unwrap();
10391039

1040+
linker
1041+
.func_wrap("ic0", "subnet_self_size", {
1042+
move |mut caller: Caller<'_, StoreData>| {
1043+
charge_for_cpu(&mut caller, overhead::SUBNET_SELF_SIZE)?;
1044+
with_system_api(&mut caller, |s| s.ic0_subnet_self_size()).and_then(|s| {
1045+
I::try_from(s).map_err(|e| {
1046+
anyhow::Error::msg(format!("ic0::subnet_self_size failed: {}", e))
1047+
})
1048+
})
1049+
}
1050+
})
1051+
.unwrap();
1052+
1053+
linker
1054+
.func_wrap("ic0", "subnet_self_copy", {
1055+
move |mut caller: Caller<'_, StoreData>, dst: I, offset: I, size: I| {
1056+
let dst: usize = dst.try_into().expect("Failed to convert I to usize");
1057+
let offset: usize = offset.try_into().expect("Failed to convert I to usize");
1058+
let size: usize = size.try_into().expect("Failed to convert I to usize");
1059+
charge_for_cpu_and_mem(&mut caller, overhead::SUBNET_SELF_COPY, size)?;
1060+
with_memory_and_system_api(&mut caller, |system_api, memory| {
1061+
system_api.ic0_subnet_self_copy(dst, offset, size, memory)
1062+
})?;
1063+
if feature_flags.write_barrier == FlagStatus::Enabled {
1064+
mark_writes_on_bytemap(&mut caller, dst, size)
1065+
} else {
1066+
Ok(())
1067+
}
1068+
}
1069+
})
1070+
.unwrap();
1071+
10401072
match main_memory_type {
10411073
WasmMemoryType::Wasm32 => {
10421074
linker

rs/embedders/src/wasmtime_embedder/system_api_complexity.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ pub mod overhead {
6262
pub const MSG_REPLY_DATA_APPEND: NumInstructions = NumInstructions::new(500);
6363
pub const MSG_REPLY: NumInstructions = NumInstructions::new(500);
6464
pub const PERFORMANCE_COUNTER: NumInstructions = NumInstructions::new(200);
65+
pub const SUBNET_SELF_SIZE: NumInstructions = NumInstructions::new(500);
66+
pub const SUBNET_SELF_COPY: NumInstructions = NumInstructions::new(500);
6567
pub const STABLE_GROW: NumInstructions = NumInstructions::new(500);
6668
pub const STABLE_READ: NumInstructions = NumInstructions::new(20);
6769
pub const STABLE_SIZE: NumInstructions = NumInstructions::new(20);

rs/embedders/tests/wasmtime_embedder.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,6 +2185,11 @@ fn wasm64_import_system_api_functions() {
21852185
(import "ic0" "is_controller"
21862186
(func $ic0_is_controller (param i64) (param i64) (result i32)))
21872187
2188+
(import "ic0" "subnet_self_size"
2189+
(func $ic0_subnet_self_size (result i64)))
2190+
(import "ic0" "subnet_self_copy"
2191+
(func $ic0_subnet_self_copy (param i64) (param i64) (param i64)))
2192+
21882193
(global $g1 (export "g1") (mut i64) (i64.const 0))
21892194
(func $test (export "canister_update test")
21902195
(i64.store (i64.const 0) (memory.grow (i64.const 1)))
@@ -2692,6 +2697,130 @@ fn wasm64_canister_self_copy() {
26922697
assert_eq!(wasm_heap, expected_heap);
26932698
}
26942699

2700+
#[test]
2701+
fn wasm64_subnet_self_size() {
2702+
let wat = r#"
2703+
(module
2704+
(import "ic0" "subnet_self_size"
2705+
(func $ic0_subnet_self_size (result i64)))
2706+
2707+
(global $g1 (export "g1") (mut i64) (i64.const 0))
2708+
(func $test (export "canister_update test")
2709+
(call $ic0_subnet_self_size)
2710+
global.set $g1
2711+
)
2712+
2713+
(memory (export "memory") i64 1)
2714+
)"#;
2715+
2716+
let caller = user_test_id(24).get();
2717+
let payload: Vec<u8> = vec![1, 3, 5, 7];
2718+
let api = ic_system_api::ApiType::update(
2719+
UNIX_EPOCH,
2720+
payload.clone(),
2721+
Cycles::zero(),
2722+
caller,
2723+
call_context_test_id(13),
2724+
);
2725+
2726+
let mut config = ic_config::embedders::Config::default();
2727+
config.feature_flags.wasm64 = FlagStatus::Enabled;
2728+
let mut instance = WasmtimeInstanceBuilder::new()
2729+
.with_config(config)
2730+
.with_wat(wat)
2731+
.with_api_type(api)
2732+
.build();
2733+
2734+
let res = instance
2735+
.run(FuncRef::Method(WasmMethod::Update("test".to_string())))
2736+
.unwrap();
2737+
2738+
assert_eq!(
2739+
res.exported_globals[0],
2740+
Global::I64(
2741+
instance
2742+
.store_data()
2743+
.system_api()
2744+
.unwrap()
2745+
.ic0_subnet_self_size()
2746+
.unwrap() as i64
2747+
)
2748+
);
2749+
}
2750+
2751+
#[test]
2752+
fn wasm64_subnet_self_copy() {
2753+
let wat = r#"
2754+
(module
2755+
(import "ic0" "subnet_self_copy"
2756+
(func $ic0_subnet_self_copy (param i64) (param i64) (param i64)))
2757+
(import "ic0" "subnet_self_size"
2758+
(func $ic0_subnet_self_size (result i64)))
2759+
2760+
(global $g1 (export "g1") (mut i64) (i64.const 0))
2761+
(func $test (export "canister_update test")
2762+
(call $ic0_subnet_self_size)
2763+
global.set $g1
2764+
(call $ic0_subnet_self_copy (i64.const 0) (i64.const 0) (call $ic0_subnet_self_size))
2765+
)
2766+
2767+
(memory (export "memory") i64 1)
2768+
)"#;
2769+
2770+
let caller = user_test_id(24).get();
2771+
let payload: Vec<u8> = vec![1, 3, 5, 7];
2772+
let api = ic_system_api::ApiType::update(
2773+
UNIX_EPOCH,
2774+
payload.clone(),
2775+
Cycles::zero(),
2776+
caller,
2777+
call_context_test_id(13),
2778+
);
2779+
2780+
let mut config = ic_config::embedders::Config::default();
2781+
config.feature_flags.wasm64 = FlagStatus::Enabled;
2782+
let mut instance = WasmtimeInstanceBuilder::new()
2783+
.with_config(config)
2784+
.with_wat(wat)
2785+
.with_api_type(api)
2786+
.build();
2787+
2788+
let res = instance
2789+
.run(FuncRef::Method(WasmMethod::Update("test".to_string())))
2790+
.unwrap();
2791+
2792+
// Only first os page should have been touched.
2793+
assert_eq!(res.wasm_dirty_pages, vec![ic_sys::PageIndex::new(0)]);
2794+
2795+
// Actual heap is larger, but we can only access first os page, the rest is protected.
2796+
let dirty_heap_size = ic_sys::PAGE_SIZE;
2797+
2798+
let wasm_heap: &[u8] = unsafe {
2799+
let addr = instance.heap_addr(CanisterMemoryType::Heap);
2800+
let size_in_bytes =
2801+
instance.heap_size(CanisterMemoryType::Heap).get() * WASM_PAGE_SIZE_IN_BYTES;
2802+
assert!(size_in_bytes >= dirty_heap_size);
2803+
std::slice::from_raw_parts_mut(addr as *mut _, dirty_heap_size)
2804+
};
2805+
2806+
let mut expected_heap = vec![0; dirty_heap_size];
2807+
let subnet_id_size = instance
2808+
.store_data()
2809+
.system_api()
2810+
.unwrap()
2811+
.ic0_subnet_self_size()
2812+
.unwrap();
2813+
2814+
instance
2815+
.store_data()
2816+
.system_api()
2817+
.unwrap()
2818+
.ic0_subnet_self_copy(0, 0, subnet_id_size, &mut expected_heap)
2819+
.unwrap();
2820+
2821+
assert_eq!(wasm_heap, expected_heap);
2822+
}
2823+
26952824
#[test]
26962825
fn wasm64_trap() {
26972826
let wat = r#"

rs/execution_environment/src/query_handler/query_cache/tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,8 @@ fn query_cache_future_proof_test() {
15101510
| SystemApiCallId::MsgReplyDataAppend
15111511
| SystemApiCallId::OutOfInstructions
15121512
| SystemApiCallId::PerformanceCounter
1513+
| SystemApiCallId::SubnetSelfSize
1514+
| SystemApiCallId::SubnetSelfCopy
15131515
| SystemApiCallId::Stable64Grow
15141516
| SystemApiCallId::Stable64Read
15151517
| SystemApiCallId::Stable64Size

rs/execution_environment/tests/hypervisor.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,69 @@ fn ic0_canister_self_copy_works() {
20062006
assert_eq!(WasmResult::Reply(canister_id.get().into_vec()), result);
20072007
}
20082008

2009+
#[test]
2010+
fn ic0_subnet_self_size_works() {
2011+
let mut test = ExecutionTestBuilder::new().build();
2012+
let wat = r#"
2013+
(module
2014+
(import "ic0" "subnet_self_size"
2015+
(func $subnet_self_size (result i32))
2016+
)
2017+
(import "ic0" "msg_reply" (func $msg_reply))
2018+
(import "ic0" "msg_reply_data_append"
2019+
(func $msg_reply_data_append (param i32 i32)))
2020+
(func (export "canister_update test")
2021+
;; heap[0] = $subnet_self_size()
2022+
(i32.store (i32.const 0) (call $subnet_self_size))
2023+
;; return heap[0-4]
2024+
(call $msg_reply_data_append (i32.const 0) (i32.const 4))
2025+
(call $msg_reply)
2026+
)
2027+
(memory 1 1)
2028+
)"#;
2029+
let canister_id = test.canister_from_wat(wat).unwrap();
2030+
let result = test.ingress(canister_id, "test", vec![]).unwrap();
2031+
assert_eq!(
2032+
WasmResult::Reply(vec![
2033+
test.get_own_subnet_id().get().as_slice().len() as u8,
2034+
0,
2035+
0,
2036+
0
2037+
]),
2038+
result
2039+
);
2040+
}
2041+
2042+
#[test]
2043+
fn ic0_subnet_self_copy_works() {
2044+
let mut test = ExecutionTestBuilder::new().build();
2045+
let wat = r#"
2046+
(module
2047+
(import "ic0" "subnet_self_copy"
2048+
(func $subnet_self_copy (param i32 i32 i32))
2049+
)
2050+
(import "ic0" "msg_reply" (func $msg_reply))
2051+
(import "ic0" "msg_reply_data_append"
2052+
(func $msg_reply_data_append (param i32 i32)))
2053+
(func (export "canister_update test")
2054+
;; heap[0..4] = subnet_id_bytes[0..4]
2055+
(call $subnet_self_copy (i32.const 0) (i32.const 0) (i32.const 4))
2056+
;; heap[4..10] = subnet_id_bytes[4..8]
2057+
(call $subnet_self_copy (i32.const 4) (i32.const 4) (i32.const 6))
2058+
;; return heap[0..10]
2059+
(call $msg_reply_data_append (i32.const 0) (i32.const 10))
2060+
(call $msg_reply)
2061+
)
2062+
(memory 1 1)
2063+
)"#;
2064+
let canister_id = test.canister_from_wat(wat).unwrap();
2065+
let result = test.ingress(canister_id, "test", vec![]).unwrap();
2066+
assert_eq!(
2067+
WasmResult::Reply(test.get_own_subnet_id().get().into_vec()),
2068+
result
2069+
);
2070+
}
2071+
20092072
#[test]
20102073
fn ic0_call_has_no_effect_on_trap() {
20112074
let mut test = ExecutionTestBuilder::new().build();

rs/interfaces/src/execution_environment.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ pub enum SystemApiCallId {
219219
OutOfInstructions,
220220
/// Tracker for `ic0.performance_counter()`
221221
PerformanceCounter,
222+
/// Tracker for `ic0.subnet_self_size()`
223+
SubnetSelfSize,
224+
/// Tracker for `ic0.subnet_self_copy()`
225+
SubnetSelfCopy,
222226
/// Tracker for `ic0.stable64_grow()`
223227
Stable64Grow,
224228
/// Tracker for `ic0.stable64_read()`
@@ -1172,6 +1176,19 @@ pub trait SystemApi {
11721176
dst: usize,
11731177
heap: &mut [u8],
11741178
) -> HypervisorResult<()>;
1179+
1180+
/// Used to look up the size of the subnet Id of the calling canister.
1181+
fn ic0_subnet_self_size(&self) -> HypervisorResult<usize>;
1182+
1183+
/// Used to copy the subnet Id of the calling canister to its heap
1184+
/// at the location specified by `dst` and `offset`.
1185+
fn ic0_subnet_self_copy(
1186+
&self,
1187+
dst: usize,
1188+
offset: usize,
1189+
size: usize,
1190+
heap: &mut [u8],
1191+
) -> HypervisorResult<()>;
11751192
}
11761193

11771194
#[derive(Copy, Clone, Eq, PartialEq, Debug)]

0 commit comments

Comments
 (0)