1use crate::interpreter::{ListRepr, RuntimeValue};
15use logicaffeine_base::{Word16, WordVal};
16use logicaffeine_data::LogosSeq;
17use logicaffeine_system::{mldsa, mlkem, ntt};
18use std::cell::RefCell;
19use std::rc::Rc;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum NativeKernel {
24 MlkemNtt,
25 MlkemBaseMulW16,
26 ToMontW16,
27 Cbd2W16,
28 CompressW16,
29 DecompressW16,
30 ByteEncodeW16,
31 ByteDecodeW16,
32 SampleAW16,
33 SampleMatrixW16,
34 MlkemNoiseBatch,
35 AddModQW16,
36 SubModQW16,
37 ZerosW16,
38 MlkemInvNtt,
39 MlkemBaseMul,
40 Cbd2,
41 Cbd3,
42 Compress,
43 Decompress,
44 ByteEncode,
45 ByteDecode,
46 SampleNtt,
47 SampleA,
48 ToMont,
49 MlkemKeypair,
50 MlkemEncapsKem,
51 MlkemDecapsKem,
52 MldsaKeypair,
53 MldsaSign,
54 MldsaVerify,
55}
56
57impl NativeKernel {
58 pub const ALL: &'static [NativeKernel] = &[
60 NativeKernel::MlkemNtt,
61 NativeKernel::MlkemBaseMulW16,
62 NativeKernel::ToMontW16,
63 NativeKernel::Cbd2W16,
64 NativeKernel::CompressW16,
65 NativeKernel::DecompressW16,
66 NativeKernel::ByteEncodeW16,
67 NativeKernel::ByteDecodeW16,
68 NativeKernel::SampleAW16,
69 NativeKernel::SampleMatrixW16,
70 NativeKernel::MlkemNoiseBatch,
71 NativeKernel::AddModQW16,
72 NativeKernel::SubModQW16,
73 NativeKernel::ZerosW16,
74 NativeKernel::MlkemInvNtt,
75 NativeKernel::MlkemBaseMul,
76 NativeKernel::Cbd2,
77 NativeKernel::Cbd3,
78 NativeKernel::Compress,
79 NativeKernel::Decompress,
80 NativeKernel::ByteEncode,
81 NativeKernel::ByteDecode,
82 NativeKernel::SampleNtt,
83 NativeKernel::SampleA,
84 NativeKernel::ToMont,
85 NativeKernel::MlkemKeypair,
86 NativeKernel::MlkemEncapsKem,
87 NativeKernel::MlkemDecapsKem,
88 NativeKernel::MldsaKeypair,
89 NativeKernel::MldsaSign,
90 NativeKernel::MldsaVerify,
91 ];
92
93 pub const fn name(self) -> &'static str {
95 match self {
96 NativeKernel::MlkemNtt => "mlkemNtt",
97 NativeKernel::MlkemBaseMulW16 => "mlkemBaseMulW16",
98 NativeKernel::ToMontW16 => "toMontW16",
99 NativeKernel::Cbd2W16 => "cbd2W16",
100 NativeKernel::CompressW16 => "compressW16",
101 NativeKernel::DecompressW16 => "decompressW16",
102 NativeKernel::ByteEncodeW16 => "byteEncodeW16",
103 NativeKernel::ByteDecodeW16 => "byteDecodeW16",
104 NativeKernel::SampleAW16 => "sampleAW16",
105 NativeKernel::SampleMatrixW16 => "sampleMatrixW16",
106 NativeKernel::MlkemNoiseBatch => "mlkemNoiseBatch",
107 NativeKernel::AddModQW16 => "addModQW16",
108 NativeKernel::SubModQW16 => "subModQW16",
109 NativeKernel::ZerosW16 => "zerosW16",
110 NativeKernel::MlkemInvNtt => "mlkemInvNtt",
111 NativeKernel::MlkemBaseMul => "mlkemBaseMul",
112 NativeKernel::Cbd2 => "cbd2",
113 NativeKernel::Cbd3 => "cbd3",
114 NativeKernel::Compress => "compress",
115 NativeKernel::Decompress => "decompress",
116 NativeKernel::ByteEncode => "byteEncode",
117 NativeKernel::ByteDecode => "byteDecode",
118 NativeKernel::SampleNtt => "sampleNtt",
119 NativeKernel::SampleA => "sampleA",
120 NativeKernel::ToMont => "toMont",
121 NativeKernel::MlkemKeypair => "mlkemKeypair",
122 NativeKernel::MlkemEncapsKem => "mlkemEncapsKem",
123 NativeKernel::MlkemDecapsKem => "mlkemDecapsKem",
124 NativeKernel::MldsaKeypair => "mldsaKeypair",
125 NativeKernel::MldsaSign => "mldsaSign",
126 NativeKernel::MldsaVerify => "mldsaVerify",
127 }
128 }
129
130 pub const fn arity(self) -> usize {
132 match self {
133 NativeKernel::MlkemNtt
134 | NativeKernel::ToMontW16
135 | NativeKernel::Cbd2W16
136 | NativeKernel::SampleMatrixW16
137 | NativeKernel::ZerosW16
138 | NativeKernel::MlkemInvNtt
139 | NativeKernel::Cbd2
140 | NativeKernel::Cbd3
141 | NativeKernel::SampleNtt
142 | NativeKernel::ToMont
143 | NativeKernel::MldsaKeypair => 1,
144 NativeKernel::MlkemBaseMulW16
145 | NativeKernel::CompressW16
146 | NativeKernel::DecompressW16
147 | NativeKernel::ByteEncodeW16
148 | NativeKernel::ByteDecodeW16
149 | NativeKernel::AddModQW16
150 | NativeKernel::SubModQW16
151 | NativeKernel::MlkemBaseMul
152 | NativeKernel::Compress
153 | NativeKernel::Decompress
154 | NativeKernel::ByteEncode
155 | NativeKernel::ByteDecode
156 | NativeKernel::MlkemKeypair
157 | NativeKernel::MlkemEncapsKem
158 | NativeKernel::MlkemDecapsKem => 2,
159 NativeKernel::SampleAW16
160 | NativeKernel::MlkemNoiseBatch
161 | NativeKernel::SampleA
162 | NativeKernel::MldsaSign => 3,
163 NativeKernel::MldsaVerify => 4,
164 }
165 }
166
167 pub const fn rust_path(self) -> (&'static str, &'static str) {
169 match self {
170 NativeKernel::MlkemNtt => ("ntt", "mlkem_ntt"),
171 NativeKernel::MlkemBaseMulW16 => ("ntt", "mlkem_base_mul_w16_seq"),
172 NativeKernel::ToMontW16 => ("ntt", "mlkem_to_mont_w16_seq"),
173 NativeKernel::Cbd2W16 => ("ntt", "mlkem_cbd2_w16_from_int"),
174 NativeKernel::CompressW16 => ("ntt", "mlkem_compress_w16_seq"),
175 NativeKernel::DecompressW16 => ("ntt", "mlkem_decompress_w16_seq"),
176 NativeKernel::ByteEncodeW16 => ("ntt", "mlkem_byte_encode_w16_to_int"),
177 NativeKernel::ByteDecodeW16 => ("ntt", "mlkem_byte_decode_w16_from_int"),
178 NativeKernel::SampleAW16 => ("ntt", "mlkem_sample_a_w16_from_int"),
179 NativeKernel::SampleMatrixW16 => ("ntt", "mlkem_sample_matrix_w16_from_int"),
180 NativeKernel::MlkemNoiseBatch => ("mlkem", "mlkem_noise_batch_from_int"),
181 NativeKernel::AddModQW16 => ("ntt", "mlkem_add_mod_q_w16"),
182 NativeKernel::SubModQW16 => ("ntt", "mlkem_sub_mod_q_w16"),
183 NativeKernel::ZerosW16 => ("ntt", "mlkem_zeros_w16"),
184 NativeKernel::MlkemInvNtt => ("ntt", "mlkem_inv_ntt"),
185 NativeKernel::MlkemBaseMul => ("ntt", "mlkem_base_mul"),
186 NativeKernel::Cbd2 => ("ntt", "mlkem_cbd2"),
187 NativeKernel::Cbd3 => ("ntt", "mlkem_cbd3"),
188 NativeKernel::Compress => ("ntt", "mlkem_compress"),
189 NativeKernel::Decompress => ("ntt", "mlkem_decompress"),
190 NativeKernel::ByteEncode => ("ntt", "mlkem_byte_encode"),
191 NativeKernel::ByteDecode => ("ntt", "mlkem_byte_decode"),
192 NativeKernel::SampleNtt => ("ntt", "mlkem_sample_ntt"),
193 NativeKernel::SampleA => ("ntt", "mlkem_sample_a"),
194 NativeKernel::ToMont => ("ntt", "mlkem_to_mont"),
195 NativeKernel::MlkemKeypair => ("mlkem", "mlkem_keypair_seq"),
196 NativeKernel::MlkemEncapsKem => ("mlkem", "mlkem_encaps_seq"),
197 NativeKernel::MlkemDecapsKem => ("mlkem", "mlkem_decaps_seq"),
198 NativeKernel::MldsaKeypair => ("mldsa", "mldsa_keypair_seq"),
199 NativeKernel::MldsaSign => ("mldsa", "mldsa_sign_seq"),
200 NativeKernel::MldsaVerify => ("mldsa", "mldsa_verify_seq"),
201 }
202 }
203
204 pub fn code(self) -> i64 {
206 Self::ALL
207 .iter()
208 .position(|&k| k == self)
209 .expect("every kernel is in ALL") as i64
210 }
211
212 pub fn from_code(code: i64) -> Option<NativeKernel> {
214 usize::try_from(code)
215 .ok()
216 .and_then(|i| Self::ALL.get(i).copied())
217 }
218
219 pub fn from_name(name: &str) -> Option<NativeKernel> {
221 Self::ALL.iter().copied().find(|k| k.name() == name)
222 }
223
224 pub fn refusal(self, backend: &str) -> String {
226 format!(
227 "`{}` is a standard-library native kernel (crypto.lg) implemented in Rust — the {backend} \
228 backend cannot link it; run the program on the interpreter or the VM, or build it with the \
229 Rust tier (`largo build`)",
230 self.name()
231 )
232 }
233
234 pub fn call(self, args: &[RuntimeValue]) -> Result<RuntimeValue, String> {
236 if args.len() != self.arity() {
237 return Err(format!(
238 "{}() takes {} argument{}, got {}",
239 self.name(),
240 self.arity(),
241 if self.arity() == 1 { "" } else { "s" },
242 args.len()
243 ));
244 }
245 let name = self.name();
246 let int = |i: usize| int_arg(name, &args[i]);
247 let ints = |i: usize| int_seq(name, &args[i]);
248 let w16 = |i: usize| word16_seq(name, &args[i]);
249 let ran = match self {
250 NativeKernel::MlkemNtt => ntt::mlkem_ntt(&ints(0)?).map(from_ints),
251 NativeKernel::MlkemBaseMulW16 => {
252 ntt::mlkem_base_mul_w16_seq(&w16(0)?, &w16(1)?).map(from_w16)
253 }
254 NativeKernel::ToMontW16 => ntt::mlkem_to_mont_w16_seq(&w16(0)?).map(from_w16),
255 NativeKernel::Cbd2W16 => ntt::mlkem_cbd2_w16_from_int(&ints(0)?).map(from_w16),
256 NativeKernel::CompressW16 => {
257 ntt::mlkem_compress_w16_seq(&w16(0)?, int(1)?).map(from_w16)
258 }
259 NativeKernel::DecompressW16 => {
260 ntt::mlkem_decompress_w16_seq(&w16(0)?, int(1)?).map(from_w16)
261 }
262 NativeKernel::ByteEncodeW16 => {
263 ntt::mlkem_byte_encode_w16_to_int(&w16(0)?, int(1)?).map(from_ints)
264 }
265 NativeKernel::ByteDecodeW16 => {
266 ntt::mlkem_byte_decode_w16_from_int(&ints(0)?, int(1)?).map(from_w16)
267 }
268 NativeKernel::SampleAW16 => {
269 ntt::mlkem_sample_a_w16_from_int(&ints(0)?, int(1)?, int(2)?).map(from_w16)
270 }
271 NativeKernel::SampleMatrixW16 => {
272 ntt::mlkem_sample_matrix_w16_from_int(&ints(0)?).map(from_w16)
273 }
274 NativeKernel::MlkemNoiseBatch => {
275 mlkem::mlkem_noise_batch_from_int(&ints(0)?, int(1)?, int(2)?).map(from_w16)
276 }
277 NativeKernel::AddModQW16 => ntt::mlkem_add_mod_q_w16(&w16(0)?, &w16(1)?).map(from_w16),
278 NativeKernel::SubModQW16 => ntt::mlkem_sub_mod_q_w16(&w16(0)?, &w16(1)?).map(from_w16),
279 NativeKernel::ZerosW16 => ntt::mlkem_zeros_w16(int(0)?).map(from_w16),
280 NativeKernel::MlkemInvNtt => ntt::mlkem_inv_ntt(&ints(0)?).map(from_ints),
281 NativeKernel::MlkemBaseMul => ntt::mlkem_base_mul(&ints(0)?, &ints(1)?).map(from_ints),
282 NativeKernel::Cbd2 => ntt::mlkem_cbd2(&ints(0)?).map(from_ints),
283 NativeKernel::Cbd3 => ntt::mlkem_cbd3(&ints(0)?).map(from_ints),
284 NativeKernel::Compress => ntt::mlkem_compress(&ints(0)?, int(1)?).map(from_ints),
285 NativeKernel::Decompress => ntt::mlkem_decompress(&ints(0)?, int(1)?).map(from_ints),
286 NativeKernel::ByteEncode => ntt::mlkem_byte_encode(&ints(0)?, int(1)?).map(from_ints),
287 NativeKernel::ByteDecode => ntt::mlkem_byte_decode(&ints(0)?, int(1)?).map(from_ints),
288 NativeKernel::SampleNtt => ntt::mlkem_sample_ntt(&ints(0)?).map(from_ints),
289 NativeKernel::SampleA => {
290 ntt::mlkem_sample_a(&ints(0)?, int(1)?, int(2)?).map(from_ints)
291 }
292 NativeKernel::ToMont => ntt::mlkem_to_mont(&ints(0)?).map(from_ints),
293 NativeKernel::MlkemKeypair => {
294 mlkem::mlkem_keypair_seq(&ints(0)?, &ints(1)?).map(from_ints)
295 }
296 NativeKernel::MlkemEncapsKem => {
297 mlkem::mlkem_encaps_seq(&ints(0)?, &ints(1)?).map(from_ints)
298 }
299 NativeKernel::MlkemDecapsKem => {
300 mlkem::mlkem_decaps_seq(&ints(0)?, &ints(1)?).map(from_ints)
301 }
302 NativeKernel::MldsaKeypair => mldsa::mldsa_keypair_seq(&ints(0)?).map(from_ints),
303 NativeKernel::MldsaSign => {
304 mldsa::mldsa_sign_seq(&ints(0)?, &ints(1)?, &ints(2)?).map(from_ints)
305 }
306 NativeKernel::MldsaVerify => {
307 mldsa::mldsa_verify_seq(&ints(0)?, &ints(1)?, &ints(2)?, &ints(3)?)
308 .map(RuntimeValue::Int)
309 }
310 };
311 ran.map_err(|why| self.failure(&why))
312 }
313
314 pub fn failure(self, why: &str) -> String {
318 format!("{}(): {why}", self.name())
319 }
320}
321
322pub fn refuse_kernel_calls(
327 program: &crate::vm::CompiledProgram,
328 interner: &crate::Interner,
329 backend: &str,
330) -> Result<(), String> {
331 refuse_kernel_calls_with(
332 program,
333 interner,
334 backend,
335 &crate::vm::reach::HostEntries::default(),
336 )
337}
338
339pub fn refuse_kernel_calls_with(
342 program: &crate::vm::CompiledProgram,
343 interner: &crate::Interner,
344 backend: &str,
345 entries: &crate::vm::reach::HostEntries,
346) -> Result<(), String> {
347 use crate::semantics::builtins::BuiltinId;
348 use crate::vm::{Constant, Op};
349 for (pc, op) in crate::vm::reach::running_ops_with(program, interner, entries) {
350 let Op::CallBuiltin {
351 builtin: BuiltinId::NativeKernel,
352 args_start,
353 ..
354 } = *op
355 else {
356 continue;
357 };
358 let kernel = program.code[..pc]
359 .iter()
360 .rev()
361 .find_map(|prior| match *prior {
362 Op::LoadConst { dst, idx } if dst == args_start => {
363 match program.constants.get(idx as usize) {
364 Some(Constant::Int(code)) => Some(NativeKernel::from_code(*code)),
365 _ => Some(None),
366 }
367 }
368 _ => None,
369 });
370 return Err(match kernel.flatten() {
371 Some(k) => k.refusal(backend),
372 None => format!(
373 "the program calls a standard-library native kernel (crypto.lg) implemented in Rust — the \
374 {backend} backend cannot link it"
375 ),
376 });
377 }
378 Ok(())
379}
380
381fn int_arg(kernel: &str, v: &RuntimeValue) -> Result<i64, String> {
382 match v {
383 RuntimeValue::Int(n) => Ok(*n),
384 other => Err(format!(
385 "{kernel}() needs an Int here, got {}",
386 other.type_name()
387 )),
388 }
389}
390
391fn int_seq(kernel: &str, v: &RuntimeValue) -> Result<Vec<i64>, String> {
392 let RuntimeValue::List(items) = v else {
393 return Err(format!(
394 "{kernel}() needs a Seq of Int here, got {}",
395 v.type_name()
396 ));
397 };
398 items
399 .borrow()
400 .to_values()
401 .iter()
402 .enumerate()
403 .map(|(i, e)| match e {
404 RuntimeValue::Int(n) => Ok(*n),
405 other => Err(format!(
406 "{kernel}() needs a Seq of Int here; item {} is {}",
407 i + 1,
408 other.type_name()
409 )),
410 })
411 .collect()
412}
413
414fn word16_seq(kernel: &str, v: &RuntimeValue) -> Result<Vec<Word16>, String> {
415 let RuntimeValue::List(items) = v else {
416 return Err(format!(
417 "{kernel}() needs a Seq of Word16 here, got {}",
418 v.type_name()
419 ));
420 };
421 items
422 .borrow()
423 .to_values()
424 .iter()
425 .enumerate()
426 .map(|(i, e)| match e {
427 RuntimeValue::Word(WordVal::W16(w)) => Ok(*w),
428 other => Err(format!(
429 "{kernel}() needs a Seq of Word16 here; item {} is {}",
430 i + 1,
431 other.type_name()
432 )),
433 })
434 .collect()
435}
436
437fn from_ints(seq: LogosSeq<i64>) -> RuntimeValue {
438 RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(seq.to_vec()))))
439}
440
441fn from_w16(seq: LogosSeq<Word16>) -> RuntimeValue {
442 let values = seq
443 .to_vec()
444 .into_iter()
445 .map(|w| RuntimeValue::Word(WordVal::W16(w)))
446 .collect();
447 RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Boxed(values))))
448}
449
450#[cfg(test)]
451#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/semantics/native_kernels/tests.rs"]
452mod tests;