1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use crate::ir::abi::{AbiParam, ParamRole};
use crate::ir::codec::{EnumLayout, VecLayout};
use crate::ir::definitions::FunctionDef;
use crate::ir::ops::WriteOp;
use super::super::ast::{
CSharpArgumentList, CSharpExpression, CSharpIdentity, CSharpLocalName, CSharpMethodName,
CSharpParamName, CSharpStatement,
};
use super::super::plan::CSharpWireWriterPlan;
use super::lowerer::CSharpLowerer;
use super::{encode, size, value};
impl<'a> CSharpLowerer<'a> {
/// Builds one [`CSharpWireWriterPlan`] per param that needs a
/// `WireWriter` setup block (non-blittable records, data enums,
/// encoded vecs, options), in param order. Returns `None` if the
/// function's ABI call can't be found (shouldn't happen for validated
/// contracts).
pub(super) fn wire_writers_for_params(
&self,
function: &FunctionDef,
) -> Option<Vec<CSharpWireWriterPlan>> {
let call = self.abi_call_for_function(function)?;
// One size/encode context per function body so all `using var
// _wire_*` declarations and `sizeOpt{n}` / `opt{n}` pattern
// bindings get unique names within the same method scope.
let mut size_locals = size::SizeLocalCounters::default();
let mut encode_locals = encode::EncodeLocalCounters::default();
Some(
call.params
.iter()
.filter_map(|abi_param| {
self.wire_writer_for_param(abi_param, &mut size_locals, &mut encode_locals)
})
.collect(),
)
}
/// Builds the [`CSharpWireWriterPlan`] for one param. Returns `None`
/// when the param doesn't need wire-buffer setup (see
/// [`Self::param_needs_wire_buffer`]).
pub(super) fn wire_writer_for_param(
&self,
param: &AbiParam,
size_locals: &mut size::SizeLocalCounters,
encode_locals: &mut encode::EncodeLocalCounters,
) -> Option<CSharpWireWriterPlan> {
let raw_encode_ops = match ¶m.role {
ParamRole::Input {
encode_ops: Some(encode_ops),
..
} => encode_ops.clone(),
_ => return None,
};
if !self.param_needs_wire_buffer(raw_encode_ops.ops.first()?) {
return None;
}
// Strip Custom wrappers so downstream encode/size walkers see
// only the repr ops; otherwise nested `WriteOp::Vec` whose
// element_type is `Custom` would feed a Custom TypeExpr into
// `from_type_expr` for the foreach element-type annotation.
let encode_ops = self.normalize_custom_write_seq(&raw_encode_ops);
let param_name: CSharpParamName = (¶m.name).into();
let binding_name = CSharpLocalName::for_wire_writer(¶m_name);
let bytes_binding_name = CSharpLocalName::for_bytes(¶m_name);
let writer = CSharpExpression::Identity(CSharpIdentity::Local(binding_name.clone()));
let encode_stmts =
encode::lower_encode_expr(&encode_ops, &writer, &value::Renames::new(), encode_locals);
Some(CSharpWireWriterPlan {
binding_name,
bytes_binding_name,
param_name,
size_expr: size::lower_size_expr(&encode_ops.size, &value::Renames::new(), size_locals),
encode_stmts,
})
}
/// Whether a param's encode op requires a `WireWriter` setup block
/// before the native call.
///
/// Primitives pass as value types, strings go through the UTF-8 byte
/// path, raw bytes ride as `byte[]` directly. Blittable records and
/// C-style enums also pass by value. Variable-width payloads
/// (non-blittable records, data enums, vecs) need a length-prefixed
/// buffer serialized up front.
fn param_needs_wire_buffer(&self, op: &WriteOp) -> bool {
match op {
WriteOp::Primitive { .. } | WriteOp::String { .. } | WriteOp::Bytes { .. } => false,
WriteOp::Record { id, .. } => !self.is_blittable_record(id),
WriteOp::Enum {
layout: EnumLayout::Data { .. },
..
} => true,
WriteOp::Enum { .. } => false,
WriteOp::Vec {
layout: VecLayout::Blittable { .. },
..
} => false,
WriteOp::Vec {
layout: VecLayout::Encoded,
..
} => true,
WriteOp::Option { .. } => true,
// The macro exposes every Custom-typed param as a wire-
// buffer signature on the C ABI, even when the repr is a
// fixed-width primitive — so we always allocate and write
// through the wire path. Sending the underlying value raw
// would misalign the Rust decoder.
WriteOp::Custom { .. } => true,
WriteOp::Result { .. } | WriteOp::Builtin { .. } => {
todo!("C# backend has not yet implemented param support for {op:?}")
}
}
}
}
/// Synthesize the [`CSharpWireWriterPlan`] for an `InstanceNative`
/// receiver whose owner is wire-encoded (data enums today, non-
/// blittable records once their instance methods land). Lets the
/// template iterate `wire_writers` uniformly instead of hardcoding
/// the receiver's encode block as a special case. The size and encode
/// expressions root at `this` rather than going through the IR's
/// `Named("self")` reference.
///
/// Exposed at `pub(super)` so the templates' snapshot fixtures (which
/// build hand-rolled [`CSharpMethodPlan`]s) can mirror the lowerer's
/// new contract: an `InstanceNative` plan must include a self-writer
/// in `wire_writers[0]`.
pub(crate) fn self_wire_writer() -> CSharpWireWriterPlan {
let self_param_name = CSharpParamName::new("self");
let binding_name = CSharpLocalName::for_wire_writer(&self_param_name);
let bytes_binding_name = CSharpLocalName::for_bytes(&self_param_name);
let this_expr = CSharpExpression::Identity(CSharpIdentity::This);
let size_expr = CSharpExpression::MethodCall {
receiver: Box::new(this_expr.clone()),
method: CSharpMethodName::from_source("wire_encoded_size"),
type_args: vec![],
args: CSharpArgumentList::default(),
};
let encode_stmts = vec![CSharpStatement::Expression(CSharpExpression::MethodCall {
receiver: Box::new(this_expr),
method: CSharpMethodName::from_source("wire_encode_to"),
type_args: vec![],
args: vec![CSharpExpression::Identity(CSharpIdentity::Local(
binding_name.clone(),
))]
.into(),
})];
CSharpWireWriterPlan {
binding_name,
bytes_binding_name,
param_name: self_param_name,
size_expr,
encode_stmts,
}
}