static_config/
config.rs

1use anyhow::{bail, Context, Result};
2use serde::Deserialize;
3use walrus::{ir::Value, ConstExpr, DataKind, ExportItem, GlobalKind, Module};
4
5use crate::walrus_ops::{bump_stack_global, get_active_data_segment};
6
7#[derive(Deserialize, Debug, Clone, Default)]
8#[serde(deny_unknown_fields)]
9pub struct Config {
10    /// Set specific runtime overrides
11    #[serde(default)]
12    pub overrides: Vec<(String, String)>,
13}
14
15pub(crate) fn create_config<'a>(module: &'a mut Module, config: &Config) -> Result<()> {
16    let config_ptr_addr = {
17        let config_ptr_export = module
18            .exports
19            .iter()
20            .find(|expt| expt.name.as_str() == "CONFIG")
21            .context("Adapter 'CONFIG' is not exported")?;
22        let ExportItem::Global(config_ptr_global) = config_ptr_export.item else {
23            bail!("Adapter 'config' not a global");
24        };
25        let GlobalKind::Local(ConstExpr::Value(Value::I32(config_ptr_addr))) =
26            &module.globals.get(config_ptr_global).kind
27        else {
28            bail!("Adapter 'config' not a local I32 global value");
29        };
30        *config_ptr_addr as u32
31    };
32
33    let memory = module.get_memory_id()?;
34
35    // prepare the field data list vector for writing
36    // strings must be sorted as binary searches are used against this data
37    let mut field_data_vec: Vec<&str> = Vec::new();
38    let mut sorted_overrides = config.overrides.clone();
39    sorted_overrides.sort_by(|(a, _), (b, _)| a.cmp(b));
40    for (key, value) in &sorted_overrides {
41        field_data_vec.push(key.as_ref());
42        field_data_vec.push(value.as_ref());
43    }
44
45    let mut field_data_bytes = Vec::new();
46    for str in field_data_vec {
47        assert!(field_data_bytes.len() % 4 == 0);
48        // write the length at the aligned offset
49        field_data_bytes.extend_from_slice(&(str.len() as u32).to_le_bytes());
50        let str_bytes = str.as_bytes();
51        field_data_bytes.extend_from_slice(str_bytes);
52        let rem = str_bytes.len() % 4;
53        // add padding for alignment if necessary
54        if rem > 0 {
55            field_data_bytes.extend((0..4 - rem).map(|_| 0));
56        }
57    }
58
59    if field_data_bytes.len() % 8 != 0 {
60        field_data_bytes.resize(field_data_bytes.len() + 4, 0);
61    }
62
63    let field_data_addr = if field_data_bytes.len() > 0 {
64        // Offset the stack global by the static field data length
65        let field_data_addr = bump_stack_global(module, field_data_bytes.len() as i32)?;
66
67        // Add a new data segment for this new range created at the top of the stack
68        module.data.add(
69            DataKind::Active {
70                memory,
71                offset: ConstExpr::Value(Value::I32(field_data_addr as i32)),
72            },
73            field_data_bytes,
74        );
75        Some(field_data_addr)
76    } else {
77        None
78    };
79
80    // In the existing static data segment, update the static data options.
81    //
82    // From virtual-adapter/src/config.rs:
83    //
84    // #[repr(C)]
85    // pub struct Config {
86    //     /// [byte 0]
87    //     unused: bool,
88    //     /// How many host fields are defined in the data pointer
89    //     /// [byte 4]
90    //     host_field_cnt: u32,
91    //     /// Byte data of u32 byte len followed by string bytes
92    //     /// up to the lengths previously provided.
93    //     /// [byte 8]
94    //     host_field_data: *const u8,
95    // }
96    let (data, data_offset) = get_active_data_segment(module, memory, config_ptr_addr)?;
97    let bytes = data.value.as_mut_slice();
98
99    let host_field_cnt = config.overrides.len() as u32;
100    bytes[data_offset + 4..data_offset + 8].copy_from_slice(&host_field_cnt.to_le_bytes());
101    if let Some(field_data_addr) = field_data_addr {
102        bytes[data_offset + 8..data_offset + 12].copy_from_slice(&field_data_addr.to_le_bytes());
103    }
104
105    Ok(())
106}