1use std::fmt::Write;
23
24use super::runtime::{
25 c_net_runtime, c_runtime, c_vfs_runtime, C_CRDT_RUNTIME, C_GC_COLLECTOR, C_TCP_RUNTIME,
26};
27use super::words;
28use crate::analysis::{PolicyCondition, PolicyRegistry};
29use crate::intern::Interner;
30use crate::interpreter::RuntimeValue;
31use crate::vm::{BoundaryType, CompiledProgram, Constant, Op, SlotKind};
32
33mod exactness;
34mod numbers;
35mod tower;
36
37const SETTLE_ROUNDS: usize = 64;
40
41#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44enum Kind {
45 Int,
46 Bool,
47 Float,
48 Text,
49 Optional(u16),
54 BigInt,
59 Rational,
65 SeqInt,
67 SeqFloat,
68 SeqText,
69 SeqBool,
70 SeqOf(u16),
75 SeqUnknown,
77 SetInt,
80 SetWord(u8),
82 SetText,
83 SetUnknown,
85 CrdtCounter,
91 Struct(u16),
94 StructSynth(u16),
100 Enum(u16),
103 Inductive(u16),
109 Nothing,
112 Tuple(u16),
116 Map(u16),
120 MapUnknown,
123 Peer,
125 Date,
127 Moment,
129 Time,
131 Span,
133 Word(u8),
136 Tower,
140 Never,
144 Unknown,
145}
146
147impl Kind {
148 fn elem(self) -> Kind {
151 match self {
152 Kind::SeqInt | Kind::SetInt => Kind::Int,
153 Kind::SetWord(bits) => Kind::Word(bits),
154 Kind::SeqFloat => Kind::Float,
155 Kind::SeqText | Kind::SetText => Kind::Text,
156 Kind::SeqBool => Kind::Bool,
157 _ => Kind::Unknown,
158 }
159 }
160 fn set_of(self) -> Kind {
162 match self {
163 Kind::Int => Kind::SetInt,
164 Kind::Word(bits) => Kind::SetWord(bits),
165 Kind::Text => Kind::SetText,
166 _ => Kind::SetUnknown,
167 }
168 }
169 fn seq_c_type(self) -> Option<&'static str> {
171 match self {
172 Kind::SeqInt => Some("Seq_i64"),
173 Kind::SeqFloat => Some("Seq_f64"),
174 Kind::SeqText => Some("Seq_str"),
175 Kind::SeqBool => Some("Seq_bool"),
176 Kind::SeqOf(_) => Some("Seq_i64"),
178 _ => None,
179 }
180 }
181 fn set_c_type(self) -> Option<&'static str> {
183 match self {
184 Kind::SetInt | Kind::SetWord(_) => Some("Set_i64"),
185 Kind::SetText => Some("Set_str"),
186 _ => None,
187 }
188 }
189 fn seq_infix(self) -> Option<&'static str> {
191 match self {
192 Kind::SeqInt | Kind::SetInt | Kind::SetWord(_) | Kind::SeqOf(_) => Some("i64"),
193 Kind::SeqFloat => Some("f64"),
194 Kind::SeqText | Kind::SetText => Some("str"),
195 Kind::SeqBool => Some("bool"),
196 _ => None,
197 }
198 }
199}
200
201fn is_concrete_set(k: Kind) -> bool {
203 matches!(k, Kind::SetInt | Kind::SetWord(_) | Kind::SetText)
204}
205
206fn resolve_set(k: Kind) -> Option<Kind> {
209 match k {
210 Kind::SetInt | Kind::SetUnknown => Some(Kind::SetInt),
211 Kind::SetWord(bits) => Some(Kind::SetWord(bits)),
212 Kind::SetText => Some(Kind::SetText),
213 _ => None,
214 }
215}
216
217fn seq_kind_of(k: Kind) -> Kind {
220 if k == Kind::SeqUnknown {
221 Kind::SeqInt
222 } else {
223 k
224 }
225}
226
227fn is_sequence(k: Kind) -> bool {
229 k.seq_c_type().is_some() || k == Kind::SeqUnknown
230}
231
232fn is_concrete_seq(k: Kind) -> bool {
234 matches!(
235 k,
236 Kind::SeqInt | Kind::SeqFloat | Kind::SeqText | Kind::SeqBool | Kind::SeqOf(_)
237 )
238}
239
240fn map_key_storage(k: Kind) -> Option<&'static str> {
251 match k {
252 Kind::Int | Kind::Word(_) => Some("i64"),
253 Kind::Text => Some("str"),
254 _ => None,
255 }
256}
257fn map_val_storage(k: Kind) -> Option<&'static str> {
260 match k {
261 Kind::Unknown => None,
262 Kind::Text => Some("str"),
263 _ => Some("i64"),
264 }
265}
266fn map_type_name(key: Kind, val: Kind) -> Option<String> {
268 Some(format!(
269 "Map_{}_{}",
270 map_key_storage(key)?,
271 map_val_storage(val)?
272 ))
273}
274fn map_fn(key: Kind, val: Kind) -> Option<String> {
276 Some(format!(
277 "map_{}_{}",
278 map_key_storage(key)?,
279 map_val_storage(val)?
280 ))
281}
282fn map_ptr_kv(reg: u16, key: Kind, val: Kind) -> Option<String> {
284 Some(format!(
285 "(({}*)(intptr_t)r[{reg}])",
286 map_type_name(key, val)?
287 ))
288}
289fn map_key_arg(reg: u16, key: Kind) -> String {
291 match key {
292 Kind::Text => format!("(const char*)(intptr_t)r[{reg}]"),
293 _ => format!("r[{reg}]"),
294 }
295}
296fn map_val_store(reg: u16, val: Kind) -> String {
300 match val {
301 Kind::Text => format!("(char*)(intptr_t)r[{reg}]"),
302 Kind::Int => format!("lc_xof(r[{reg}])"),
303 _ => format!("r[{reg}]"),
304 }
305}
306fn map_val_load(get: &str, val: Kind) -> String {
308 match val {
309 Kind::Text => format!("(int64_t)(intptr_t)({get})"),
310 _ => get.to_string(),
311 }
312}
313fn is_concrete_map(k: Kind) -> bool {
314 matches!(k, Kind::Map(_))
315}
316
317fn in_hole(pc: usize, holes: &[(usize, usize)]) -> bool {
322 holes.iter().any(|&(s, e)| pc >= s && pc < e)
323}
324
325struct WalkerRegion<'e, 'a> {
327 emitter: &'e Emitter<'a>,
328 start: usize,
329 end: usize,
330 holes: &'e [(usize, usize)],
331}
332
333impl crate::vm::variables::Flow for WalkerRegion<'_, '_> {
334 fn pcs(&self) -> Vec<usize> {
335 (self.start..self.end).filter(|pc| !in_hole(*pc, self.holes)).collect()
336 }
337
338 fn successors(&self, pc: usize) -> Vec<usize> {
339 self.emitter.successors(pc, self.start, self.end, self.holes)
340 }
341
342 fn written(&self, op: &Op, f: &mut dyn FnMut(u16)) {
343 self.emitter.for_each_written(op, f);
344 }
345
346 fn read(&self, op: &Op, f: &mut dyn FnMut(u16)) {
347 op.for_each_read_reg(|func| self.emitter.prog.functions[func as usize].captures.len(), f);
348 }
349}
350
351fn is_inline_closure(prog: &CompiledProgram, entry: usize) -> Option<usize> {
355 if entry >= 1 {
356 if let Op::Jump { target } = prog.code[entry - 1] {
357 if target > entry
358 && target <= prog.code.len()
359 && matches!(
360 prog.code.get(target - 1),
361 Some(Op::Return { .. }) | Some(Op::ReturnNothing)
362 )
363 {
364 return Some(target);
365 }
366 }
367 }
368 None
369}
370
371fn boundary_to_kind_c(ty: &Option<BoundaryType>) -> Kind {
372 match ty {
373 Some(BoundaryType::Int) => Kind::Int,
374 Some(BoundaryType::Word32) => Kind::Word(32),
375 Some(BoundaryType::Word64) => Kind::Word(64),
376 Some(BoundaryType::Bool) => Kind::Bool,
377 Some(BoundaryType::Float) => Kind::Float,
378 Some(BoundaryType::Text) => Kind::Text,
379 Some(BoundaryType::Builtin(name)) if name == "Rational" => Kind::Rational,
380 Some(BoundaryType::Seq(elem)) => match **elem {
381 BoundaryType::Int => Kind::SeqInt,
382 BoundaryType::Float => Kind::SeqFloat,
383 BoundaryType::Text => Kind::SeqText,
384 BoundaryType::Bool => Kind::SeqBool,
385 _ => Kind::SeqUnknown,
386 },
387 Some(BoundaryType::Set(elem)) => match **elem {
388 BoundaryType::Int => Kind::SetInt,
389 BoundaryType::Word32 => Kind::SetWord(32),
390 BoundaryType::Word64 => Kind::SetWord(64),
391 BoundaryType::Text => Kind::SetText,
392 _ => Kind::SetUnknown,
393 },
394 _ => Kind::Unknown,
395 }
396}
397
398fn join(a: Kind, b: Kind) -> Kind {
401 if a == b {
402 a
403 } else if a == Kind::Tower || b == Kind::Tower {
404 Kind::Tower
406 } else if a == Kind::Never {
407 b
408 } else if b == Kind::Never {
409 a
410 } else if a == Kind::Float || b == Kind::Float {
411 Kind::Float
412 } else if a == Kind::Rational || b == Kind::Rational {
413 Kind::Rational
415 } else if a == Kind::BigInt || b == Kind::BigInt {
416 Kind::BigInt
418 } else if a == Kind::Unknown {
419 b
420 } else if b == Kind::Unknown {
421 a
422 } else {
423 Kind::Unknown
424 }
425}
426
427fn bounded_by(joined: Kind, bound: Kind) -> Kind {
430 if joined == Kind::BigInt && bound != Kind::BigInt {
431 Kind::Int
432 } else {
433 joined
434 }
435}
436
437fn kind_at(kinds: &[Kind], reg: u16) -> Kind {
439 *kinds.get(reg as usize).unwrap_or(&Kind::Unknown)
440}
441
442fn as_double(reg: u16, k: Kind) -> String {
445 match k {
446 Kind::Float => format!("as_f64(r[{reg}])"),
447 Kind::BigInt => format!("lint_to_f64(lc_xget(r[{reg}]))"),
448 _ => format!("(double)r[{reg}]"),
449 }
450}
451
452fn as_cstr(reg: u16) -> String {
454 format!("(const char*)(intptr_t)r[{reg}]")
455}
456
457fn as_ldec(reg: u16, k: Kind) -> Result<String, String> {
460 match k {
461 Kind::Rational => Ok(format!("(*(LDec*)(intptr_t)r[{reg}])")),
462 Kind::Int => Ok(format!("ldec_from_i64(r[{reg}])")),
463 Kind::BigInt => Ok(format!("ldec_from_lint(lc_xget(r[{reg}]))")),
464 other => Err(format!(
465 "expected a Rational/Int operand for exact arithmetic, got {other:?}"
466 )),
467 }
468}
469
470fn as_exact(reg: u16, k: Kind) -> Result<String, String> {
473 match k {
474 Kind::BigInt => Ok(format!("r[{reg}]")),
475 Kind::Int | Kind::Bool => Ok(format!("lc_xof(r[{reg}])")),
476 other => Err(format!(
477 "expected a BigInt/Int operand for exact-integer arithmetic, got {other:?}"
478 )),
479 }
480}
481
482fn exact_zero_test(reg: u16, k: Kind) -> String {
484 match k {
485 Kind::Rational => format!("((LDec*)(intptr_t)r[{reg}])->a.len == 0"),
486 _ => format!("r[{reg}] == 0"),
487 }
488}
489
490fn sample_divisor(k: Kind) -> Option<RuntimeValue> {
492 Some(match k {
493 Kind::Int | Kind::BigInt => RuntimeValue::Int(1),
494 Kind::Float => RuntimeValue::Float(1.0),
495 Kind::Word(bits) => RuntimeValue::Word(match bits {
496 8 => logicaffeine_base::WordVal::W8(logicaffeine_base::Word8(1)),
497 16 => logicaffeine_base::WordVal::W16(logicaffeine_base::Word16(1)),
498 32 => logicaffeine_base::WordVal::W32(logicaffeine_base::Word32(1)),
499 _ => logicaffeine_base::WordVal::W64(logicaffeine_base::Word64(1)),
500 }),
501 other => return sample_value(other),
502 })
503}
504
505fn sample_value(k: Kind) -> Option<RuntimeValue> {
508 use std::rc::Rc;
509 Some(match k {
510 Kind::Int | Kind::BigInt => RuntimeValue::Int(0),
511 Kind::Rational => RuntimeValue::from_rational(
512 logicaffeine_base::Rational::from_ratio_i64(1, 2).expect("a nonzero denominator"),
513 ),
514 Kind::Float => RuntimeValue::Float(0.0),
515 Kind::Bool => RuntimeValue::Bool(false),
516 Kind::Text => RuntimeValue::Text(Rc::new(String::new())),
517 Kind::SeqInt
518 | Kind::SeqFloat
519 | Kind::SeqText
520 | Kind::SeqBool
521 | Kind::SeqOf(_)
522 | Kind::SeqUnknown => RuntimeValue::List(Rc::new(std::cell::RefCell::new(
523 crate::interpreter::ListRepr::from_values(Vec::new()),
524 ))),
525 Kind::Tuple(_) => RuntimeValue::Tuple(Rc::new(Vec::new())),
526 Kind::Map(_) | Kind::MapUnknown => RuntimeValue::Map(Rc::new(std::cell::RefCell::new(
527 crate::interpreter::MapStorage::default(),
528 ))),
529 Kind::Peer => RuntimeValue::Peer(Rc::new(String::new())),
530 Kind::Word(bits) => RuntimeValue::Word(match bits {
531 8 => logicaffeine_base::WordVal::W8(logicaffeine_base::Word8(0)),
532 16 => logicaffeine_base::WordVal::W16(logicaffeine_base::Word16(0)),
533 32 => logicaffeine_base::WordVal::W32(logicaffeine_base::Word32(0)),
534 _ => logicaffeine_base::WordVal::W64(logicaffeine_base::Word64(0)),
535 }),
536 _ => return None,
537 })
538}
539
540fn coerce_cstr(reg: u16, k: Kind) -> String {
544 match k {
545 Kind::Text => as_cstr(reg),
546 Kind::Bool => format!("(r[{reg}] ? \"true\" : \"false\")"),
547 Kind::Float => format!("logos_fmt_f64(as_f64(r[{reg}]))"),
548 Kind::Rational => format!("ldec_to_string((LDec*)(intptr_t)r[{reg}])"),
549 Kind::BigInt => format!("lint_to_string(lc_xget(r[{reg}]))"),
550 Kind::Tower => format!("ltower_to_string((LTower)(intptr_t)r[{reg}])"),
551 _ => format!("logos_dyn_sprintf(\"%\" PRId64, r[{reg}])"),
552 }
553}
554
555fn as_elem_push(reg: u16, elem: Kind) -> String {
557 match elem {
558 Kind::Float => format!("as_f64(r[{reg}])"),
559 Kind::Text => format!("(char*)(intptr_t)r[{reg}]"),
560 Kind::Bool => format!("(bool)r[{reg}]"),
561 _ => format!("r[{reg}]"),
562 }
563}
564
565fn elem_to_reg(expr: &str, elem: Kind) -> String {
567 match elem {
568 Kind::Float => format!("as_bits({expr})"),
569 Kind::Text => format!("(int64_t)(intptr_t)({expr})"),
570 _ => expr.to_string(),
571 }
572}
573
574fn c_escape(s: &str) -> String {
576 let mut out = String::with_capacity(s.len() + 2);
577 for c in s.chars() {
578 match c {
579 '\\' => out.push_str("\\\\"),
580 '"' => out.push_str("\\\""),
581 '\n' => out.push_str("\\n"),
582 '\t' => out.push_str("\\t"),
583 '\r' => out.push_str("\\r"),
584 c if (c as u32) < 0x20 => out.push_str(&format!("\\{:03o}", c as u32)),
585 c => out.push(c),
586 }
587 }
588 out
589}
590
591fn walker_preamble() -> String {
596 let utf8 = words::invalid_utf8();
597 WALKER_PREAMBLE
598 .replace("@FLOOR_DIVISION_BY_ZERO@", &words::floor_division_by_zero())
599 .replace(
600 "@REAL_FLOOR_DIVISION_BY_ZERO@",
601 &words::real_floor_division_by_zero(),
602 )
603 .replace("@EXACT_DIVISION_BY_ZERO@", &words::exact_division_by_zero())
604 .replace("@NEGATIVE_EXPONENT@", &words::negative_exponent())
605 .replace("@EXPONENT_TOO_LARGE@", &words::exponent_too_large())
606 .replace("@BIG_EXPONENT@", &words::big_exponent_refused())
607 .replace("@DIVISION_BY_ZERO@", &words::division_by_zero())
608 .replace("@MODULO_BY_ZERO@", &words::modulo_by_zero())
609 .replace("@UTF8_1@", &utf8[0])
610 .replace("@UTF8_2@", &utf8[1])
611 .replace("@UTF8_3@", &utf8[2])
612 .replace("@UTF8_INCOMPLETE@", &utf8[3])
613 .replace("@NOT_AN_INT@", &words::not_an_int())
614 .replace("@NOT_A_CHARACTER@", &words::not_a_character())
615 .replace("@CANNOT_READ_FILE@", &words::cannot_read_file())
616 .replace("@RANDOM_BOUNDS@", &words::random_bounds_refused())
617}
618
619const WALKER_PREAMBLE: &str = "\
620#include <errno.h>
621static inline double as_f64(int64_t b){ double d; __builtin_memcpy(&d,&b,8); return d; }
622static inline int64_t as_bits(double d){ int64_t b; __builtin_memcpy(&b,&d,8); return b; }
623static inline int64_t ipow64(int64_t b, int64_t e){ int64_t r=1; if(e<0) logos_fatalf(@NEGATIVE_EXPONENT@); while(e){ if(e&1) r*=b; e>>=1; if(e) b*=b; } return r; }
624static inline int64_t fldiv64(int64_t a, int64_t b){ if(b==0) logos_fatalf(@FLOOR_DIVISION_BY_ZERO@); if(b==-1) return (int64_t)(0-(uint64_t)a); int64_t q=a/b, r=a%b; if(r!=0 && ((r<0)!=(b<0))) q--; return q; }
625static inline double lc_rfldiv(double a, double b){ if(b==0.0) logos_fatalf(@REAL_FLOOR_DIVISION_BY_ZERO@); return floor(a/b); }
626static inline void lc_exact_divisor(int zero){ if(zero) logos_fatalf(@EXACT_DIVISION_BY_ZERO@); }
627static inline int lc_approx_eq(double a, double b){ if(a==b) return 1; double d=(a-b)<0?(b-a):(a-b); double x=a<0?-a:a, y=b<0?-b:b, m=x>y?x:y, t=1e-9*m; if(t<1e-12) t=1e-12; return d<=t; }
628/* Each enum cell buffer is a traced object; its children live exactly as long as a reachable parent. */
629__attribute__((unused)) static int64_t *lc_enum_alloc(size_t n){
630 if(n > SIZE_MAX / sizeof(int64_t)) logos_fatal(\"memory allocation failed\");
631 return (int64_t*)lc_gc_alloc(n * sizeof(int64_t));
632}
633static Set_i64 lc_set_i64_copy(Set_i64 *s){ Set_i64 r=set_i64_new(); for(size_t oi=0;oi<s->ord_len;oi++){ size_t si=s->order[oi]; if(s->state[si]) set_i64_add(&r,s->keys[si]); } return r; }
634static Set_str lc_set_str_copy(Set_str *s){ Set_str r=set_str_new(); for(size_t oi=0;oi<s->ord_len;oi++){ size_t si=s->order[oi]; if(s->state[si]) set_str_add(&r,s->keys[si]); } return r; }
635__attribute__((unused)) static Map_i64_i64 lc_map_i64_i64_copy(Map_i64_i64 *m){ Map_i64_i64 r=map_i64_i64_new(); for(size_t oi=0;oi<m->ord_len;oi++){ size_t si=m->order[oi]; if(m->state[si]) map_i64_i64_set(&r,m->keys[si],m->vals[si]); } return r; }
636__attribute__((unused)) static Map_i64_str lc_map_i64_str_copy(Map_i64_str *m){ Map_i64_str r=map_i64_str_new(); for(size_t oi=0;oi<m->ord_len;oi++){ size_t si=m->order[oi]; if(m->state[si]) map_i64_str_set(&r,m->keys[si],m->vals[si]); } return r; }
637__attribute__((unused)) static Map_str_i64 lc_map_str_i64_copy(Map_str_i64 *m){ Map_str_i64 r=map_str_i64_new(); for(size_t oi=0;oi<m->ord_len;oi++){ size_t si=m->order[oi]; if(m->state[si]) map_str_i64_set(&r,m->keys[si],m->vals[si]); } return r; }
638__attribute__((unused)) static Map_str_str lc_map_str_str_copy(Map_str_str *m){ Map_str_str r=map_str_str_new(); for(size_t oi=0;oi<m->ord_len;oi++){ size_t si=m->order[oi]; if(m->state[si]) map_str_str_set(&r,m->keys[si],m->vals[si]); } return r; }
639/* A list of integers as the list of the rationals they equal, each item a boxed `LDec`. */
640__attribute__((unused)) static Seq_i64 *lc_seq_i64_as_ldec(Seq_i64 *s){
641 Seq_i64 *r=(Seq_i64*)lc_gc_alloc(sizeof(Seq_i64)); *r=seq_i64_new();
642 for(size_t i=0;i<s->len;i++){ LDec *d=(LDec*)lc_gc_alloc(sizeof(LDec)); *d=ldec_from_i64(s->data[i]); seq_i64_push(r,(int64_t)(intptr_t)d); }
643 return r;
644}
645/* The byte length of the UTF-8 character that starts with `lead`. */
646static inline size_t lc_utf8_len(unsigned char lead){ return lead<0x80?1:lead<0xE0?2:lead<0xF0?3:4; }
647/* Every ASCII character as its own one-character Text, so reading one out of a text allocates nothing. */
648static char lc_ascii_texts[128][2];
649static inline char *lc_ascii_text(unsigned char c){ lc_ascii_texts[c][0]=(char)c; lc_ascii_texts[c][1]=0; return lc_ascii_texts[c]; }
650/* `item i of text`: the i-th character (not byte), as its own Text. */
651static char *lc_text_item(const char *s, int64_t i){
652 size_t chars=0; for(const unsigned char *p=(const unsigned char*)s; *p; p++) if((*p&0xC0)!=0x80) chars++;
653 size_t k=logos_idx0(i, chars), at=0;
654 const unsigned char *p=(const unsigned char*)s;
655 while(at<k){ p+=lc_utf8_len(*p); at++; }
656 if(*p<0x80) return lc_ascii_text(*p);
657 size_t n=lc_utf8_len(*p); char *c=(char*)lc_gc_alloc_atomic(n+1); memcpy(c,p,n); c[n]=0; return c;
658}
659__attribute__((noreturn)) static void lc_utf8_fail(int error_len, size_t at){
660 switch(error_len){
661 case 1: logos_fatalf(@UTF8_1@, (int64_t)at);
662 case 2: logos_fatalf(@UTF8_2@, (int64_t)at);
663 case 3: logos_fatalf(@UTF8_3@, (int64_t)at);
664 default: logos_fatalf(@UTF8_INCOMPLETE@, (int64_t)at);
665 }
666}
667/* `text_from_bytes(bytes)`: each Int's low byte, validated as UTF-8 exactly as Rust's `str::from_utf8` validates
668 it, so bytes that are not UTF-8 fail at the same offset with the same sentence. */
669static char *lc_text_from_bytes(Seq_i64 *b){
670 size_t n=b->len; unsigned char *v=(unsigned char*)lc_gc_alloc_atomic(n+1);
671 for(size_t k=0;k<n;k++) v[k]=(unsigned char)(b->data[k]&0xff);
672 v[n]=0;
673 size_t i=0;
674 while(i<n){
675 size_t at=i; unsigned char c=v[i];
676 if(c<0x80){ i++; continue; }
677 if(c>=0xC2 && c<=0xDF){
678 if(++i>=n) lc_utf8_fail(0,at);
679 if((v[i]&0xC0)!=0x80) lc_utf8_fail(1,at);
680 } else if(c>=0xE0 && c<=0xEF){
681 if(++i>=n) lc_utf8_fail(0,at);
682 unsigned char d=v[i];
683 int ok=(c==0xE0)?(d>=0xA0&&d<=0xBF):(c==0xED)?(d>=0x80&&d<=0x9F):(d>=0x80&&d<=0xBF);
684 if(!ok) lc_utf8_fail(1,at);
685 if(++i>=n) lc_utf8_fail(0,at);
686 if((v[i]&0xC0)!=0x80) lc_utf8_fail(2,at);
687 } else if(c>=0xF0 && c<=0xF4){
688 if(++i>=n) lc_utf8_fail(0,at);
689 unsigned char d=v[i];
690 int ok=(c==0xF0)?(d>=0x90&&d<=0xBF):(c==0xF4)?(d>=0x80&&d<=0x8F):(d>=0x80&&d<=0xBF);
691 if(!ok) lc_utf8_fail(1,at);
692 if(++i>=n) lc_utf8_fail(0,at);
693 if((v[i]&0xC0)!=0x80) lc_utf8_fail(2,at);
694 if(++i>=n) lc_utf8_fail(0,at);
695 if((v[i]&0xC0)!=0x80) lc_utf8_fail(3,at);
696 } else lc_utf8_fail(1,at);
697 i++;
698 }
699 return (char*)v;
700}
701/* An exact integer rides its register as a tagged word: even, the value shifted left once (any integer within 63
702 bits); odd, a boxed `LInt*` with its low bit set. Each operation stays on the word while its result fits one, and
703 goes through the `lint_*` tower when it does not. */
704#define LC_XLIM ((int64_t)1 << 62)
705static inline LInt lc_xget(int64_t w){ return (w & 1) ? *(LInt*)(intptr_t)(w ^ 1) : lint_small(w >> 1); }
706static inline int64_t lc_xput(LInt v){
707 if(!v.big && v.small >= -LC_XLIM && v.small < LC_XLIM) return (int64_t)((uint64_t)v.small << 1);
708 LInt *b=(LInt*)lc_gc_alloc(sizeof(LInt)); *b=v; return (int64_t)(intptr_t)b | 1;
709}
710static inline int64_t lc_xof(int64_t v){ return (v >= -LC_XLIM && v < LC_XLIM) ? (int64_t)((uint64_t)v << 1) : lc_xput(lint_small(v)); }
711/* The exact integer as a machine one, where only a machine integer can go. */
712__attribute__((unused)) static inline int64_t lc_xnarrow(int64_t w){ return (w & 1) ? lint_expect_i64(*(LInt*)(intptr_t)(w ^ 1)) : (w >> 1); }
713/* The low 64 bits of an exact integer, reduced modulo 2^64 whatever its width and sign: what a word built from it holds. */
714__attribute__((unused)) static inline uint64_t lc_xword_bits(int64_t w){ return (w & 1) ? lint_word_bits(*(LInt*)(intptr_t)(w ^ 1)) : (uint64_t)(w >> 1); }
715/* The exact integer as an index, refused past a machine word with the VM's sentence for that index (`why`). */
716__attribute__((unused)) static inline int64_t lc_xindex(int64_t w, const char *why){ return (w & 1) ? lint_index_i64(*(LInt*)(intptr_t)(w ^ 1), why) : (w >> 1); }
717/* Whether the exact integer is within i64 — what the VM holds as an `Int` — and its value when it is. */
718__attribute__((unused)) static int lc_xi64(int64_t w, int64_t *v){
719 if(!(w & 1)){ *v=w >> 1; return 1; }
720 LInt x=*(LInt*)(intptr_t)(w ^ 1);
721 if(!x.big){ *v=x.small; return 1; }
722 LBig lo=lbig_from_i64(INT64_MIN), hi=lbig_from_i64(INT64_MAX);
723 int in=lbig_cmp(x.big, &lo) >= 0 && lbig_cmp(x.big, &hi) <= 0; lbig_free(&lo); lbig_free(&hi);
724 if(in) *v=lint_expect_i64(x);
725 return in;
726}
727__attribute__((unused)) static inline int64_t lc_xadd(int64_t a, int64_t b){ int64_t s; if(!((a|b)&1) && !__builtin_add_overflow(a,b,&s)) return s; return lc_xput(lint_add(lc_xget(a), lc_xget(b))); }
728__attribute__((unused)) static inline int64_t lc_xsub(int64_t a, int64_t b){ int64_t s; if(!((a|b)&1) && !__builtin_sub_overflow(a,b,&s)) return s; return lc_xput(lint_sub(lc_xget(a), lc_xget(b))); }
729__attribute__((unused)) static inline int64_t lc_xmul(int64_t a, int64_t b){ int64_t s; if(!((a|b)&1) && !__builtin_mul_overflow(a>>1,b,&s)) return s; return lc_xput(lint_mul(lc_xget(a), lc_xget(b))); }
730__attribute__((unused)) static inline int lc_xcmp(int64_t a, int64_t b){ if(!((a|b)&1)) return (a>b)-(a<b); return lint_cmp(lc_xget(a), lc_xget(b)); }
731__attribute__((unused)) static int64_t lc_xdiv(int64_t a, int64_t b){
732 if(b==0) logos_fatalf(@DIVISION_BY_ZERO@);
733 if(!((a|b)&1)) return lc_xof((a>>1)/(b>>1));
734 return lc_xput(lint_div(lc_xget(a), lc_xget(b)));
735}
736__attribute__((unused)) static int64_t lc_xmod(int64_t a, int64_t b){
737 if(b==0) logos_fatalf(@MODULO_BY_ZERO@);
738 if(!((a|b)&1)) return (int64_t)((uint64_t)((a>>1)%(b>>1)) << 1);
739 return lc_xput(lint_mod(lc_xget(a), lc_xget(b)));
740}
741__attribute__((unused)) static int64_t lc_xfloordiv(int64_t a, int64_t b){
742 if(b==0) logos_fatalf(@FLOOR_DIVISION_BY_ZERO@);
743 if(!((a|b)&1)){ int64_t x=a>>1, y=b>>1, q=x/y, m=x%y; if(m!=0 && ((m<0)!=(y<0))) q--; return lc_xof(q); }
744 LInt x=lc_xget(a), y=lc_xget(b), q=lint_div(x, y), m=lint_mod(x, y);
745 int ms=lint_cmp(m, lint_small(0)), ys=lint_cmp(y, lint_small(0));
746 if(ms!=0 && ((ms<0)!=(ys<0))){
747 LInt previous=q;
748 q=lint_sub(q, lint_small(1));
749 if(previous.big){ lbig_free(previous.big); lc_gc_free(previous.big); }
750 }
751 if(m.big){ lbig_free(m.big); lc_gc_free(m.big); }
752 return lc_xput(q);
753}
754/* `b ** e` on machine words: 1 when the power outgrows the word, else 0 with the power in `*out`. An exponent the VM
755 refuses fails the run in its words. */
756__attribute__((unused)) static inline int lc_ipow_ovf(int64_t b, int64_t e, int64_t *out){
757 if(e<0) logos_fatalf(@NEGATIVE_EXPONENT@);
758 if(e>(int64_t)UINT32_MAX) logos_fatalf(@EXPONENT_TOO_LARGE@);
759 int64_t p=1; uint64_t k=(uint64_t)e;
760 while(k){
761 if((k&1) && __builtin_mul_overflow(p, b, &p)) return 1;
762 k>>=1;
763 if(k && __builtin_mul_overflow(b, b, &b)) return 1;
764 }
765 *out=p; return 0;
766}
767/* `a ** e`: a machine base stays on the word while the power fits one. */
768__attribute__((unused)) static int64_t lc_xpow(int64_t a, int64_t e){
769 if(a & 1){
770 if(e<0 || e>(int64_t)UINT32_MAX) logos_fatalf(@BIG_EXPONENT@);
771 } else {
772 if(e<0) logos_fatalf(@NEGATIVE_EXPONENT@);
773 if(e>(int64_t)UINT32_MAX) logos_fatalf(@EXPONENT_TOO_LARGE@);
774 int64_t p=1, b=a>>1; uint64_t k=(uint64_t)e; int fits=1;
775 while(k){
776 if((k&1) && __builtin_mul_overflow(p, b, &p)){ fits=0; break; }
777 k>>=1;
778 if(k && __builtin_mul_overflow(b, b, &b)){ fits=0; break; }
779 }
780 if(fits) return lc_xof(p);
781 }
782 LBig b=lint_as_big(lc_xget(a)), p=lbig_pow(&b, (uint64_t)e); lbig_free(&b); return lc_xput(lint_norm(p));
783}
784/* The bitwise operations over exact integers: the machine's on two words, the runtime's `lint_*` past them. */
785__attribute__((unused)) static int64_t lc_xbitand(int64_t a, int64_t b){ if(!((a|b)&1)) return (a>>1) & (b>>1); return lint_bitand(lc_xget(a), lc_xget(b)); }
786__attribute__((unused)) static int64_t lc_xbitor(int64_t a, int64_t b){ if(!((a|b)&1)) return (a>>1) | (b>>1); return lint_bitor(lc_xget(a), lc_xget(b)); }
787__attribute__((unused)) static int64_t lc_xbitxor(int64_t a, int64_t b){ if(!((a|b)&1)) return (a>>1) ^ (b>>1); return lint_bitxor(lc_xget(a), lc_xget(b)); }
788__attribute__((unused)) static int64_t lc_xshl(int64_t a, int64_t b){ if(!((a|b)&1)) return (int64_t)((uint64_t)(a>>1) << ((uint32_t)(b>>1) & 63u)); return lint_shl(lc_xget(a), lc_xget(b)); }
789__attribute__((unused)) static int64_t lc_xshr(int64_t a, int64_t b){ if(!((a|b)&1)) return (a>>1) >> ((uint32_t)(b>>1) & 63u); return lint_shr(lc_xget(a), lc_xget(b)); }
790/* `parseInt(text)`: the text trimmed as Rust trims it, read as Rust reads an i64 — an optional sign, then only
791 digits, within range — or the VM's failure naming the text as given. */
792__attribute__((unused)) static int64_t lc_parse_int(const char *s){
793 char *t=logos_trim(s); const char *p=t; int neg=0;
794 if(*p=='+'||*p=='-'){ neg=(*p=='-'); p++; }
795 if(!*p) logos_fatalf(@NOT_AN_INT@, s);
796 uint64_t v=0, limit=neg?(uint64_t)INT64_MAX+1u:(uint64_t)INT64_MAX;
797 for(; *p; p++){
798 if(*p<'0'||*p>'9') logos_fatalf(@NOT_AN_INT@, s);
799 uint64_t d=(uint64_t)(*p-'0');
800 if(v>(limit-d)/10) logos_fatalf(@NOT_AN_INT@, s);
801 v=v*10+d;
802 }
803 return neg?(int64_t)(0-v):(int64_t)v;
804}
805/* A text a register grows by appending: the characters are preceded by this head, which keeps what a plain C
806 string would have to rescan for — its byte length, its capacity, its character count, whether it is ASCII.
807 Only a register whose owned flag is set holds one (it built it, and nothing else holds it), so only such a
808 register ever reads the head or grows the text in place. */
809typedef struct { size_t len, cap, chars; int ascii; } lc_text_head;
810static inline lc_text_head *lc_head(int64_t text){ return ((lc_text_head*)(intptr_t)text) - 1; }
811/* `text + tail`, appended in place when the register owns its text (`owned`), otherwise into a fresh growable
812 text the register will own — amortized linear, however long the text grows. */
813static int64_t lc_text_append(int64_t text, int owned, const char *tail){
814 size_t tl=strlen(tail), tc=0; int ta=1;
815 for(const unsigned char *p=(const unsigned char*)tail; *p; p++){ if((*p&0xC0)!=0x80) tc++; if(*p>=0x80) ta=0; }
816 lc_text_head *h;
817 if(owned){ h=lc_head(text); }
818 else {
819 const char *s=(const char*)(intptr_t)text; size_t sl=strlen(s), sc=0; int sa=1;
820 for(const unsigned char *p=(const unsigned char*)s; *p; p++){ if((*p&0xC0)!=0x80) sc++; if(*p>=0x80) sa=0; }
821 size_t cap=(sl+tl)*2+16;
822 h=(lc_text_head*)lc_gc_alloc_atomic(sizeof(lc_text_head)+cap+1);
823 h->len=sl; h->cap=cap; h->chars=sc; h->ascii=sa;
824 memcpy((char*)(h+1), s, sl+1);
825 }
826 if(h->len+tl>h->cap){ size_t cap=(h->len+tl)*2; h=(lc_text_head*)lc_gc_realloc_atomic(h, sizeof(lc_text_head)+cap+1); h->cap=cap; }
827 memcpy((char*)(h+1)+h->len, tail, tl+1);
828 h->len+=tl; h->chars+=tc; h->ascii=h->ascii&&ta;
829 return (int64_t)(intptr_t)(char*)(h+1);
830}
831/* Append an Int without allocating through logos_dyn_sprintf. The unsigned
832 subtraction preserves INT64_MIN's magnitude without signed overflow. */
833static int64_t lc_text_append_i64(int64_t text, int owned, int64_t value){
834 char digits[21]; size_t at=sizeof digits;
835 uint64_t magnitude=value<0 ? (uint64_t)0-(uint64_t)value : (uint64_t)value;
836 do {
837 digits[--at]=(char)('0'+magnitude%10);
838 magnitude/=10;
839 } while(magnitude);
840 if(value<0) digits[--at]='-';
841 lc_text_head *h;
842 if(owned) h=lc_head(text);
843 else {
844 const char *s=(const char*)(intptr_t)text; size_t sl=strlen(s), sc=0; int sa=1;
845 for(const unsigned char *p=(const unsigned char*)s; *p; p++){
846 if((*p&0xC0)!=0x80) sc++;
847 if(*p>=0x80) sa=0;
848 }
849 size_t cap=(sl+sizeof digits-at)*2+16;
850 h=(lc_text_head*)lc_gc_alloc_atomic(sizeof(lc_text_head)+cap+1);
851 h->len=sl; h->cap=cap; h->chars=sc; h->ascii=sa;
852 memcpy((char*)(h+1), s, sl+1);
853 }
854 size_t len=sizeof digits-at;
855 if(h->len+len>h->cap){
856 size_t cap=(h->len+len)*2;
857 h=(lc_text_head*)lc_gc_realloc_atomic(h, sizeof(lc_text_head)+cap+1);
858 h->cap=cap;
859 }
860 memcpy((char*)(h+1)+h->len, digits+at, len);
861 h->len+=len; h->chars+=len; ((char*)(h+1))[h->len]=0;
862 return (int64_t)(intptr_t)(char*)(h+1);
863}
864/* `length of text` — from the head when the register owns its growing text. */
865static inline int64_t lc_text_len(int64_t text, int owned){ return owned ? (int64_t)lc_head(text)->len : (int64_t)strlen((const char*)(intptr_t)text); }
866/* `item i of text` — in constant time when the register owns an ASCII growing text. */
867static inline char *lc_text_item_of(int64_t text, int owned, int64_t i){
868 if(owned){ lc_text_head *h=lc_head(text); if(h->ascii && i>=1 && (uint64_t)i<=(uint64_t)h->len) return lc_ascii_text((unsigned char)((const char*)(intptr_t)text)[i-1]); }
869 return lc_text_item((const char*)(intptr_t)text, i);
870}
871/* `chr(code)`: the character with that Unicode scalar value, or the VM's failure for a code that names none
872 (a surrogate, or past U+10FFFF — judged on the low 32 bits, as `char::from_u32(code as u32)` judges it). */
873__attribute__((unused)) static char *lc_chr(int64_t code){
874 uint32_t c=(uint32_t)code;
875 if(c>0x10FFFF || (c>=0xD800 && c<=0xDFFF)) logos_fatalf(@NOT_A_CHARACTER@, code);
876 return logos_chr(code);
877}
878/* `tryReadBytes(path)`: the file's bytes from the real file system, `Ok` (tag `ok`) with a Seq of Int, or
879 `Err` (tag `err`) with the facade's sentence for why it could not be read. */
880__attribute__((unused)) static int64_t lc_try_read_bytes(const char *path, int64_t ok, int64_t err){
881 int64_t *res=(int64_t*)lc_gc_calloc(2, sizeof(int64_t));
882 FILE *f=fopen(path, \"rb\");
883 Seq_i64 *bytes=(Seq_i64*)lc_gc_alloc(sizeof(Seq_i64)); *bytes=seq_i64_new();
884 int fail=0;
885 if(!f) fail=errno;
886 else {
887 unsigned char buf[4096]; size_t n;
888 while((n=fread(buf, 1, sizeof buf, f))>0) for(size_t i=0;i<n;i++) seq_i64_push(bytes, (int64_t)buf[i]);
889 if(ferror(f)) fail=errno;
890 fclose(f);
891 }
892 if(fail){ res[0]=err; res[1]=(int64_t)(intptr_t)logos_dyn_sprintf(@CANNOT_READ_FILE@, path, strerror(fail), fail); }
893 else { res[0]=ok; res[1]=(int64_t)(intptr_t)bytes; }
894 return (int64_t)(intptr_t)res;
895}
896/* The text of the environment variable `name`, `\"\"` when it is unset: with whether it is set, the two
897 halves the VM expands `get(name)` into. */
898__attribute__((unused)) static char *lc_env_value(const char *name){
899 const char *v=getenv(name);
900 return lc_gc_strdup(v ? v : \"\");
901}
902/* The program's argument vector, as `main` received it. */
903static int lc_argc; static char **lc_argv;
904/* A growing string, for showing a value made of other values. */
905typedef struct { char *s; size_t len, cap; } lc_sb;
906__attribute__((unused)) static void lc_sb_add(lc_sb *b, const char *t){
907 size_t n=strlen(t);
908 if(b->len+n+1>b->cap){ size_t c=b->cap?b->cap*2:64; while(c<b->len+n+1) c*=2; b->s=(char*)lc_gc_realloc_atomic(b->s,c); b->cap=c; }
909 memcpy(b->s+b->len,t,n+1); b->len+=n;
910}
911";
912
913struct IterFrame {
916 seq_kind: Kind,
919 text_bytes: bool,
921 id: usize,
923}
924
925pub fn compile_program_to_c(prog: &CompiledProgram) -> Result<String, String> {
928 compile_program_to_c_with_policy(prog, None)
929}
930
931pub fn compile_program_to_c_with_policy<'a>(
936 prog: &'a CompiledProgram,
937 policy_ctx: Option<(&'a Interner, &'a PolicyRegistry)>,
938) -> Result<String, String> {
939 compile_program_to_c_with_offloads(prog, policy_ctx, &[])
940}
941
942#[derive(Debug, Clone, PartialEq, Eq)]
945pub struct Offload {
946 pub func: usize,
947 pub region: u32,
948 pub params: Vec<SlotKind>,
949 pub ret: SlotKind,
950}
951
952#[derive(Debug, Clone, PartialEq, Eq)]
955pub struct OffloadedC {
956 pub c: String,
957 pub kept: Vec<(usize, String)>,
958}
959
960pub fn compile_program_to_c_with_offloads<'a>(
963 prog: &'a CompiledProgram,
964 policy_ctx: Option<(&'a Interner, &'a PolicyRegistry)>,
965 offloads: &[Offload],
966) -> Result<String, String> {
967 compile_program_to_c_offloaded(prog, policy_ctx, offloads).map(|o| o.c)
968}
969
970pub fn compile_program_to_c_offloaded<'a>(
976 prog: &'a CompiledProgram,
977 policy_ctx: Option<(&'a Interner, &'a PolicyRegistry)>,
978 offloads: &[Offload],
979) -> Result<OffloadedC, String> {
980 let closure_bodies: std::collections::BTreeSet<usize> = prog
982 .code
983 .iter()
984 .filter_map(|op| match op {
985 Op::MakeClosure { func, .. } => Some(*func as usize),
986 _ => None,
987 })
988 .collect();
989 let mut em = Emitter {
990 prog,
991 fn_ret: Vec::new(),
992 global_kinds: Vec::new(),
993 out: String::new(),
994 iter_stack: Vec::new(),
995 tmp: 0,
996 tuple_types: std::cell::RefCell::new(Vec::new()),
997 map_types: std::cell::RefCell::new(Vec::new()),
998 opt_types: std::cell::RefCell::new(Vec::new()),
999 inductive_types: std::cell::RefCell::new(Vec::new()),
1000 seq_elems: std::cell::RefCell::new(Vec::new()),
1001 param_kinds: Vec::new(),
1002 current_fn: None,
1003 net_message: std::cell::Cell::new(Kind::Unknown),
1004 net_stream: std::cell::Cell::new(Kind::Unknown),
1005 ctor_tags: ctor_tags(prog),
1006 exact_int: std::cell::Cell::new(false),
1007 closure_bodies,
1008 counter_fields: std::collections::HashSet::new(),
1009 synth_structs: std::cell::RefCell::new(Vec::new()),
1010 policy_ctx,
1011 owned: std::collections::BTreeSet::new(),
1012 handle_kinds: std::collections::BTreeMap::new(),
1013 region_span: (0, 0),
1014 region_targets: std::collections::HashSet::new(),
1015 live_out: Vec::new(),
1016 exact_defs: std::collections::HashSet::new(),
1017 speculative_defs: std::collections::HashSet::new(),
1018 exact_outs: std::collections::HashSet::new(),
1019 entry_speculative: std::collections::HashMap::new(),
1020 on_words: std::cell::Cell::new(false),
1021 promoted: std::cell::RefCell::new([
1022 std::collections::HashSet::new(),
1023 std::collections::HashSet::new(),
1024 ]),
1025 text_byte_calls: std::collections::HashSet::new(),
1026 text_byte_iters: std::collections::HashSet::new(),
1027 label: 'L',
1028 deopt: std::collections::BTreeSet::new(),
1029 arrivals: std::collections::BTreeSet::new(),
1030 word_twins: std::collections::HashSet::new(),
1031 word_entry: false,
1032 twin_arrival: false,
1033 held_body: String::new(),
1034 held_owns: false,
1035 held_entries: Vec::new(),
1036 held_start: None,
1037 offloads: offloads.to_vec(),
1038 kept: Vec::new(),
1039 tower: false,
1040 tower_from: std::cell::RefCell::new([
1041 std::collections::HashMap::new(),
1042 std::collections::HashMap::new(),
1043 ]),
1044 tower_entries: std::cell::RefCell::new(std::collections::HashMap::new()),
1045 number_from: std::cell::RefCell::new([
1046 std::collections::HashMap::new(),
1047 std::collections::HashMap::new(),
1048 ]),
1049 number_entries: std::cell::RefCell::new(std::collections::HashMap::new()),
1050 };
1051 let fn_ret: Vec<Kind> = prog
1052 .functions
1053 .iter()
1054 .map(|f| em.boundary_kind(&f.return_type))
1055 .collect();
1056 em.fn_ret = fn_ret;
1057 em.compile()?;
1058 Ok(OffloadedC {
1059 c: em.out,
1060 kept: em.kept,
1061 })
1062}
1063
1064struct Emitter<'a> {
1065 prog: &'a CompiledProgram,
1066 fn_ret: Vec<Kind>,
1067 global_kinds: Vec<Kind>,
1070 out: String,
1071 iter_stack: Vec<IterFrame>,
1073 tmp: usize,
1075 tuple_types: std::cell::RefCell<Vec<Vec<Kind>>>,
1079 map_types: std::cell::RefCell<Vec<(Kind, Kind)>>,
1083 opt_types: std::cell::RefCell<Vec<Kind>>,
1086 inductive_types: std::cell::RefCell<Vec<(String, Vec<(String, Option<Vec<Kind>>)>)>>,
1090 seq_elems: std::cell::RefCell<Vec<Kind>>,
1092 param_kinds: Vec<Vec<Kind>>,
1094 current_fn: Option<usize>,
1096 net_message: std::cell::Cell<Kind>,
1099 net_stream: std::cell::Cell<Kind>,
1100 ctor_tags: std::collections::HashMap<String, Vec<String>>,
1104 exact_int: std::cell::Cell<bool>,
1111 closure_bodies: std::collections::BTreeSet<usize>,
1115 counter_fields: std::collections::HashSet<(String, usize)>,
1121 synth_structs: std::cell::RefCell<Vec<(String, Vec<(String, Kind)>)>>,
1128 policy_ctx: Option<(&'a Interner, &'a PolicyRegistry)>,
1133 owned: std::collections::BTreeSet<u16>,
1139 handle_kinds: std::collections::BTreeMap<u16, Kind>,
1142 region_span: (usize, usize),
1145 region_targets: std::collections::HashSet<usize>,
1146 text_byte_calls: std::collections::HashSet<usize>,
1150 text_byte_iters: std::collections::HashSet<usize>,
1151 live_out: Vec<std::collections::BTreeSet<u16>>,
1154 exact_defs: std::collections::HashSet<usize>,
1157 speculative_defs: std::collections::HashSet<usize>,
1158 exact_outs: std::collections::HashSet<(usize, u16)>,
1161 entry_speculative: std::collections::HashMap<usize, std::collections::BTreeSet<u16>>,
1163 on_words: std::cell::Cell<bool>,
1165 promoted: std::cell::RefCell<[std::collections::HashSet<usize>; 2]>,
1168 label: char,
1170 deopt: std::collections::BTreeSet<usize>,
1173 arrivals: std::collections::BTreeSet<usize>,
1174 word_twins: std::collections::HashSet<usize>,
1178 word_entry: bool,
1180 twin_arrival: bool,
1182 held_body: String,
1186 held_owns: bool,
1187 held_entries: Vec<(usize, Vec<usize>, usize, Vec<u16>)>,
1188 held_start: Option<(usize, Vec<u16>)>,
1191 offloads: Vec<Offload>,
1193 kept: Vec<(usize, String)>,
1194 tower: bool,
1197 tower_from: std::cell::RefCell<[std::collections::HashMap<usize, Kind>; 2]>,
1200 tower_entries: std::cell::RefCell<std::collections::HashMap<usize, Vec<(u16, Kind)>>>,
1203 number_from: std::cell::RefCell<[std::collections::HashMap<usize, (Kind, Kind)>; 2]>,
1206 number_entries: std::cell::RefCell<
1209 std::collections::HashMap<usize, (Vec<(u16, Kind, Kind)>, Vec<crate::vm::variables::Site>)>,
1210 >,
1211}
1212
1213impl<'a> Emitter<'a> {
1214 fn computes_exactly(&self, fi: usize) -> bool {
1218 if self.closure_bodies.contains(&fi) {
1219 return true;
1220 }
1221 let f = &self.prog.functions[fi];
1222 if f.return_type.is_some() {
1223 return false;
1224 }
1225 let end = self
1226 .prog
1227 .functions
1228 .iter()
1229 .map(|g| g.entry_pc)
1230 .filter(|&e| e > f.entry_pc)
1231 .min()
1232 .unwrap_or(self.prog.code.len());
1233 self.prog.code[f.entry_pc..end]
1234 .iter()
1235 .any(|op| matches!(op, Op::Return { .. }))
1236 }
1237
1238 fn takes_words(&self, fi: usize) -> bool {
1245 let exact_param = self.param_kinds[fi].contains(&Kind::BigInt);
1246 let tower = self.param_kinds[fi].contains(&Kind::Tower) || self.fn_ret[fi] == Kind::Tower;
1247 (exact_param || self.fn_ret[fi] == Kind::BigInt)
1248 && !tower
1249 && !self.computes_exactly(fi)
1250 && self.inout_regs(fi).is_empty()
1251 && !self.offloads.iter().any(|o| o.func == fi)
1252 }
1253
1254 fn ret(&self, value: &str, escaped: bool) -> String {
1257 if self.word_entry {
1258 format!("return (lc_wret){{ {value}, {} }};", u8::from(escaped))
1259 } else {
1260 format!("return {value};")
1261 }
1262 }
1263
1264 fn intern_tuple(&self, ks: Vec<Kind>) -> u16 {
1267 let mut t = self.tuple_types.borrow_mut();
1268 if let Some(i) = t.iter().position(|e| e == &ks) {
1269 return i as u16;
1270 }
1271 t.push(ks);
1272 (t.len() - 1) as u16
1273 }
1274
1275 fn tuple_positions(&self, idx: u16) -> Vec<Kind> {
1277 self.tuple_types
1278 .borrow()
1279 .get(idx as usize)
1280 .cloned()
1281 .unwrap_or_default()
1282 }
1283
1284 fn intern_opt(&self, inner: Kind) -> u16 {
1286 let mut t = self.opt_types.borrow_mut();
1287 if let Some(i) = t.iter().position(|e| e == &inner) {
1288 return i as u16;
1289 }
1290 t.push(inner);
1291 (t.len() - 1) as u16
1292 }
1293
1294 fn opt_inner(&self, idx: u16) -> Kind {
1296 self.opt_types
1297 .borrow()
1298 .get(idx as usize)
1299 .copied()
1300 .unwrap_or(Kind::Unknown)
1301 }
1302
1303 fn elem_kind(&self, k: Kind) -> Kind {
1305 match k {
1306 Kind::SeqOf(ei) => self
1307 .seq_elems
1308 .borrow()
1309 .get(ei as usize)
1310 .copied()
1311 .unwrap_or(Kind::Unknown),
1312 Kind::SeqUnknown | Kind::SetUnknown => Kind::Never,
1314 Kind::Tower => Kind::Tower,
1316 other => other.elem(),
1317 }
1318 }
1319
1320 fn seq_kind_for(&self, elem: Kind) -> Kind {
1323 match elem {
1324 Kind::Int => Kind::SeqInt,
1325 Kind::Float => Kind::SeqFloat,
1326 Kind::Text => Kind::SeqText,
1327 Kind::Bool => Kind::SeqBool,
1328 Kind::Unknown => Kind::SeqUnknown,
1329 other => {
1330 let mut t = self.seq_elems.borrow_mut();
1331 let i = match t.iter().position(|e| *e == other) {
1332 Some(i) => i,
1333 None => {
1334 t.push(other);
1335 t.len() - 1
1336 }
1337 };
1338 Kind::SeqOf(i as u16)
1339 }
1340 }
1341 }
1342
1343 fn ctor_tag(&self, ty: &str, ctor: &str) -> Option<usize> {
1346 self.ctor_tags.get(ty)?.iter().position(|c| c == ctor)
1347 }
1348
1349 fn inductive_kind(&self, ty: &str, known: &[(&str, Vec<Kind>)]) -> Kind {
1352 let ctors: Vec<(String, Option<Vec<Kind>>)> = self
1353 .ctor_tags
1354 .get(ty)
1355 .map(|cs| cs.as_slice())
1356 .unwrap_or(&[])
1357 .iter()
1358 .map(|c| {
1359 (
1360 c.clone(),
1361 known.iter().find(|(k, _)| k == c).map(|(_, ks)| ks.clone()),
1362 )
1363 })
1364 .collect();
1365 self.intern_inductive(ty.to_string(), ctors)
1366 }
1367
1368 fn intern_inductive(&self, ty: String, ctors: Vec<(String, Option<Vec<Kind>>)>) -> Kind {
1369 let mut t = self.inductive_types.borrow_mut();
1370 let entry = (ty, ctors);
1371 let idx = match t.iter().position(|e| *e == entry) {
1372 Some(i) => i,
1373 None => {
1374 t.push(entry);
1375 t.len() - 1
1376 }
1377 };
1378 Kind::Inductive(idx as u16)
1379 }
1380
1381 fn inductive(&self, idx: u16) -> (String, Vec<(String, Option<Vec<Kind>>)>) {
1384 self.inductive_types
1385 .borrow()
1386 .get(idx as usize)
1387 .cloned()
1388 .unwrap_or_default()
1389 }
1390
1391 fn inductive_payload(&self, idx: u16, ctor: &str, index: usize) -> Kind {
1393 let (_, ctors) = self.inductive(idx);
1394 ctors
1395 .iter()
1396 .find(|(c, _)| c == ctor)
1397 .and_then(|(_, ks)| ks.as_ref()?.get(index).copied())
1398 .unwrap_or(Kind::Unknown)
1399 }
1400
1401 fn unify_inductive(&self, a: u16, b: u16) -> Option<Kind> {
1405 let (ta, ca) = self.inductive(a);
1406 let (tb, cb) = self.inductive(b);
1407 if ta != tb || ca.len() != cb.len() {
1408 return None;
1409 }
1410 let mut ctors = Vec::with_capacity(ca.len());
1411 for ((name, ka), (_, kb)) in ca.iter().zip(cb.iter()) {
1412 let merged = match (ka, kb) {
1413 (None, None) => None,
1414 (Some(k), None) | (None, Some(k)) => Some(k.clone()),
1415 (Some(ka), Some(kb)) => {
1416 let n = ka.len().max(kb.len());
1417 let mut ks = Vec::with_capacity(n);
1418 for i in 0..n {
1419 let (x, y) = (
1420 ka.get(i).copied().unwrap_or(Kind::Unknown),
1421 kb.get(i).copied().unwrap_or(Kind::Unknown),
1422 );
1423 ks.push(match (x, y) {
1424 (Kind::Unknown, y) => y,
1425 (x, Kind::Unknown) => x,
1426 (x, y) if x == y => x,
1427 _ => return None,
1428 });
1429 }
1430 Some(ks)
1431 }
1432 };
1433 ctors.push((name.clone(), merged));
1434 }
1435 Some(self.intern_inductive(ta, ctors))
1436 }
1437
1438 fn intern_map(&self, key: Kind, val: Kind) -> u16 {
1440 let mut t = self.map_types.borrow_mut();
1441 if let Some(i) = t.iter().position(|e| *e == (key, val)) {
1442 return i as u16;
1443 }
1444 t.push((key, val));
1445 (t.len() - 1) as u16
1446 }
1447 fn map_pair(&self, idx: u16) -> (Kind, Kind) {
1449 self.map_types
1450 .borrow()
1451 .get(idx as usize)
1452 .copied()
1453 .unwrap_or((Kind::Int, Kind::Int))
1454 }
1455 fn map_of(&self, key: Kind, val: Kind) -> Kind {
1459 let key = if key == Kind::BigInt { Kind::Int } else { key };
1463 let val = if val == Kind::Int { Kind::BigInt } else { val };
1464 if key == Kind::Unknown || val == Kind::Unknown {
1465 return Kind::MapUnknown;
1466 }
1467 if map_key_storage(key).is_none() {
1468 return Kind::Unknown;
1469 }
1470 Kind::Map(self.intern_map(key, val))
1471 }
1472 fn resolve_map_kv(&self, k: Kind) -> Option<(Kind, Kind)> {
1474 match k {
1475 Kind::Map(idx) => Some(self.map_pair(idx)),
1476 Kind::MapUnknown => Some((Kind::Int, Kind::BigInt)),
1477 _ => None,
1478 }
1479 }
1480
1481 fn const_int_at(&self, start: usize, pc: usize, reg: u16) -> Option<i64> {
1486 for p in (start..pc).rev() {
1487 if let Op::LoadConst { dst, idx } = &self.prog.code[p] {
1488 if *dst == reg {
1489 return match &self.prog.constants[*idx as usize] {
1490 Constant::Int(v) => Some(*v),
1491 _ => None,
1492 };
1493 }
1494 }
1495 }
1496 None
1497 }
1498
1499 fn const_text_at(&self, start: usize, pc: usize, reg: u16) -> Option<String> {
1502 for p in (start..pc).rev() {
1503 let mut writes = false;
1504 self.for_each_written(&self.prog.code[p], |w| writes |= w == reg);
1505 if writes {
1506 return match &self.prog.code[p] {
1507 Op::LoadConst { idx, .. } => match &self.prog.constants[*idx as usize] {
1508 Constant::Text(t) => Some(t.clone()),
1509 _ => None,
1510 },
1511 _ => None,
1512 };
1513 }
1514 }
1515 None
1516 }
1517
1518 fn field_cell_of_name(&self, k: Kind, name: &str) -> Option<usize> {
1520 match k {
1521 Kind::Struct(si) => self
1522 .prog
1523 .struct_types
1524 .get(si as usize)?
1525 .fields
1526 .iter()
1527 .position(|(n, _)| n == name),
1528 Kind::StructSynth(idx) => self
1529 .synth_structs
1530 .borrow()
1531 .get(idx as usize)?
1532 .1
1533 .iter()
1534 .position(|(n, _)| n == name),
1535 _ => None,
1536 }
1537 }
1538
1539 fn arith_result_kind(&self, lk: Kind, rk: Kind) -> Kind {
1543 let j = join(lk, rk);
1544 if self.exact_int.get() && matches!(j, Kind::Int | Kind::BigInt) {
1545 Kind::BigInt
1546 } else {
1547 j
1548 }
1549 }
1550
1551 fn emit_exact_bin(
1553 &mut self,
1554 dst: u16,
1555 lhs: u16,
1556 rhs: u16,
1557 lk: Kind,
1558 rk: Kind,
1559 func: &str,
1560 ) -> Result<(), String> {
1561 let (l, r) = (as_exact(lhs, lk)?, as_exact(rhs, rk)?);
1562 self.line(&format!("r[{dst}] = {func}({l}, {r});"));
1563 Ok(())
1564 }
1565
1566 fn emit_exact_bounded(
1569 &mut self,
1570 dst: u16,
1571 lhs: u16,
1572 rhs: u16,
1573 kinds: &[Kind],
1574 result: Kind,
1575 func: &str,
1576 ) -> Result<(), String> {
1577 let (l, r) = (
1578 as_exact(lhs, kind_at(kinds, lhs))?,
1579 as_exact(rhs, kind_at(kinds, rhs))?,
1580 );
1581 let value = format!("{func}({l}, {r})");
1582 let value = match (result, func) {
1583 (Kind::BigInt, _) => value,
1584 (_, "lc_xdiv" | "lc_xmod" | "lc_xfloordiv") => format!("lc_xnarrow({value})"),
1585 _ => value,
1586 };
1587 self.line(&format!("r[{dst}] = {value};"));
1588 Ok(())
1589 }
1590
1591 fn exact_integers(&self, operands: &[u16], kinds: &[Kind], result: Kind) -> bool {
1594 let ks: Vec<Kind> = operands.iter().map(|r| kind_at(kinds, *r)).collect();
1595 (result == Kind::BigInt || ks.contains(&Kind::BigInt))
1596 && ks
1597 .iter()
1598 .all(|k| matches!(k, Kind::Int | Kind::BigInt | Kind::Bool))
1599 }
1600
1601 fn line(&mut self, s: &str) {
1603 self.out.push_str(" ");
1604 self.out.push_str(s);
1605 self.out.push('\n');
1606 }
1607
1608 fn compile(&mut self) -> Result<(), String> {
1609 self.out.push_str(C_GC_COLLECTOR);
1610 self.out.push_str(&c_runtime());
1611 self.out.push('\n');
1612 self.tower = crate::vm::tower::reaches(self.prog);
1614 if self.tower {
1615 self.out.push_str(super::tower::RUNTIME);
1616 self.out.push('\n');
1617 }
1618 let uses_crdt = self.prog.code.iter().any(op_is_crdt);
1623 let uses_net = self.prog.code.iter().any(op_is_net);
1624 let uses_tcp = self.prog.code.iter().any(op_is_tcp);
1625 if uses_crdt {
1626 self.out.push_str(C_CRDT_RUNTIME);
1627 self.out.push('\n');
1628 }
1629 if uses_net {
1630 self.out.push_str(&c_net_runtime());
1631 self.out.push('\n');
1632 }
1633 if self.prog.code.iter().any(op_is_file) {
1635 self.out.push_str(&c_vfs_runtime());
1636 self.out.push('\n');
1637 }
1638 if uses_tcp {
1641 self.out.push_str(C_TCP_RUNTIME);
1642 self.out.push('\n');
1643 }
1644 self.out.push_str(&walker_preamble());
1645 self.out.push('\n');
1646
1647 let inline_extents: Vec<(usize, usize)> = self
1651 .prog
1652 .functions
1653 .iter()
1654 .filter_map(|f| is_inline_closure(self.prog, f.entry_pc).map(|e| (f.entry_pc, e)))
1655 .collect();
1656 let mut top_starts: Vec<usize> = vec![0];
1657 for f in &self.prog.functions {
1658 if is_inline_closure(self.prog, f.entry_pc).is_none() {
1659 top_starts.push(f.entry_pc);
1660 }
1661 }
1662 top_starts.sort_unstable();
1663 top_starts.dedup();
1664 let code_len = self.prog.code.len();
1665 let block_end = |s: usize| -> usize {
1666 top_starts
1667 .iter()
1668 .copied()
1669 .find(|&x| x > s)
1670 .unwrap_or(code_len)
1671 };
1672 let holes_in = |s: usize, e: usize| -> Vec<(usize, usize)> {
1675 inline_extents
1676 .iter()
1677 .copied()
1678 .filter(|&(hs, _)| hs > s && hs < e)
1679 .collect()
1680 };
1681 let main_end = block_end(0);
1682 if main_end == 0 {
1683 return Err("no Main region".into());
1684 }
1685 let mut regions: Vec<(Option<usize>, usize, usize, Vec<(usize, usize)>)> =
1687 vec![(None, 0, main_end, holes_in(0, main_end))];
1688 for (fi, f) in self.prog.functions.iter().enumerate() {
1689 let end =
1690 is_inline_closure(self.prog, f.entry_pc).unwrap_or_else(|| block_end(f.entry_pc));
1691 regions.push((Some(fi), f.entry_pc, end, holes_in(f.entry_pc, end)));
1692 }
1693 self.param_kinds = self.declared_param_kinds();
1697 self.derive_global_kinds(main_end);
1698 self.refine_signatures(®ions);
1699 self.derive_global_kinds(main_end);
1700 self.derive_counter_fields(main_end);
1703 self.word_twins = (0..self.prog.functions.len())
1704 .filter(|fi| self.takes_words(*fi))
1705 .collect();
1706 self.out.push_str(super::USER_CODE_SENTINEL);
1709 self.out.push('\n');
1710 if !self.prog.globals.is_empty() {
1711 writeln!(
1712 self.out,
1713 "static int64_t g[{}] = {{0}};",
1714 self.prog.globals.len()
1715 )
1716 .unwrap();
1717 writeln!(
1719 self.out,
1720 "static unsigned char gown[{}] = {{0}};",
1721 self.prog.globals.len()
1722 )
1723 .unwrap();
1724 }
1725
1726 if !self.offloads.is_empty() {
1728 writeln!(
1729 self.out,
1730 "int logos_dev_call(int region, const int64_t *args, int64_t *out);"
1731 )
1732 .unwrap();
1733 }
1734 if !self.word_twins.is_empty() {
1737 writeln!(
1738 self.out,
1739 "typedef struct {{ int64_t v; int64_t escaped; }} lc_wret;"
1740 )
1741 .unwrap();
1742 let regs = self
1745 .word_twins
1746 .iter()
1747 .map(|fi| self.prog.functions[*fi].register_count.max(1))
1748 .max()
1749 .unwrap_or(1);
1750 let loops = self
1751 .word_twins
1752 .iter()
1753 .map(|fi| {
1754 let f = &self.prog.functions[*fi];
1755 let end = is_inline_closure(self.prog, f.entry_pc)
1756 .unwrap_or_else(|| block_end(f.entry_pc));
1757 self.prog.code[f.entry_pc..end]
1758 .iter()
1759 .filter(|op| matches!(op, Op::IterPrepare { .. }))
1760 .count()
1761 })
1762 .max()
1763 .unwrap_or(0);
1764 writeln!(self.out, "static __thread int64_t lc_held_regs[{regs}];").unwrap();
1765 writeln!(
1766 self.out,
1767 "static __thread unsigned char lc_held_owns[{regs}];"
1768 )
1769 .unwrap();
1770 writeln!(
1771 self.out,
1772 "static __thread int64_t lc_held_loops[{}];",
1773 2 * loops + 2
1774 )
1775 .unwrap();
1776 }
1777 for (fi, f) in self.prog.functions.iter().enumerate() {
1779 let mut params: Vec<String> =
1780 (0..f.param_count).map(|i| self.param_decl(fi, i)).collect();
1781 if self.closure_bodies.contains(&fi) {
1782 params.push("int64_t clo".to_string());
1783 }
1784 params.push("unsigned char *__ret_own".to_string());
1787 let sig = params.join(", ");
1788 writeln!(self.out, "static int64_t fn_{fi}({sig});").unwrap();
1789 if self.word_twins.contains(&fi) {
1790 writeln!(self.out, "static lc_wret fn_{fi}_w({sig});").unwrap();
1791 writeln!(
1794 self.out,
1795 "__attribute__((cold, noinline)) static lc_wret fn_{fi}_x(int __resume, int __owned, unsigned char *__ret_own);"
1796 )
1797 .unwrap();
1798 }
1799 }
1800 self.out.push('\n');
1801 self.emit_defining_reader()?;
1802
1803 for fi in 0..self.prog.functions.len() {
1805 let f = &self.prog.functions[fi];
1806 let entry = f.entry_pc;
1807 let end = is_inline_closure(self.prog, entry).unwrap_or_else(|| block_end(entry));
1808 let (param_count, register_count) = (f.param_count, f.register_count);
1809 let captures = f.captures.clone();
1810 let holes = holes_in(entry, end);
1811 self.word_entry = self.word_twins.contains(&fi);
1812 let body = self.emit_function(
1813 fi,
1814 entry,
1815 end,
1816 param_count,
1817 register_count,
1818 &holes,
1819 &captures,
1820 );
1821 let word_entry = std::mem::take(&mut self.word_entry);
1822 body.map_err(|e| format!("fn_{fi}: {e}"))?;
1823 if word_entry {
1824 self.emit_held_function(fi, register_count);
1825 self.emit_tagged_entry(fi);
1826 }
1827 }
1828
1829 self.exact_int.set(false);
1832 writeln!(self.out, "int main(int argc, char **argv) {{").unwrap();
1833 writeln!(self.out, " lc_argc = argc; lc_argv = argv;").unwrap();
1834 writeln!(
1835 self.out,
1836 " int64_t r[{}] = {{0}};",
1837 self.prog.register_count.max(1)
1838 )
1839 .unwrap();
1840 writeln!(self.out, " (void)r;").unwrap();
1841 let main_holes = holes_in(0, main_end);
1842 self.emit_region(0, main_end, false, None, &main_holes)
1843 .map_err(|e| format!("Main: {e}"))?;
1844 writeln!(self.out, " return 0;\n}}").unwrap();
1845 Ok(())
1846 }
1847
1848 fn seed_of(&self, fi: usize) -> Vec<Kind> {
1851 let f = &self.prog.functions[fi];
1852 let mut seed = vec![Kind::Unknown; f.register_count.max(1)];
1853 for (i, k) in self.param_kinds[fi].iter().enumerate() {
1854 if i < seed.len() {
1855 seed[i] = *k;
1856 }
1857 }
1858 seed
1859 }
1860
1861 fn seed_of_region(&self, func: Option<usize>) -> Vec<Kind> {
1863 match func {
1864 Some(fi) => self.seed_of(fi),
1865 None => vec![Kind::Unknown; self.prog.register_count.max(1)],
1866 }
1867 }
1868
1869 fn words_seed_of_region(&self, func: Option<usize>) -> Vec<Kind> {
1872 self.seed_of_region(func)
1873 .into_iter()
1874 .map(|k| if k == Kind::BigInt { Kind::Int } else { k })
1875 .collect()
1876 }
1877
1878 fn arrives_exact(&self, op: &Op, kinds: &[Kind]) -> bool {
1881 match op {
1882 Op::Index { collection, .. } | Op::IndexUnchecked { collection, .. } => self
1883 .resolve_map_kv(kind_at(kinds, *collection))
1884 .is_some_and(|(_, value)| value == Kind::BigInt),
1885 Op::GetField { obj, field, .. } => self
1886 .resolve_field_any(kind_at(kinds, *obj), *field)
1887 .is_some_and(|(cell, _)| {
1888 self.field_storage_kind(kind_at(kinds, *obj), cell) == Some(Kind::BigInt)
1889 }),
1890 Op::Call { func, .. } => self.fn_ret.get(*func as usize) == Some(&Kind::BigInt),
1891 Op::CallValue { .. } => self
1892 .closure_bodies
1893 .iter()
1894 .any(|b| self.fn_ret.get(*b) == Some(&Kind::BigInt)),
1895 Op::GlobalGet { idx, .. } => {
1896 self.global_kinds.get(*idx as usize) == Some(&Kind::BigInt)
1897 }
1898 _ => false,
1899 }
1900 }
1901
1902 fn exact_seed_of_region(&self, func: Option<usize>) -> Vec<Kind> {
1905 let mut seed = self.seed_of_region(func);
1906 if let Some(regs) = func.and_then(|fi| self.entry_speculative.get(&fi)) {
1907 for reg in regs {
1908 if let Some(k @ (Kind::Int | Kind::Bool)) = seed.get_mut(*reg as usize) {
1909 *k = Kind::BigInt;
1910 }
1911 }
1912 }
1913 seed
1914 }
1915
1916 fn held_exact(&self, pc: usize) -> bool {
1919 self.exact_defs.contains(&pc)
1920 && !(self.on_words.get() && self.speculative_defs.contains(&pc))
1921 }
1922
1923 fn declared_param_kinds(&self) -> Vec<Vec<Kind>> {
1925 self.prog
1926 .functions
1927 .iter()
1928 .map(|f| {
1929 f.param_types
1930 .iter()
1931 .map(|t| self.boundary_kind(t))
1932 .collect()
1933 })
1934 .collect()
1935 }
1936
1937 fn refine_signatures(
1942 &mut self,
1943 regions: &[(Option<usize>, usize, usize, Vec<(usize, usize)>)],
1944 ) {
1945 let nf = self.prog.functions.len();
1946 self.param_kinds = self.declared_param_kinds();
1947 let declared_params = self.param_kinds.clone();
1948 for (func, start, end, holes) in regions {
1951 if let Some(fi) = func {
1952 if !(*start..*end).any(|pc| {
1953 !in_hole(pc, holes) && matches!(self.prog.code[pc], Op::Return { .. })
1954 }) {
1955 self.fn_ret[*fi] = Kind::Nothing;
1956 }
1957 }
1958 }
1959 let declared_ret: Vec<Kind> = self.fn_ret.clone();
1960 for _ in 0..16 {
1961 let before = (
1962 self.param_kinds.clone(),
1963 self.fn_ret.clone(),
1964 self.net_message.get(),
1965 self.net_stream.get(),
1966 self.exact_defs.len(),
1967 self.speculative_defs.len(),
1968 self.exact_outs.len(),
1969 self.entry_speculative.clone(),
1970 );
1971 let mut passed: Vec<Vec<Option<Kind>>> = (0..nf)
1972 .map(|fi| vec![None; self.param_kinds[fi].len()])
1973 .collect();
1974 let (mut sent, mut streamed): (Option<Kind>, Option<Kind>) = (None, None);
1975 for (func, start, end, holes) in regions {
1976 self.exact_int
1977 .set(func.is_some_and(|fi| self.computes_exactly(fi)));
1978 self.on_words.set(true);
1981 let on_words =
1982 self.infer_kinds(*start, *end, self.words_seed_of_region(*func), holes);
1983 self.on_words.set(false);
1984 let exact = self.exactness(
1985 *start,
1986 *end,
1987 holes,
1988 &on_words,
1989 &self.seed_of_region(*func),
1990 *func,
1991 );
1992 self.speculative_defs
1993 .retain(|pc| !(*start..*end).contains(pc));
1994 self.speculative_defs
1995 .extend(exact.speculative.iter().copied());
1996 self.exact_defs.extend(exact.defs);
1997 self.exact_outs.extend(exact.outs);
1998 if let Some(fi) = func {
1999 self.entry_speculative.insert(*fi, exact.entry_speculative);
2000 }
2001 let per_pc =
2003 self.infer_kinds(*start, *end, self.exact_seed_of_region(*func), holes);
2004 self.exact_int.set(false);
2005 for pc in *start..*end {
2006 if in_hole(pc, holes) {
2007 continue;
2008 }
2009 match &self.prog.code[pc] {
2010 Op::Call {
2011 func: callee,
2012 args_start,
2013 arg_count,
2014 ..
2015 } => {
2016 for i in 0..*arg_count as usize {
2017 let k = kind_at(&per_pc[pc - start], args_start + i as u16);
2018 if let Some(slot) = passed[*callee as usize].get_mut(i) {
2019 *slot = Some(match *slot {
2020 None => k,
2021 Some(prev) => self.join_passed(prev, k),
2022 });
2023 }
2024 }
2025 }
2026 Op::Return { src } => {
2027 if let Some(fi) = func {
2028 let k = kind_at(&per_pc[pc - start], *src);
2029 self.refine_return(*fi, declared_ret[*fi], k);
2030 }
2031 }
2032 _ => {}
2033 }
2034 if let (Some(fi), Op::Return { .. } | Op::ReturnNothing) =
2037 (func, &self.prog.code[pc])
2038 {
2039 for p in self.inout_regs(*fi) {
2040 let k = kind_at(&per_pc[pc - start], p);
2041 if let Some(slot) = passed[*fi].get_mut(p as usize) {
2042 *slot = Some(match *slot {
2043 None => k,
2044 Some(prev) => self.join_passed(prev, k),
2045 });
2046 }
2047 }
2048 }
2049 match &self.prog.code[pc] {
2050 Op::NetSend { msg, .. } => {
2051 let k = kind_at(&per_pc[pc - start], *msg);
2052 sent = Some(sent.map_or(k, |s| self.join_passed(s, k)));
2053 }
2054 Op::NetStream { values, .. } => {
2055 let k = kind_at(&per_pc[pc - start], *values);
2056 streamed = Some(streamed.map_or(k, |s| self.join_passed(s, k)));
2057 }
2058 _ => {}
2059 }
2060 }
2061 }
2062 for fi in 0..nf {
2063 for i in 0..passed[fi].len() {
2064 if let Some(got) = passed[fi][i] {
2065 self.param_kinds[fi][i] =
2066 self.refine_param(declared_params[fi][i], self.param_kinds[fi][i], got);
2067 }
2068 }
2069 }
2070 self.net_message.set(sent.unwrap_or(Kind::Unknown));
2071 self.net_stream.set(streamed.unwrap_or(Kind::Unknown));
2072 if (
2073 self.param_kinds.clone(),
2074 self.fn_ret.clone(),
2075 self.net_message.get(),
2076 self.net_stream.get(),
2077 self.exact_defs.len(),
2078 self.speculative_defs.len(),
2079 self.exact_outs.len(),
2080 self.entry_speculative.clone(),
2081 ) == before
2082 {
2083 break;
2084 }
2085 }
2086 }
2087
2088 fn join_passed(&self, a: Kind, b: Kind) -> Kind {
2091 match (a, b) {
2092 (a, b) if a == b => a,
2093 (Kind::Tower, _) | (_, Kind::Tower) => Kind::Tower,
2094 (Kind::Int, Kind::BigInt) | (Kind::BigInt, Kind::Int) => Kind::BigInt,
2095 (Kind::Inductive(x), Kind::Inductive(y)) => {
2096 self.unify_inductive(x, y).unwrap_or(Kind::Unknown)
2097 }
2098 _ => Kind::Unknown,
2099 }
2100 }
2101
2102 fn refine_param(&self, declared: Kind, current: Kind, passed: Kind) -> Kind {
2107 let refined = match (declared, passed) {
2108 (_, Kind::Tower) => Kind::Tower,
2110 (Kind::Unknown, p) if p != Kind::Unknown => p,
2111 (Kind::Int, Kind::BigInt) => Kind::BigInt,
2112 (d, Kind::Tuple(ti)) if d.seq_c_type().is_some() || d == Kind::SeqUnknown => {
2113 let elem = self.elem_kind(d);
2114 if self
2115 .tuple_positions(ti)
2116 .iter()
2117 .all(|k| *k == elem || elem == Kind::Unknown)
2118 {
2119 passed
2120 } else {
2121 declared
2122 }
2123 }
2124 (Kind::Inductive(d), Kind::Inductive(p)) => {
2125 self.unify_inductive(d, p).unwrap_or(declared)
2126 }
2127 _ => declared,
2128 };
2129 match (current, refined) {
2130 (Kind::BigInt, Kind::Int) => Kind::BigInt,
2131 (c, r) if r == declared && c != declared => c,
2132 _ => refined,
2133 }
2134 }
2135
2136 fn refine_return(&mut self, fi: usize, declared: Kind, k: Kind) {
2140 let current = self.fn_ret[fi];
2141 self.fn_ret[fi] = match (current, k) {
2142 (_, Kind::Unknown) => current,
2143 (Kind::Tower, _) | (_, Kind::Tower) => Kind::Tower,
2145 (Kind::Inductive(c), Kind::Inductive(r)) => {
2146 self.unify_inductive(c, r).unwrap_or(current)
2147 }
2148 (Kind::Int, Kind::BigInt) | (Kind::BigInt, Kind::Int) => Kind::BigInt,
2149 (c, r) if c == r => c,
2150 (c, r @ (Kind::Tuple(_) | Kind::BigInt | Kind::Rational | Kind::Optional(_)))
2151 if c == declared =>
2152 {
2153 r
2154 }
2155 (Kind::Unknown, r) if declared == Kind::Unknown => r,
2156 (c, r) if declared == Kind::Unknown => join(c, r),
2157 (c, _) => c,
2158 };
2159 }
2160
2161 fn derive_global_kinds(&mut self, main_end: usize) {
2164 let n = self.prog.globals.len();
2165 if n == 0 {
2166 return;
2167 }
2168 self.global_kinds = (0..n)
2172 .map(|i| self.boundary_kind(self.prog.global_types.get(i).unwrap_or(&None)))
2173 .collect();
2174 let declared: Vec<Kind> = self.global_kinds.clone();
2175 let takes = |idx: usize, k: Kind| {
2177 k == Kind::Tower
2178 || match declared.get(idx).copied().unwrap_or(Kind::Unknown) {
2179 Kind::Unknown => true,
2180 Kind::Int => matches!(k, Kind::Int | Kind::BigInt),
2181 _ => false,
2182 }
2183 };
2184 let regions = self.regions_with_seeds(main_end);
2185 for _ in 0..2 {
2186 let mut updates: Vec<(usize, Kind)> = Vec::new();
2187 for (start, end, seed) in ®ions {
2188 let per_pc = self.infer_kinds(*start, *end, seed.clone(), &[]);
2189 for pc in *start..*end {
2190 if let Op::GlobalSet { idx, src } = &self.prog.code[pc] {
2191 let k = per_pc[pc - start]
2192 .get(*src as usize)
2193 .copied()
2194 .unwrap_or(Kind::Unknown);
2195 if !matches!(k, Kind::Unknown | Kind::Nothing) && takes(*idx as usize, k) {
2196 updates.push((*idx as usize, k));
2197 }
2198 }
2199 }
2200 }
2201 for (idx, k) in updates {
2202 if let Some(slot) = self.global_kinds.get_mut(idx) {
2203 *slot = match (*slot, k) {
2204 (Kind::Tower, _) | (_, Kind::Tower) => Kind::Tower,
2205 (Kind::BigInt, Kind::Int) | (Kind::Int, Kind::BigInt) => Kind::BigInt,
2206 _ => k,
2207 };
2208 }
2209 }
2210 }
2211 }
2212
2213 fn regions_with_seeds(&self, main_end: usize) -> Vec<(usize, usize, Vec<Kind>)> {
2216 let mut regions: Vec<(usize, usize, Vec<Kind>)> = vec![(
2217 0,
2218 main_end,
2219 vec![Kind::Unknown; self.prog.register_count.max(1)],
2220 )];
2221 let mut starts: Vec<usize> = self.prog.functions.iter().map(|f| f.entry_pc).collect();
2222 starts.sort_unstable();
2223 for (fi, f) in self.prog.functions.iter().enumerate() {
2224 let end = starts
2225 .iter()
2226 .copied()
2227 .find(|&s| s > f.entry_pc)
2228 .unwrap_or(self.prog.code.len());
2229 regions.push((f.entry_pc, end, self.seed_of(fi)));
2230 }
2231 regions
2232 }
2233
2234 fn derive_counter_fields(&mut self, main_end: usize) {
2242 let regions = self.regions_with_seeds(main_end);
2243 let mut found: std::collections::HashSet<(String, usize)> =
2244 std::collections::HashSet::new();
2245 for (start, end, seed) in ®ions {
2246 let per_pc = self.infer_kinds(*start, *end, seed.clone(), &[]);
2247 for pc in *start..*end {
2248 if let Op::StructInsert { obj, field, value } = &self.prog.code[pc] {
2249 let cur = &per_pc[pc - start];
2250 let vk = cur.get(*value as usize).copied().unwrap_or(Kind::Unknown);
2251 if vk == Kind::CrdtCounter {
2252 let ok = cur.get(*obj as usize).copied().unwrap_or(Kind::Unknown);
2253 if let (Some(name), Some((cell, _))) = (
2254 self.struct_name_of_kind(ok),
2255 self.resolve_field_any(ok, *field),
2256 ) {
2257 found.insert((name, cell));
2258 }
2259 }
2260 }
2261 }
2262 }
2263 self.counter_fields = found;
2264 }
2265
2266 fn emit_function(
2267 &mut self,
2268 fi: usize,
2269 entry: usize,
2270 end: usize,
2271 param_count: u16,
2272 register_count: usize,
2273 holes: &[(usize, usize)],
2274 captures: &[(crate::intern::Symbol, Option<u16>)],
2275 ) -> Result<(), String> {
2276 let is_closure = self.closure_bodies.contains(&fi);
2277 if is_closure && !self.inout_regs(fi).is_empty() {
2278 return Err("a function value's body with a `mutable` parameter".into());
2279 }
2280 self.exact_int.set(self.computes_exactly(fi));
2282 let mut params: Vec<String> = (0..param_count).map(|i| self.param_decl(fi, i)).collect();
2283 if is_closure {
2284 params.push("int64_t clo".to_string());
2285 }
2286 params.push("unsigned char *__ret_own".to_string());
2287 let sig = params.join(", ");
2288 if self.word_entry {
2289 writeln!(self.out, "static lc_wret fn_{fi}_w({sig}) {{").unwrap();
2290 } else {
2291 writeln!(self.out, "static int64_t fn_{fi}({sig}) {{").unwrap();
2292 }
2293 writeln!(
2294 self.out,
2295 " int64_t r[{}] = {{0}};",
2296 register_count.max(1)
2297 )
2298 .unwrap();
2299 writeln!(self.out, " (void)r;").unwrap();
2300 writeln!(self.out, " if (__ret_own) *__ret_own = 0;").unwrap();
2301 let inout = self.inout_regs(fi);
2302 for i in 0..param_count {
2303 let deref = if inout.contains(&i) { "*" } else { "" };
2304 writeln!(self.out, " r[{i}] = {deref}p{i};").unwrap();
2305 }
2306 if is_closure && !captures.is_empty() {
2309 let cap = captures.len();
2310 writeln!(self.out, " int64_t *_clo = (int64_t*)(intptr_t)clo;").unwrap();
2311 for k in 0..cap {
2312 writeln!(
2313 self.out,
2314 " r[{}] = _clo[{}];",
2315 param_count as usize + k,
2316 1 + k
2317 )
2318 .unwrap();
2319 writeln!(
2320 self.out,
2321 " r[{}] = _clo[{}];",
2322 param_count as usize + cap + k,
2323 1 + cap + k
2324 )
2325 .unwrap();
2326 }
2327 } else if is_closure {
2328 writeln!(self.out, " (void)clo;").unwrap();
2329 }
2330 self.dispatch_to_device(fi);
2331 self.current_fn = Some(fi);
2332 self.emit_region(entry, end, true, Some(fi), holes)?;
2333 self.current_fn = None;
2334 let fall = self.ret("0", false);
2335 writeln!(self.out, " {fall}\n}}\n").unwrap();
2336 Ok(())
2337 }
2338
2339 fn emit_held_function(&mut self, fi: usize, register_count: usize) {
2344 let n = register_count.max(1);
2345 let body = std::mem::take(&mut self.held_body);
2346 let entries = std::mem::take(&mut self.held_entries);
2347 let owns = std::mem::take(&mut self.held_owns);
2348 writeln!(
2349 self.out,
2350 "static lc_wret fn_{fi}_x(int __resume, int __owned, unsigned char *__ret_own) {{"
2351 )
2352 .unwrap();
2353 if entries.is_empty() {
2354 writeln!(
2355 self.out,
2356 " (void)__resume; (void)__owned; (void)__ret_own;"
2357 )
2358 .unwrap();
2359 writeln!(self.out, " return (lc_wret){{ 0, 0 }};\n}}\n").unwrap();
2360 return;
2361 }
2362 writeln!(self.out, " int64_t r[{n}];").unwrap();
2363 writeln!(self.out, " memcpy(r, lc_held_regs, sizeof r);").unwrap();
2364 if owns {
2365 writeln!(self.out, " unsigned char own[{n}]; (void)own;").unwrap();
2366 writeln!(
2367 self.out,
2368 " if (__owned) memcpy(own, lc_held_owns, sizeof own); else memset(own, 0, sizeof own);"
2369 )
2370 .unwrap();
2371 } else {
2372 writeln!(self.out, " (void)__owned;").unwrap();
2373 }
2374 writeln!(self.out, " switch (__resume) {{").unwrap();
2375 for (id, ..) in &entries {
2376 writeln!(self.out, " case {id}: goto R{id};").unwrap();
2377 }
2378 writeln!(self.out, " default: abort();\n }}").unwrap();
2379 self.out.push_str(&body);
2380 for (id, loops, resume, tags) in &entries {
2381 writeln!(self.out, "R{id}:;").unwrap();
2382 for reg in tags {
2383 writeln!(self.out, " r[{reg}] = lc_xof(r[{reg}]);").unwrap();
2384 }
2385 for (k, h) in loops.iter().enumerate() {
2386 writeln!(
2387 self.out,
2388 " _itv{h} = (__typeof__(_itv{h}))(intptr_t)lc_held_loops[{}]; _it{h} = (size_t)lc_held_loops[{}];",
2389 2 * k,
2390 2 * k + 1
2391 )
2392 .unwrap();
2393 }
2394 writeln!(self.out, " goto X{resume};").unwrap();
2395 }
2396 writeln!(self.out, "}}\n").unwrap();
2397 }
2398
2399 fn emit_tagged_entry(&mut self, fi: usize) {
2404 let params = self.param_kinds[fi].clone();
2405 let count = self.prog.functions[fi].param_count as usize;
2406 let mut decls: Vec<String> = (0..count as u16).map(|i| self.param_decl(fi, i)).collect();
2407 decls.push("unsigned char *__ret_own".to_string());
2408 let exact: Vec<usize> = (0..count)
2409 .filter(|i| params.get(*i) == Some(&Kind::BigInt))
2410 .collect();
2411 let mut words: Vec<String> = (0..count)
2412 .map(|i| {
2413 if exact.contains(&i) {
2414 format!("p{i} >> 1")
2415 } else {
2416 format!("p{i}")
2417 }
2418 })
2419 .collect();
2420 words.push("__ret_own".to_string());
2421 let start = self.held_start.take();
2422 writeln!(self.out, "static int64_t fn_{fi}({}) {{", decls.join(", ")).unwrap();
2423 writeln!(self.out, " lc_wret _w;").unwrap();
2424 match (&start, exact.is_empty()) {
2425 (Some((id, live)), false) => {
2426 let tags: Vec<String> = exact.iter().map(|i| format!("p{i}")).collect();
2427 writeln!(self.out, " if (({}) & 1) {{", tags.join(" | ")).unwrap();
2428 for reg in live {
2429 let value = if (*reg as usize) < count {
2430 format!("p{reg}")
2431 } else {
2432 "0".to_string()
2433 };
2434 writeln!(self.out, " lc_held_regs[{reg}] = {value};").unwrap();
2435 }
2436 writeln!(self.out, " _w = fn_{fi}_x({id}, 0, __ret_own);").unwrap();
2437 writeln!(self.out, " }} else {{").unwrap();
2438 writeln!(self.out, " _w = fn_{fi}_w({});", words.join(", ")).unwrap();
2439 writeln!(self.out, " }}").unwrap();
2440 }
2441 _ => {
2442 writeln!(self.out, " _w = fn_{fi}_w({});", words.join(", ")).unwrap();
2443 }
2444 }
2445 if self.fn_ret[fi] == Kind::BigInt {
2446 writeln!(self.out, " return _w.escaped ? _w.v : lc_xof(_w.v);").unwrap();
2447 } else {
2448 writeln!(self.out, " return _w.v;").unwrap();
2449 }
2450 writeln!(self.out, "}}\n").unwrap();
2451 }
2452
2453 fn for_each_written(&self, op: &Op, mut f: impl FnMut(u16)) {
2456 op.for_each_written_reg(&mut f);
2457 if let Op::Call {
2458 func, args_start, ..
2459 } = op
2460 {
2461 for p in self.inout_regs(*func as usize) {
2462 f(args_start + p);
2463 }
2464 }
2465 }
2466
2467 fn inout_regs(&self, fi: usize) -> Vec<u16> {
2470 let f = &self.prog.functions[fi];
2471 f.mutable_param_regs
2472 .iter()
2473 .copied()
2474 .filter(|p| *p < f.param_count)
2475 .collect()
2476 }
2477
2478 fn consumed_borrow_arg(&self, func: u16, args_start: u16, arg_count: u16) -> Option<u16> {
2484 let f = self.prog.functions.get(func as usize)?;
2485 let mut params = f
2486 .cow_exempt_regs
2487 .iter()
2488 .copied()
2489 .filter(|p| *p < arg_count && *p < f.param_count);
2490 let param = params.next()?;
2491 if params.next().is_some() {
2494 return None;
2495 }
2496 Some(args_start + param)
2497 }
2498
2499 fn consumed_borrow_transfer(&self, op: &Op) -> Option<(u16, u16)> {
2500 let Op::Call {
2501 dst,
2502 func,
2503 args_start,
2504 arg_count,
2505 } = op
2506 else {
2507 return None;
2508 };
2509 self.consumed_borrow_arg(*func, *args_start, *arg_count)
2510 .map(|arg| (*dst, arg))
2511 }
2512
2513 fn feeds_readonly_call_arg(&self, pc: usize, dst: u16) -> bool {
2520 let (_, end) = self.region_span;
2521 for at in pc + 1..end.min(pc + 16) {
2522 if self.region_targets.contains(&at) {
2523 return false;
2524 }
2525 match &self.prog.code[at] {
2526 Op::Move { .. } => continue,
2527 Op::Call {
2528 func,
2529 args_start,
2530 arg_count,
2531 ..
2532 } => {
2533 if dst < *args_start || dst >= args_start.saturating_add(*arg_count) {
2534 return false;
2535 }
2536 let param = dst - *args_start;
2537 let f = &self.prog.functions[*func as usize];
2538 return !self.inout_regs(*func as usize).contains(¶m)
2539 && !f.cow_exempt_regs.contains(¶m);
2540 }
2541 _ => return false,
2542 }
2543 }
2544 false
2545 }
2546
2547 fn param_decl(&self, fi: usize, i: u16) -> String {
2550 if self.inout_regs(fi).contains(&i) {
2551 format!("int64_t *p{i}")
2552 } else {
2553 format!("int64_t p{i}")
2554 }
2555 }
2556
2557 fn store_inout_params(&mut self, kinds: &[Kind]) -> Result<(), String> {
2562 let Some(fi) = self.current_fn else {
2563 return Ok(());
2564 };
2565 for p in self.inout_regs(fi) {
2566 let settled = self.param_kinds[fi]
2567 .get(p as usize)
2568 .copied()
2569 .unwrap_or(Kind::Unknown);
2570 match (settled, kind_at(kinds, p)) {
2571 (Kind::BigInt, Kind::Int | Kind::Bool) => self.line(&format!("*p{p} = lc_xof(r[{p}]);")),
2572 (Kind::Int | Kind::Bool, Kind::BigInt) => {
2573 return Err(format!("`mutable` parameter {p} is settled as {settled:?} and returns an exact integer"))
2574 }
2575 _ => self.line(&format!("*p{p} = r[{p}];")),
2576 }
2577 }
2578 Ok(())
2579 }
2580
2581 fn dispatch_to_device(&mut self, fi: usize) {
2587 let Some(offload) = self.offloads.iter().find(|o| o.func == fi).cloned() else {
2588 return;
2589 };
2590 if !self.inout_regs(fi).is_empty() {
2591 self.kept.push((
2592 fi,
2593 "a `mutable` parameter is the caller's variable, which a port does not carry"
2594 .into(),
2595 ));
2596 return;
2597 }
2598 let arity = self.prog.functions[fi].param_count as usize;
2599 if offload.params.len() != arity {
2600 self.kept.push((
2601 fi,
2602 format!(
2603 "the region takes {} ports and the function {arity} parameters",
2604 offload.params.len()
2605 ),
2606 ));
2607 return;
2608 }
2609 let mut marshal = Vec::new();
2610 for (i, port) in offload.params.iter().enumerate() {
2611 let settled = self.param_kinds[fi]
2612 .get(i)
2613 .copied()
2614 .unwrap_or(Kind::Unknown);
2615 let cross = match (port, settled) {
2616 (SlotKind::Int, Kind::Int)
2617 | (SlotKind::Float, Kind::Float)
2618 | (SlotKind::Word64, Kind::Word(64)) => {
2619 format!("_dv[{i}] = r[{i}];")
2620 }
2621 (SlotKind::Int, Kind::BigInt) => {
2622 format!("if (!lc_xi64(r[{i}], &_dv[{i}])) _dm = 0;")
2623 }
2624 (SlotKind::Bool, Kind::Bool) => format!("_dv[{i}] = r[{i}] != 0;"),
2625 (SlotKind::Word32, Kind::Word(32)) => {
2626 format!("_dv[{i}] = (int64_t)((uint64_t)r[{i}] & 0xFFFFFFFFu);")
2627 }
2628 _ => {
2629 self.kept.push((fi, format!("parameter {i} is settled as {settled:?}, which the {port:?} port does not carry")));
2630 return;
2631 }
2632 };
2633 marshal.push(cross);
2634 }
2635 let settled = self.fn_ret[fi];
2636 let answer = match (offload.ret, settled) {
2637 (SlotKind::Int, Kind::Int)
2638 | (SlotKind::Float, Kind::Float)
2639 | (SlotKind::Word64, Kind::Word(64)) => "_do".to_string(),
2640 (SlotKind::Int, Kind::BigInt) => "lc_xof(_do)".to_string(),
2641 (SlotKind::Bool, Kind::Bool) => "(_do != 0)".to_string(),
2642 (SlotKind::Word32, Kind::Word(32)) => {
2643 "(int64_t)((uint64_t)_do & 0xFFFFFFFFu)".to_string()
2644 }
2645 (SlotKind::CharText, Kind::Text) => "(int64_t)(intptr_t)lc_chr(_do)".to_string(),
2646 (port, _) => {
2647 self.kept.push((fi, format!("the result is settled as {settled:?}, which the {port:?} port does not carry")));
2648 return;
2649 }
2650 };
2651 self.line(&format!(
2652 "{{ int64_t _dv[{}], _do; int _dm = 1;",
2653 arity.max(1)
2654 ));
2655 for m in marshal {
2656 self.line(&format!(" {m}"));
2657 }
2658 self.line(&format!(
2659 " if (_dm && logos_dev_call({}, _dv, &_do)) return {answer}; }}",
2660 offload.region
2661 ));
2662 }
2663
2664 fn emit_region(
2675 &mut self,
2676 start: usize,
2677 end: usize,
2678 in_function: bool,
2679 func: Option<usize>,
2680 holes: &[(usize, usize)],
2681 ) -> Result<(), String> {
2682 let mut is_target = std::collections::HashSet::new();
2683 for op in &self.prog.code[start..end] {
2684 if let Some(t) = jump_target(op) {
2685 is_target.insert(t);
2686 }
2687 }
2688 let entry_seed = self.seed_of_region(func);
2689 let register_count = entry_seed.len();
2690 self.on_words.set(true);
2691 let on_words = self.infer_kinds(start, end, self.words_seed_of_region(func), holes);
2692 self.on_words.set(false);
2693 let held = self.infer_kinds(start, end, self.exact_seed_of_region(func), holes);
2694 let reachable = self.reachable(start, end, holes, &on_words);
2695 let integer = |k: Kind| matches!(k, Kind::Int | Kind::Bool);
2696 let dst_of = |op: &Op| {
2697 let mut dst = None;
2698 op.for_each_written_reg(|w| dst = Some(w));
2699 dst
2700 };
2701 let words_to_exact = |pc: usize, d: u16| {
2702 integer(kind_at(&on_words[pc - start + 1], d))
2703 && kind_at(&held[pc - start + 1], d) == Kind::BigInt
2704 };
2705 let live_ops: Vec<usize> = (start..end)
2706 .filter(|pc| !in_hole(*pc, holes) && reachable.contains(pc))
2707 .collect();
2708 let deopt: std::collections::BTreeSet<usize> = live_ops
2709 .iter()
2710 .copied()
2711 .filter(|pc| self.speculative_defs.contains(pc) && overflows(&self.prog.code[*pc]))
2712 .filter(|pc| dst_of(&self.prog.code[*pc]).is_some_and(|d| words_to_exact(*pc, d)))
2713 .collect();
2714 let arrivals: std::collections::BTreeSet<usize> = live_ops
2715 .iter()
2716 .copied()
2717 .filter(|pc| self.arrives_exact(&self.prog.code[*pc], &on_words[*pc - start]))
2718 .filter(|pc| dst_of(&self.prog.code[*pc]).is_some_and(|d| words_to_exact(*pc, d)))
2719 .collect();
2720 let exact_params: Vec<u16> = (0..register_count as u16)
2721 .filter(|r| entry_seed[*r as usize] == Kind::BigInt)
2722 .collect();
2723 let owned_of = |per_pc: &[Vec<Kind>]| -> std::collections::BTreeSet<u16> {
2724 let mut owned = std::collections::BTreeSet::new();
2725 for pc in start..end {
2726 if in_hole(pc, holes) {
2727 continue;
2728 }
2729 match (mutation(&self.prog.code[pc]), &self.prog.code[pc]) {
2730 (Some(Mutation::Owned(target)), _) => {
2731 owned.insert(target);
2732 }
2733 (_, Op::AddAssign { dst, .. })
2735 if kind_at(&per_pc[pc - start], *dst) == Kind::Text =>
2736 {
2737 owned.insert(*dst);
2738 }
2739 _ => {}
2740 }
2741 if let Some((dst, arg)) = self.consumed_borrow_transfer(&self.prog.code[pc]) {
2742 owned.insert(dst);
2743 owned.insert(arg);
2744 }
2745 }
2746 owned
2747 };
2748 let speculates = !deopt.is_empty() || !arrivals.is_empty() || !exact_params.is_empty();
2749 let (owned_words, owned_held) = (owned_of(&on_words), owned_of(&held));
2750 let handle_kinds = {
2753 let mut seen: std::collections::BTreeMap<u16, Option<Kind>> =
2754 std::collections::BTreeMap::new();
2755 for per_pc in [&on_words, &held] {
2756 for pc in (start..end).filter(|pc| !in_hole(*pc, holes)) {
2757 self.for_each_written(&self.prog.code[pc], |w| {
2758 let k = kind_at(&per_pc[pc - start + 1], w);
2759 if !carries_no_handle(k) {
2760 let one = seen.entry(w).or_insert(Some(k));
2761 if *one != Some(k) {
2762 *one = None;
2763 }
2764 }
2765 });
2766 }
2767 }
2768 seen.into_iter()
2769 .filter_map(|(r, k)| Some((r, k?)))
2770 .collect()
2771 };
2772 self.handle_kinds = handle_kinds;
2773 let arriving = self.tower_entries.borrow().get(&start).cloned().unwrap_or_default();
2775 for (reg, k) in arriving {
2776 let cell = self.tower_of(&format!("r[{reg}]"), k)?;
2777 self.line(&format!("r[{reg}] = (int64_t)(intptr_t)({cell});"));
2778 }
2779 let (widened, refused) = self.number_entries.borrow().get(&start).cloned().unwrap_or_default();
2782 if let Some(site) = refused.first() {
2783 let at = match site {
2784 crate::vm::variables::Site::Op(pc) => format!("pc {pc}"),
2785 crate::vm::variables::Site::Entry(reg) => format!("parameter register {reg}"),
2786 };
2787 return Err(format!(
2788 "{at}: a variable that holds an exact number and a real where control merges is not yet lowered"
2789 ));
2790 }
2791 for (reg, from, to) in widened {
2792 let v = self.convert_number(&format!("r[{reg}]"), from, to)?;
2793 self.line(&format!("r[{reg}] = {v};"));
2794 }
2795 self.region_span = (start, end);
2796 self.live_out = self.liveness(start, end, holes);
2797 let hot_owns =
2799 !owned_words.is_empty() || (speculates && !self.word_entry && !owned_held.is_empty());
2800 if hot_owns {
2801 writeln!(
2802 self.out,
2803 " unsigned char own[{}] = {{0}}; (void)own;",
2804 register_count.max(1)
2805 )
2806 .unwrap();
2807 for p in self.current_fn.map(|fi| self.inout_regs(fi)).unwrap_or_default() {
2809 if owned_words.contains(&p) || owned_held.contains(&p) {
2810 writeln!(self.out, " own[{p}] = 1;").unwrap();
2811 }
2812 }
2813 }
2814
2815 if !exact_params.is_empty() && !self.word_entry {
2818 let tags: Vec<String> = exact_params.iter().map(|r| format!("r[{r}]")).collect();
2819 self.line(&format!("if (({}) & 1) goto N{start};", tags.join(" | ")));
2820 for r in &exact_params {
2821 self.line(&format!("r[{r}] >>= 1;"));
2822 }
2823 }
2824 self.on_words.set(true);
2825 self.owned = owned_words;
2826 self.region_targets = is_target.clone();
2827 let (text_byte_calls, text_byte_iters) =
2828 self.text_byte_iteration_plan(start, end, holes, &reachable);
2829 self.text_byte_calls = text_byte_calls;
2830 self.text_byte_iters = text_byte_iters;
2831 if !self.text_byte_calls.is_empty() {
2832 crate::optimize::mark_fired(crate::optimization::Opt::TextBytesView);
2833 }
2834 self.deopt = deopt.clone();
2835 self.arrivals = arrivals.clone();
2836 let words_frames = self.emit_version(start, end, in_function, holes, &on_words, &reachable);
2837 self.on_words.set(false);
2838 self.deopt.clear();
2839 self.arrivals.clear();
2840 let words_frames = words_frames?;
2841 if !speculates {
2842 return Ok(());
2843 }
2844 self.line(&self.ret("0", false));
2845
2846 let next_of = |pc: usize| live_ops.iter().copied().find(|p| *p > pc);
2848 let first = live_ops.first().copied();
2849 let resumed: Vec<usize> = arrivals.iter().filter_map(|pc| next_of(*pc)).collect();
2850 let held_owns = hot_owns || !owned_held.is_empty();
2851 self.owned = owned_held;
2852 self.region_targets = is_target
2853 .iter()
2854 .chain(&deopt)
2855 .chain(&first)
2856 .chain(&resumed)
2857 .copied()
2858 .collect();
2859 let hot = self.word_entry.then(|| std::mem::take(&mut self.out));
2860 self.label = 'X';
2861 let held_frames = self.emit_version(start, end, in_function, holes, &held, &reachable);
2862 self.label = 'L';
2863 self.line(&self.ret("0", false));
2864 if let Some(hot) = hot {
2865 self.held_body = std::mem::replace(&mut self.out, hot);
2866 self.held_owns = held_owns;
2867 }
2868 let held_frames = held_frames?;
2869 let split = func.filter(|_| self.word_entry);
2872
2873 let first = first.ok_or("a region with exact parameters and no op")?;
2874 if !exact_params.is_empty() {
2875 if split.is_none() {
2876 writeln!(self.out, "N{start}:;").unwrap();
2877 }
2878 let starts_exact: Vec<u16> = func
2879 .and_then(|fi| self.entry_speculative.get(&fi))
2880 .into_iter()
2881 .flatten()
2882 .copied()
2883 .collect();
2884 for r in starts_exact.iter().filter(|_| split.is_none()) {
2885 if !exact_params.contains(r) && integer(entry_seed[*r as usize]) {
2886 self.line(&format!("r[{r}] = lc_xof(r[{r}]);"));
2887 }
2888 }
2889 if split.is_some() {
2890 let id = self.held_entries.len();
2891 let live: Vec<u16> = self.live_in(first).into_iter().collect();
2892 let tags: Vec<u16> = live
2893 .iter()
2894 .copied()
2895 .filter(|r| {
2896 !exact_params.contains(r)
2897 && starts_exact.contains(r)
2898 && integer(entry_seed[*r as usize])
2899 })
2900 .collect();
2901 self.held_entries.push((id, Vec::new(), first, tags));
2902 self.held_start = Some((id, live));
2903 } else {
2904 self.line(&format!("goto X{first};"));
2905 }
2906 }
2907 let stubs = deopt
2908 .iter()
2909 .map(|pc| ('D', *pc, *pc, None))
2910 .chain(arrivals.iter().map(|pc| {
2911 (
2912 'A',
2913 *pc,
2914 next_of(*pc).unwrap_or(*pc),
2915 dst_of(&self.prog.code[*pc]),
2916 )
2917 }));
2918 for (kind, pc, resume, arrived) in stubs.collect::<Vec<_>>() {
2919 writeln!(self.out, "{kind}{pc}:;").unwrap();
2920 let at = if kind == 'D' {
2922 pc - start
2923 } else {
2924 pc - start + 1
2925 };
2926 let live = split.map(|_| self.live_in(resume));
2929 for reg in 0..register_count as u16 {
2930 if Some(reg) != arrived
2931 && live.as_ref().is_none_or(|l| l.contains(®))
2932 && kind_at(&held[at], reg) == Kind::BigInt
2933 && integer(kind_at(&on_words[at], reg))
2934 {
2935 self.line(&format!("r[{reg}] = lc_xof(r[{reg}]);"));
2936 }
2937 }
2938 let (fw, fh) = (&words_frames[&pc], &held_frames[&pc]);
2939 if fw.len() != fh.len() {
2940 return Err(format!(
2941 "pc {pc}: the two versions of the region iterate {} and {} loops",
2942 fw.len(),
2943 fh.len()
2944 ));
2945 }
2946 if let Some(fi) = split {
2947 let (fw, fh) = (fw.clone(), fh.clone());
2948 self.hand_over(fi, resume, &fw, &fh, hot_owns);
2949 continue;
2950 }
2951 for (w, h) in fw.iter().zip(fh) {
2952 self.line(&format!("_itv{h} = _itv{w}; _it{h} = _it{w};"));
2953 }
2954 self.line(&format!("goto X{resume};"));
2955 }
2956 Ok(())
2957 }
2958
2959 fn live_in(&self, pc: usize) -> std::collections::BTreeSet<u16> {
2962 let op = &self.prog.code[pc];
2963 let mut live = self.live_out[pc - self.region_span.0].clone();
2964 self.for_each_written(op, |w| {
2965 live.remove(&w);
2966 });
2967 op.for_each_read_reg(
2968 |f| self.prog.functions[f as usize].captures.len(),
2969 |r| {
2970 live.insert(r);
2971 },
2972 );
2973 live
2974 }
2975
2976 fn hand_over(&mut self, fi: usize, resume: usize, words: &[usize], held: &[usize], owns: bool) {
2980 for reg in self.live_in(resume) {
2981 self.line(&format!("lc_held_regs[{reg}] = r[{reg}];"));
2982 if owns {
2983 self.line(&format!("lc_held_owns[{reg}] = own[{reg}];"));
2984 }
2985 }
2986 for (k, w) in words.iter().enumerate() {
2987 self.line(&format!(
2988 "lc_held_loops[{}] = (int64_t)(intptr_t)_itv{w}; lc_held_loops[{}] = (int64_t)_it{w};",
2989 2 * k,
2990 2 * k + 1
2991 ));
2992 }
2993 let id = self.held_entries.len();
2994 self.held_entries
2995 .push((id, held.to_vec(), resume, Vec::new()));
2996 self.line(&format!(
2997 "return fn_{fi}_x({id}, {}, __ret_own);",
2998 u8::from(owns)
2999 ));
3000 }
3001
3002 fn emit_version(
3005 &mut self,
3006 start: usize,
3007 end: usize,
3008 in_function: bool,
3009 holes: &[(usize, usize)],
3010 per_pc: &[Vec<Kind>],
3011 reachable: &std::collections::HashSet<usize>,
3012 ) -> Result<std::collections::HashMap<usize, Vec<usize>>, String> {
3013 self.iter_stack.clear();
3014 let mut frames = std::collections::HashMap::new();
3015 for pc in start..end {
3016 if in_hole(pc, holes) || !reachable.contains(&pc) {
3019 continue;
3020 }
3021 if self.region_targets.contains(&pc) {
3022 writeln!(self.out, "{}{pc}:;", self.label).unwrap();
3023 }
3024 if self.label == 'L'
3029 && crate::optimize::active_config()
3030 .is_on(crate::optimization::Opt::BranchlessAccumulate)
3031 {
3032 if let (
3033 Some(Op::JumpIfFalse { target, .. }),
3034 Some(Op::LoadConst { dst: delta, idx }),
3035 Some(Op::AddAssign { src, .. }),
3036 ) = (
3037 self.prog.code.get(pc),
3038 self.prog.code.get(pc + 1),
3039 self.prog.code.get(pc + 2),
3040 ) {
3041 if *target == pc + 3
3042 && delta == src
3043 && matches!(
3044 self.prog.constants.get(*idx as usize),
3045 Some(Constant::Int(_))
3046 )
3047 {
3048 crate::optimize::mark_backend_decline(
3049 crate::optimization::Opt::BranchlessAccumulate,
3050 "branchlessaccumulate: retained the faster predictable bytecode branch",
3051 );
3052 }
3053 }
3054 }
3055 frames.insert(pc, self.iter_stack.iter().map(|f| f.id).collect());
3056 let before = per_pc[pc - start].clone();
3059 let after = per_pc[pc - start + 1].clone();
3060 let op = self.prog.code[pc].clone();
3061 self.try_emit_byte_compare_window(pc, end, &before);
3062 let version = self.on_words.get() as usize;
3066 let natural = self.tower_from.borrow()[version].get(&pc).copied();
3067 let number = self.number_from.borrow()[version].get(&pc).copied();
3068 let mut computed = after.clone();
3069 let mut landing = None;
3070 op.for_each_written_reg(|w| landing = Some(w));
3071 if let Some(k) = natural.or(number.map(|(from, _)| from)) {
3072 if let Some(slot) = landing.and_then(|w| computed.get_mut(w as usize)) {
3073 *slot = k;
3074 }
3075 }
3076 let lowered = if self.tower { self.lower_tower(&op, &before, pc)? } else { None };
3077 if lowered.is_none() {
3078 self.lower_over_exact(&op, &before, &computed, in_function, pc)
3079 .map_err(|e| format!("pc {pc}: {e}"))?;
3080 }
3081 if let (Some(k), Some(w), false) = (natural, landing, lowered == Some(true)) {
3082 let cell = self.tower_of(&format!("r[{w}]"), k).map_err(|e| format!("pc {pc}: {e}"))?;
3083 self.line(&format!("r[{w}] = (int64_t)(intptr_t)({cell});"));
3084 }
3085 if let (Some((from, to)), Some(w)) = (number, landing) {
3086 let v = self.convert_number(&format!("r[{w}]"), from, to).map_err(|e| format!("pc {pc}: {e}"))?;
3087 self.line(&format!("r[{w}] = {v};"));
3088 }
3089 let tested = std::mem::take(&mut self.twin_arrival);
3091 if self.arrivals.contains(&pc) && !tested {
3092 let mut dst = 0;
3094 op.for_each_written_reg(|w| dst = w);
3095 self.line(&format!("if (r[{dst}] & 1) goto A{pc};"));
3096 self.line(&format!("r[{dst}] >>= 1;"));
3097 }
3098 self.write_owned_flags(&op, &after, pc);
3099 }
3100 Ok(frames)
3101 }
3102
3103 fn lower_checked(
3111 &mut self,
3112 op: &Op,
3113 kinds: &[Kind],
3114 after: &[Kind],
3115 in_function: bool,
3116 pc: usize,
3117 ) -> Result<(), String> {
3118 let word = |v: i128| {
3119 if v == i64::MIN as i128 {
3120 "INT64_MIN".to_string()
3121 } else {
3122 format!("{v}LL")
3123 }
3124 };
3125 let known = |reg: u16| self.const_operand(pc, reg).map(i128::from);
3126 let operand = |reg: u16| known(reg).map_or_else(|| format!("r[{reg}]"), word);
3127 let checked = |f: &str, dst: u16, a: u16, b: u16| {
3128 format!(
3129 "{{ int64_t _o; if ({f}({}, {}, &_o)) goto D{pc}; r[{dst}] = _o; }}",
3130 operand(a),
3131 operand(b)
3132 )
3133 };
3134 let line = match *op {
3135 Op::Add { dst, lhs, rhs } => checked("__builtin_add_overflow", dst, lhs, rhs),
3136 Op::Sub { dst, lhs, rhs } => checked("__builtin_sub_overflow", dst, lhs, rhs),
3137 Op::AddAssign { dst, src } => checked("__builtin_add_overflow", dst, dst, src),
3138 Op::Pow { dst, lhs, rhs } => checked("lc_ipow_ovf", dst, lhs, rhs),
3139 Op::Mul { dst, lhs, rhs } => {
3140 let by_constant = known(rhs)
3141 .map(|c| (lhs, c))
3142 .or_else(|| known(lhs).map(|c| (rhs, c)));
3143 let Some((x, c)) = by_constant else {
3144 self.line(&checked("__builtin_mul_overflow", dst, lhs, rhs));
3145 return Ok(());
3146 };
3147 let (lo, hi) = product_domain(c);
3148 let guard = match (lo == i64::MIN as i128, hi == i64::MAX as i128) {
3149 (true, true) => None,
3150 (true, false) => Some(format!("r[{x}] > {}", word(hi))),
3151 (false, true) => Some(format!("r[{x}] < {}", word(lo))),
3152 (false, false) => Some(format!(
3153 "(uint64_t)r[{x}] - (uint64_t){} > {}ULL",
3154 word(lo),
3155 (hi - lo) as u64
3156 )),
3157 };
3158 if let Some(g) = guard {
3159 self.line(&format!("if ({g}) goto D{pc};"));
3160 }
3161 format!("r[{dst}] = r[{lhs}] * r[{rhs}];")
3162 }
3163 Op::Div { lhs, rhs, .. } | Op::FloorDiv { lhs, rhs, .. } => {
3165 self.line(&format!(
3166 "if (r[{rhs}] == -1 && r[{lhs}] == INT64_MIN) goto D{pc};"
3167 ));
3168 return self.lower_op(op, kinds, after, in_function, pc);
3169 }
3170 _ => return Err(format!("an overflow check on {}", op.name())),
3171 };
3172 self.line(&line);
3173 Ok(())
3174 }
3175
3176 fn const_operand(&self, pc: usize, reg: u16) -> Option<i64> {
3179 let (start, _) = self.region_span;
3180 let p = (start..pc).rev().find(|p| {
3181 let mut writes = false;
3182 self.for_each_written(&self.prog.code[*p], |w| writes |= w == reg);
3183 writes
3184 })?;
3185 if (p + 1..=pc).any(|q| self.region_targets.contains(&q)) {
3186 return None;
3187 }
3188 match &self.prog.code[p] {
3189 Op::LoadConst { idx, .. } => match &self.prog.constants[*idx as usize] {
3190 Constant::Int(v) => Some(*v),
3191 _ => None,
3192 },
3193 _ => None,
3194 }
3195 }
3196
3197 fn optional_operands(&self, op: &Op, kinds: &[Kind]) -> Vec<(u16, Kind, String)> {
3200 use crate::ast::stmt::BinaryOpKind as B;
3201 let (kind, lhs, rhs) = match *op {
3202 Op::Add { lhs, rhs, .. } => (B::Add, lhs, rhs),
3203 Op::AddAssign { dst, src } => (B::Add, dst, src),
3204 Op::Sub { lhs, rhs, .. } => (B::Subtract, lhs, rhs),
3205 Op::Mul { lhs, rhs, .. } => (B::Multiply, lhs, rhs),
3206 Op::Div { lhs, rhs, .. } => (B::Divide, lhs, rhs),
3207 Op::ExactDiv { lhs, rhs, .. } => (B::ExactDivide, lhs, rhs),
3208 Op::FloorDiv { lhs, rhs, .. } => (B::FloorDivide, lhs, rhs),
3209 Op::Mod { lhs, rhs, .. } => (B::Modulo, lhs, rhs),
3210 Op::Pow { lhs, rhs, .. } => (B::Pow, lhs, rhs),
3211 Op::Lt { lhs, rhs, .. } => (B::Lt, lhs, rhs),
3212 Op::Gt { lhs, rhs, .. } => (B::Gt, lhs, rhs),
3213 Op::LtEq { lhs, rhs, .. } => (B::LtEq, lhs, rhs),
3214 Op::GtEq { lhs, rhs, .. } => (B::GtEq, lhs, rhs),
3215 Op::BitAnd { lhs, rhs, .. } => (B::BitAnd, lhs, rhs),
3216 Op::BitOr { lhs, rhs, .. } => (B::BitOr, lhs, rhs),
3217 Op::BitXor { lhs, rhs, .. } => (B::BitXor, lhs, rhs),
3218 Op::Shl { lhs, rhs, .. } => (B::Shl, lhs, rhs),
3219 Op::Shr { lhs, rhs, .. } => (B::Shr, lhs, rhs),
3220 _ => return Vec::new(),
3221 };
3222 let number = |k: Kind| matches!(k, Kind::Int | Kind::BigInt | Kind::Float | Kind::Rational);
3223 let held = |r: u16| match kind_at(kinds, r) {
3224 Kind::Optional(oi) => Some(self.opt_inner(oi)).filter(|k| number(*k)),
3225 _ => None,
3226 };
3227 let plain = |r: u16| held(r).unwrap_or_else(|| kind_at(kinds, r));
3228 let mut out: Vec<(u16, Kind, String)> = Vec::new();
3229 for (reg, other, nothing_left) in [(lhs, rhs, true), (rhs, lhs, false)] {
3230 let Some(inner) = held(reg) else { continue };
3231 if out.iter().any(|(r, _, _)| *r == reg) || !number(plain(other)) {
3232 continue;
3233 }
3234 let Some(sentence) =
3235 sample_value(plain(other)).and_then(|v| words::nothing_refused(kind, nothing_left, v))
3236 else {
3237 continue;
3238 };
3239 out.push((reg, inner, sentence));
3240 }
3241 out
3242 }
3243
3244 fn lower_over_exact(
3248 &mut self,
3249 op: &Op,
3250 before: &[Kind],
3251 after: &[Kind],
3252 in_function: bool,
3253 pc: usize,
3254 ) -> Result<(), String> {
3255 let presented = self.optional_operands(op, before);
3257 let mut opened = Vec::new();
3258 let mut present_kinds;
3259 let before = if presented.is_empty() {
3260 before
3261 } else {
3262 present_kinds = before.to_vec();
3263 for (reg, inner, sentence) in &presented {
3264 let n = self.fresh();
3265 self.line(&format!(
3266 "int64_t _op{n} = r[{reg}]; if (!((int64_t*)(intptr_t)_op{n})[0]) logos_fatalf({sentence}); r[{reg}] = ((int64_t*)(intptr_t)_op{n})[1];"
3267 ));
3268 present_kinds[*reg as usize] = *inner;
3269 opened.push((*reg, n));
3270 }
3271 &present_kinds[..]
3272 };
3273 let lowered = if self.deopt.contains(&pc) {
3274 self.lower_checked(op, before, after, in_function, pc)
3275 } else {
3276 self.lower_over_present(op, before, after, in_function, pc)
3277 };
3278 let mut written = Vec::new();
3279 op.for_each_written_reg(|w| written.push(w));
3280 for (reg, n) in opened {
3281 if !written.contains(®) {
3282 self.line(&format!("r[{reg}] = _op{n};"));
3283 }
3284 }
3285 lowered
3286 }
3287
3288 fn lower_over_present(
3290 &mut self,
3291 op: &Op,
3292 before: &[Kind],
3293 after: &[Kind],
3294 in_function: bool,
3295 pc: usize,
3296 ) -> Result<(), String> {
3297 let mut read = Vec::new();
3298 op.for_each_read_reg(
3299 |f| self.prog.functions[f as usize].captures.len(),
3300 |r| read.push(r),
3301 );
3302 read.sort_unstable();
3303 read.dedup();
3304 let narrowed: Vec<u16> = read
3305 .into_iter()
3306 .filter(|r| kind_at(before, *r) == Kind::BigInt && !reads_exact_reg(op, before, *r))
3307 .collect();
3308 let mut written = Vec::new();
3309 op.for_each_written_reg(|w| written.push(w));
3310 let tagged: Vec<u16> = if self.promoted.borrow()[self.on_words.get() as usize].contains(&pc)
3311 && !makes_exact(op)
3312 && !self.arrives_exact(op, before)
3313 {
3314 written
3315 .iter()
3316 .copied()
3317 .filter(|w| kind_at(after, *w) == Kind::BigInt)
3318 .collect()
3319 } else {
3320 Vec::new()
3321 };
3322 if narrowed.is_empty() && tagged.is_empty() {
3323 return self.lower_op(op, before, after, in_function, pc);
3324 }
3325 let mut kinds = before.to_vec();
3326 let mut saved = Vec::new();
3327 for reg in &narrowed {
3328 kinds[*reg as usize] = Kind::Int;
3329 let n = self.fresh();
3330 let narrow = match index_too_wide(op, before, *reg) {
3331 Some(why) => format!("lc_xindex(_xs{n}, {})", words::index_too_wide(why)),
3332 None => format!("lc_xnarrow(_xs{n})"),
3333 };
3334 self.line(&format!("int64_t _xs{n} = r[{reg}]; r[{reg}] = {narrow};"));
3335 saved.push((*reg, n));
3336 }
3337 let mut made = after.to_vec();
3338 for reg in &tagged {
3339 made[*reg as usize] = Kind::Int;
3340 }
3341 self.lower_op(op, &kinds, &made, in_function, pc)?;
3342 for (reg, n) in saved {
3343 if !written.contains(®) {
3344 self.line(&format!("r[{reg}] = _xs{n};"));
3345 }
3346 }
3347 for reg in tagged {
3348 self.line(&format!("r[{reg}] = lc_xof(r[{reg}]);"));
3349 }
3350 Ok(())
3351 }
3352
3353 fn reachable(
3356 &self,
3357 start: usize,
3358 end: usize,
3359 holes: &[(usize, usize)],
3360 per_pc: &[Vec<Kind>],
3361 ) -> std::collections::HashSet<usize> {
3362 let mut seen = std::collections::HashSet::new();
3363 let mut work = vec![start];
3364 while let Some(pc) = work.pop() {
3365 if pc < start || pc >= end || !seen.insert(pc) {
3366 continue;
3367 }
3368 let op = &self.prog.code[pc];
3369 let next = {
3370 let mut n = pc + 1;
3371 while n < end && in_hole(n, holes) {
3372 n += 1;
3373 }
3374 n
3375 };
3376 let never = |cond: u16| -> bool {
3377 (start..pc)
3378 .rev()
3379 .take_while(|p| !matches!(self.prog.code[*p], Op::Jump { .. }))
3380 .find_map(|p| match &self.prog.code[p] {
3381 Op::TestArm {
3382 dst,
3383 target,
3384 variant,
3385 } if *dst == cond => Some(
3386 self.static_arm_test(kind_at(&per_pc[p - start], *target), *variant)
3387 == Some(false),
3388 ),
3389 other => {
3390 let mut writes = false;
3391 self.for_each_written(other, |w| writes |= w == cond);
3392 writes.then_some(false)
3393 }
3394 })
3395 == Some(true)
3396 };
3397 let yields_nothing = |dst: u16| kind_at(&per_pc[pc - start + 1], dst) == Kind::Never;
3398 let mut writes_nothing = false;
3399 op.for_each_written_reg(|w| writes_nothing |= yields_nothing(w));
3400 match op {
3401 Op::Return { .. } | Op::ReturnNothing | Op::Halt | Op::FailWith { .. } => {}
3402 Op::IterNext { dst, exit } if yields_nothing(*dst) => work.push(*exit),
3406 _ if writes_nothing => {}
3407 Op::Jump { target } => work.push(*target),
3408 Op::JumpIfFalse { cond, target } if never(*cond) => work.push(*target),
3409 Op::JumpIfTrue { cond, .. } if never(*cond) => work.push(next),
3410 _ => {
3411 if let Some(t) = jump_target(op) {
3412 work.push(t);
3413 }
3414 work.push(next);
3415 }
3416 }
3417 }
3418 seen
3419 }
3420
3421 fn static_arm_test(&self, tk: Kind, variant: u32) -> Option<bool> {
3424 let name = self.const_text(variant)?;
3425 match tk {
3426 Kind::Enum(ei) => self.variant_index(ei, variant).is_none().then_some(false),
3427 Kind::Inductive(ii) => {
3428 let (ty, ctors) = self.inductive(ii);
3429 let possible = ctors.iter().any(|(c, ks)| c == name && ks.is_some());
3430 (self.ctor_tag(&ty, name).is_none() || !possible).then_some(false)
3431 }
3432 Kind::Struct(_) | Kind::StructSynth(_) => {
3433 Some(self.struct_name_of_kind(tk).as_deref() == Some(name))
3434 }
3435 Kind::Nothing => Some(name == "None"),
3436 Kind::Int | Kind::Bool | Kind::Float | Kind::Text | Kind::BigInt | Kind::Rational => {
3437 Some(false)
3438 }
3439 _ => None,
3440 }
3441 }
3442
3443 fn reuses_list_buffer(&self, dst: u16, kind: Kind) -> bool {
3446 self.owned.contains(&dst) && self.handle_kinds.get(&dst) == Some(&kind)
3447 }
3448
3449 fn write_owned_flags(&mut self, op: &Op, after: &[Kind], pc: usize) {
3454 if self.owned.is_empty()
3455 || matches!(
3456 op,
3457 Op::Return { .. } | Op::ReturnNothing | Op::Halt | Op::FailWith { .. }
3458 )
3459 {
3460 return;
3461 }
3462 if matches!(op, Op::RevSwap { .. }) {
3466 return;
3467 }
3468 let transfer = self.consumed_borrow_transfer(op);
3469 let mut kept: Vec<u16> = match (mutation(op), op) {
3470 (Some(Mutation::Owned(target)), _) => vec![target],
3471 (_, Op::AddAssign { dst, .. }) if kind_at(after, *dst) == Kind::Text => vec![*dst],
3472 (_, Op::Call { dst, .. } | Op::CallValue { dst, .. })
3473 if crate::optimize::active_config().is_on(crate::optimization::Opt::Borrow) =>
3474 {
3475 vec![*dst]
3476 }
3477 _ => Vec::new(),
3478 };
3479 if let Some((dst, arg)) = transfer {
3480 kept.push(dst);
3481 kept.push(arg);
3482 }
3483 if let Op::Move { dst, src } = op {
3486 if self.owned.contains(src)
3487 && !self.owned.contains(dst)
3488 && crate::optimize::active_config().is_on(crate::optimization::Opt::Borrow)
3489 && self.feeds_readonly_call_arg(pc, *dst)
3490 {
3491 self.line("/* retain ownership across read-only call argument */");
3495 crate::optimize::mark_fired(crate::optimization::Opt::Borrow);
3496 return;
3497 }
3498 if self.owned.contains(dst)
3499 && self.owned.contains(src)
3500 && dst != src
3501 && self.dead_after(pc, *src)
3502 {
3503 self.line(&format!("own[{dst}] = own[{src}]; own[{src}] = 0;"));
3504 return;
3505 }
3506 }
3507 let taken = match op {
3508 Op::GetField { dst, .. } => Some(*dst).filter(|_| self.takes_out_field(pc)),
3509 Op::GlobalGet { dst, .. } => Some(*dst).filter(|_| self.takes_out_global(pc)),
3510 _ => None,
3511 };
3512 let inspected = inspected_regs(op);
3513 let fresh = fresh_regs(op, after);
3514 let mut written = Vec::new();
3515 self.for_each_written(op, |w| written.push(w));
3516 let mut read = Vec::new();
3517 op.for_each_read_reg(
3518 |f| self.prog.functions[f as usize].captures.len(),
3519 |r| read.push(r),
3520 );
3521 let mut escaped: Vec<u16> = read
3522 .into_iter()
3523 .filter(|r| {
3524 self.owned.contains(r)
3525 && !written.contains(r)
3526 && !inspected.contains(r)
3527 && !kept.contains(r)
3528 })
3529 .collect();
3530 escaped.sort_unstable();
3531 escaped.dedup();
3532 for r in escaped {
3533 self.line(&format!("own[{r}] = 0;"));
3534 }
3535 written.sort_unstable();
3536 written.dedup();
3537 for w in written {
3538 if !self.owned.contains(&w) || kept.contains(&w) || taken == Some(w) {
3539 continue;
3540 }
3541 self.line(&format!(
3542 "own[{w}] = {};",
3543 if fresh.contains(&w) { 1 } else { 0 }
3544 ));
3545 }
3546 if let Some((dst, arg)) = transfer {
3547 if self.owned.contains(&dst) && self.owned.contains(&arg) && dst != arg {
3548 self.line("/* preserve sole ownership through consumed borrow */");
3549 self.line(&format!("own[{dst}] = own[{arg}]; own[{arg}] = 0;"));
3550 crate::optimize::mark_fired(crate::optimization::Opt::Borrow);
3551 }
3552 }
3553 }
3554
3555 fn builtin_refusal(
3557 &mut self,
3558 id: crate::semantics::builtins::BuiltinId,
3559 ks: &[Kind],
3560 ) -> Result<(), String> {
3561 let samples: Option<Vec<RuntimeValue>> = ks.iter().map(|k| sample_value(*k)).collect();
3562 let samples = samples.ok_or_else(|| format!("{id:?} over {ks:?}"))?;
3563 self.line(&format!(
3564 "logos_fatalf({});",
3565 words::builtin_refused(id, samples)
3566 ));
3567 Ok(())
3568 }
3569
3570 fn owned_flag(&self, reg: u16) -> String {
3573 if self.owned.contains(®) {
3574 format!("own[{reg}]")
3575 } else {
3576 "0".to_string()
3577 }
3578 }
3579
3580 fn release_owned_scalar_sequences(&mut self, except: Option<u16>) {
3587 if !crate::optimize::active_config().is_on(crate::optimization::Opt::Unbox) {
3588 return;
3589 }
3590 let inout = self
3594 .current_fn
3595 .map(|fi| self.inout_regs(fi))
3596 .unwrap_or_default();
3597 let drops: Vec<(u16, Kind)> = self
3598 .owned
3599 .iter()
3600 .copied()
3601 .filter(|reg| Some(*reg) != except)
3602 .filter(|reg| !inout.contains(reg))
3603 .filter_map(|reg| self.handle_kinds.get(®).copied().map(|kind| (reg, kind)))
3604 .filter(|(_, kind)| matches!(kind, Kind::SeqInt | Kind::SeqFloat | Kind::SeqBool))
3605 .collect();
3606 for (reg, kind) in drops {
3607 let ty = kind
3608 .seq_c_type()
3609 .expect("a scalar sequence has a C representation");
3610 let n = self.fresh();
3611 self.line(&format!(
3612 "if (own[{reg}] && r[{reg}]) {{ {ty} *_drop{n} = ({ty}*)(intptr_t)r[{reg}]; lc_gc_free(_drop{n}->data); lc_gc_free(_drop{n}); own[{reg}] = 0; }} /* unique local drop */"
3613 ));
3614 crate::optimize::mark_fired(crate::optimization::Opt::Unbox);
3615 }
3616 }
3617
3618 fn own_target(&mut self, target: u16, k: Kind) -> Result<(), String> {
3623 if matches!(k, Kind::CrdtCounter) {
3624 return Ok(());
3625 }
3626 let mark = self.out.len();
3627 self.line(&format!("if (!own[{target}]) {{"));
3628 match self.clone_collection_expr(target, k)? {
3629 Some(copy) => {
3630 self.line(&format!("r[{target}] = {copy};"));
3631 self.line(&format!("own[{target}] = 1;"));
3632 self.line("}");
3633 }
3634 None => self.out.truncate(mark),
3635 }
3636 Ok(())
3637 }
3638
3639 fn liveness(
3642 &self,
3643 start: usize,
3644 end: usize,
3645 holes: &[(usize, usize)],
3646 ) -> Vec<std::collections::BTreeSet<u16>> {
3647 use std::collections::BTreeSet;
3648 let n = end - start;
3649 let succs = |pc: usize| self.successors(pc, start, end, holes);
3650 let mut live_in: Vec<BTreeSet<u16>> = vec![BTreeSet::new(); n];
3651 let mut live_out: Vec<BTreeSet<u16>> = vec![BTreeSet::new(); n];
3652 loop {
3653 let mut changed = false;
3654 for pc in (start..end).rev() {
3655 if in_hole(pc, holes) {
3656 continue;
3657 }
3658 let mut out = BTreeSet::new();
3659 for s in succs(pc) {
3660 out.extend(live_in[s - start].iter().copied());
3661 }
3662 let op = &self.prog.code[pc];
3663 let mut inn = out.clone();
3664 self.for_each_written(op, |w| {
3665 inn.remove(&w);
3666 });
3667 op.for_each_read_reg(
3668 |f| self.prog.functions[f as usize].captures.len(),
3669 |r| {
3670 inn.insert(r);
3671 },
3672 );
3673 if out != live_out[pc - start] || inn != live_in[pc - start] {
3674 live_out[pc - start] = out;
3675 live_in[pc - start] = inn;
3676 changed = true;
3677 }
3678 }
3679 if !changed {
3680 return live_out;
3681 }
3682 }
3683 }
3684
3685 fn successors(
3687 &self,
3688 pc: usize,
3689 start: usize,
3690 end: usize,
3691 holes: &[(usize, usize)],
3692 ) -> Vec<usize> {
3693 let op = &self.prog.code[pc];
3694 let mut out = Vec::new();
3695 match op {
3696 Op::Return { .. } | Op::ReturnNothing | Op::Halt | Op::FailWith { .. } => {}
3697 Op::Jump { target } => out.push(*target),
3698 _ => {
3699 if let Some(t) = jump_target(op) {
3700 out.push(t);
3701 }
3702 let mut next = pc + 1;
3703 while next < end && in_hole(next, holes) {
3704 next += 1;
3705 }
3706 out.push(next);
3707 }
3708 }
3709 out.retain(|p| *p >= start && *p < end);
3710 out
3711 }
3712
3713 fn dead_after(&self, pc: usize, reg: u16) -> bool {
3715 let (start, _) = self.region_span;
3716 self.live_out
3717 .get(pc - start)
3718 .is_some_and(|live| !live.contains(®))
3719 }
3720
3721 fn dead_before(&self, pc: usize, reg: u16) -> bool {
3726 let (start, _) = self.region_span;
3727 let Some(live_out) = self.live_out.get(pc - start) else {
3728 return false;
3729 };
3730 let mut live = live_out.clone();
3731 self.for_each_written(&self.prog.code[pc], |written| {
3732 live.remove(&written);
3733 });
3734 self.prog.code[pc].for_each_read_reg(
3735 |function| self.prog.functions[function as usize].captures.len(),
3736 |read| {
3737 live.insert(read);
3738 },
3739 );
3740 !live.contains(®)
3741 }
3742
3743 fn try_emit_byte_compare_window(&mut self, pc: usize, end: usize, before: &[Kind]) -> bool {
3751 if self.label != 'L'
3752 || !crate::optimize::active_config().is_on(crate::optimization::Opt::Simd)
3753 || pc + 17 > end
3754 {
3755 return false;
3756 }
3757
3758 let op = |offset: usize| &self.prog.code[pc + offset];
3759 let int_const = |idx: u32| match self.prog.constants.get(idx as usize) {
3760 Some(Constant::Int(value)) => Some(*value),
3761 _ => None,
3762 };
3763 let indexed = |op: &Op| match op {
3764 Op::Index {
3765 dst,
3766 collection,
3767 index,
3768 }
3769 | Op::IndexUnchecked {
3770 dst,
3771 collection,
3772 index,
3773 } => Some((*dst, *collection, *index)),
3774 _ => None,
3775 };
3776
3777 let (len_reg, len) = match op(0) {
3778 Op::LoadConst { dst, idx } => (*dst, int_const(*idx)),
3779 _ => return false,
3780 };
3781 let Some(len) = len.filter(|len| (1..=16).contains(len)) else {
3782 return false;
3783 };
3784 let (cond_reg, j_reg) = match op(1) {
3785 Op::Lt { dst, lhs, rhs } if *rhs == len_reg => (*dst, *lhs),
3786 _ => return false,
3787 };
3788 let exit = match op(2) {
3789 Op::JumpIfFalse { cond, target } if *cond == cond_reg && *target == pc + 17 => *target,
3790 _ => return false,
3791 };
3792 let (text_index, text_base) = match op(3) {
3793 Op::Add { dst, lhs, rhs } if *lhs == j_reg => (*dst, *rhs),
3794 Op::Add { dst, lhs, rhs } if *rhs == j_reg => (*dst, *lhs),
3795 _ => return false,
3796 };
3797 let Some((text_char, text, index)) = indexed(op(4)) else {
3798 return false;
3799 };
3800 if index != text_index {
3801 return false;
3802 }
3803 let one_reg = match op(5) {
3804 Op::LoadConst { dst, idx } if int_const(*idx) == Some(1) => *dst,
3805 _ => return false,
3806 };
3807 let needle_index = match op(6) {
3808 Op::Add { dst, lhs, rhs }
3809 if (*lhs == j_reg && *rhs == one_reg) || (*rhs == j_reg && *lhs == one_reg) =>
3810 {
3811 *dst
3812 }
3813 _ => return false,
3814 };
3815 let Some((needle_char, needle, index)) = indexed(op(7)) else {
3816 return false;
3817 };
3818 if index != needle_index {
3819 return false;
3820 }
3821 let mismatch = match op(8) {
3822 Op::NotEq { dst, lhs, rhs }
3823 if (*lhs == text_char && *rhs == needle_char)
3824 || (*rhs == text_char && *lhs == needle_char) =>
3825 {
3826 *dst
3827 }
3828 _ => return false,
3829 };
3830 if !matches!(op(9), Op::JumpIfFalse { cond, target } if *cond == mismatch && *target == pc + 14)
3831 {
3832 return false;
3833 }
3834 let (flag_value_reg, flag_value) = match op(10) {
3835 Op::LoadConst { dst, idx } => (*dst, int_const(*idx)),
3836 _ => return false,
3837 };
3838 let Some(flag_value) = flag_value else {
3839 return false;
3840 };
3841 let flag = match op(11) {
3842 Op::Move { dst, src } if *src == flag_value_reg => *dst,
3843 _ => return false,
3844 };
3845 let break_reg = match op(12) {
3846 Op::LoadConst { dst, idx } if int_const(*idx) == Some(len) => *dst,
3847 _ => return false,
3848 };
3849 if !matches!(op(13), Op::Move { dst, src } if *dst == j_reg && *src == break_reg) {
3850 return false;
3851 }
3852 let inc_reg = match op(14) {
3853 Op::LoadConst { dst, idx } if int_const(*idx) == Some(1) => *dst,
3854 _ => return false,
3855 };
3856 if !matches!(op(15), Op::AddAssign { dst, src } if *dst == j_reg && *src == inc_reg)
3857 || !matches!(op(16), Op::Jump { target } if *target == pc)
3858 {
3859 return false;
3860 }
3861
3862 let (region_start, region_end) = self.region_span;
3863 if (region_start..region_end).any(|source| {
3864 !(pc..exit).contains(&source)
3865 && jump_target(&self.prog.code[source])
3866 .is_some_and(|target| (pc + 1..exit).contains(&target))
3867 }) {
3868 return false;
3869 }
3870 let mut written = std::collections::BTreeSet::new();
3871 let mut flag_writes = 0;
3872 let mut index_writes = 0;
3873 for inner in pc..exit {
3874 self.for_each_written(&self.prog.code[inner], |reg| {
3875 written.insert(reg);
3876 flag_writes += usize::from(reg == flag);
3877 index_writes += usize::from(reg == j_reg);
3878 });
3879 }
3880 if [text, text_base, needle]
3881 .iter()
3882 .any(|reg| written.contains(reg))
3883 || flag_writes != 1
3884 || index_writes != 2
3885 || written
3886 .iter()
3887 .any(|reg| *reg != flag && !self.dead_before(exit, *reg))
3888 {
3889 return false;
3890 }
3891
3892 if kind_at(before, text) != Kind::Text
3893 || kind_at(before, needle) != Kind::Text
3894 || kind_at(before, text_base) != Kind::Int
3895 || !self.owned.contains(&text)
3896 {
3897 return false;
3898 }
3899
3900 let text_ptr = format!("((const unsigned char *)(intptr_t)r[{text}])");
3901 let needle_ptr = format!("((const unsigned char *)(intptr_t)r[{needle}])");
3902 let ascii_needle = (0..len)
3903 .map(|offset| format!("{needle_ptr}[{offset}] < 0x80"))
3904 .collect::<Vec<_>>()
3905 .join(" && ");
3906 let equal = (0..len)
3907 .map(|offset| {
3908 format!(
3909 "{text_ptr}[(size_t)(r[{text_base}] - 1LL) + {offset}] == {needle_ptr}[{offset}]"
3910 )
3911 })
3912 .collect::<Vec<_>>()
3913 .join(" && ");
3914 self.line("/* shared fixed-window byte compare: guarded bytecode-C fast path */");
3915 self.line(&format!(
3916 "if (own[{text}] && lc_head(r[{text}])->ascii && lc_head(r[{text}])->len >= {len}u && r[{text_base}] >= 1LL && (uint64_t)(r[{text_base}] - 1LL) <= (uint64_t)(lc_head(r[{text}])->len - {len}u) && strlen((const char *)(intptr_t)r[{needle}]) >= {len}u && {ascii_needle}) {{"
3917 ));
3918 self.line(&format!("if (!({equal})) r[{flag}] = {flag_value}LL;"));
3919 self.line(&format!("goto L{exit};"));
3920 self.line("}");
3921 crate::optimize::mark_fired(crate::optimization::Opt::Simd);
3922 true
3923 }
3924
3925 fn store_field(
3929 &mut self,
3930 obj: u16,
3931 ok: Kind,
3932 cell: usize,
3933 value: u16,
3934 value_kind: Kind,
3935 pc: usize,
3936 ) -> Result<(), String> {
3937 self.own_target(obj, ok)?;
3938 let stored = self.field_store_value(ok, cell, value, value_kind)?;
3939 self.line(&format!(
3940 "((int64_t*)(intptr_t)r[{obj}])[{cell}] = {stored};"
3941 ));
3942 if cell < 64 {
3943 let word = self
3944 .field_owner_word(obj, ok)
3945 .ok_or("a field store on a record of unknown layout")?;
3946 let sole = if self.owned.contains(&value) && self.dead_after(pc, value) {
3947 format!("((uint64_t)(own[{value}] != 0) << {cell})")
3948 } else {
3949 "0".to_string()
3950 };
3951 self.line(&format!("{word} = ({word} & ~(1ULL << {cell})) | {sole};"));
3952 }
3953 Ok(())
3954 }
3955
3956 fn read_field_collection(&mut self, dst: u16, obj: u16, ok: Kind, cell: usize, pc: usize) {
3961 let Some(word) = self.field_owner_word(obj, ok) else {
3962 return;
3963 };
3964 if cell >= 64 {
3965 return;
3966 }
3967 if self.takes_out_field(pc) {
3968 if self.owned.contains(&dst) {
3969 let record = if self.owned.contains(&obj) {
3970 format!("own[{obj}]")
3971 } else {
3972 "0".to_string()
3973 };
3974 self.line(&format!(
3975 "own[{dst}] = {record} && (({word} >> {cell}) & 1);"
3976 ));
3977 }
3978 } else if !self.stays_an_inspection(dst) {
3979 self.line(&format!("{word} &= ~(1ULL << {cell});"));
3980 }
3981 }
3982
3983 fn stays_an_inspection(&self, reg: u16) -> bool {
3987 let (start, end) = self.region_span;
3988 (start..end).all(|pc| {
3989 let op = &self.prog.code[pc];
3990 let mut read = false;
3991 op.for_each_read_reg(
3992 |f| self.prog.functions[f as usize].captures.len(),
3993 |r| read |= r == reg,
3994 );
3995 let inspected = !read || inspected_regs(op).contains(®);
3996 let changes_fields = matches!(
3997 op,
3998 Op::ListPushField { .. } | Op::Call { .. } | Op::CallValue { .. }
3999 );
4000 inspected && (!changes_fields || self.dead_after(pc, reg))
4001 })
4002 }
4003
4004 fn takes_out_global(&self, pc: usize) -> bool {
4007 let Op::GlobalGet { idx, .. } = &self.prog.code[pc] else {
4008 return false;
4009 };
4010 let (_, end) = self.region_span;
4011 let mut loaded: std::collections::HashMap<u16, &Constant> =
4012 std::collections::HashMap::new();
4013 for at in pc + 1..end {
4014 if self.region_targets.contains(&at) {
4015 return false;
4016 }
4017 match &self.prog.code[at] {
4018 Op::LoadConst { dst, idx: c } => {
4019 loaded.insert(*dst, &self.prog.constants[*c as usize]);
4020 }
4021 Op::GlobalSet { idx: g, src } => {
4022 return g == idx && matches!(loaded.get(src), Some(Constant::Nothing))
4023 }
4024 _ => return false,
4025 }
4026 }
4027 false
4028 }
4029
4030 fn takes_out_field(&self, pc: usize) -> bool {
4035 let Op::GetField { obj, field, .. } = &self.prog.code[pc] else {
4036 return false;
4037 };
4038 let Some(field_name) = self.const_text(*field) else {
4039 return false;
4040 };
4041 let (_, end) = self.region_span;
4042 let mut loaded: std::collections::HashMap<u16, &Constant> =
4043 std::collections::HashMap::new();
4044 for at in pc + 1..end {
4045 if self.region_targets.contains(&at) {
4046 return false;
4047 }
4048 match &self.prog.code[at] {
4049 Op::LoadConst { dst, idx } => {
4050 loaded.insert(*dst, &self.prog.constants[*idx as usize]);
4051 }
4052 Op::SetIndex {
4053 collection,
4054 index,
4055 value,
4056 }
4057 | Op::SetIndexUnchecked {
4058 collection,
4059 index,
4060 value,
4061 } => {
4062 let names_field =
4063 matches!(loaded.get(index), Some(Constant::Text(t)) if t == field_name);
4064 let clears = matches!(loaded.get(value), Some(Constant::Nothing));
4065 return *collection == *obj && names_field && clears;
4066 }
4067 _ => return false,
4068 }
4069 }
4070 false
4071 }
4072 fn text_byte_iteration_plan(
4076 &self,
4077 start: usize,
4078 end: usize,
4079 holes: &[(usize, usize)],
4080 reachable: &std::collections::HashSet<usize>,
4081 ) -> (
4082 std::collections::HashSet<usize>,
4083 std::collections::HashSet<usize>,
4084 ) {
4085 use crate::semantics::builtins::BuiltinId;
4086
4087 if !crate::optimize::active_config().is_on(crate::optimization::Opt::TextBytesView) {
4088 return Default::default();
4089 }
4090
4091 let move_root = |pc: usize, mut reg: u16| {
4092 let mut before = pc;
4093 loop {
4094 let Some(def) = (start..before).rev().find(|at| {
4095 let mut writes = false;
4096 self.prog.code[*at].for_each_written_reg(|w| writes |= w == reg);
4097 writes
4098 }) else {
4099 return reg;
4100 };
4101 if (def + 1..=before).any(|at| self.region_targets.contains(&at)) {
4102 return reg;
4103 }
4104 match self.prog.code[def] {
4105 Op::Move { dst, src } if dst == reg => {
4106 reg = src;
4107 before = def;
4108 }
4109 _ => return reg,
4110 }
4111 }
4112 };
4113
4114 let mut calls = std::collections::HashSet::new();
4115 let mut iters = std::collections::HashSet::new();
4116 for pc in start..end {
4117 let Op::CallBuiltin {
4118 dst,
4119 builtin: BuiltinId::TextBytes,
4120 args_start,
4121 ..
4122 } = self.prog.code[pc]
4123 else {
4124 continue;
4125 };
4126 if !reachable.contains(&pc) || in_hole(pc, holes) {
4127 continue;
4128 }
4129
4130 let mut aliases = std::collections::BTreeSet::from([dst]);
4131 let source_root = move_root(pc, args_start);
4132 let sources = std::collections::BTreeSet::from([args_start, source_root]);
4133 let mut selected_iter = None;
4134 let mut valid = true;
4135
4136 for at in pc + 1..end {
4137 if in_hole(at, holes) || !reachable.contains(&at) {
4138 continue;
4139 }
4140 let op = &self.prog.code[at];
4141 if selected_iter.is_none()
4142 && (self.region_targets.contains(&at) || jump_target(op).is_some())
4143 {
4144 valid = false;
4145 break;
4146 }
4147
4148 let mut reads = std::collections::BTreeSet::new();
4149 op.for_each_read_reg(
4150 |f| self.prog.functions[f as usize].captures.len(),
4151 |r| {
4152 reads.insert(r);
4153 },
4154 );
4155 if reads.iter().any(|r| sources.contains(r)) {
4156 valid = false;
4157 break;
4158 }
4159 let alias_reads: Vec<u16> = reads
4160 .iter()
4161 .copied()
4162 .filter(|r| aliases.contains(r))
4163 .collect();
4164
4165 match op {
4166 Op::Move { dst, src }
4167 if aliases.contains(src) && alias_reads.as_slice() == [*src] =>
4168 {
4169 aliases.insert(*dst);
4170 }
4171 Op::IterPrepare { iterable }
4172 if aliases.contains(iterable) && selected_iter.is_none() =>
4173 {
4174 selected_iter = Some(at);
4175 }
4176 _ if !alias_reads.is_empty() => {
4177 valid = false;
4178 break;
4179 }
4180 _ => {
4181 op.for_each_written_reg(|w| {
4182 aliases.remove(&w);
4183 });
4184 }
4185 }
4186 }
4187
4188 if valid {
4189 if let Some(iter_pc) = selected_iter {
4190 calls.insert(pc);
4191 iters.insert(iter_pc);
4192 }
4193 }
4194 }
4195 (calls, iters)
4196 }
4197
4198 fn infer_kinds(
4204 &self,
4205 start: usize,
4206 end: usize,
4207 seed: Vec<Kind>,
4208 holes: &[(usize, usize)],
4209 ) -> Vec<Vec<Kind>> {
4210 let mut forced = crate::vm::tower::Webs::default();
4214 let mut landings = numbers::Landings::default();
4215 for round in 0.. {
4216 let mut entry = seed.clone();
4217 for (r, k) in &landings.entries {
4218 if let Some(slot) = entry.get_mut(*r as usize) {
4219 *slot = *k;
4220 }
4221 }
4222 for r in &forced.entries {
4223 if let Some(k) = entry.get_mut(*r as usize) {
4224 *k = Kind::Tower;
4225 }
4226 }
4227 let kinds = self.infer_kinds_forced(start, end, entry.clone(), holes, &forced, &landings.defs);
4228 let next = if self.tower {
4229 self.tower_webs(start, end, holes, &kinds, &entry)
4230 } else {
4231 crate::vm::tower::Webs::default()
4232 };
4233 let mut next_landings = self.number_landings(start, end, holes, &kinds, &seed);
4234 if round == SETTLE_ROUNDS {
4235 next_landings = landings.clone();
4236 next_landings.refused.push(crate::vm::variables::Site::Op(start));
4237 }
4238 if next == forced && next_landings == landings {
4239 let converted = forced
4240 .entries
4241 .iter()
4242 .filter_map(|r| {
4243 let k = seed.get(*r as usize).copied().unwrap_or(Kind::Unknown);
4244 (k != Kind::Tower).then_some((*r, k))
4245 })
4246 .collect();
4247 self.tower_entries.borrow_mut().insert(start, converted);
4248 let widened = landings
4249 .entries
4250 .iter()
4251 .filter(|(r, _)| !forced.entries.contains(r))
4252 .map(|(r, k)| (*r, seed.get(*r as usize).copied().unwrap_or(Kind::Unknown), *k))
4253 .collect();
4254 self.number_entries.borrow_mut().insert(start, (widened, landings.refused));
4255 return kinds;
4256 }
4257 forced = next;
4258 landings = next_landings;
4259 }
4260 unreachable!("the rounds end when the webs and joins hold still")
4261 }
4262
4263 fn infer_kinds_forced(
4266 &self,
4267 start: usize,
4268 end: usize,
4269 seed: Vec<Kind>,
4270 holes: &[(usize, usize)],
4271 forced: &crate::vm::tower::Webs,
4272 lands: &std::collections::BTreeMap<usize, Kind>,
4273 ) -> Vec<Vec<Kind>> {
4274 let n = seed.len();
4275 let mut overrides = vec![Kind::Unknown; n];
4276 let mut per_pc: Vec<Vec<Kind>> = Vec::new();
4277 let set = |cur: &mut Vec<Kind>, reg: u16, k: Kind| {
4278 let i = reg as usize;
4279 if i < cur.len() {
4280 cur[i] = k;
4281 }
4282 };
4283 let kind_of =
4284 |cur: &[Kind], reg: u16| -> Kind { *cur.get(reg as usize).unwrap_or(&Kind::Unknown) };
4285 for _pass in 0..8 {
4286 let mut cur = seed.clone();
4287 per_pc = Vec::with_capacity(end - start + 1);
4288 let mut iter_elems: Vec<Kind> = Vec::new();
4289 let mut last_test: Option<(u16, u16, usize)> = None;
4292 let mut last_test_ctor: Option<(u16, String)> = None;
4295 for pc in start..end {
4296 per_pc.push(cur.clone());
4297 if in_hole(pc, holes) {
4300 continue;
4301 }
4302 let presented: Vec<(u16, Kind)> = self
4306 .optional_operands(&self.prog.code[pc], &cur)
4307 .into_iter()
4308 .map(|(r, inner, _)| {
4309 let held = kind_of(&cur, r);
4310 set(&mut cur, r, inner);
4311 (r, held)
4312 })
4313 .collect();
4314 let mut narrowed: Vec<u16> = Vec::new();
4315 self.prog.code[pc].for_each_read_reg(
4316 |f| self.prog.functions[f as usize].captures.len(),
4317 |r| {
4318 if kind_of(&cur, r) == Kind::BigInt
4319 && !reads_exact_reg(&self.prog.code[pc], &cur, r)
4320 && !narrowed.contains(&r)
4321 {
4322 narrowed.push(r);
4323 }
4324 },
4325 );
4326 for r in &narrowed {
4327 set(&mut cur, *r, Kind::Int);
4328 }
4329 let write: Option<(u16, Kind)> = match &self.prog.code[pc] {
4331 Op::LoadConst { dst, idx } => {
4332 let k = match &self.prog.constants[*idx as usize] {
4333 Constant::Int(_) | Constant::Duration(_) => Kind::Int,
4334 Constant::Bool(_) => Kind::Bool,
4335 Constant::Float(_) => Kind::Float,
4336 Constant::Text(_) => Kind::Text,
4337 Constant::Nothing => Kind::Nothing,
4340 Constant::Date(_) => Kind::Date,
4341 Constant::Moment(_) => Kind::Moment,
4342 Constant::Time(_) => Kind::Time,
4343 Constant::Span { .. } => Kind::Span,
4344 _ => Kind::Unknown,
4345 };
4346 Some((*dst, k))
4347 }
4348 Op::Move { dst, src } => Some((*dst, kind_of(&cur, *src))),
4349 Op::Add { dst, lhs, rhs } => {
4351 let (lk, rk) = (kind_of(&cur, *lhs), kind_of(&cur, *rhs));
4352 let k = if lk == Kind::Text || rk == Kind::Text {
4354 Kind::Text
4355 } else {
4356 self.arith_result_kind(lk, rk)
4357 };
4358 Some((*dst, k))
4359 }
4360 Op::Sub { dst, lhs, rhs }
4361 | Op::Mul { dst, lhs, rhs }
4362 | Op::Pow { dst, lhs, rhs } => Some((
4363 *dst,
4364 self.arith_result_kind(kind_of(&cur, *lhs), kind_of(&cur, *rhs)),
4365 )),
4366 Op::Div { dst, lhs, rhs } | Op::FloorDiv { dst, lhs, rhs } => Some((
4369 *dst,
4370 bounded_by(
4371 join(kind_of(&cur, *lhs), kind_of(&cur, *rhs)),
4372 kind_of(&cur, *lhs),
4373 ),
4374 )),
4375 Op::Mod { dst, lhs, rhs } => Some((
4376 *dst,
4377 bounded_by(
4378 join(kind_of(&cur, *lhs), kind_of(&cur, *rhs)),
4379 kind_of(&cur, *rhs),
4380 ),
4381 )),
4382 Op::BitXor { dst, lhs, rhs }
4384 | Op::BitAnd { dst, lhs, rhs }
4385 | Op::BitOr { dst, lhs, rhs } => Some((
4386 *dst,
4387 match join(kind_of(&cur, *lhs), kind_of(&cur, *rhs)) {
4388 Kind::Rational => Kind::Int,
4389 joined => bounded_by(joined, Kind::Int),
4390 },
4391 )),
4392 Op::Shl { dst, lhs, rhs } | Op::Shr { dst, lhs, rhs } => {
4394 let lk = kind_of(&cur, *lhs);
4395 Some((
4396 *dst,
4397 match (lk, join(lk, kind_of(&cur, *rhs))) {
4398 (Kind::Word(_), _) => lk,
4399 (_, Kind::Rational) => Kind::Int,
4400 (_, joined) => bounded_by(joined, Kind::Int),
4401 },
4402 ))
4403 }
4404 Op::AddAssign { dst, src } => {
4405 let (dk, sk) = (kind_of(&cur, *dst), kind_of(&cur, *src));
4406 Some((
4407 *dst,
4408 if dk == Kind::Text || sk == Kind::Text {
4409 Kind::Text
4410 } else {
4411 join(dk, sk)
4412 },
4413 ))
4414 }
4415 Op::Concat { dst, .. } | Op::FormatValue { dst, .. } => {
4416 Some((*dst, Kind::Text))
4417 }
4418 Op::Lt { dst, .. }
4419 | Op::Gt { dst, .. }
4420 | Op::LtEq { dst, .. }
4421 | Op::GtEq { dst, .. }
4422 | Op::Eq { dst, .. }
4423 | Op::NotEq { dst, .. }
4424 | Op::ApproxEq { dst, .. }
4425 | Op::Not { dst, .. }
4426 | Op::Contains { dst, .. } => Some((*dst, Kind::Bool)),
4427 Op::Length { dst, .. } => Some((*dst, Kind::Int)),
4428 Op::DivPow2 { dst, lhs, .. }
4431 | Op::MagicDivU {
4432 dst,
4433 lhs,
4434 mul_back: 0,
4435 ..
4436 } => Some((*dst, bounded_by(kind_of(&cur, *lhs), kind_of(&cur, *lhs)))),
4437 Op::MagicDivU { dst, lhs, .. } if kind_of(&cur, *lhs) == Kind::Rational => Some((*dst, Kind::Rational)),
4438 Op::MagicDivU { dst, .. } => Some((*dst, Kind::Int)),
4439 Op::ExactDiv { dst, .. } => Some((*dst, Kind::Rational)),
4441 Op::NewRange { dst, .. } => Some((*dst, Kind::SeqInt)),
4442 Op::NewEmptyListI32 { dst } => Some((*dst, Kind::SeqInt)),
4443 Op::NewEmptyList { dst }
4444 | Op::NewEmptyListWithCapacity { dst, .. }
4445 | Op::NewEmptyListFixed { dst, .. } => Some((*dst, Kind::SeqUnknown)),
4446 Op::NewList { dst, start, count } => {
4447 let k = if *count == 0 {
4448 Kind::SeqUnknown
4449 } else {
4450 let items: Vec<Kind> = (0..*count).map(|i| kind_of(&cur, *start + i)).collect();
4451 self.list_kind(&items).unwrap_or_else(|_| self.seq_kind_for(items[0]))
4452 };
4453 Some((*dst, k))
4454 }
4455 Op::ListPush { list, value } => {
4456 Some((*list, self.seq_kind_for(kind_of(&cur, *value))))
4457 }
4458 Op::ListPop { list, dst } => Some((
4461 *dst,
4462 Kind::Optional(self.intern_opt(self.elem_kind(kind_of(&cur, *list)))),
4463 )),
4464 Op::NewEmptySet { dst } => Some((*dst, Kind::SetUnknown)),
4465 Op::SetAdd { set, value } => Some((*set, kind_of(&cur, *value).set_of())),
4466 Op::NewEmptyMap { dst } | Op::NewEmptyMapWithCapacity { dst, .. } => {
4467 Some((*dst, Kind::MapUnknown))
4468 }
4469 Op::SetIndex {
4473 collection,
4474 index,
4475 value,
4476 }
4477 | Op::SetIndexUnchecked {
4478 collection,
4479 index,
4480 value,
4481 } => {
4482 let ck = kind_of(&cur, *collection);
4483 if matches!(ck, Kind::Map(_) | Kind::MapUnknown) {
4484 let held = match ck {
4486 Kind::Map(mi) if self.map_pair(mi).1 == Kind::Tower => Kind::Tower,
4487 _ => kind_of(&cur, *value),
4488 };
4489 Some((*collection, self.map_of(kind_of(&cur, *index), held)))
4490 } else if kind_of(&cur, *value) == Kind::Rational
4491 && matches!(self.elem_kind(ck), Kind::Int | Kind::BigInt)
4492 {
4493 Some((*collection, self.seq_kind_for(Kind::Rational)))
4494 } else {
4495 None
4496 }
4497 }
4498 Op::SliceOp {
4501 dst, collection, ..
4502 } => Some((*dst, kind_of(&cur, *collection))),
4503 Op::SeqConcat { dst, lhs, rhs } => {
4504 let lk = kind_of(&cur, *lhs);
4505 Some((
4506 *dst,
4507 if lk == Kind::SeqUnknown {
4508 kind_of(&cur, *rhs)
4509 } else {
4510 lk
4511 },
4512 ))
4513 }
4514 Op::UnionOp { dst, lhs, .. } | Op::IntersectOp { dst, lhs, .. } => {
4515 Some((*dst, kind_of(&cur, *lhs)))
4516 }
4517 Op::Index {
4518 dst,
4519 collection,
4520 index,
4521 }
4522 | Op::IndexUnchecked {
4523 dst,
4524 collection,
4525 index,
4526 } => {
4527 let ck = kind_of(&cur, *collection);
4528 let k = if let Kind::Tuple(ti) = ck {
4531 let poss = self.tuple_positions(ti);
4534 let at = self.const_int_at(start, pc, *index).and_then(|i| match i {
4535 i if i >= 1 => Some((i - 1) as usize),
4536 i if i <= -1 => poss.len().checked_sub(i.unsigned_abs() as usize),
4537 _ => None,
4538 });
4539 match at.and_then(|p| poss.get(p).copied()) {
4540 Some(k) => k,
4541 None if !poss.is_empty() && poss.iter().all(|k| *k == poss[0]) => {
4542 poss[0]
4543 }
4544 None => Kind::Unknown,
4545 }
4546 } else if ck == Kind::Text {
4547 Kind::Text
4549 } else if let Some((_, v)) = self.resolve_map_kv(ck) {
4550 v
4552 } else {
4553 self.elem_kind(ck)
4554 };
4555 Some((*dst, k))
4556 }
4557 Op::Call {
4558 dst,
4559 func,
4560 args_start,
4561 ..
4562 } => {
4563 if let Some(f) = self.prog.functions.get(*func as usize) {
4566 for &p in &f.mutable_param_regs {
4567 let pk = self
4568 .param_kinds
4569 .get(*func as usize)
4570 .and_then(|ks| ks.get(p as usize))
4571 .copied();
4572 if let Some(pk) = pk.filter(|k| *k != Kind::Unknown) {
4573 set(&mut cur, *args_start + p, pk);
4574 }
4575 }
4576 }
4577 Some((
4578 *dst,
4579 self.fn_ret
4580 .get(*func as usize)
4581 .copied()
4582 .unwrap_or(Kind::Unknown),
4583 ))
4584 }
4585 Op::MakeClosure { dst, .. } => Some((*dst, Kind::Int)),
4587 Op::CallValue { dst, .. } => {
4589 let k = self
4590 .closure_bodies
4591 .iter()
4592 .map(|&b| self.fn_ret.get(b).copied().unwrap_or(Kind::Unknown))
4593 .reduce(join)
4594 .unwrap_or(Kind::Unknown);
4595 Some((*dst, k))
4596 }
4597 Op::CallBuiltin {
4601 dst,
4602 builtin,
4603 args_start,
4604 ..
4605 } => {
4606 use crate::semantics::builtins::BuiltinId;
4607 let a = kind_of(&cur, *args_start);
4608 let k = match builtin {
4609 BuiltinId::Sqrt | BuiltinId::ParseFloat => Kind::Float,
4610 BuiltinId::Floor
4611 | BuiltinId::Ceil
4612 | BuiltinId::Round
4613 | BuiltinId::ParseInt => Kind::Int,
4614 BuiltinId::Abs => a,
4615 BuiltinId::Min | BuiltinId::Max => {
4616 join(a, kind_of(&cur, *args_start + 1))
4617 }
4618 BuiltinId::Chr | BuiltinId::Format | BuiltinId::TextFromBytes => {
4619 Kind::Text
4620 }
4621 BuiltinId::TextBytes => Kind::SeqInt,
4622 BuiltinId::ReadReals => Kind::SeqFloat,
4623 BuiltinId::Contains => Kind::Bool,
4624 BuiltinId::CountOnes
4625 | BuiltinId::LeadingZeros
4626 | BuiltinId::TrailingZeros => Kind::Int,
4627 BuiltinId::Word8Make => Kind::Word(8),
4628 BuiltinId::Word16Make => Kind::Word(16),
4629 BuiltinId::Word32 | BuiltinId::Word32Shr => Kind::Word(32),
4630 BuiltinId::Word64
4631 | BuiltinId::Word64Shl
4632 | BuiltinId::Word64Shr
4633 | BuiltinId::Word64And => Kind::Word(64),
4634 BuiltinId::IntOfWord8
4635 | BuiltinId::IntOfWord16
4636 | BuiltinId::IntOfWord32
4637 | BuiltinId::IntOfWord64 => Kind::Int,
4638 BuiltinId::Rotl | BuiltinId::Rotr | BuiltinId::Wnot => a,
4639 BuiltinId::Wand | BuiltinId::Wor => a,
4640 BuiltinId::TryReadBytes => self.inductive_kind(
4641 "Result",
4642 &[("Ok", vec![Kind::SeqInt]), ("Err", vec![Kind::Text])],
4643 ),
4644 BuiltinId::VfsReadFile | BuiltinId::VfsMount => Kind::Text,
4645 BuiltinId::VfsWriteFile => Kind::Nothing,
4646 BuiltinId::RandomInt => Kind::Int,
4647 BuiltinId::RandomFloat => Kind::Float,
4648 BuiltinId::EnvHas => Kind::Bool,
4649 BuiltinId::EnvValue => Kind::Text,
4650 BuiltinId::TimeNow => Kind::Int,
4651 _ => Kind::Unknown,
4652 };
4653 Some((*dst, k))
4654 }
4655 Op::NewTuple { dst, start, count } => {
4658 let ks: Vec<Kind> =
4659 (0..*count).map(|i| kind_of(&cur, *start + i)).collect();
4660 Some((*dst, Kind::Tuple(self.intern_tuple(ks))))
4661 }
4662 Op::DestructureTuple { src, start, count } => {
4665 if let Kind::Tuple(ti) = kind_of(&cur, *src) {
4666 let poss = self.tuple_positions(ti);
4667 for i in 0..*count {
4668 let k = poss.get(i as usize).copied().unwrap_or(Kind::Unknown);
4669 let idx = (*start + i) as usize;
4670 if idx < cur.len() {
4671 cur[idx] = k;
4672 }
4673 }
4674 }
4675 None
4676 }
4677 Op::NewStruct { dst, type_name } => {
4678 Some((*dst, self.struct_kind_of_type_name(*type_name)))
4679 }
4680 Op::NewCrdt { dst, kind } => Some((
4686 *dst,
4687 match kind {
4688 4 | 5 => Kind::CrdtCounter,
4689 1 => Kind::SeqUnknown,
4690 _ => Kind::Unknown,
4691 },
4692 )),
4693 Op::StructInsert { obj, field, value } => {
4698 if let Kind::StructSynth(idx) = kind_of(&cur, *obj) {
4699 self.synth_refine_field_kind(idx, *field, kind_of(&cur, *value));
4700 }
4701 None
4702 }
4703 Op::CrdtAppend { seq, value } => {
4705 Some((*seq, self.seq_kind_for(kind_of(&cur, *value))))
4706 }
4707 Op::CrdtBump { obj, field, .. } => {
4710 if let Kind::StructSynth(idx) = kind_of(&cur, *obj) {
4711 self.synth_refine_field_kind(idx, *field, Kind::Int);
4712 }
4713 None
4714 }
4715 Op::GetField { dst, obj, field } => {
4716 let ok = kind_of(&cur, *obj);
4717 let is_counter = self
4721 .struct_name_of_kind(ok)
4722 .zip(self.resolve_field_any(ok, *field))
4723 .map(|(n, (cell, _))| self.counter_fields.contains(&(n, cell)))
4724 .unwrap_or(false);
4725 let k = if is_counter {
4726 Kind::Int
4727 } else {
4728 self.resolve_field_any(ok, *field)
4729 .map(|(_, k)| k)
4730 .unwrap_or(Kind::Unknown)
4731 };
4732 Some((*dst, k))
4733 }
4734 Op::NewInductive {
4735 dst,
4736 type_name,
4737 ctor,
4738 args_start,
4739 count,
4740 } => {
4741 let k = match self.enum_index_of_type_name(*type_name) {
4742 Some(ei) => Kind::Enum(ei),
4743 None => match (self.const_text(*type_name), self.const_text(*ctor)) {
4744 (Some(ty), Some(c)) => {
4745 let payload: Vec<Kind> = (0..*count)
4746 .map(|i| kind_of(&cur, *args_start + i))
4747 .collect();
4748 self.inductive_kind(ty, &[(c, payload)])
4749 }
4750 _ => Kind::Unknown,
4751 },
4752 };
4753 Some((*dst, k))
4754 }
4755 Op::TestArm {
4756 dst,
4757 target,
4758 variant,
4759 } => {
4760 match kind_of(&cur, *target) {
4761 Kind::Enum(ei) => {
4762 if let Some(vi) = self.variant_index(ei, *variant) {
4763 last_test = Some((*target, ei, vi));
4764 }
4765 }
4766 Kind::Inductive(_) => {
4767 if let Some(c) = self.const_text(*variant) {
4768 last_test_ctor = Some((*target, c.to_string()));
4769 }
4770 }
4771 _ => {}
4772 }
4773 Some((*dst, Kind::Bool))
4774 }
4775 Op::BindArm {
4776 dst, target, index, ..
4777 } => {
4778 let k = match (kind_of(&cur, *target), &last_test, &last_test_ctor) {
4779 (Kind::Enum(_), Some((t, ei, vi)), _) if t == target => {
4780 self.variant_field_kind(*ei, *vi, *index as usize)
4781 }
4782 (Kind::Inductive(ii), _, Some((t, c))) if t == target => {
4783 self.inductive_payload(ii, c, *index as usize)
4784 }
4785 (Kind::Optional(oi), _, _) => self.opt_inner(oi),
4787 _ => Kind::Unknown,
4788 };
4789 Some((*dst, k))
4790 }
4791 Op::RevXor { place, .. }
4793 | Op::RevNeg { place }
4794 | Op::RevAdd { place, .. }
4795 | Op::RevSub { place, .. } => Some((*place, Kind::Int)),
4796 Op::RevSwap { a, b } => {
4797 let (ai, bi) = (*a as usize, *b as usize);
4798 if ai < cur.len() && bi < cur.len() {
4799 cur.swap(ai, bi);
4800 if !self.on_words.get() {
4802 for r in [*a, *b] {
4803 if self.exact_outs.contains(&(pc, r))
4804 && matches!(cur[r as usize], Kind::Int | Kind::Bool)
4805 {
4806 cur[r as usize] = Kind::BigInt;
4807 }
4808 }
4809 }
4810 }
4811 None
4812 }
4813 Op::DeepClone { dst, src } => Some((*dst, kind_of(&cur, *src))),
4814 Op::GlobalGet { dst, idx } => Some((
4815 *dst,
4816 self.global_kinds
4817 .get(*idx as usize)
4818 .copied()
4819 .unwrap_or(Kind::Unknown),
4820 )),
4821 Op::IterPrepare { iterable } => {
4822 let ik = kind_of(&cur, *iterable);
4823 iter_elems.push(match ik {
4826 Kind::Text => Kind::Text,
4827 Kind::Map(_) | Kind::MapUnknown => {
4828 let (kk, vk) = self.resolve_map_kv(ik).unwrap();
4829 Kind::Tuple(self.intern_tuple(vec![kk, vk]))
4830 }
4831 other => self.elem_kind(other),
4832 });
4833 None
4834 }
4835 Op::Args { dst } => Some((*dst, Kind::SeqText)),
4836 Op::LoadToday { dst } => Some((*dst, Kind::Date)),
4837 Op::LoadNow { dst } => Some((*dst, Kind::Moment)),
4838 Op::IterNext { dst, .. } => {
4839 Some((*dst, iter_elems.last().copied().unwrap_or(Kind::Unknown)))
4840 }
4841 Op::IterPop => {
4842 iter_elems.pop();
4843 None
4844 }
4845 Op::NetMakePeer { dst, .. } => Some((*dst, Kind::Peer)),
4849 Op::NetAwait { dst, stream, .. } => Some((
4850 *dst,
4851 if *stream {
4852 self.net_stream.get()
4853 } else {
4854 self.net_message.get()
4855 },
4856 )),
4857 Op::Tcp { dst, kind, .. } => Some((*dst, self.tcp_result_kind(*kind))),
4859 _ => None,
4860 };
4861 let write = match (&self.prog.code[pc], write) {
4865 (op, Some((dst, _))) if refused_for_kinds(op, &cur) => Some((dst, Kind::Never)),
4866 (_, w) => w,
4867 };
4868 let write = match write {
4870 Some((dst, Kind::BigInt))
4871 if self.on_words.get() && self.arrives_exact(&self.prog.code[pc], &cur) =>
4872 {
4873 Some((dst, Kind::Int))
4874 }
4875 w => w,
4876 };
4877 let write = match write {
4881 Some((dst, k @ (Kind::Int | Kind::BigInt))) if self.held_exact(pc) => {
4882 let version = self.on_words.get() as usize;
4883 if k == Kind::Int {
4884 self.promoted.borrow_mut()[version].insert(pc);
4885 } else {
4886 self.promoted.borrow_mut()[version].remove(&pc);
4887 }
4888 Some((dst, Kind::BigInt))
4889 }
4890 w => w,
4891 };
4892 let write = match self.tower.then(|| self.tower_write(&self.prog.code[pc], &cur)).flatten() {
4895 Some(w) => Some(w),
4896 None => write,
4897 };
4898 let write = match write {
4899 Some((dst, k)) if forced.defs.contains(&pc) => {
4900 let version = self.on_words.get() as usize;
4901 if k == Kind::Tower {
4902 self.tower_from.borrow_mut()[version].remove(&pc);
4903 } else {
4904 self.tower_from.borrow_mut()[version].insert(pc, k);
4905 }
4906 Some((dst, Kind::Tower))
4907 }
4908 w => w,
4909 };
4910 let write = {
4912 let version = self.on_words.get() as usize;
4913 match (write, lands.get(&pc)) {
4914 (Some((dst, k)), Some(&to)) if k != Kind::Tower && k != to => {
4915 self.number_from.borrow_mut()[version].insert(pc, (k, to));
4916 Some((dst, to))
4917 }
4918 (w, _) => {
4919 self.number_from.borrow_mut()[version].remove(&pc);
4920 w
4921 }
4922 }
4923 };
4924 for r in &narrowed {
4925 set(&mut cur, *r, Kind::BigInt);
4926 }
4927 for (r, held) in &presented {
4928 set(&mut cur, *r, *held);
4929 }
4930 if let Some((dst, k)) = write {
4931 let k = match k {
4934 Kind::Unknown | Kind::SeqUnknown | Kind::SetUnknown | Kind::MapUnknown => {
4935 match self.declared_kind_at(pc) {
4936 Some(d) if d != Kind::Unknown => d,
4937 _ => k,
4938 }
4939 }
4940 other => other,
4941 };
4942 let k = match (kind_of(&cur, dst), k) {
4945 (Kind::Inductive(a), Kind::Inductive(b)) => {
4946 self.unify_inductive(a, b).unwrap_or(k)
4947 }
4948 _ => k,
4949 };
4950 let ov = kind_of(&overrides, dst);
4953 let k = match k {
4954 Kind::SeqUnknown if is_concrete_seq(ov) => ov,
4955 Kind::SetUnknown if is_concrete_set(ov) => ov,
4956 Kind::MapUnknown if is_concrete_map(ov) => ov,
4957 other => other,
4958 };
4959 set(&mut cur, dst, k);
4960 }
4961 }
4962 per_pc.push(cur.clone()); if cur == overrides {
4964 break;
4965 }
4966 overrides = cur;
4967 }
4968 per_pc
4969 }
4970
4971 fn fresh(&mut self) -> usize {
4972 let n = self.tmp;
4973 self.tmp += 1;
4974 n
4975 }
4976
4977 fn boundary_kind(&self, ty: &Option<BoundaryType>) -> Kind {
4980 match ty {
4981 Some(BoundaryType::Struct(name)) => self
4982 .prog
4983 .struct_types
4984 .iter()
4985 .position(|s| &s.name == name)
4986 .map(|i| Kind::Struct(i as u16))
4987 .unwrap_or(Kind::Unknown),
4988 Some(BoundaryType::Enum(name)) => self
4989 .prog
4990 .enum_types
4991 .iter()
4992 .position(|e| &e.name == name)
4993 .map(|i| Kind::Enum(i as u16))
4994 .unwrap_or(Kind::Unknown),
4995 Some(BoundaryType::Tuple(elems)) => {
4998 let ks: Vec<Kind> = elems
4999 .iter()
5000 .map(|e| self.boundary_kind(&Some(e.clone())))
5001 .collect();
5002 Kind::Tuple(self.intern_tuple(ks))
5003 }
5004 Some(BoundaryType::Seq(elem)) => match self.boundary_kind(&Some((**elem).clone())) {
5007 Kind::Tower => Kind::Tower,
5008 k => self.seq_kind_for(k),
5009 },
5010 Some(BoundaryType::Option(inner)) => {
5012 let inner = self.boundary_kind(&Some((**inner).clone()));
5013 self.inductive_kind("Option", &[("Some", vec![inner]), ("None", Vec::new())])
5014 }
5015 Some(BoundaryType::Result(ok, err)) => {
5016 let (ok, err) = (
5017 self.boundary_kind(&Some((**ok).clone())),
5018 self.boundary_kind(&Some((**err).clone())),
5019 );
5020 self.inductive_kind("Result", &[("Ok", vec![ok]), ("Err", vec![err])])
5021 }
5022 Some(BoundaryType::Map(k, v)) => self.map_of(
5025 self.boundary_kind(&Some((**k).clone())),
5026 self.boundary_kind(&Some((**v).clone())),
5027 ),
5028 Some(BoundaryType::Builtin(name)) if super::tower::TYPE_NAMES.contains(&name.as_str()) => Kind::Tower,
5030 other => boundary_to_kind_c(other),
5031 }
5032 }
5033
5034 fn declared_kind_at(&self, pc: usize) -> Option<Kind> {
5036 let at = self
5037 .prog
5038 .declared_types
5039 .binary_search_by_key(&(pc as u32), |(p, _)| *p)
5040 .ok()?;
5041 Some(self.boundary_kind(&Some(self.prog.declared_types[at].1.clone())))
5042 }
5043
5044 fn tcp_result_kind(&self, code: u8) -> Kind {
5047 let ok = match code {
5048 0 | 1 | 6 | 7 | 8 => Kind::Int,
5049 2 | 3 => Kind::SeqInt,
5050 4 | 5 | 9 => Kind::Nothing,
5051 _ => Kind::Unknown,
5052 };
5053 self.inductive_kind("Result", &[("Ok", vec![ok]), ("Err", vec![Kind::Text])])
5054 }
5055
5056 fn enum_index_of_type_name(&self, type_name: u32) -> Option<u16> {
5058 let ename = match self.prog.constants.get(type_name as usize) {
5059 Some(Constant::Text(s)) => s,
5060 _ => return None,
5061 };
5062 self.prog
5063 .enum_types
5064 .iter()
5065 .position(|e| &e.name == ename)
5066 .map(|i| i as u16)
5067 }
5068
5069 fn variant_index(&self, enum_idx: u16, variant: u32) -> Option<usize> {
5071 let ed = self.prog.enum_types.get(enum_idx as usize)?;
5072 let vname = match self.prog.constants.get(variant as usize) {
5073 Some(Constant::Text(s)) => s.as_str(),
5074 _ => return None,
5075 };
5076 ed.variants.iter().position(|v| v.name == vname)
5077 }
5078
5079 fn variant_field_kind(&self, enum_idx: u16, variant_idx: usize, index: usize) -> Kind {
5081 self.prog
5082 .enum_types
5083 .get(enum_idx as usize)
5084 .and_then(|e| e.variants.get(variant_idx))
5085 .and_then(|v| v.field_types.get(index))
5086 .map(|t| self.boundary_kind(&Some(t.clone())))
5087 .unwrap_or(Kind::Unknown)
5088 }
5089
5090 fn struct_index_of_type_name(&self, type_name: u32) -> Option<u16> {
5092 let sname = match self.prog.constants.get(type_name as usize) {
5093 Some(Constant::Text(s)) => s,
5094 _ => return None,
5095 };
5096 self.prog
5097 .struct_types
5098 .iter()
5099 .position(|s| &s.name == sname)
5100 .map(|i| i as u16)
5101 }
5102
5103 fn const_text(&self, idx: u32) -> Option<&str> {
5105 match self.prog.constants.get(idx as usize) {
5106 Some(Constant::Text(s)) => Some(s.as_str()),
5107 _ => None,
5108 }
5109 }
5110
5111 fn synth_index_of_name(&self, name: &str) -> u16 {
5113 let mut t = self.synth_structs.borrow_mut();
5114 if let Some(i) = t.iter().position(|(n, _)| n == name) {
5115 return i as u16;
5116 }
5117 t.push((name.to_string(), Vec::new()));
5118 (t.len() - 1) as u16
5119 }
5120
5121 fn struct_kind_of_type_name(&self, type_name: u32) -> Kind {
5125 if let Some(si) = self.struct_index_of_type_name(type_name) {
5126 return Kind::Struct(si);
5127 }
5128 match self.const_text(type_name) {
5129 Some(name) => Kind::StructSynth(self.synth_index_of_name(name)),
5130 None => Kind::Unknown,
5131 }
5132 }
5133
5134 fn struct_name_of_kind(&self, k: Kind) -> Option<String> {
5136 match k {
5137 Kind::Struct(si) => self
5138 .prog
5139 .struct_types
5140 .get(si as usize)
5141 .map(|s| s.name.clone()),
5142 Kind::StructSynth(idx) => self
5143 .synth_structs
5144 .borrow()
5145 .get(idx as usize)
5146 .map(|(n, _)| n.clone()),
5147 _ => None,
5148 }
5149 }
5150
5151 fn synth_field_cell(&self, idx: u16, field: u32) -> Option<usize> {
5155 let fname = self.const_text(field)?.to_string();
5156 let mut t = self.synth_structs.borrow_mut();
5157 let (_, fields) = t.get_mut(idx as usize)?;
5158 Some(match fields.iter().position(|(f, _)| f == &fname) {
5159 Some(cell) => cell,
5160 None => {
5161 fields.push((fname, Kind::Unknown));
5162 fields.len() - 1
5163 }
5164 })
5165 }
5166
5167 fn synth_refine_field_kind(&self, idx: u16, field: u32, k: Kind) {
5171 if k == Kind::Unknown {
5172 return;
5173 }
5174 let cell = match self.synth_field_cell(idx, field) {
5175 Some(c) => c,
5176 None => return,
5177 };
5178 let mut t = self.synth_structs.borrow_mut();
5179 if let Some((_, fields)) = t.get_mut(idx as usize) {
5180 if let Some((_, fk)) = fields.get_mut(cell) {
5181 *fk = k;
5182 }
5183 }
5184 }
5185
5186 fn struct_cell_count(&self, k: Kind) -> Option<usize> {
5188 match k {
5189 Kind::Struct(si) => self
5190 .prog
5191 .struct_types
5192 .get(si as usize)
5193 .map(|s| s.fields.len()),
5194 Kind::StructSynth(idx) => self
5195 .synth_structs
5196 .borrow()
5197 .get(idx as usize)
5198 .map(|(_, f)| f.len()),
5199 _ => None,
5200 }
5201 }
5202
5203 fn field_storage_kind(&self, obj: Kind, cell: usize) -> Option<Kind> {
5206 if self
5207 .struct_name_of_kind(obj)
5208 .is_some_and(|name| self.counter_fields.contains(&(name, cell)))
5209 {
5210 return Some(Kind::CrdtCounter);
5211 }
5212 let kind = match obj {
5213 Kind::Struct(si) => {
5214 let (_, ty) = self.prog.struct_types.get(si as usize)?.fields.get(cell)?;
5215 self.boundary_kind(&Some(ty.clone()))
5216 }
5217 Kind::StructSynth(si) => self.synth_structs.borrow().get(si as usize)?.1.get(cell)?.1,
5218 _ => return None,
5219 };
5220 Some(if kind == Kind::Int {
5221 Kind::BigInt
5222 } else {
5223 kind
5224 })
5225 }
5226
5227 fn field_store_value(
5228 &mut self,
5229 obj: Kind,
5230 cell: usize,
5231 value: u16,
5232 kind: Kind,
5233 ) -> Result<String, String> {
5234 if self.field_storage_kind(obj, cell) == Some(Kind::Tower) && kind != Kind::Tower {
5235 let converted = self.tower_of(&format!("r[{value}]"), kind)?;
5236 return Ok(format!("(int64_t)(intptr_t)({converted})"));
5237 }
5238 if self.field_storage_kind(obj, cell) == Some(Kind::Rational) && kind != Kind::Rational {
5241 if kind == Kind::Nothing {
5242 return Err(
5243 "a record's rational field holding nothing: a compiled rational holds no nothing".to_string(),
5244 );
5245 }
5246 return self.convert_number(&format!("r[{value}]"), kind, Kind::Rational);
5247 }
5248 if self.field_storage_kind(obj, cell) == Some(Kind::BigInt) {
5249 if kind == Kind::Nothing {
5252 return Ok("0".to_string());
5253 }
5254 as_exact(value, kind)
5255 } else {
5256 Ok(format!("r[{value}]"))
5257 }
5258 }
5259
5260 fn resolve_field_any(&self, obj_kind: Kind, field: u32) -> Option<(usize, Kind)> {
5265 match obj_kind {
5266 Kind::Struct(si) => self.resolve_field(si, field),
5267 Kind::StructSynth(idx) => {
5268 let cell = self.synth_field_cell(idx, field)?;
5269 let fk = self
5270 .synth_structs
5271 .borrow()
5272 .get(idx as usize)
5273 .and_then(|(_, f)| f.get(cell))
5274 .map(|(_, k)| *k)
5275 .unwrap_or(Kind::Unknown);
5276 Some((cell, self.field_storage_kind(obj_kind, cell).unwrap_or(fk)))
5277 }
5278 _ => None,
5279 }
5280 }
5281
5282 fn resolve_field(&self, struct_idx: u16, field: u32) -> Option<(usize, Kind)> {
5285 let sd = self.prog.struct_types.get(struct_idx as usize)?;
5286 let fname = match self.prog.constants.get(field as usize) {
5287 Some(Constant::Text(s)) => s.clone(),
5288 _ => return None,
5289 };
5290 let idx = sd.fields.iter().position(|(n, _)| *n == fname)?;
5291 Some((idx, self.field_storage_kind(Kind::Struct(struct_idx), idx)?))
5292 }
5293
5294 fn field_cell_by_name(&self, type_name: &str, field_name: &str) -> Option<(usize, Kind)> {
5298 let sd = self
5299 .prog
5300 .struct_types
5301 .iter()
5302 .position(|s| s.name == type_name)?;
5303 let struct_idx = sd as u16;
5304 let sd = &self.prog.struct_types[sd];
5305 let idx = sd.fields.iter().position(|(n, _)| n == field_name)?;
5306 Some((idx, self.field_storage_kind(Kind::Struct(struct_idx), idx)?))
5307 }
5308
5309 fn policy_condition_expr(
5313 &self,
5314 cond: &PolicyCondition,
5315 subject: u16,
5316 subject_type: &str,
5317 object: u16,
5318 object_type: Option<&str>,
5319 interner: &Interner,
5320 ) -> Result<String, String> {
5321 let cell_c = |reg: u16, cell: usize| format!("((int64_t*)(intptr_t)r[{reg}])[{cell}]");
5322 match cond {
5323 PolicyCondition::FieldEquals {
5324 field,
5325 value,
5326 is_string_literal,
5327 } => {
5328 let fname = interner.resolve(*field);
5329 let vstr = interner.resolve(*value);
5330 let (cell, fk) = self
5331 .field_cell_by_name(subject_type, fname)
5332 .ok_or("policy references an unknown field")?;
5333 let lv = cell_c(subject, cell);
5334 Ok(match fk {
5335 Kind::Text => format!(
5336 "(strcmp((const char*)(intptr_t){lv}, \"{}\")==0)",
5337 c_escape(vstr)
5338 ),
5339 Kind::BigInt if !*is_string_literal => match vstr.parse::<i64>() {
5340 Ok(n) => format!("(lc_xcmp({lv}, lc_xof({n}LL)) == 0)"),
5341 Err(_) => "0".to_string(),
5342 },
5343 Kind::Bool if !*is_string_literal => match vstr.parse::<bool>() {
5344 Ok(b) => format!("({lv} == {})", b as i64),
5345 Err(_) => "0".to_string(),
5346 },
5347 _ => "0".to_string(), })
5349 }
5350 PolicyCondition::FieldBool { field, value } => {
5351 let fname = interner.resolve(*field);
5352 let (cell, _) = self
5353 .field_cell_by_name(subject_type, fname)
5354 .ok_or("policy references an unknown field")?;
5355 Ok(format!("({} == {})", cell_c(subject, cell), *value as i64))
5356 }
5357 PolicyCondition::Predicate { predicate, .. } => {
5358 let sub = self.policy_ctx.and_then(|(_, policies)| {
5359 interner.lookup(subject_type).and_then(|ts| {
5360 policies.get_predicates(ts).and_then(|ps| {
5361 ps.iter()
5362 .find(|p| p.predicate_name == *predicate)
5363 .map(|p| p.condition.clone())
5364 })
5365 })
5366 });
5367 match sub {
5368 Some(c) => self.policy_condition_expr(
5369 &c,
5370 subject,
5371 subject_type,
5372 object,
5373 object_type,
5374 interner,
5375 ),
5376 None => Ok("0".to_string()),
5377 }
5378 }
5379 PolicyCondition::ObjectFieldEquals {
5380 subject: subj_field,
5381 field,
5382 ..
5383 } => self.field_equality_expr(
5384 subject,
5385 subject_type,
5386 *subj_field,
5387 object,
5388 object_type,
5389 *field,
5390 interner,
5391 ),
5392 PolicyCondition::SubjectFieldEqualsObjectField {
5393 subject_field,
5394 object_field,
5395 ..
5396 } => self.field_equality_expr(
5397 subject,
5398 subject_type,
5399 *subject_field,
5400 object,
5401 object_type,
5402 *object_field,
5403 interner,
5404 ),
5405 PolicyCondition::Or(l, r) => {
5406 let le = self.policy_condition_expr(
5407 l,
5408 subject,
5409 subject_type,
5410 object,
5411 object_type,
5412 interner,
5413 )?;
5414 let re = self.policy_condition_expr(
5415 r,
5416 subject,
5417 subject_type,
5418 object,
5419 object_type,
5420 interner,
5421 )?;
5422 Ok(format!("(({le}) || ({re}))"))
5423 }
5424 PolicyCondition::And(l, r) => {
5425 let le = self.policy_condition_expr(
5426 l,
5427 subject,
5428 subject_type,
5429 object,
5430 object_type,
5431 interner,
5432 )?;
5433 let re = self.policy_condition_expr(
5434 r,
5435 subject,
5436 subject_type,
5437 object,
5438 object_type,
5439 interner,
5440 )?;
5441 Ok(format!("(({le}) && ({re}))"))
5442 }
5443 }
5444 }
5445
5446 fn field_equality_expr(
5449 &self,
5450 subject: u16,
5451 subject_type: &str,
5452 subj_field: crate::intern::Symbol,
5453 object: u16,
5454 object_type: Option<&str>,
5455 obj_field: crate::intern::Symbol,
5456 interner: &Interner,
5457 ) -> Result<String, String> {
5458 let obj_type = match (object_type, object != u16::MAX) {
5459 (Some(t), true) => t,
5460 _ => return Ok("0".to_string()),
5461 };
5462 let sf = interner.resolve(subj_field);
5463 let of = interner.resolve(obj_field);
5464 let (sc, sk) = self
5465 .field_cell_by_name(subject_type, sf)
5466 .ok_or("policy references an unknown subject field")?;
5467 let (oc, _) = self
5468 .field_cell_by_name(obj_type, of)
5469 .ok_or("policy references an unknown object field")?;
5470 let sl = format!("((int64_t*)(intptr_t)r[{subject}])[{sc}]");
5471 let ol = format!("((int64_t*)(intptr_t)r[{object}])[{oc}]");
5472 Ok(match sk {
5473 Kind::Text => {
5474 format!("(strcmp((const char*)(intptr_t){sl}, (const char*)(intptr_t){ol})==0)")
5475 }
5476 _ => format!("({sl} == {ol})"),
5477 })
5478 }
5479
5480 fn seq_ptr(reg: u16, seq_kind: Kind) -> Result<String, String> {
5482 let ty = seq_kind
5483 .seq_c_type()
5484 .ok_or_else(|| format!("sequence op on a non-sequence kind {seq_kind:?}"))?;
5485 Ok(format!("(({ty}*)(intptr_t)r[{reg}])"))
5486 }
5487
5488 fn set_ptr(reg: u16, set_kind: Kind) -> Result<String, String> {
5490 let ty = set_kind
5491 .set_c_type()
5492 .ok_or_else(|| format!("set op on a non-set kind {set_kind:?}"))?;
5493 Ok(format!("(({ty}*)(intptr_t)r[{reg}])"))
5494 }
5495
5496 fn clone_collection_expr(&mut self, reg: u16, k: Kind) -> Result<Option<String>, String> {
5500 if k == Kind::Tower {
5501 return Ok(Some(format!("(int64_t)(intptr_t)ltower_own((LTower)(intptr_t)r[{reg}])")));
5502 }
5503 let k = match k {
5504 Kind::SeqUnknown => Kind::SeqInt,
5505 Kind::SetUnknown => Kind::SetInt,
5506 other => other,
5507 };
5508 if let (Some(ty), Some(infix)) = (k.seq_c_type(), k.seq_infix()) {
5509 let ptr = Self::seq_ptr(reg, k)?;
5510 let t = self.fresh();
5511 self.line(&format!(
5512 "{ty} *_cl{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_cl{t} = seq_{infix}_copy({ptr});"
5513 ));
5514 Ok(Some(format!("(int64_t)(intptr_t)_cl{t}")))
5515 } else if is_concrete_set(k) {
5516 let (ty, infix) = (k.set_c_type().unwrap(), k.seq_infix().unwrap());
5517 let ptr = Self::set_ptr(reg, k)?;
5518 let t = self.fresh();
5519 self.line(&format!(
5520 "{ty} *_cl{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_cl{t} = lc_set_{infix}_copy({ptr});"
5521 ));
5522 Ok(Some(format!("(int64_t)(intptr_t)_cl{t}")))
5523 } else if let Some((key, val)) = self.resolve_map_kv(k) {
5524 let (ty, pre) = (map_type_name(key, val).unwrap(), map_fn(key, val).unwrap());
5525 let t = self.fresh();
5526 self.line(&format!("{ty} *_cl{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_cl{t} = lc_{pre}_copy(({ty}*)(intptr_t)r[{reg}]);"));
5527 Ok(Some(format!("(int64_t)(intptr_t)_cl{t}")))
5528 } else if let Some(n) = self.struct_cell_count(k) {
5529 let t = self.fresh();
5533 self.line(&format!(
5534 "int64_t *_cl{t} = (int64_t*)lc_gc_alloc({} * sizeof(int64_t)); memcpy(_cl{t}, (void*)(intptr_t)r[{reg}], {n} * sizeof(int64_t)); _cl{t}[{n}] = 0;",
5535 n + 1
5536 ));
5537 Ok(Some(format!("(int64_t)(intptr_t)_cl{t}")))
5538 } else {
5539 Ok(None)
5540 }
5541 }
5542
5543 fn field_owner_word(&self, obj: u16, k: Kind) -> Option<String> {
5546 let n = self.struct_cell_count(k)?;
5547 Some(format!("((uint64_t*)(intptr_t)r[{obj}])[{n}]"))
5548 }
5549
5550 fn render(&mut self, v: &str, k: Kind) -> Result<String, String> {
5555 let n = self.fresh();
5556 Ok(match k {
5557 Kind::Int => format!("logos_dyn_sprintf(\"%\" PRId64, (int64_t)({v}))"),
5558 Kind::Bool => format!("(({v}) ? \"true\" : \"false\")"),
5559 Kind::Float => format!("logos_fmt_f64(as_f64({v}))"),
5560 Kind::Text => format!("((const char*)(intptr_t)({v}))"),
5561 Kind::Rational => format!("ldec_to_string((LDec*)(intptr_t)({v}))"),
5562 Kind::BigInt => format!("lint_to_string(lc_xget({v}))"),
5563 Kind::Tower => format!("ltower_to_string((LTower)(intptr_t)({v}))"),
5564 Kind::Nothing => "\"nothing\"".to_string(),
5565 Kind::Peer => format!("logos_dyn_sprintf(\"<peer %s>\", (const char*)(intptr_t)({v}))"),
5566 Kind::Date => format!("logos_date_to_string((int64_t)({v}))"),
5567 Kind::Word(_) => format!("logos_dyn_sprintf(\"%\" PRIu64, (uint64_t)({v}))"),
5568 Kind::Moment => format!("logos_moment_to_string((int64_t)({v}))"),
5569 Kind::Time => format!("logos_format_time_of_day((int64_t)({v}))"),
5570 Kind::Span => format!(
5571 "logos_span_to_string((LogosSpan){{(int32_t)((int64_t)({v}) >> 32), (int32_t)(int64_t)({v})}})"
5572 ),
5573 Kind::CrdtCounter => {
5574 format!("logos_dyn_sprintf(\"%lld\", logos_crdt_sum((logos_crdt*)(intptr_t)({v})))")
5575 }
5576 Kind::SeqInt | Kind::SeqFloat | Kind::SeqText | Kind::SeqBool | Kind::SeqUnknown => {
5577 let sk = seq_kind_of(k);
5578 format!(
5579 "seq_{}_to_string(({}*)(intptr_t)({v}))",
5580 sk.seq_infix().unwrap(),
5581 sk.seq_c_type().unwrap()
5582 )
5583 }
5584 Kind::SetWord(_) => format!("set_i64_to_string_as((Set_i64*)(intptr_t)({v}), 1)"),
5585 Kind::SetInt | Kind::SetText | Kind::SetUnknown => {
5586 let sk = resolve_set(k).unwrap();
5587 format!(
5588 "set_{}_to_string(({}*)(intptr_t)({v}))",
5589 sk.seq_infix().unwrap(),
5590 sk.set_c_type().unwrap()
5591 )
5592 }
5593 Kind::Map(_) | Kind::MapUnknown => {
5594 let (kk, vk) = self.resolve_map_kv(k).unwrap();
5595 let ty = map_type_name(kk, vk).unwrap();
5596 let key = if kk == Kind::Text {
5597 format!("_m{n}->keys[_si{n}]")
5598 } else {
5599 format!("_m{n}->keys[_si{n}]")
5600 };
5601 let key_text = self.render(&format!("(int64_t)(intptr_t){key}"), kk)?;
5602 let val = map_val_load(&format!("_m{n}->vals[_si{n}]"), vk);
5603 let val_text = self.render(&val, vk)?;
5604 format!(
5605 "({{ {ty} *_m{n} = ({ty}*)(intptr_t)({v}); lc_sb _b{n} = {{0}}; lc_sb_add(&_b{n}, \"{{\"); int _f{n} = 1; \
5606 for (size_t _oi{n} = 0; _oi{n} < _m{n}->ord_len; _oi{n}++) {{ size_t _si{n} = _m{n}->order[_oi{n}]; if (!_m{n}->state[_si{n}]) continue; \
5607 if (!_f{n}) lc_sb_add(&_b{n}, \", \"); _f{n} = 0; lc_sb_add(&_b{n}, {key_text}); lc_sb_add(&_b{n}, \": \"); lc_sb_add(&_b{n}, {val_text}); }} \
5608 lc_sb_add(&_b{n}, \"}}\"); _b{n}.s; }})"
5609 )
5610 }
5611 Kind::Tuple(ti) => {
5612 let poss = self.tuple_positions(ti);
5613 let mut parts = String::new();
5614 for (i, pk) in poss.iter().enumerate() {
5615 if i > 0 {
5616 parts.push_str(&format!("lc_sb_add(&_b{n}, \", \"); "));
5617 }
5618 let cell = self.render(&format!("_t{n}[{i}]"), *pk)?;
5619 parts.push_str(&format!("lc_sb_add(&_b{n}, {cell}); "));
5620 }
5621 format!("({{ int64_t *_t{n} = (int64_t*)(intptr_t)({v}); lc_sb _b{n} = {{0}}; lc_sb_add(&_b{n}, \"(\"); {parts}lc_sb_add(&_b{n}, \")\"); _b{n}.s; }})")
5622 }
5623 Kind::SeqOf(ei) => {
5624 let item = self.render(
5625 &format!("_s{n}->data[_i{n}]"),
5626 self.elem_kind(Kind::SeqOf(ei)),
5627 )?;
5628 format!(
5629 "({{ Seq_i64 *_s{n} = (Seq_i64*)(intptr_t)({v}); lc_sb _b{n} = {{0}}; lc_sb_add(&_b{n}, \"[\"); \
5630 for (size_t _i{n} = 0; _i{n} < _s{n}->len; _i{n}++) {{ if (_i{n}) lc_sb_add(&_b{n}, \", \"); lc_sb_add(&_b{n}, {item}); }} \
5631 lc_sb_add(&_b{n}, \"]\"); _b{n}.s; }})"
5632 )
5633 }
5634 Kind::Struct(_) | Kind::StructSynth(_) => {
5635 let name = self.struct_name_of_kind(k).ok_or("a record with no name")?;
5636 let mut fields: Vec<(String, usize, Kind)> = match k {
5637 Kind::Struct(si) => {
5638 let sd = self.prog.struct_types[si as usize].clone();
5639 sd.fields
5640 .iter()
5641 .enumerate()
5642 .map(|(c, (f, _))| {
5643 (f.clone(), c, self.field_storage_kind(k, c).unwrap())
5644 })
5645 .collect()
5646 }
5647 Kind::StructSynth(idx) => {
5648 let fs = self.synth_structs.borrow()[idx as usize].1.clone();
5649 fs.into_iter()
5650 .enumerate()
5651 .map(|(c, (f, _))| (f, c, self.field_storage_kind(k, c).unwrap()))
5652 .collect()
5653 }
5654 _ => unreachable!(),
5655 };
5656 if fields.is_empty() {
5657 return Ok(format!("\"{}\"", c_escape(&name)));
5658 }
5659 fields.sort_by(|a, b| a.0.cmp(&b.0));
5660 let mut parts = String::new();
5661 for (i, (f, c, fk)) in fields.iter().enumerate() {
5662 let is_counter = self.counter_fields.contains(&(name.clone(), *c));
5663 let fk = if is_counter { Kind::CrdtCounter } else { *fk };
5664 let cell = self.render(&format!("_r{n}[{c}]"), fk)?;
5665 let label = format!("{}{}: ", if i > 0 { ", " } else { "" }, f);
5666 parts.push_str(&format!(
5667 "lc_sb_add(&_b{n}, \"{}\"); lc_sb_add(&_b{n}, {cell}); ",
5668 c_escape(&label)
5669 ));
5670 }
5671 format!(
5672 "({{ int64_t *_r{n} = (int64_t*)(intptr_t)({v}); lc_sb _b{n} = {{0}}; lc_sb_add(&_b{n}, \"{} {{ \"); {parts}lc_sb_add(&_b{n}, \" }}\"); _b{n}.s; }})",
5673 c_escape(&name)
5674 )
5675 }
5676 Kind::Enum(ei) => {
5677 let ed = self.prog.enum_types[ei as usize].clone();
5678 let arms: Vec<(String, Option<Vec<Kind>>)> = ed
5679 .variants
5680 .iter()
5681 .map(|var| {
5682 (
5683 var.name.clone(),
5684 Some(
5685 var.field_types
5686 .iter()
5687 .map(|t| self.boundary_kind(&Some(t.clone())))
5688 .collect(),
5689 ),
5690 )
5691 })
5692 .collect();
5693 self.render_constructors(v, &arms, n)?
5694 }
5695 Kind::Inductive(ii) => {
5696 let (ty, ctors) = self.inductive(ii);
5697 if ty == "Option" {
5698 let can_be_some = ctors.iter().any(|(c, ks)| c == "Some" && ks.is_some());
5700 if !can_be_some {
5701 "\"nothing\"".to_string()
5702 } else {
5703 let some = self.ctor_tag("Option", "Some").unwrap();
5704 let payload = self.inductive_payload(ii, "Some", 0);
5705 let inner = self.render(&format!("_o{n}[1]"), payload)?;
5706 format!("({{ int64_t *_o{n} = (int64_t*)(intptr_t)({v}); (_o{n}[0] == {some}) ? {inner} : \"nothing\"; }})")
5707 }
5708 } else {
5709 self.render_constructors(v, &ctors, n)?
5710 }
5711 }
5712 Kind::Optional(oi) if self.opt_inner(oi) == Kind::Never => "\"nothing\"".to_string(),
5714 Kind::Optional(oi) => {
5715 let inner = self.render(&format!("_o{n}[1]"), self.opt_inner(oi))?;
5716 format!("({{ int64_t *_o{n} = (int64_t*)(intptr_t)({v}); _o{n}[0] ? {inner} : \"nothing\"; }})")
5717 }
5718 other => return Err(format!("the display of a {other:?} value")),
5719 })
5720 }
5721
5722 fn render_constructors(
5725 &mut self,
5726 v: &str,
5727 arms: &[(String, Option<Vec<Kind>>)],
5728 n: usize,
5729 ) -> Result<String, String> {
5730 let mut cases = String::new();
5731 for (tag, (name, payloads)) in arms.iter().enumerate() {
5732 let Some(payloads) = payloads else { continue };
5734 let mut body = format!("lc_sb_add(&_b{n}, \"{}\"); ", c_escape(name));
5735 if !payloads.is_empty() {
5736 body.push_str(&format!("lc_sb_add(&_b{n}, \"(\"); "));
5737 for (i, pk) in payloads.iter().enumerate() {
5738 if i > 0 {
5739 body.push_str(&format!("lc_sb_add(&_b{n}, \", \"); "));
5740 }
5741 let part = self.render(&format!("_e{n}[{}]", i + 1), *pk)?;
5742 body.push_str(&format!("lc_sb_add(&_b{n}, {part}); "));
5743 }
5744 body.push_str(&format!("lc_sb_add(&_b{n}, \")\"); "));
5745 }
5746 cases.push_str(&format!("case {tag}: {body}break; "));
5747 }
5748 Ok(format!(
5749 "({{ int64_t *_e{n} = (int64_t*)(intptr_t)({v}); lc_sb _b{n} = {{0}}; lc_sb_add(&_b{n}, \"\"); switch (_e{n}[0]) {{ {cases}}} _b{n}.s; }})"
5750 ))
5751 }
5752
5753 fn peer_address(&mut self, reg: u16, kinds: &[Kind]) -> Result<Option<String>, String> {
5756 match kind_at(kinds, reg) {
5757 Kind::Peer | Kind::Text => Ok(Some(as_cstr(reg))),
5758 k => {
5759 let sample =
5760 sample_value(k).ok_or_else(|| format!("a peer address of kind {k:?}"))?;
5761 self.line(&format!("logos_fatalf({});", words::not_a_peer(sample)));
5762 Ok(None)
5763 }
5764 }
5765 }
5766
5767 fn eq_expr(&mut self, a: &str, ka: Kind, b: &str, kb: Kind) -> Result<String, String> {
5772 let numeric =
5773 |k: Kind| matches!(k, Kind::Int | Kind::Float | Kind::BigInt | Kind::Rational);
5774 let n = self.fresh();
5775 Ok(match (ka, kb) {
5776 (x, y) if numeric(x) && numeric(y) => {
5777 let (la, lb) = (self.scalar_c(a, x), self.scalar_c(b, y));
5778 match (x, y) {
5779 (Kind::Int, Kind::Int) => format!("({a} == {b})"),
5780 (Kind::BigInt | Kind::Int, Kind::BigInt | Kind::Int) => {
5781 let tagged = |v: &str, k: Kind| {
5782 if k == Kind::BigInt {
5783 v.to_string()
5784 } else {
5785 format!("lc_xof({v})")
5786 }
5787 };
5788 format!("(lc_xcmp({}, {}) == 0)", tagged(a, x), tagged(b, y))
5789 }
5790 (Kind::Rational, Kind::Rational) => format!("(ldec_cmp({la}, {lb}) == 0)"),
5791 (Kind::Rational, Kind::Int) => format!("(ldec_cmp({la}, ldec_from_i64({b})) == 0)"),
5792 (Kind::Int, Kind::Rational) => format!("(ldec_cmp(ldec_from_i64({a}), {lb}) == 0)"),
5793 (Kind::Rational, Kind::BigInt) => format!("(ldec_cmp({la}, ldec_from_lint({lb})) == 0)"),
5794 (Kind::BigInt, Kind::Rational) => format!("(ldec_cmp(ldec_from_lint({la}), {lb}) == 0)"),
5795 (Kind::Rational, Kind::Float) => format!("(ldec_cmp_f64({la}, {lb}) == 0)"),
5796 (Kind::Float, Kind::Rational) => format!("(ldec_cmp_f64({lb}, {la}) == 0)"),
5797 (Kind::BigInt, Kind::Float) => format!("(lint_to_f64({la}) == {lb})"),
5798 (Kind::Float, Kind::BigInt) => format!("({la} == lint_to_f64({lb}))"),
5799 _ => format!("({la} == {lb})"),
5800 }
5801 }
5802 (Kind::Bool, Kind::Bool) | (Kind::Date, Kind::Date) | (Kind::Moment, Kind::Moment) => {
5803 format!("({a} == {b})")
5804 }
5805 (Kind::Word(x), Kind::Word(y)) => {
5806 if x == y {
5807 format!("({a} == {b})")
5808 } else {
5809 "0".to_string()
5810 }
5811 }
5812 (Kind::Text, Kind::Text) | (Kind::Peer, Kind::Peer) => {
5813 format!("str_equals((const char*)(intptr_t)({a}), (const char*)(intptr_t)({b}))")
5814 }
5815 (Kind::Nothing, Kind::Nothing) => "1".to_string(),
5816 (Kind::Nothing, Kind::Inductive(ii)) | (Kind::Inductive(ii), Kind::Nothing) => {
5817 let v = if ka == Kind::Nothing { b } else { a };
5818 match (
5819 self.inductive(ii).0.as_str(),
5820 self.ctor_tag("Option", "None"),
5821 ) {
5822 ("Option", Some(none)) => format!("(((int64_t*)(intptr_t)({v}))[0] == {none})"),
5823 _ => "0".to_string(),
5824 }
5825 }
5826 (Kind::Optional(oi), other) | (other, Kind::Optional(oi))
5827 if !matches!(other, Kind::Optional(_)) =>
5828 {
5829 let (o, v) = if matches!(ka, Kind::Optional(_)) {
5830 (a, b)
5831 } else {
5832 (b, a)
5833 };
5834 let inner = self.opt_inner(oi);
5835 if other == Kind::Nothing {
5836 format!("(!((int64_t*)(intptr_t)({o}))[0])")
5837 } else {
5838 let same = self.eq_expr(&format!("_o{n}[1]"), inner, v, other)?;
5839 format!(
5840 "({{ int64_t *_o{n} = (int64_t*)(intptr_t)({o}); _o{n}[0] && {same}; }})"
5841 )
5842 }
5843 }
5844 (Kind::Optional(x), Kind::Optional(y)) => {
5845 let same = self.eq_expr(
5846 &format!("_p{n}[1]"),
5847 self.opt_inner(x),
5848 &format!("_q{n}[1]"),
5849 self.opt_inner(y),
5850 )?;
5851 format!("({{ int64_t *_p{n} = (int64_t*)(intptr_t)({a}), *_q{n} = (int64_t*)(intptr_t)({b}); (_p{n}[0] == _q{n}[0]) && (!_p{n}[0] || {same}); }})")
5852 }
5853 (x, y) if is_sequence(x) && is_sequence(y) => {
5854 let (sx, sy) = (seq_kind_of(x), seq_kind_of(y));
5855 let (tx, ty) = (sx.seq_c_type().unwrap(), sy.seq_c_type().unwrap());
5856 let (ex, ey) = (self.elem_kind(x), self.elem_kind(y));
5857 let same = if ex == Kind::Never || ey == Kind::Never {
5858 "1".to_string()
5859 } else {
5860 let (ca, cb) = (
5861 elem_to_reg(&format!("_s{n}->data[_i{n}]"), ex),
5862 elem_to_reg(&format!("_t{n}->data[_i{n}]"), ey),
5863 );
5864 self.eq_expr(&ca, ex, &cb, ey)?
5865 };
5866 format!(
5867 "({{ {tx} *_s{n} = ({tx}*)(intptr_t)({a}); {ty} *_t{n} = ({ty}*)(intptr_t)({b}); int _e{n} = _s{n}->len == _t{n}->len; \
5868 for (size_t _i{n} = 0; _e{n} && _i{n} < _s{n}->len; _i{n}++) _e{n} = {same}; _e{n}; }})"
5869 )
5870 }
5871 (Kind::Tuple(x), Kind::Tuple(y)) => {
5872 let (px, py) = (self.tuple_positions(x), self.tuple_positions(y));
5873 if px.len() != py.len() {
5874 return Ok("0".to_string());
5875 }
5876 let mut parts = Vec::new();
5877 for (i, (kx, ky)) in px.iter().zip(py.iter()).enumerate() {
5878 parts.push(self.eq_expr(
5879 &format!("_s{n}[{i}]"),
5880 *kx,
5881 &format!("_t{n}[{i}]"),
5882 *ky,
5883 )?);
5884 }
5885 let all = if parts.is_empty() {
5886 "1".to_string()
5887 } else {
5888 parts.join(" && ")
5889 };
5890 format!("({{ int64_t *_s{n} = (int64_t*)(intptr_t)({a}), *_t{n} = (int64_t*)(intptr_t)({b}); {all}; }})")
5891 }
5892 (
5893 x @ (Kind::Struct(_) | Kind::StructSynth(_)),
5894 y @ (Kind::Struct(_) | Kind::StructSynth(_)),
5895 ) => {
5896 if self.struct_name_of_kind(x) != self.struct_name_of_kind(y) || x != y {
5897 return Ok("0".to_string());
5898 }
5899 let name = self.struct_name_of_kind(x).unwrap_or_default();
5900 let fields: Vec<(usize, Kind)> = match x {
5901 Kind::Struct(si) => {
5902 let sd = self.prog.struct_types[si as usize].clone();
5903 sd.fields
5904 .iter()
5905 .enumerate()
5906 .map(|(c, (_, _))| (c, self.field_storage_kind(x, c).unwrap()))
5907 .collect()
5908 }
5909 Kind::StructSynth(idx) => self.synth_structs.borrow()[idx as usize]
5910 .1
5911 .iter()
5912 .enumerate()
5913 .map(|(c, (_, _))| (c, self.field_storage_kind(x, c).unwrap()))
5914 .collect(),
5915 _ => unreachable!(),
5916 };
5917 let mut parts = Vec::new();
5918 for (c, fk) in fields {
5919 let fk = if self.counter_fields.contains(&(name.clone(), c)) {
5920 Kind::CrdtCounter
5921 } else {
5922 fk
5923 };
5924 let (ca, cb) = (format!("_s{n}[{c}]"), format!("_t{n}[{c}]"));
5925 parts.push(if fk == Kind::CrdtCounter {
5926 format!("(logos_crdt_sum((logos_crdt*)(intptr_t){ca}) == logos_crdt_sum((logos_crdt*)(intptr_t){cb}))")
5927 } else {
5928 self.eq_expr(&ca, fk, &cb, fk)?
5929 });
5930 }
5931 let all = if parts.is_empty() {
5932 "1".to_string()
5933 } else {
5934 parts.join(" && ")
5935 };
5936 format!("({{ int64_t *_s{n} = (int64_t*)(intptr_t)({a}), *_t{n} = (int64_t*)(intptr_t)({b}); {all}; }})")
5937 }
5938 (Kind::Enum(x), Kind::Enum(y)) if x == y => {
5939 let ed = self.prog.enum_types[x as usize].clone();
5940 let arms: Vec<(String, Option<Vec<Kind>>)> = ed
5941 .variants
5942 .iter()
5943 .map(|var| {
5944 (
5945 var.name.clone(),
5946 Some(
5947 var.field_types
5948 .iter()
5949 .map(|t| self.boundary_kind(&Some(t.clone())))
5950 .collect(),
5951 ),
5952 )
5953 })
5954 .collect();
5955 self.eq_constructors(a, b, &arms, n)?
5956 }
5957 (Kind::Inductive(x), Kind::Inductive(y)) => {
5958 let (tx, _) = self.inductive(x);
5959 let (ty, _) = self.inductive(y);
5960 if tx != ty {
5961 return Ok("0".to_string());
5962 }
5963 let merged = self
5964 .unify_inductive(x, y)
5965 .ok_or("an equality of two differently-shaped enums")?;
5966 let Kind::Inductive(m) = merged else {
5967 unreachable!()
5968 };
5969 let arms = self.inductive(m).1;
5970 self.eq_constructors(a, b, &arms, n)?
5971 }
5972 (
5973 Kind::SetInt | Kind::SetWord(_) | Kind::SetText | Kind::SetUnknown,
5974 Kind::SetInt | Kind::SetWord(_) | Kind::SetText | Kind::SetUnknown,
5975 ) => {
5976 let (sx, sy) = (resolve_set(ka).unwrap(), resolve_set(kb).unwrap());
5977 if sx != sy && ka != Kind::SetUnknown && kb != Kind::SetUnknown {
5978 return Ok("0".to_string());
5979 }
5980 let (ty, infix) = (sx.set_c_type().unwrap(), sx.seq_infix().unwrap());
5981 format!(
5982 "({{ {ty} *_s{n} = ({ty}*)(intptr_t)({a}), *_t{n} = ({ty}*)(intptr_t)({b}); int _e{n} = _s{n}->len == _t{n}->len; \
5983 for (size_t _o{n} = 0; _e{n} && _o{n} < _s{n}->ord_len; _o{n}++) {{ size_t _k{n} = _s{n}->order[_o{n}]; if (_s{n}->state[_k{n}]) _e{n} = set_{infix}_contains(_t{n}, _s{n}->keys[_k{n}]); }} _e{n}; }})"
5984 )
5985 }
5986 (Kind::Map(_) | Kind::MapUnknown, Kind::Map(_) | Kind::MapUnknown) => {
5987 let ((kx, vx), (ky, vy)) = (
5988 self.resolve_map_kv(ka).unwrap(),
5989 self.resolve_map_kv(kb).unwrap(),
5990 );
5991 if kx != ky {
5992 return Ok("0".to_string());
5993 }
5994 let (tx, ty) = (
5995 map_type_name(kx, vx).unwrap(),
5996 map_type_name(ky, vy).unwrap(),
5997 );
5998 let (fy, key) = (map_fn(ky, vy).unwrap(), format!("_s{n}->keys[_k{n}]"));
5999 let va = map_val_load(&format!("_s{n}->vals[_k{n}]"), vx);
6000 let vb = map_val_load(&format!("{fy}_get(_t{n}, {key})"), vy);
6001 let same = self.eq_expr(&va, vx, &vb, vy)?;
6002 format!(
6003 "({{ {tx} *_s{n} = ({tx}*)(intptr_t)({a}); {ty} *_t{n} = ({ty}*)(intptr_t)({b}); int _e{n} = _s{n}->len == _t{n}->len; \
6004 for (size_t _o{n} = 0; _e{n} && _o{n} < _s{n}->ord_len; _o{n}++) {{ size_t _k{n} = _s{n}->order[_o{n}]; if (_s{n}->state[_k{n}]) _e{n} = {fy}_contains(_t{n}, {key}) && {same}; }} _e{n}; }})"
6005 )
6006 }
6007 (Kind::Unknown, _) | (_, Kind::Unknown) => {
6008 return Err(format!("an equality over {ka:?} and {kb:?}"))
6009 }
6010 _ => "0".to_string(),
6011 })
6012 }
6013
6014 fn eq_constructors(
6017 &mut self,
6018 a: &str,
6019 b: &str,
6020 arms: &[(String, Option<Vec<Kind>>)],
6021 n: usize,
6022 ) -> Result<String, String> {
6023 let mut cases = String::new();
6024 for (tag, (_, payloads)) in arms.iter().enumerate() {
6025 let Some(payloads) = payloads else { continue };
6026 let mut parts = Vec::new();
6027 for (i, pk) in payloads.iter().enumerate() {
6028 parts.push(self.eq_expr(
6029 &format!("_s{n}[{}]", i + 1),
6030 *pk,
6031 &format!("_t{n}[{}]", i + 1),
6032 *pk,
6033 )?);
6034 }
6035 let all = if parts.is_empty() {
6036 "1".to_string()
6037 } else {
6038 parts.join(" && ")
6039 };
6040 cases.push_str(&format!("case {tag}: _e{n} = {all}; break; "));
6041 }
6042 Ok(format!(
6043 "({{ int64_t *_s{n} = (int64_t*)(intptr_t)({a}), *_t{n} = (int64_t*)(intptr_t)({b}); int _e{n} = _s{n}[0] == _t{n}[0]; if (_e{n}) switch (_s{n}[0]) {{ {cases}default: break; }} _e{n}; }})"
6044 ))
6045 }
6046
6047 fn real_of(&self, reg: u16, k: Kind) -> String {
6052 if k == Kind::Rational {
6053 format!("ldec_to_f64(*(LDec*)(intptr_t)r[{reg}])")
6054 } else {
6055 as_double(reg, k)
6056 }
6057 }
6058
6059 fn int_operand(&self, kinds: &[Kind], reg: u16, place: words::IntegerPlace) -> String {
6063 match kind_at(kinds, reg) {
6064 Kind::Tower => format!(
6065 "ltower_int_at((LTower)(intptr_t)r[{reg}], {}, {})",
6066 words::integer_past_a_word(place),
6067 words::fraction_for_an_integer(place)
6068 ),
6069 Kind::Rational => format!(
6070 "ldec_as_i64(*(LDec*)(intptr_t)r[{reg}], {}, {})",
6071 words::integer_past_a_word(place),
6072 words::fraction_for_an_integer(place)
6073 ),
6074 _ => format!("r[{reg}]"),
6075 }
6076 }
6077
6078 fn rational_remainder(&mut self, dst: u16, lhs: u16, rhs: u16, lk: Kind, rk: Kind) -> Result<(), String> {
6081 if lk != Kind::Float && rk != Kind::Float {
6082 let (l, r) = (as_ldec(lhs, lk)?, as_ldec(rhs, rk)?);
6083 self.box_ldec(dst, &format!("ldec_remainder({l}, {r})"));
6084 return Ok(());
6085 }
6086 let fraction = RuntimeValue::from_rational(
6087 logicaffeine_base::Rational::from_ratio_i64(1, 2).expect("a nonzero denominator"),
6088 );
6089 let side = |k: Kind, whole: bool| {
6090 if k == Kind::Rational && !whole {
6091 Some(fraction.clone())
6092 } else {
6093 sample_value(if k == Kind::Rational { Kind::Int } else { k })
6094 }
6095 };
6096 let refused = |whole: bool| -> Result<String, String> {
6097 let (Some(l), Some(r)) = (side(lk, whole), side(rk, whole)) else {
6098 return Err(format!("a remainder of {lk:?} and {rk:?}"));
6099 };
6100 Ok(words::modulo_refused(l, r))
6101 };
6102 let (whole, fractional) = (refused(true)?, refused(false)?);
6103 let rational = if lk == Kind::Rational { lhs } else { rhs };
6104 self.line(&format!(
6105 "logos_fatalf(ldec_whole((LDec*)(intptr_t)r[{rational}]) ? {whole} : {fractional});"
6106 ));
6107 Ok(())
6108 }
6109
6110 fn list_kind(&self, items: &[Kind]) -> Result<Kind, String> {
6114 let integer = |k: Kind| matches!(k, Kind::Int | Kind::BigInt);
6115 let exact = |k: Kind| integer(k) || k == Kind::Rational;
6116 let known: Vec<Kind> = items.iter().copied().filter(|k| *k != Kind::Never).collect();
6117 let Some(&first) = known.first() else {
6118 return Ok(self.seq_kind_for(items.first().copied().unwrap_or(Kind::Unknown)));
6119 };
6120 if known.iter().all(|k| *k == first || (integer(*k) && integer(first))) {
6121 return Ok(self.seq_kind_for(first));
6122 }
6123 if known.iter().all(|k| exact(*k)) {
6124 return Ok(self.seq_kind_for(Kind::Rational));
6125 }
6126 Err("a list whose items differ in kind is not yet lowered".to_string())
6127 }
6128
6129 fn widens_to(&self, from: Kind, to: Kind) -> bool {
6132 match (from, to) {
6133 (Kind::Int | Kind::BigInt, Kind::Rational) => true,
6134 (Kind::SeqInt, Kind::SeqOf(_)) => self.elem_kind(to) == Kind::Rational,
6135 _ => false,
6136 }
6137 }
6138
6139 fn elem_value(&mut self, reg: u16, k: Kind, elem: Kind) -> Result<String, String> {
6142 if elem == Kind::Rational && matches!(k, Kind::Int | Kind::BigInt) {
6143 return self.convert_number(&format!("r[{reg}]"), k, Kind::Rational);
6144 }
6145 Ok(as_elem_push(reg, elem))
6146 }
6147
6148 fn box_ldec(&mut self, dst: u16, value: &str) {
6150 let n = self.fresh();
6151 self.line(&format!(
6152 "LDec *_rat{n} = (LDec*)lc_gc_alloc(sizeof(LDec)); *_rat{n} = {value}; r[{dst}] = (int64_t)(intptr_t)_rat{n};"
6153 ));
6154 }
6155
6156 fn scalar_c(&self, v: &str, k: Kind) -> String {
6157 match k {
6158 Kind::Float => format!("as_f64({v})"),
6159 Kind::BigInt => format!("lc_xget({v})"),
6160 Kind::Rational => format!("(*(LDec*)(intptr_t)({v}))"),
6161 _ => format!("({v})"),
6162 }
6163 }
6164
6165 fn text_of(&mut self, reg: u16, k: Kind) -> Result<String, String> {
6168 match k {
6169 Kind::Text | Kind::Bool | Kind::Float | Kind::Rational | Kind::BigInt | Kind::Int => {
6170 Ok(coerce_cstr(reg, k))
6171 }
6172 Kind::Word(_) => Ok(format!(
6173 "logos_dyn_sprintf(\"%\" PRIu64, (uint64_t)r[{reg}])"
6174 )),
6175 other => self.render(&format!("r[{reg}]"), other),
6176 }
6177 }
6178
6179 fn lower_op(
6180 &mut self,
6181 op: &Op,
6182 kinds: &[Kind],
6183 after: &[Kind],
6184 in_function: bool,
6185 pc: usize,
6186 ) -> Result<(), String> {
6187 match op {
6188 Op::LoadConst { dst, idx } => match &self.prog.constants[*idx as usize] {
6189 Constant::Int(n) => self.line(&format!("r[{dst}] = {n}LL;")),
6190 Constant::Duration(n) => self.line(&format!("r[{dst}] = {n}LL;")),
6191 Constant::Bool(b) => self.line(&format!("r[{dst}] = {};", if *b { 1 } else { 0 })),
6192 Constant::Float(f) => self.line(&format!(
6193 "r[{dst}] = (int64_t)0x{:016X}ULL; /* {} */",
6194 f.to_bits(),
6195 f
6196 )),
6197 Constant::Text(s) => self.line(&format!(
6198 "r[{dst}] = (int64_t)(intptr_t)\"{}\";",
6199 c_escape(s)
6200 )),
6201 Constant::Nothing => self.line(&format!("r[{dst}] = 0;")),
6206 Constant::Date(days) => self.line(&format!("r[{dst}] = {days}LL;")),
6207 Constant::Moment(nanos) | Constant::Time(nanos) => self.line(&format!("r[{dst}] = {nanos}LL;")),
6208 Constant::Span { months, days } => self.line(&format!(
6209 "r[{dst}] = (int64_t)(((uint64_t)(uint32_t){months} << 32) | (uint32_t){days});"
6210 )),
6211 c => return Err(format!("LoadConst {c:?} not yet lowered")),
6212 },
6213 Op::Move { dst, src } => self.line(&format!("r[{dst}] = r[{src}];")),
6214 Op::Add { dst, lhs, rhs }
6217 if self.exact_integers(&[*lhs, *rhs], kinds, kind_at(after, *dst)) =>
6218 {
6219 self.emit_exact_bin(
6220 *dst,
6221 *lhs,
6222 *rhs,
6223 kind_at(kinds, *lhs),
6224 kind_at(kinds, *rhs),
6225 "lc_xadd",
6226 )?;
6227 }
6228 Op::Add { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "+", kinds)?,
6229 Op::Sub { dst, lhs, rhs }
6230 if self.exact_integers(&[*lhs, *rhs], kinds, kind_at(after, *dst)) =>
6231 {
6232 self.emit_exact_bin(
6233 *dst,
6234 *lhs,
6235 *rhs,
6236 kind_at(kinds, *lhs),
6237 kind_at(kinds, *rhs),
6238 "lc_xsub",
6239 )?;
6240 }
6241 Op::Sub { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "-", kinds)?,
6242 Op::Mul { dst, lhs, rhs }
6243 if self.exact_integers(&[*lhs, *rhs], kinds, kind_at(after, *dst)) =>
6244 {
6245 self.emit_exact_bin(
6246 *dst,
6247 *lhs,
6248 *rhs,
6249 kind_at(kinds, *lhs),
6250 kind_at(kinds, *rhs),
6251 "lc_xmul",
6252 )?;
6253 }
6254 Op::Mul { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "*", kinds)?,
6255 Op::Div { dst, lhs, rhs }
6258 if self.exact_integers(&[*lhs, *rhs], kinds, kind_at(after, *dst)) =>
6259 {
6260 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, kind_at(after, *dst), "lc_xdiv")?;
6261 }
6262 Op::Div { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "/", kinds)?,
6263 Op::ExactDiv { dst, lhs, rhs } => {
6267 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
6268 let (l, r) = (as_ldec(*lhs, lk)?, as_ldec(*rhs, rk)?);
6269 self.line(&format!("lc_exact_divisor({});", exact_zero_test(*rhs, rk)));
6270 let n = self.fresh();
6271 self.line(&format!(
6272 "LDec *_rat{n} = (LDec*)lc_gc_alloc(sizeof(LDec)); *_rat{n} = ldec_div({l}, {r}); r[{dst}] = (int64_t)(intptr_t)_rat{n};"
6273 ));
6274 }
6275 Op::Mod { dst, lhs, rhs }
6278 if self.exact_integers(&[*lhs, *rhs], kinds, kind_at(after, *dst)) =>
6279 {
6280 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, kind_at(after, *dst), "lc_xmod")?;
6281 }
6282 Op::Mod { dst, lhs, rhs } => {
6283 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
6284 if lk == Kind::Rational || rk == Kind::Rational {
6285 self.rational_remainder(*dst, *lhs, *rhs, lk, rk)?;
6286 } else if matches!(lk, Kind::Word(_)) || matches!(rk, Kind::Word(_)) {
6287 self.word_arith(*dst, *lhs, *rhs, "%", lk, rk)?;
6289 } else if lk == Kind::Float || rk == Kind::Float {
6290 let (lv, rv) = (sample_value(lk), sample_value(rk));
6291 let (Some(lv), Some(rv)) = (lv, rv) else {
6292 return Err(format!("a remainder of {lk:?} and {rk:?}"));
6293 };
6294 self.line(&format!("logos_fatalf({});", words::modulo_refused(lv, rv)));
6295 } else {
6296 self.line(&format!("r[{dst}] = logos_imod(r[{lhs}], r[{rhs}]);"));
6297 }
6298 }
6299 Op::AddAssign { dst, src }
6300 if self.exact_integers(&[*dst, *src], kinds, kind_at(after, *dst)) =>
6301 {
6302 self.emit_exact_bin(
6303 *dst,
6304 *dst,
6305 *src,
6306 kind_at(kinds, *dst),
6307 kind_at(kinds, *src),
6308 "lc_xadd",
6309 )?;
6310 }
6311 Op::AddAssign { dst, src } => {
6312 let (dk, sk) = (kind_at(kinds, *dst), kind_at(kinds, *src));
6313 if dk == Kind::Text {
6314 if sk == Kind::Int
6317 && crate::optimize::active_config()
6318 .is_on(crate::optimization::Opt::StringAccumReuse)
6319 {
6320 crate::optimize::mark_fired(crate::optimization::Opt::StringAccumReuse);
6321 self.line(&format!("r[{dst}] = lc_text_append_i64(r[{dst}], own[{dst}], r[{src}]); own[{dst}] = 1;"));
6322 return Ok(());
6323 }
6324 let tail = self.text_of(*src, sk)?;
6325 if !matches!(sk, Kind::Int | Kind::Float | Kind::Rational | Kind::BigInt) {
6326 self.line(&format!("r[{dst}] = lc_text_append(r[{dst}], own[{dst}], {tail}); own[{dst}] = 1;"));
6327 } else {
6328 let n = self.fresh();
6329 self.line(&format!(
6330 "{{ char *_tail{n} = (char*)({tail}); r[{dst}] = lc_text_append(r[{dst}], own[{dst}], _tail{n}); lc_gc_free(_tail{n}); own[{dst}] = 1; }}"
6331 ));
6332 }
6333 } else if sk == Kind::Text {
6334 let (l, r) = (self.text_of(*dst, dk)?, self.text_of(*src, sk)?);
6335 self.line(&format!(
6336 "r[{dst}] = (int64_t)(intptr_t)str_concat({l}, {r});"
6337 ));
6338 } else if dk == Kind::Rational || sk == Kind::Rational {
6339 self.arith(*dst, *dst, *src, "+", kinds)?;
6340 } else if matches!(dk, Kind::Word(_)) || matches!(sk, Kind::Word(_)) {
6341 self.word_arith(*dst, *dst, *src, "+", dk, sk)?;
6343 } else if dk == Kind::Float || sk == Kind::Float {
6344 self.line(&format!(
6345 "r[{dst}] = as_bits({} + {});",
6346 as_double(*dst, dk),
6347 as_double(*src, sk)
6348 ));
6349 } else {
6350 self.line(&format!("r[{dst}] = r[{dst}] + r[{src}];"));
6351 }
6352 }
6353 Op::Pow { dst, lhs, rhs } => {
6354 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
6355 if (lk == Kind::Rational || rk == Kind::Rational) && lk != Kind::Float && rk != Kind::Float {
6356 let (b, e) = (as_ldec(*lhs, lk)?, as_ldec(*rhs, rk)?);
6357 self.box_ldec(*dst, &format!("ldec_power({b}, {e})"));
6358 } else if lk == Kind::Rational || rk == Kind::Rational {
6359 self.line(&format!(
6360 "r[{dst}] = as_bits(pow({}, {}));",
6361 self.real_of(*lhs, lk),
6362 self.real_of(*rhs, rk)
6363 ));
6364 } else if kind_at(after, *dst) == Kind::BigInt {
6365 let base = as_exact(*lhs, lk)?;
6366 let exponent = if rk == Kind::BigInt {
6367 format!("lc_xnarrow(r[{rhs}])")
6368 } else {
6369 format!("r[{rhs}]")
6370 };
6371 self.line(&format!("r[{dst}] = lc_xpow({base}, {exponent});"));
6372 } else if lk == Kind::Float || rk == Kind::Float {
6373 self.line(&format!(
6374 "r[{dst}] = as_bits(pow({}, {}));",
6375 as_double(*lhs, lk),
6376 as_double(*rhs, rk)
6377 ));
6378 } else {
6379 self.line(&format!("r[{dst}] = ipow64(r[{lhs}], r[{rhs}]);"));
6380 }
6381 }
6382 Op::FloorDiv { dst, lhs, rhs }
6383 if self.exact_integers(&[*lhs, *rhs], kinds, kind_at(after, *dst)) =>
6384 {
6385 self.emit_exact_bounded(
6386 *dst,
6387 *lhs,
6388 *rhs,
6389 kinds,
6390 kind_at(after, *dst),
6391 "lc_xfloordiv",
6392 )?;
6393 }
6394 Op::FloorDiv { dst, lhs, rhs } => {
6395 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
6396 if lk == Kind::Float || rk == Kind::Float {
6397 self.line(&format!(
6398 "r[{dst}] = as_bits(lc_rfldiv({}, {}));",
6399 self.real_of(*lhs, lk),
6400 self.real_of(*rhs, rk)
6401 ));
6402 } else if lk == Kind::Rational || rk == Kind::Rational {
6403 let (l, r) = (as_ldec(*lhs, lk)?, as_ldec(*rhs, rk)?);
6404 self.box_ldec(*dst, &format!("ldec_floor_quotient({l}, {r})"));
6405 } else {
6406 self.line(&format!("r[{dst}] = fldiv64(r[{lhs}], r[{rhs}]);"));
6407 }
6408 }
6409 Op::CallBuiltin {
6415 dst,
6416 builtin,
6417 args_start,
6418 ..
6419 } => {
6420 use crate::semantics::builtins::BuiltinId;
6421 let a = *args_start;
6422 let ak = kind_at(kinds, a);
6423 match builtin {
6424 BuiltinId::Fail | BuiltinId::FailReason => {
6429 let text = match (builtin, ak) {
6430 (_, Kind::Text) => as_cstr(a),
6431 (BuiltinId::Fail, Kind::Int | Kind::Bool | Kind::Float | Kind::Rational | Kind::BigInt) => {
6432 coerce_cstr(a, ak)
6433 }
6434 (BuiltinId::FailReason, Kind::Int | Kind::BigInt) => {
6435 "\"The reason after `otherwise` must be Text, but it was Int.\"".to_string()
6436 }
6437 (BuiltinId::FailReason, Kind::Float) => {
6438 "\"The reason after `otherwise` must be Text, but it was Real.\"".to_string()
6439 }
6440 (BuiltinId::FailReason, Kind::Bool) => {
6441 "\"The reason after `otherwise` must be Text, but it was Bool.\"".to_string()
6442 }
6443 (_, k) => return Err(format!("a failure reason of kind {k:?} is not yet rendered by the C backend")),
6444 };
6445 self.line(&format!(
6446 "fflush(stdout); fprintf(stderr, \"%s\\n\", {text}); exit(1);"
6447 ));
6448 }
6449 BuiltinId::Format => match ak {
6451 Kind::Text => self.line(&format!("r[{dst}] = r[{a}];")),
6452 k => {
6453 let text = self.text_of(a, k)?;
6454 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)({text});"));
6455 }
6456 },
6457 BuiltinId::Sqrt => {
6458 self.line(&format!("r[{dst}] = as_bits(sqrt({}));", as_double(a, ak)));
6459 }
6460 BuiltinId::Abs => match ak {
6461 Kind::Float => {
6462 self.line(&format!("r[{dst}] = as_bits(fabs(as_f64(r[{a}])));"))
6463 }
6464 Kind::Int => {
6465 self.line(&format!("r[{dst}] = (r[{a}] < 0) ? -r[{a}] : r[{a}];"))
6466 }
6467 k => return Err(format!("abs of a non-number kind {k:?}")),
6468 },
6469 BuiltinId::Floor | BuiltinId::Ceil | BuiltinId::Round => match ak {
6470 Kind::Float => {
6471 let f = match builtin {
6472 BuiltinId::Floor => "floor",
6473 BuiltinId::Ceil => "ceil",
6474 _ => "round",
6475 };
6476 self.line(&format!("r[{dst}] = (int64_t){f}(as_f64(r[{a}]));"));
6477 }
6478 Kind::Int => self.line(&format!("r[{dst}] = r[{a}];")),
6479 k => return Err(format!("floor/ceil/round of a non-number kind {k:?}")),
6480 },
6481 BuiltinId::Min | BuiltinId::Max => {
6482 let b = *args_start + 1;
6483 let bk = kind_at(kinds, b);
6484 if ak == Kind::Float || bk == Kind::Float {
6485 let f = if matches!(builtin, BuiltinId::Min) {
6486 "logos_min_f64"
6487 } else {
6488 "logos_max_f64"
6489 };
6490 self.line(&format!(
6491 "r[{dst}] = as_bits({f}({}, {}));",
6492 as_double(a, ak),
6493 as_double(b, bk)
6494 ));
6495 } else {
6496 let op = if matches!(builtin, BuiltinId::Min) {
6497 "<"
6498 } else {
6499 ">"
6500 };
6501 self.line(&format!(
6502 "r[{dst}] = (r[{a}] {op} r[{b}]) ? r[{a}] : r[{b}];"
6503 ));
6504 }
6505 }
6506 BuiltinId::TextBytes => {
6508 if self.text_byte_calls.contains(&pc) {
6509 self.line(&format!("r[{dst}] = r[{a}];"));
6510 } else {
6511 let n = self.fresh();
6512 self.line(&format!(
6513 "Seq_i64 *_tb{n} = (Seq_i64*)lc_gc_alloc(sizeof(Seq_i64)); *_tb{n} = logos_text_bytes({}); r[{dst}] = (int64_t)(intptr_t)_tb{n};",
6514 as_cstr(a)
6515 ));
6516 }
6517 }
6518 BuiltinId::TextFromBytes => {
6519 let ptr = Self::seq_ptr(a, seq_kind_of(ak))?;
6520 self.line(&format!(
6521 "r[{dst}] = (int64_t)(intptr_t)lc_text_from_bytes({ptr});"
6522 ));
6523 }
6524 BuiltinId::Contains => {
6527 let b = a + 1;
6528 let bk = kind_at(kinds, b);
6529 if ak == Kind::Text && bk == Kind::Text {
6530 self.line(&format!(
6531 "r[{dst}] = (strstr({}, {}) != NULL);",
6532 as_cstr(a),
6533 as_cstr(b)
6534 ));
6535 } else {
6536 let (Some(x), Some(y)) = (sample_value(ak), sample_value(bk)) else {
6537 return Err(format!("contains over {ak:?} and {bk:?}"));
6538 };
6539 self.line(&format!(
6540 "logos_fatalf({});",
6541 words::builtin_refused(*builtin, vec![x, y])
6542 ));
6543 }
6544 }
6545 BuiltinId::VfsWriteFile => {
6548 let content = self.text_of(a, ak)?;
6549 let path = self.text_of(a + 1, kind_at(kinds, a + 1))?;
6550 self.line(&format!(
6551 "logos_vfs_write({path}, {content}); r[{dst}] = 0;"
6552 ));
6553 }
6554 BuiltinId::VfsReadFile | BuiltinId::VfsMount => {
6555 let path = self.text_of(a, ak)?;
6556 let f = if *builtin == BuiltinId::VfsReadFile {
6557 "logos_vfs_read"
6558 } else {
6559 "logos_vfs_mount"
6560 };
6561 self.line(&format!("r[{dst}] = (int64_t)(intptr_t){f}({path});"));
6562 }
6563 BuiltinId::CountOnes | BuiltinId::LeadingZeros | BuiltinId::TrailingZeros => {
6565 match ak {
6566 Kind::Int => {
6567 let count = match builtin {
6568 BuiltinId::CountOnes => format!("__builtin_popcountll((unsigned long long)r[{a}])"),
6569 BuiltinId::LeadingZeros => format!("(r[{a}] ? __builtin_clzll((unsigned long long)r[{a}]) : 64)"),
6570 _ => format!("(r[{a}] ? __builtin_ctzll((unsigned long long)r[{a}]) : 64)"),
6571 };
6572 self.line(&format!("r[{dst}] = (int64_t){count};"));
6573 }
6574 k => {
6575 let sample = sample_value(k)
6576 .ok_or_else(|| format!("a bit count of a {k:?}"))?;
6577 self.line(&format!(
6578 "logos_fatalf({});",
6579 words::builtin_refused(*builtin, vec![sample])
6580 ));
6581 }
6582 }
6583 }
6584 BuiltinId::Word8Make
6587 | BuiltinId::Word16Make
6588 | BuiltinId::Word32
6589 | BuiltinId::Word64 => match ak {
6590 Kind::Int | Kind::Word(_) | Kind::BigInt => {
6591 let bits = match builtin {
6592 BuiltinId::Word8Make => 8,
6593 BuiltinId::Word16Make => 16,
6594 BuiltinId::Word32 => 32,
6595 _ => 64,
6596 };
6597 let mask = if bits == 64 {
6598 "UINT64_MAX".to_string()
6599 } else {
6600 format!("((1ULL << {bits}) - 1)")
6601 };
6602 let low = if ak == Kind::BigInt {
6603 format!("lc_xword_bits(r[{a}])")
6604 } else {
6605 format!("(uint64_t)r[{a}]")
6606 };
6607 self.line(&format!("r[{dst}] = (int64_t)({low} & {mask});"));
6608 }
6609 k => self.builtin_refusal(*builtin, &[k])?,
6610 },
6611 BuiltinId::IntOfWord8
6612 | BuiltinId::IntOfWord16
6613 | BuiltinId::IntOfWord32
6614 | BuiltinId::IntOfWord64 => match (builtin, ak) {
6615 (BuiltinId::IntOfWord8, Kind::Int) => {
6616 self.line(&format!("r[{dst}] = (int64_t)(uint8_t)r[{a}];"))
6617 }
6618 (BuiltinId::IntOfWord16, Kind::Int) => {
6619 self.line(&format!("r[{dst}] = (int64_t)(uint16_t)r[{a}];"))
6620 }
6621 (_, Kind::Int | Kind::Word(_)) => self.line(&format!("r[{dst}] = r[{a}];")),
6622 (_, k) => self.builtin_refusal(*builtin, &[k])?,
6623 },
6624 BuiltinId::Rotl | BuiltinId::Rotr => {
6625 let nk = kind_at(kinds, a + 1);
6626 match (ak, nk) {
6627 (Kind::Word(bits), Kind::Int | Kind::Word(_)) => {
6628 let mask = if bits == 64 {
6629 "UINT64_MAX".to_string()
6630 } else {
6631 format!("((1ULL << {bits}) - 1)")
6632 };
6633 let (there, back) = if *builtin == BuiltinId::Rotl {
6634 ("<<", ">>")
6635 } else {
6636 (">>", "<<")
6637 };
6638 let n = self.fresh();
6639 self.line(&format!(
6640 "{{ uint64_t _w{n} = (uint64_t)r[{a}]; uint32_t _c{n} = (uint32_t)r[{}] % {bits}; r[{dst}] = (int64_t)(_c{n} ? (((_w{n} {there} _c{n}) | (_w{n} {back} ({bits} - _c{n}))) & {mask}) : _w{n}); }}",
6641 a + 1
6642 ));
6643 }
6644 (x, y) => self.builtin_refusal(*builtin, &[x, y])?,
6645 }
6646 }
6647 BuiltinId::Wand | BuiltinId::Wor => {
6648 let bk = kind_at(kinds, a + 1);
6649 match (ak, bk) {
6650 (Kind::Word(x), Kind::Word(y)) if x == y => {
6651 let op = if *builtin == BuiltinId::Wand {
6652 "&"
6653 } else {
6654 "|"
6655 };
6656 self.line(&format!("r[{dst}] = r[{a}] {op} r[{}];", a + 1));
6657 }
6658 (x, y) => self.builtin_refusal(*builtin, &[x, y])?,
6659 }
6660 }
6661 BuiltinId::Wnot => match ak {
6662 Kind::Word(bits) => {
6663 let mask = if bits == 64 {
6664 "UINT64_MAX".to_string()
6665 } else {
6666 format!("((1ULL << {bits}) - 1)")
6667 };
6668 self.line(&format!(
6669 "r[{dst}] = (int64_t)(~(uint64_t)r[{a}] & {mask});"
6670 ));
6671 }
6672 k => self.builtin_refusal(*builtin, &[k])?,
6673 },
6674 BuiltinId::Word32Shr => {
6675 let nk = kind_at(kinds, a + 1);
6676 match (ak, nk) {
6677 (Kind::Int | Kind::Word(_), Kind::Int) => {
6678 self.line(&format!("r[{dst}] = (int64_t)(uint32_t)((uint32_t)r[{a}] >> ((uint32_t)r[{}] & 31));", a + 1));
6679 }
6680 (x, y) => self.builtin_refusal(*builtin, &[x, y])?,
6681 }
6682 }
6683 BuiltinId::Word64Shl | BuiltinId::Word64Shr => {
6684 let nk = kind_at(kinds, a + 1);
6685 match (ak, nk) {
6686 (Kind::Int | Kind::Word(_), Kind::Int) => {
6687 let dir = if *builtin == BuiltinId::Word64Shl {
6688 "<<"
6689 } else {
6690 ">>"
6691 };
6692 self.line(&format!("r[{dst}] = (int64_t)((uint64_t)r[{a}] {dir} ((uint32_t)r[{}] & 63));", a + 1));
6693 }
6694 (x, y) => self.builtin_refusal(*builtin, &[x, y])?,
6695 }
6696 }
6697 BuiltinId::Word64And => {
6698 let bk = kind_at(kinds, a + 1);
6699 match (ak, bk) {
6700 (Kind::Int | Kind::Word(_), Kind::Int | Kind::Word(_)) => {
6701 self.line(&format!(
6702 "r[{dst}] = (int64_t)((uint64_t)r[{a}] & (uint64_t)r[{}]);",
6703 a + 1
6704 ));
6705 }
6706 (x, y) => self.builtin_refusal(*builtin, &[x, y])?,
6707 }
6708 }
6709 BuiltinId::ReadReals => {
6710 let path = self.text_of(a, ak)?;
6711 let n = self.fresh();
6712 self.line(&format!(
6713 "Seq_f64 *_rr{n} = (Seq_f64*)lc_gc_alloc(sizeof(Seq_f64)); *_rr{n} = logos_read_reals({path}); r[{dst}] = (int64_t)(intptr_t)_rr{n};"
6714 ));
6715 }
6716 BuiltinId::TryReadBytes => {
6717 let path = self.text_of(a, ak)?;
6718 let (ok, err) = (
6719 self.ctor_tag("Result", "Ok").unwrap(),
6720 self.ctor_tag("Result", "Err").unwrap(),
6721 );
6722 self.line(&format!(
6723 "r[{dst}] = lc_try_read_bytes({path}, {ok}, {err});"
6724 ));
6725 }
6726 BuiltinId::Chr => match ak {
6727 Kind::Int => {
6728 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)lc_chr(r[{a}]);"))
6729 }
6730 k => {
6731 let sample =
6732 sample_value(k).ok_or_else(|| format!("chr of a {k:?}"))?;
6733 self.line(&format!(
6734 "logos_fatalf({});",
6735 words::builtin_refused(*builtin, vec![sample])
6736 ));
6737 }
6738 },
6739 BuiltinId::ParseInt => match ak {
6741 Kind::Text => {
6742 self.line(&format!("r[{dst}] = lc_parse_int({});", as_cstr(a)))
6743 }
6744 k => {
6745 let sample =
6746 sample_value(k).ok_or_else(|| format!("parseInt of a {k:?}"))?;
6747 self.line(&format!(
6748 "logos_fatalf({});",
6749 words::builtin_refused(*builtin, vec![sample])
6750 ));
6751 }
6752 },
6753 BuiltinId::RandomInt => match (ak, kind_at(kinds, a + 1)) {
6756 (Kind::Int, Kind::Int) => {
6757 self.line(&format!("r[{dst}] = lc_random_int(r[{a}], r[{}]);", a + 1))
6758 }
6759 (x, y) => self.builtin_refusal(*builtin, &[x, y])?,
6760 },
6761 BuiltinId::RandomFloat => {
6762 self.line(&format!("r[{dst}] = as_bits(lc_random_float());"))
6763 }
6764 BuiltinId::EnvHas => match ak {
6768 Kind::Text => {
6769 self.line(&format!("r[{dst}] = (getenv({}) != NULL);", as_cstr(a)))
6770 }
6771 k => self.builtin_refusal(*builtin, &[k])?,
6772 },
6773 BuiltinId::EnvValue => match ak {
6774 Kind::Text => self.line(&format!(
6775 "r[{dst}] = (int64_t)(intptr_t)lc_env_value({});",
6776 as_cstr(a)
6777 )),
6778 k => self.builtin_refusal(*builtin, &[k])?,
6779 },
6780 BuiltinId::TimeNow => self.line(&format!("r[{dst}] = logos_time_now();")),
6782 other => return Err(format!("CallBuiltin {other:?} not yet lowered")),
6783 }
6784 }
6785 Op::NewTuple { dst, start, count } => {
6788 let t = self.fresh();
6789 let n = (*count).max(1); self.line(&format!(
6791 "int64_t *_tup{t} = (int64_t*)lc_gc_calloc({n}, sizeof(int64_t));"
6792 ));
6793 for i in 0..*count {
6794 self.line(&format!("_tup{t}[{i}] = r[{}];", *start + i));
6795 }
6796 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_tup{t};"));
6797 }
6798 Op::DestructureTuple { src, start, count } => {
6799 for i in 0..*count {
6800 self.line(&format!(
6801 "r[{}] = ((int64_t*)(intptr_t)r[{src}])[{i}];",
6802 *start + i
6803 ));
6804 }
6805 }
6806 Op::BitXor { dst, lhs, rhs }
6809 if self.exact_integers(&[*lhs, *rhs], kinds, Kind::Int) =>
6810 {
6811 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, Kind::Int, "lc_xbitxor")?;
6812 }
6813 Op::BitAnd { dst, lhs, rhs }
6814 if self.exact_integers(&[*lhs, *rhs], kinds, Kind::Int) =>
6815 {
6816 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, Kind::Int, "lc_xbitand")?;
6817 }
6818 Op::BitOr { dst, lhs, rhs } if self.exact_integers(&[*lhs, *rhs], kinds, Kind::Int) => {
6819 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, Kind::Int, "lc_xbitor")?;
6820 }
6821 Op::BitXor { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "^", kinds)?,
6822 Op::BitAnd { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "&", kinds)?,
6823 Op::BitOr { dst, lhs, rhs } => self.arith(*dst, *lhs, *rhs, "|", kinds)?,
6824 Op::Shl { dst, lhs, rhs } | Op::Shr { dst, lhs, rhs }
6827 if matches!(kind_at(kinds, *lhs), Kind::Word(_)) =>
6828 {
6829 let Kind::Word(bits) = kind_at(kinds, *lhs) else {
6830 unreachable!()
6831 };
6832 let mask = if bits == 64 {
6833 "UINT64_MAX".to_string()
6834 } else {
6835 format!("((1ULL << {bits}) - 1)")
6836 };
6837 let dir = if matches!(op, Op::Shl { .. }) {
6838 "<<"
6839 } else {
6840 ">>"
6841 };
6842 self.line(&format!("r[{dst}] = (int64_t)(((uint64_t)r[{lhs}] {dir} ((uint32_t)r[{rhs}] & {})) & {mask});", bits - 1));
6843 }
6844 Op::Shl { dst, lhs, rhs } | Op::Shr { dst, lhs, rhs }
6845 if kind_at(kinds, *lhs) == Kind::Rational || kind_at(kinds, *rhs) == Kind::Rational =>
6846 {
6847 let (l, r) = (as_ldec(*lhs, kind_at(kinds, *lhs))?, as_ldec(*rhs, kind_at(kinds, *rhs))?);
6848 let dir = if matches!(op, Op::Shl { .. }) { '<' } else { '>' };
6849 self.line(&format!("r[{dst}] = ldec_bitwise({l}, {r}, '{dir}');"));
6850 }
6851 Op::Shl { dst, lhs, rhs } if self.exact_integers(&[*lhs, *rhs], kinds, Kind::Int) => {
6852 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, Kind::Int, "lc_xshl")?;
6853 }
6854 Op::Shr { dst, lhs, rhs } if self.exact_integers(&[*lhs, *rhs], kinds, Kind::Int) => {
6855 self.emit_exact_bounded(*dst, *lhs, *rhs, kinds, Kind::Int, "lc_xshr")?;
6856 }
6857 Op::Shl { dst, lhs, rhs } => self.line(&format!(
6858 "r[{dst}] = (int64_t)((uint64_t)r[{lhs}] << (r[{rhs}] & 63));"
6859 )),
6860 Op::Shr { dst, lhs, rhs } => {
6861 self.line(&format!("r[{dst}] = r[{lhs}] >> (r[{rhs}] & 63);"))
6862 }
6863 Op::ApproxEq { dst, lhs, rhs } => {
6865 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
6866 let numeric = |k: Kind| matches!(k, Kind::Int | Kind::Float);
6867 if numeric(lk) && numeric(rk) {
6868 self.line(&format!(
6869 "r[{dst}] = lc_approx_eq({}, {});",
6870 as_double(*lhs, lk),
6871 as_double(*rhs, rk)
6872 ));
6873 } else {
6874 let (Some(lv), Some(rv)) = (sample_value(lk), sample_value(rk)) else {
6875 return Err(format!("`is approximately` over {lk:?} and {rk:?}"));
6876 };
6877 self.line(&format!("logos_fatalf({});", words::approx_refused(lv, rv)));
6878 }
6879 }
6880 Op::DivPow2 { .. } | Op::MagicDivU { .. } if crate::vm::tower::by_constant(op).is_some_and(|(_, lhs, _, _)| kind_at(kinds, lhs) == Kind::Rational) => {
6884 let (dst, lhs, divisor, f) = crate::vm::tower::by_constant(op).expect("a division by a constant");
6885 let f = if f == "div" { "ldec_quotient" } else { "ldec_remainder" };
6886 let l = as_ldec(lhs, Kind::Rational)?;
6887 self.box_ldec(dst, &format!("{f}({l}, ldec_from_i64((int64_t){divisor}LL))"));
6888 }
6889 Op::DivPow2 { dst, lhs, k } if kind_at(kinds, *lhs) == Kind::BigInt => {
6890 let q = format!("lc_xdiv(r[{lhs}], lc_xof((int64_t)1 << {k}))");
6891 self.line(&format!(
6892 "r[{dst}] = {};",
6893 if kind_at(after, *dst) == Kind::BigInt {
6894 q
6895 } else {
6896 format!("lc_xnarrow({q})")
6897 }
6898 ));
6899 }
6900 Op::MagicDivU {
6901 dst,
6902 lhs,
6903 mul_back,
6904 divisor,
6905 ..
6906 } if kind_at(kinds, *lhs) == Kind::BigInt => {
6907 let f = if *mul_back == 0 { "lc_xdiv" } else { "lc_xmod" };
6908 let v = format!("{f}(r[{lhs}], lc_xof({divisor}LL))");
6909 self.line(&format!(
6910 "r[{dst}] = {};",
6911 if kind_at(after, *dst) == Kind::BigInt {
6912 v
6913 } else {
6914 format!("lc_xnarrow({v})")
6915 }
6916 ));
6917 }
6918 Op::DivPow2 { dst, lhs, k } => {
6919 self.line(&format!("r[{dst}] = r[{lhs}] / (1LL << {k});"))
6920 }
6921 Op::MagicDivU {
6922 dst,
6923 lhs,
6924 mul_back,
6925 divisor,
6926 wrap,
6927 ..
6928 } => {
6929 if *mul_back == 0 {
6930 self.line(&format!("r[{dst}] = r[{lhs}] / {divisor}LL;"));
6931 } else if *wrap {
6932 crate::optimize::mark_fired(crate::optimization::Opt::ModWrap);
6933 self.line(&format!(
6934 "r[{dst}] = r[{lhs}] >= {divisor}LL ? r[{lhs}] - {divisor}LL : r[{lhs}];"
6935 ));
6936 } else {
6937 self.line(&format!("r[{dst}] = r[{lhs}] % {divisor}LL;"));
6938 }
6939 }
6940 Op::Concat { dst, lhs, rhs } => {
6941 let l = self.text_of(*lhs, kind_at(kinds, *lhs))?;
6943 let r = self.text_of(*rhs, kind_at(kinds, *rhs))?;
6944 self.line(&format!(
6945 "r[{dst}] = (int64_t)(intptr_t)str_concat({l}, {r});"
6946 ));
6947 }
6948 Op::FormatValue {
6951 dst,
6952 src,
6953 spec,
6954 debug_prefix,
6955 } => {
6956 let sk = kind_at(kinds, *src);
6957 let body = if *spec != u32::MAX {
6960 let spec_str = match &self.prog.constants[*spec as usize] {
6961 Constant::Text(s) => s.clone(),
6962 _ => return Err("FormatValue spec is not Text".into()),
6963 };
6964 self.format_spec_body(*src, sk, &spec_str)?
6965 } else {
6966 self.text_of(*src, sk)?
6967 };
6968 if *debug_prefix != u32::MAX {
6969 let prefix = match &self.prog.constants[*debug_prefix as usize] {
6970 Constant::Text(p) => c_escape(p),
6971 _ => return Err("FormatValue debug prefix is not Text".into()),
6972 };
6973 self.line(&format!(
6974 "r[{dst}] = (int64_t)(intptr_t)str_concat(\"{prefix}=\", {body});"
6975 ));
6976 } else {
6977 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)({body});"));
6978 }
6979 }
6980 Op::Lt { dst, lhs, rhs } => self.cmp(*dst, *lhs, *rhs, "<", kinds)?,
6981 Op::Gt { dst, lhs, rhs } => self.cmp(*dst, *lhs, *rhs, ">", kinds)?,
6982 Op::LtEq { dst, lhs, rhs } => self.cmp(*dst, *lhs, *rhs, "<=", kinds)?,
6983 Op::GtEq { dst, lhs, rhs } => self.cmp(*dst, *lhs, *rhs, ">=", kinds)?,
6984 Op::Eq { dst, lhs, rhs } | Op::NotEq { dst, lhs, rhs } => {
6986 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
6987 let scalar = |k: Kind| {
6988 matches!(
6989 k,
6990 Kind::Int | Kind::Bool | Kind::Float | Kind::BigInt | Kind::Rational
6991 )
6992 };
6993 let not = if matches!(op, Op::NotEq { .. }) {
6994 "!"
6995 } else {
6996 ""
6997 };
6998 if scalar(lk) && scalar(rk) && !(lk == Kind::Bool) == !(rk == Kind::Bool) {
6999 self.cmp(
7000 *dst,
7001 *lhs,
7002 *rhs,
7003 if not.is_empty() { "==" } else { "!=" },
7004 kinds,
7005 )?;
7006 } else {
7007 let same = self.eq_expr(&format!("r[{lhs}]"), lk, &format!("r[{rhs}]"), rk)?;
7008 self.line(&format!("r[{dst}] = {not}({same});"));
7009 }
7010 }
7011 Op::Not { dst, src } => {
7014 if kind_at(kinds, *src) == Kind::Text {
7015 self.line(&format!("r[{dst}] = (str_len({}) == 0);", as_cstr(*src)));
7016 } else {
7017 self.line(&format!("r[{dst}] = !r[{src}];"));
7018 }
7019 }
7020 Op::Jump { target } => self.line(&format!("goto {}{target};", self.label)),
7021 Op::JumpIfFalse { cond, target } => {
7022 self.line(&format!("if (!r[{cond}]) goto {}{target};", self.label))
7023 }
7024 Op::JumpIfTrue { cond, target } => {
7025 self.line(&format!("if (r[{cond}]) goto {}{target};", self.label))
7026 }
7027 Op::Call {
7032 dst,
7033 func,
7034 args_start,
7035 arg_count,
7036 } => {
7037 let params = self
7038 .param_kinds
7039 .get(*func as usize)
7040 .cloned()
7041 .unwrap_or_default();
7042 let inout = self.inout_regs(*func as usize);
7043 let exact_result = self.fn_ret.get(*func as usize) == Some(&Kind::BigInt);
7047 let twin = self.on_words.get()
7048 && self.word_twins.contains(&(*func as usize))
7049 && (0..*arg_count).all(|i| {
7050 params.get(i as usize) != Some(&Kind::BigInt)
7051 || matches!(kind_at(kinds, args_start + i), Kind::Int | Kind::Bool)
7052 });
7053 let mut args = Vec::with_capacity(*arg_count as usize);
7054 for i in 0..*arg_count {
7055 let reg = args_start + i;
7056 let widen = !twin
7058 && matches!(
7059 (params.get(i as usize), kind_at(kinds, reg)),
7060 (Some(Kind::BigInt), Kind::Int | Kind::Bool)
7061 );
7062 if inout.contains(&i) {
7063 if widen {
7064 self.line(&format!("r[{reg}] = lc_xof(r[{reg}]);"));
7065 }
7066 args.push(format!("&r[{reg}]"));
7067 } else if widen {
7068 args.push(format!("lc_xof(r[{reg}])"));
7069 } else if params.get(i as usize) == Some(&Kind::Tower)
7070 && kind_at(kinds, reg) != Kind::Tower
7071 {
7072 let cell = self.tower_of(&format!("r[{reg}]"), kind_at(kinds, reg))?;
7074 args.push(format!("(int64_t)(intptr_t)({cell})"));
7075 } else if let Some(&to) = params
7076 .get(i as usize)
7077 .filter(|to| self.widens_to(kind_at(kinds, reg), **to))
7078 {
7079 args.push(self.convert_number(&format!("r[{reg}]"), kind_at(kinds, reg), to)?);
7081 } else if params.get(i as usize) == Some(&Kind::Float)
7082 && matches!(kind_at(kinds, reg), Kind::Int | Kind::BigInt)
7083 {
7084 args.push(format!("as_bits({})", as_double(reg, kind_at(kinds, reg))));
7086 } else {
7087 args.push(format!("r[{reg}]"));
7088 }
7089 }
7090 let ret_own = if self.owned.contains(dst)
7091 && crate::optimize::active_config().is_on(crate::optimization::Opt::Borrow)
7092 {
7093 crate::optimize::mark_fired(crate::optimization::Opt::Borrow);
7094 format!("&own[{dst}]")
7095 } else {
7096 "NULL".to_string()
7097 };
7098 args.push(ret_own);
7099 if twin && exact_result {
7100 let n = self.fresh();
7101 self.line(&format!(
7102 "{{ lc_wret _w{n} = fn_{func}_w({}); r[{dst}] = _w{n}.v; if (_w{n}.escaped) goto A{pc}; }}",
7103 args.join(", ")
7104 ));
7105 self.twin_arrival = true;
7106 } else if twin {
7107 self.line(&format!("r[{dst}] = fn_{func}_w({}).v;", args.join(", ")));
7108 } else {
7109 self.line(&format!("r[{dst}] = fn_{func}({});", args.join(", ")));
7110 }
7111 }
7112 Op::MakeClosure {
7115 dst,
7116 func,
7117 locals_start,
7118 } => {
7119 let caps = self.prog.functions[*func as usize].captures.clone();
7120 let ncap = caps.len();
7121 let n = self.fresh();
7122 self.line(&format!(
7123 "int64_t *_clo{n} = (int64_t*)lc_gc_calloc({}, sizeof(int64_t));",
7124 1 + 2 * ncap
7125 ));
7126 self.line(&format!("_clo{n}[0] = {func};"));
7127 let mut local_k: u16 = 0;
7128 for (i, (_sym, gidx)) in caps.iter().enumerate() {
7129 match gidx {
7130 Some(g) => {
7131 self.line(&format!("_clo{n}[{}] = g[{}];", 1 + i, g));
7132 self.line(&format!("_clo{n}[{}] = 1;", 1 + ncap + i));
7133 }
7134 None => {
7135 self.line(&format!(
7136 "_clo{n}[{}] = r[{}];",
7137 1 + i,
7138 locals_start + local_k
7139 ));
7140 self.line(&format!("_clo{n}[{}] = 1;", 1 + ncap + i));
7141 local_k += 1;
7142 }
7143 }
7144 }
7145 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_clo{n};"));
7146 }
7147 Op::CallValue {
7150 dst,
7151 callee,
7152 args_start,
7153 arg_count,
7154 ..
7155 } => {
7156 let bodies: Vec<usize> = self.closure_bodies.iter().copied().collect();
7157 if bodies.is_empty() {
7158 return Err("CallValue but the program builds no closures".into());
7159 }
7160 let arglist: String = (0..*arg_count)
7161 .map(|k| format!("r[{}], ", args_start + k))
7162 .collect();
7163 let ret_own = if self.owned.contains(dst)
7164 && crate::optimize::active_config().is_on(crate::optimization::Opt::Borrow)
7165 {
7166 crate::optimize::mark_fired(crate::optimization::Opt::Borrow);
7167 format!("&own[{dst}]")
7168 } else {
7169 "NULL".to_string()
7170 };
7171 self.line(&format!(
7172 "switch ((int)((int64_t*)(intptr_t)r[{callee}])[0]) {{"
7173 ));
7174 for b in bodies {
7175 self.line(&format!(
7176 "case {b}: r[{dst}] = fn_{b}({arglist}r[{callee}], {ret_own}); break;"
7177 ));
7178 }
7179 self.line("default: fprintf(stderr, \"CallValue: unknown closure body\\n\"); fflush(stdout); exit(1);");
7180 self.line("}");
7181 }
7182 Op::NewRange { dst, start, end } => {
7184 let t = self.fresh();
7185 self.line(&format!(
7186 "Seq_i64 *_s{t} = (Seq_i64*)lc_gc_alloc(sizeof(Seq_i64)); *_s{t} = seq_i64_new();"
7187 ));
7188 let (from, to) = (
7189 self.int_operand(kinds, *start, words::IntegerPlace::Range),
7190 self.int_operand(kinds, *end, words::IntegerPlace::Range),
7191 );
7192 self.line(&format!("for (int64_t _i{t} = {from}, _e{t} = {to}; _i{t} <= _e{t}; _i{t}++) seq_i64_push(_s{t}, _i{t});"));
7193 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_s{t};"));
7194 }
7195 Op::NewList { dst, start, count } => {
7196 let seq_kind = if *count == 0 {
7199 let k = kind_at(after, *dst);
7200 if is_concrete_seq(k) {
7201 k
7202 } else {
7203 Kind::SeqInt
7204 }
7205 } else {
7206 self.list_kind(&(0..*count).map(|i| kind_at(kinds, start + i)).collect::<Vec<_>>())?
7207 };
7208 let ty = seq_kind
7209 .seq_c_type()
7210 .ok_or_else(|| format!("NewList of a non-scalar element kind {seq_kind:?}"))?;
7211 let infix = seq_kind.seq_infix().unwrap();
7212 let elem = self.elem_kind(seq_kind);
7213 let t = self.fresh();
7214 self.line(&format!(
7215 "{ty} *_s{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_s{t} = seq_{infix}_new();"
7216 ));
7217 for k in 0..*count {
7218 let item = self.elem_value(start + k, kind_at(kinds, start + k), elem)?;
7219 self.line(&format!("seq_{infix}_push(_s{t}, {item});"));
7220 }
7221 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_s{t};"));
7222 }
7223 Op::NewEmptyListFixed { dst, capacity } => {
7226 let sk = match kind_at(after, *dst) {
7227 k if is_concrete_seq(k) => k,
7228 _ => Kind::SeqInt,
7229 };
7230 let ty = sk
7231 .seq_c_type()
7232 .ok_or_else(|| format!("fixed list of non-sequence kind {sk:?}"))?;
7233 let elem = match sk {
7234 Kind::SeqFloat => "double",
7235 Kind::SeqText => "char *",
7236 Kind::SeqBool => "bool",
7237 _ => "int64_t",
7238 };
7239 let t = self.fresh();
7240 self.line(&format!(
7241 "{elem} _fxd{t}[{capacity}] = {{0}}; /* shared fixed Seq */"
7242 ));
7243 self.line(&format!("{ty} _fx{t} = ({ty}){{_fxd{t}, 0, {capacity}}};"));
7244 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)&_fx{t};"));
7245 if self.owned.contains(dst) {
7246 self.line(&format!("own[{dst}] = 1;"));
7247 }
7248 crate::optimize::mark_fired(crate::optimization::Opt::Scalarize);
7249 }
7250 Op::NewEmptyListWithCapacity { dst, capacity } => {
7251 let sk = {
7252 let k = kind_at(after, *dst);
7253 if is_concrete_seq(k) {
7254 k
7255 } else {
7256 Kind::SeqInt
7257 }
7258 };
7259 let ty = sk.seq_c_type().unwrap();
7260 let infix = sk.seq_infix().unwrap();
7261 let t = self.fresh();
7262 let reuses = self.reuses_list_buffer(*dst, sk);
7263 if reuses {
7264 self.line(&format!("if (own[{dst}] && r[{dst}] != 0) {{"));
7265 self.line(&format!("{ty} *_s{t} = ({ty}*)(intptr_t)r[{dst}];"));
7266 self.line(&format!("_s{t}->len = 0;"));
7267 self.line(&format!(
7268 "if (r[{capacity}] > 0 && (uint64_t)r[{capacity}] > _s{t}->cap) {{"
7269 ));
7270 self.line(&format!("_s{t}->data = lc_gc_realloc(_s{t}->data, (size_t)r[{capacity}] * sizeof(*_s{t}->data));"));
7271 self.line(&format!("_s{t}->cap = (size_t)r[{capacity}];"));
7272 self.line("}");
7273 self.line("} else {");
7274 }
7275 self.line(&format!("{ty} *_s{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_s{t} = seq_{infix}_with_capacity(r[{capacity}]);"));
7276 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_s{t};"));
7277 if reuses {
7278 self.line("}");
7279 }
7280 }
7281 Op::NewEmptyList { dst } | Op::NewEmptyListI32 { dst } => {
7282 let sk = {
7283 let k = kind_at(after, *dst);
7284 if is_concrete_seq(k) {
7285 k
7286 } else {
7287 Kind::SeqInt
7288 }
7289 };
7290 let ty = sk.seq_c_type().unwrap();
7291 let infix = sk.seq_infix().unwrap();
7292 let t = self.fresh();
7293 let reuses = self.reuses_list_buffer(*dst, sk);
7294 if reuses {
7295 self.line(&format!("if (own[{dst}] && r[{dst}] != 0) {{"));
7296 self.line("/* reuse sole-owned list buffer */");
7297 self.line(&format!("(({ty}*)(intptr_t)r[{dst}])->len = 0;"));
7298 self.line("} else {");
7299 }
7300 self.line(&format!(
7301 "{ty} *_s{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_s{t} = seq_{infix}_new();"
7302 ));
7303 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_s{t};"));
7304 if reuses {
7305 self.line("}");
7306 }
7307 }
7308 Op::ListPop { list, dst } => {
7309 let lk = seq_kind_of(kind_at(kinds, *list));
7312 let infix = lk
7313 .seq_infix()
7314 .ok_or_else(|| format!("Pop from a non-sequence kind {lk:?}"))?;
7315 self.own_target(*list, lk)?;
7316 let ptr = Self::seq_ptr(*list, lk)?;
7317 let read = elem_to_reg(&format!("seq_{infix}_pop({ptr})"), self.elem_kind(lk));
7318 let n = self.fresh();
7319 self.line(&format!(
7320 "int64_t *_opt{n} = (int64_t*)lc_gc_calloc(2, sizeof(int64_t));"
7321 ));
7322 self.line(&format!("if (({ptr})->len > 0) {{ _opt{n}[0] = 1; _opt{n}[1] = {read}; }} else {{ _opt{n}[0] = 0; }}"));
7323 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_opt{n};"));
7324 }
7325 Op::NewEmptySet { dst } => {
7327 let sk = {
7330 let k = kind_at(after, *dst);
7331 if is_concrete_set(k) {
7332 k
7333 } else {
7334 Kind::SetInt
7335 }
7336 };
7337 let ty = sk.set_c_type().unwrap();
7338 let infix = sk.seq_infix().unwrap();
7339 let t = self.fresh();
7340 self.line(&format!(
7341 "{ty} *_s{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_s{t} = set_{infix}_new();"
7342 ));
7343 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_s{t};"));
7344 }
7345 Op::NewEmptyMap { dst } => {
7346 let (key, val) = match kind_at(after, *dst) {
7348 Kind::Map(idx) => self.map_pair(idx),
7349 _ => (Kind::Int, Kind::Int),
7350 };
7351 let (ty, pre) = (map_type_name(key, val).unwrap(), map_fn(key, val).unwrap());
7352 let t = self.fresh();
7353 self.line(&format!(
7354 "{ty} *_m{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_m{t} = {pre}_new();"
7355 ));
7356 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_m{t};"));
7357 }
7358 Op::NewEmptyMapWithCapacity { dst, capacity } => {
7359 let (key, val) = match kind_at(after, *dst) {
7360 Kind::Map(idx) => self.map_pair(idx),
7361 _ => (Kind::Int, Kind::Int),
7362 };
7363 let (ty, pre) = (map_type_name(key, val).unwrap(), map_fn(key, val).unwrap());
7364 let t = self.fresh();
7365 self.line(&format!("{ty} *_m{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_m{t} = {pre}_with_capacity(r[{capacity}]);"));
7366 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_m{t};"));
7367 }
7368 Op::SetAdd { set, value } => {
7369 let sk = kind_at(kinds, *value).set_of();
7370 let infix = sk
7371 .seq_infix()
7372 .ok_or_else(|| format!("SetAdd of a non-scalar element kind {sk:?}"))?;
7373 self.own_target(*set, sk)?;
7374 let ptr = Self::set_ptr(*set, sk)?;
7375 self.line(&format!(
7376 "set_{infix}_add({ptr}, {});",
7377 as_elem_push(*value, sk.elem())
7378 ));
7379 }
7380 Op::RemoveFrom { collection, value } => {
7381 let ck = kind_at(kinds, *collection);
7382 let sk = resolve_set(ck)
7383 .ok_or_else(|| format!("RemoveFrom on a non-set kind {ck:?}"))?;
7384 let infix = sk.seq_infix().unwrap();
7385 self.own_target(*collection, sk)?;
7386 let ptr = Self::set_ptr(*collection, sk)?;
7387 self.line(&format!(
7388 "set_{infix}_remove({ptr}, {});",
7389 as_elem_push(*value, sk.elem())
7390 ));
7391 }
7392 Op::UnionOp { dst, lhs, rhs } => {
7393 let sk = resolve_set(kind_at(kinds, *lhs))
7394 .ok_or_else(|| format!("Union on a non-set kind {:?}", kind_at(kinds, *lhs)))?;
7395 let (ty, infix) = (sk.set_c_type().unwrap(), sk.seq_infix().unwrap());
7396 let (lp, rp) = (Self::set_ptr(*lhs, sk)?, Self::set_ptr(*rhs, sk)?);
7397 let t = self.fresh();
7398 self.line(&format!("{ty} *_su{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_su{t} = set_{infix}_union({lp}, {rp});"));
7399 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_su{t};"));
7400 }
7401 Op::IntersectOp { dst, lhs, rhs } => {
7402 let sk = resolve_set(kind_at(kinds, *lhs)).ok_or_else(|| {
7403 format!("Intersect on a non-set kind {:?}", kind_at(kinds, *lhs))
7404 })?;
7405 let (ty, infix) = (sk.set_c_type().unwrap(), sk.seq_infix().unwrap());
7406 let (lp, rp) = (Self::set_ptr(*lhs, sk)?, Self::set_ptr(*rhs, sk)?);
7407 let t = self.fresh();
7408 self.line(&format!("{ty} *_si{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_si{t} = set_{infix}_intersect({lp}, {rp});"));
7409 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_si{t};"));
7410 }
7411 Op::SliceOp {
7413 dst,
7414 collection,
7415 start,
7416 end,
7417 } => {
7418 let ck = seq_kind_of(kind_at(kinds, *collection));
7419 let ty = ck
7420 .seq_c_type()
7421 .ok_or_else(|| format!("Slice of a non-sequence kind {ck:?}"))?;
7422 let infix = ck.seq_infix().unwrap();
7423 let ptr = Self::seq_ptr(*collection, ck)?;
7424 let t = self.fresh();
7425 let (from, to) = (
7428 self.int_operand(kinds, *start, words::IntegerPlace::Slice),
7429 self.int_operand(kinds, *end, words::IntegerPlace::Slice),
7430 );
7431 self.line(&format!("{ty} *_sl{t} = ({ty}*)lc_gc_alloc(sizeof({ty}));"));
7432 self.line(&format!("{{ int64_t _sf{t} = {from}, _st{t} = {to}; if (_sf{t} - 1 < _st{t} && _st{t} <= (int64_t){ptr}->len) *_sl{t} = seq_{infix}_slice({ptr}, _sf{t}, _st{t}); else *_sl{t} = seq_{infix}_new(); }}"));
7433 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_sl{t};"));
7434 }
7435 Op::SetIndex {
7436 collection,
7437 index,
7438 value,
7439 }
7440 | Op::SetIndexUnchecked {
7441 collection,
7442 index,
7443 value,
7444 } => {
7445 let unchecked = matches!(op, Op::SetIndexUnchecked { .. });
7446 let ck = kind_at(kinds, *collection);
7447 if matches!(ck, Kind::Struct(_) | Kind::StructSynth(_)) {
7448 let name = self
7451 .const_text_at(self.region_span.0, pc, *index)
7452 .ok_or("a record indexed by something other than a field name")?;
7453 let cell = self
7454 .field_cell_of_name(ck, &name)
7455 .ok_or_else(|| format!("a store to the unknown field `{name}`"))?;
7456 self.store_field(*collection, ck, cell, *value, kind_at(kinds, *value), pc)?;
7457 } else if matches!(ck, Kind::Map(_) | Kind::MapUnknown) {
7458 let (input_key, given) = (kind_at(kinds, *index), kind_at(kinds, *value));
7462 let vk = match ck {
7463 Kind::Map(mi) if self.map_pair(mi).1 == Kind::Tower => Kind::Tower,
7464 _ => given,
7465 };
7466 let kk = if input_key == Kind::BigInt {
7467 Kind::Int
7468 } else {
7469 input_key
7470 };
7471 if map_key_storage(kk).is_none() || map_val_storage(vk).is_none() {
7472 return Err(format!(
7473 "map insert with an unsupported key/value kind ({kk:?} -> {vk:?})"
7474 ));
7475 }
7476 self.own_target(*collection, self.map_of(kk, vk))?;
7477 let (pre, ptr) = (
7478 map_fn(kk, vk).unwrap(),
7479 map_ptr_kv(*collection, kk, vk).unwrap(),
7480 );
7481 let key = if input_key == Kind::BigInt {
7484 format!("lc_xnarrow(r[{index}])")
7485 } else {
7486 map_key_arg(*index, kk)
7487 };
7488 let val = if vk == Kind::Tower && given != Kind::Tower {
7489 let cell = self.tower_of(&format!("r[{value}]"), given)?;
7490 format!("(int64_t)(intptr_t)({cell})")
7491 } else {
7492 map_val_store(*value, vk)
7493 };
7494 self.line(&format!("{pre}_set({ptr}, {key}, {val});"));
7495 } else {
7496 let sk = seq_kind_of(ck);
7497 sk.seq_infix()
7498 .ok_or_else(|| format!("SetIndex on a non-sequence kind {ck:?}"))?;
7499 self.own_target(*collection, sk)?;
7500 let ptr = Self::seq_ptr(*collection, sk)?;
7501 let index_word = self.int_operand(kinds, *index, words::IntegerPlace::ListStore);
7502 let at = if unchecked {
7503 crate::optimize::mark_fired(crate::optimization::Opt::Unchecked);
7504 format!("(size_t)({index_word} - 1LL)")
7505 } else {
7506 format!("logos_idx0({index_word}, {ptr}->len)")
7507 };
7508 let item = self.elem_value(*value, kind_at(kinds, *value), self.elem_kind(sk))?;
7509 self.line(&format!("{ptr}->data[{at}] = {item};"));
7510 }
7511 }
7512 Op::SeqConcat { dst, lhs, rhs } => {
7513 let (lk, rk) = (kind_at(kinds, *lhs), kind_at(kinds, *rhs));
7514 let lk = seq_kind_of(if lk == Kind::SeqUnknown { rk } else { lk });
7516 let ty = lk
7517 .seq_c_type()
7518 .ok_or_else(|| format!("SeqConcat of a non-sequence kind {lk:?}"))?;
7519 let infix = lk.seq_infix().unwrap();
7520 let (lp, rp) = (Self::seq_ptr(*lhs, lk)?, Self::seq_ptr(*rhs, lk)?);
7521 let t = self.fresh();
7522 self.line(&format!(
7523 "{ty} *_sc{t} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_sc{t} = seq_{infix}_new();"
7524 ));
7525 self.line(&format!("for (size_t _a{t}=0; _a{t}<{lp}->len; _a{t}++) seq_{infix}_push(_sc{t}, {lp}->data[_a{t}]);"));
7526 self.line(&format!("for (size_t _b{t}=0; _b{t}<{rp}->len; _b{t}++) seq_{infix}_push(_sc{t}, {rp}->data[_b{t}]);"));
7527 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_sc{t};"));
7528 }
7529 Op::EnsureOwned { reg } => self.own_target(*reg, kind_at(kinds, *reg))?,
7533 Op::NewStruct { dst, type_name } => {
7535 let sk = self.struct_kind_of_type_name(*type_name);
7536 let n = self
7538 .struct_cell_count(sk)
7539 .ok_or("NewStruct of an unknown struct type")?
7540 + 1;
7541 let t = self.fresh();
7542 let exact_cells: String = match sk {
7544 Kind::Struct(si) => self.prog.struct_types[si as usize]
7545 .fields
7546 .iter()
7547 .enumerate()
7548 .filter(|(_, (_, ty))| self.boundary_kind(&Some(ty.clone())) == Kind::Tower)
7549 .map(|(i, _)| format!(" _st{t}[{i}] = (int64_t)(intptr_t)ltower_nothing();"))
7550 .collect(),
7551 _ => String::new(),
7552 };
7553 self.line(&format!(
7554 "{{ int64_t *_st{t} = (int64_t*)lc_gc_calloc({n}, sizeof(int64_t));{exact_cells} r[{dst}] = (int64_t)(intptr_t)_st{t}; }}"
7555 ));
7556 }
7557 Op::StructInsert { obj, field, value } => {
7558 let ok = kind_at(kinds, *obj);
7559 if !matches!(ok, Kind::Struct(_) | Kind::StructSynth(_)) {
7560 return Err(format!("StructInsert on a non-struct kind {ok:?}"));
7561 }
7562 let (cell, _) = self
7563 .resolve_field_any(ok, *field)
7564 .ok_or("StructInsert of an unknown field")?;
7565 self.store_field(*obj, ok, cell, *value, kind_at(kinds, *value), pc)?;
7566 }
7567 Op::GetField { dst, obj, field } => {
7568 let ok = kind_at(kinds, *obj);
7569 if !matches!(ok, Kind::Struct(_) | Kind::StructSynth(_)) {
7570 return Err(format!("GetField on a non-struct kind {ok:?}"));
7571 }
7572 let (cell, _) = self
7573 .resolve_field_any(ok, *field)
7574 .ok_or("GetField of an unknown field")?;
7575 let is_counter = self
7578 .struct_name_of_kind(ok)
7579 .map(|n| self.counter_fields.contains(&(n, cell)))
7580 .unwrap_or(false);
7581 if is_counter {
7582 self.line(&format!(
7583 "r[{dst}] = logos_crdt_sum((logos_crdt*)(intptr_t)((int64_t*)(intptr_t)r[{obj}])[{cell}]);"
7584 ));
7585 } else {
7586 self.line(&format!(
7587 "r[{dst}] = ((int64_t*)(intptr_t)r[{obj}])[{cell}];"
7588 ));
7589 self.read_field_collection(*dst, *obj, ok, cell, pc);
7590 }
7591 }
7592 Op::NewInductive {
7596 dst,
7597 type_name,
7598 ctor,
7599 args_start,
7600 count,
7601 } => {
7602 let tag = match self.enum_index_of_type_name(*type_name) {
7603 Some(ei) => self
7604 .variant_index(ei, *ctor)
7605 .ok_or("NewInductive of an unknown variant")?,
7606 None => {
7607 let (ty, c) = (self.const_text(*type_name), self.const_text(*ctor));
7608 let (Some(ty), Some(c)) = (ty, c) else {
7609 return Err("NewInductive with a non-Text name".into());
7610 };
7611 self.ctor_tag(ty, c)
7612 .ok_or_else(|| format!("the constructor {ty}::{c} has no tag"))?
7613 }
7614 };
7615 let n = *count as usize + 1;
7616 let t = self.fresh();
7617 if crate::optimize::active_config().is_on(crate::optimization::Opt::Scalarize) {
7618 self.line(&format!("int64_t *_en{t} = lc_enum_alloc({n});"));
7619 crate::optimize::mark_fired(crate::optimization::Opt::Scalarize);
7620 } else {
7621 self.line(&format!(
7622 "int64_t *_en{t} = (int64_t*)lc_gc_calloc({n}, sizeof(int64_t));"
7623 ));
7624 }
7625 self.line(&format!("_en{t}[0] = {tag};"));
7626 for k in 0..*count {
7627 self.line(&format!("_en{t}[{}] = r[{}];", k + 1, args_start + k));
7628 }
7629 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_en{t};"));
7630 }
7631 Op::TestArm {
7634 dst,
7635 target,
7636 variant,
7637 } => {
7638 let name = self
7639 .const_text(*variant)
7640 .ok_or("TestArm of a non-Text variant")?
7641 .to_string();
7642 let tk = kind_at(kinds, *target);
7643 let test = match tk {
7644 Kind::Enum(ei) => match self.variant_index(ei, *variant) {
7645 Some(vi) => format!("(((int64_t*)(intptr_t)r[{target}])[0] == {vi})"),
7646 None => "0".to_string(),
7647 },
7648 Kind::Inductive(ii) => match self.ctor_tag(&self.inductive(ii).0, &name) {
7649 Some(tag) => format!("(((int64_t*)(intptr_t)r[{target}])[0] == {tag})"),
7650 None => "0".to_string(),
7651 },
7652 Kind::Struct(_) | Kind::StructSynth(_) => {
7653 ((self.struct_name_of_kind(tk).as_deref() == Some(name.as_str())) as i32)
7654 .to_string()
7655 }
7656 Kind::Optional(oi) => match (name.as_str(), self.opt_inner(oi)) {
7657 ("None", _) => format!("(!((int64_t*)(intptr_t)r[{target}])[0])"),
7658 (_, inner @ (Kind::Struct(_) | Kind::StructSynth(_)))
7659 if self.struct_name_of_kind(inner).as_deref()
7660 == Some(name.as_str()) =>
7661 {
7662 format!("(((int64_t*)(intptr_t)r[{target}])[0] != 0)")
7663 }
7664 _ => "0".to_string(),
7665 },
7666 Kind::Nothing => ((name == "None") as i32).to_string(),
7667 Kind::Unknown => return Err(format!("TestArm on a non-enum kind {tk:?}")),
7668 _ => "0".to_string(),
7669 };
7670 self.line(&format!("r[{dst}] = {test};"));
7671 }
7672 Op::BindArm {
7675 dst,
7676 target,
7677 field,
7678 index,
7679 } => {
7680 let tk = kind_at(kinds, *target);
7681 match tk {
7682 Kind::Enum(_) | Kind::Inductive(_) => {
7683 self.line(&format!(
7684 "r[{dst}] = ((int64_t*)(intptr_t)r[{target}])[{}];",
7685 *index as usize + 1
7686 ));
7687 }
7688 Kind::Struct(_) | Kind::StructSynth(_) => {
7689 let fname = self
7690 .const_text(*field)
7691 .ok_or("BindArm of a non-Text field")?;
7692 if fname == crate::vm::SOME_BINDING_MARKER {
7693 self.line(&format!("r[{dst}] = r[{target}];"));
7694 } else {
7695 let cell = self.field_cell_of_name(tk, fname).ok_or_else(|| {
7696 format!("an arm binds the field `{fname}` its record does not have")
7697 })?;
7698 self.line(&format!(
7699 "r[{dst}] = ((int64_t*)(intptr_t)r[{target}])[{cell}];"
7700 ));
7701 }
7702 }
7703 Kind::Optional(_) => {
7704 self.line(&format!(
7705 "if (((int64_t*)(intptr_t)r[{target}])[0]) r[{dst}] = ((int64_t*)(intptr_t)r[{target}])[1];"
7706 ));
7707 }
7708 Kind::Nothing => {}
7709 Kind::Unknown => return Err(format!("BindArm on a non-enum kind {tk:?}")),
7710 _ if *index == 0 => self.line(&format!("r[{dst}] = r[{target}];")),
7711 _ => {}
7712 }
7713 }
7714 Op::Index {
7715 dst,
7716 collection,
7717 index,
7718 }
7719 | Op::IndexUnchecked {
7720 dst,
7721 collection,
7722 index,
7723 } => {
7724 let unchecked = matches!(op, Op::IndexUnchecked { .. });
7725 let ck = kind_at(kinds, *collection);
7726 if let Some((kk, vk)) = self.resolve_map_kv(ck) {
7727 let (pre, ptr) = (
7731 map_fn(kk, vk).unwrap(),
7732 map_ptr_kv(*collection, kk, vk).unwrap(),
7733 );
7734 let key = map_key_arg(*index, kk);
7735 let shown = match kk {
7736 Kind::Text => key.clone(),
7737 _ => format!("logos_dyn_sprintf(\"%\" PRId64, r[{index}])"),
7738 };
7739 self.line(&format!(
7740 "if (!{pre}_contains({ptr}, {key})) logos_fatalf({}, {shown});",
7741 words::missing_key()
7742 ));
7743 let get = format!("{pre}_get({ptr}, {key})");
7744 self.line(&format!("r[{dst}] = {};", map_val_load(&get, vk)));
7745 } else if let Kind::Tuple(ti) = ck {
7746 let n = self.tuple_positions(ti).len();
7750 let at = self.int_operand(kinds, *index, words::IntegerPlace::Tuple);
7751 self.line(&format!("r[{dst}] = ((int64_t*)(intptr_t)r[{collection}])[logos_idx0({at}, {n})];"));
7752 } else if ck == Kind::Text {
7753 if unchecked {
7754 crate::optimize::mark_backend_decline(
7755 crate::optimization::Opt::Unchecked,
7756 "unchecked: a Text is indexed through its bounds-checked character accessor",
7757 );
7758 }
7759 let owned = self.owned_flag(*collection);
7760 let at = self.int_operand(kinds, *index, words::IntegerPlace::Text);
7761 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)lc_text_item_of(r[{collection}], {owned}, {at});"));
7762 } else {
7763 let sk = seq_kind_of(ck);
7764 sk.seq_infix()
7765 .ok_or_else(|| format!("Index on a non-sequence kind {ck:?}"))?;
7766 let ptr = Self::seq_ptr(*collection, sk)?;
7767 let index_word = self.int_operand(kinds, *index, words::IntegerPlace::ListRead);
7768 let at = if unchecked {
7769 crate::optimize::mark_fired(crate::optimization::Opt::Unchecked);
7770 format!("(size_t)({index_word} - 1LL)")
7771 } else {
7772 format!("logos_idx0({index_word}, {ptr}->len)")
7773 };
7774 let cell = format!("{ptr}->data[{at}]");
7775 self.line(&format!(
7776 "r[{dst}] = {};",
7777 elem_to_reg(&cell, self.elem_kind(sk))
7778 ));
7779 }
7780 }
7781 Op::Length { dst, collection } => {
7782 let ck = kind_at(kinds, *collection);
7783 if ck == Kind::Text {
7784 let owned = self.owned_flag(*collection);
7785 self.line(&format!(
7786 "r[{dst}] = lc_text_len(r[{collection}], {owned});"
7787 ));
7788 } else if let Some((kk, vk)) = self.resolve_map_kv(ck) {
7789 self.line(&format!(
7791 "r[{dst}] = (int64_t){}->len;",
7792 map_ptr_kv(*collection, kk, vk).unwrap()
7793 ));
7794 } else if let Some(sk) = resolve_set(ck) {
7795 let infix = sk.seq_infix().unwrap();
7796 let ptr = Self::set_ptr(*collection, sk)?;
7797 self.line(&format!("r[{dst}] = set_{infix}_len({ptr});"));
7798 } else if let Kind::Tuple(ti) = ck {
7799 self.line(&format!("r[{dst}] = {};", self.tuple_positions(ti).len()));
7800 } else {
7801 let sk = seq_kind_of(ck);
7802 sk.seq_infix()
7803 .ok_or_else(|| format!("Length on a non-sequence kind {ck:?}"))?;
7804 let ptr = Self::seq_ptr(*collection, sk)?;
7805 self.line(&format!("r[{dst}] = (int64_t){ptr}->len;"));
7806 }
7807 }
7808 Op::Contains {
7809 dst,
7810 collection,
7811 value,
7812 } => {
7813 let ck = kind_at(kinds, *collection);
7814 if let Some((kk, vk)) = self.resolve_map_kv(ck) {
7815 let (pre, ptr) = (
7817 map_fn(kk, vk).unwrap(),
7818 map_ptr_kv(*collection, kk, vk).unwrap(),
7819 );
7820 self.line(&format!(
7821 "r[{dst}] = {pre}_contains({ptr}, {});",
7822 map_key_arg(*value, kk)
7823 ));
7824 } else if let Some(sk) = resolve_set(ck) {
7825 let infix = sk.seq_infix().unwrap();
7826 let ptr = Self::set_ptr(*collection, sk)?;
7827 self.line(&format!(
7828 "r[{dst}] = set_{infix}_contains({ptr}, {});",
7829 as_elem_push(*value, sk.elem())
7830 ));
7831 } else if ck == Kind::Text {
7832 let vk = kind_at(kinds, *value);
7834 if vk == Kind::Text {
7835 self.line(&format!(
7836 "r[{dst}] = (strstr({}, {}) != NULL);",
7837 as_cstr(*collection),
7838 as_cstr(*value)
7839 ));
7840 } else {
7841 let sample =
7842 sample_value(vk).ok_or_else(|| format!("Text contains a {vk:?}"))?;
7843 self.line(&format!(
7844 "logos_fatalf({});",
7845 words::text_contains_refused(sample)
7846 ));
7847 }
7848 } else {
7849 let sk = seq_kind_of(ck);
7850 let infix = sk
7851 .seq_infix()
7852 .ok_or_else(|| format!("Contains on a non-sequence kind {ck:?}"))?;
7853 let ptr = Self::seq_ptr(*collection, sk)?;
7854 let (ek, vk) = (self.elem_kind(sk), kind_at(kinds, *value));
7855 if ek == Kind::Rational || vk == Kind::Rational {
7856 let n = self.fresh();
7858 let item = elem_to_reg(&format!("{ptr}->data[_ci{n}]"), ek);
7859 let same = self.eq_expr(&item, ek, &format!("r[{value}]"), vk)?;
7860 self.line(&format!(
7861 "{{ int64_t _cf{n} = 0; for (size_t _ci{n} = 0; _ci{n} < {ptr}->len && !_cf{n}; _ci{n}++) _cf{n} = {same}; r[{dst}] = _cf{n}; }}"
7862 ));
7863 } else {
7864 self.line(&format!(
7865 "r[{dst}] = seq_{infix}_contains({ptr}, {});",
7866 as_elem_push(*value, ek)
7867 ));
7868 }
7869 }
7870 }
7871 Op::ListPush { list, value } => {
7872 let vk = kind_at(kinds, *value);
7876 let seq_kind = match kind_at(after, *list) {
7877 k @ Kind::SeqOf(_) if self.elem_kind(k) == Kind::Rational => k,
7878 _ => self.seq_kind_for(vk),
7879 };
7880 let infix = seq_kind.seq_infix().ok_or_else(|| {
7881 format!("Push of an element of unknown kind into {seq_kind:?}")
7882 })?;
7883 self.own_target(*list, seq_kind)?;
7884 let ptr = Self::seq_ptr(*list, seq_kind)?;
7885 let item = self.elem_value(*value, vk, self.elem_kind(seq_kind))?;
7886 self.line(&format!("seq_{infix}_push({ptr}, {item});"));
7887 }
7888 Op::ListPushField { obj, field, src } => {
7892 let si = match kind_at(kinds, *obj) {
7893 Kind::Struct(si) => si,
7894 k => return Err(format!("ListPushField on a non-struct kind {k:?}")),
7895 };
7896 let (cell, fkind) = self
7897 .resolve_field(si, *field)
7898 .ok_or("ListPushField of an unknown field")?;
7899 if fkind == Kind::Tower {
7900 return self.push_to_exact_field(*obj, si, cell, *src, kinds);
7901 }
7902 if kind_at(kinds, *src) == Kind::Tower {
7903 return Err(format!("an exact value pushed onto a field of kind {fkind:?} is not yet lowered"));
7904 }
7905 let infix = fkind.seq_infix().ok_or_else(|| {
7906 format!("ListPushField into a non-sequence field kind {fkind:?}")
7907 })?;
7908 let ty = fkind.seq_c_type().ok_or_else(|| {
7909 format!("ListPushField into a non-sequence field kind {fkind:?}")
7910 })?;
7911 self.own_target(*obj, Kind::Struct(si))?;
7914 let cell_val = format!("((int64_t*)(intptr_t)r[{obj}])[{cell}]");
7915 let word = self.field_owner_word(*obj, Kind::Struct(si)).unwrap();
7916 let held_alone = if cell < 64 {
7917 format!("(({word} >> {cell}) & 1)")
7918 } else {
7919 "0".to_string()
7920 };
7921 let n = self.fresh();
7922 self.line(&format!(
7923 "if (!{held_alone}) {{ {ty} *_fl{n} = ({ty}*)lc_gc_alloc(sizeof({ty})); *_fl{n} = seq_{infix}_copy(({ty}*)(intptr_t){cell_val}); {cell_val} = (int64_t)(intptr_t)_fl{n};{} }}",
7924 if cell < 64 { format!(" {word} |= 1ULL << {cell};") } else { String::new() }
7925 ));
7926 self.line(&format!(
7927 "seq_{infix}_push(({ty}*)(intptr_t){cell_val}, {});",
7928 as_elem_push(*src, self.elem_kind(fkind))
7929 ));
7930 }
7931 Op::IterPrepare { iterable } => {
7933 let raw = kind_at(kinds, *iterable);
7936 let id = self.fresh();
7937 if self.text_byte_iters.contains(&pc) {
7938 self.line(&format!(
7939 "const unsigned char *_itv{id} = (const unsigned char*)(intptr_t)r[{iterable}]; size_t _it{id} = 0;"
7940 ));
7941 self.iter_stack.push(IterFrame {
7942 seq_kind: Kind::SeqInt,
7943 text_bytes: true,
7944 id,
7945 });
7946 } else if raw == Kind::Text {
7947 self.line(&format!(
7948 "const char *_itv{id} = {}; size_t _it{id} = 0;",
7949 as_cstr(*iterable)
7950 ));
7951 self.iter_stack.push(IterFrame {
7952 seq_kind: Kind::Text,
7953 text_bytes: false,
7954 id,
7955 });
7956 } else if let Some((kk, vk)) = self.resolve_map_kv(raw) {
7957 let ti = self.intern_tuple(vec![kk, vk]);
7959 let (ty, ptr) = (
7960 map_type_name(kk, vk).unwrap(),
7961 map_ptr_kv(*iterable, kk, vk).unwrap(),
7962 );
7963 let key = if kk == Kind::Text {
7964 format!("(int64_t)(intptr_t)_m{id}->keys[_si]")
7965 } else {
7966 format!("_m{id}->keys[_si]")
7967 };
7968 let val = map_val_load(&format!("_m{id}->vals[_si]"), vk);
7969 self.line(&format!(
7970 "{ty} *_m{id} = {ptr}; Seq_i64 *_itv{id} = (Seq_i64*)lc_gc_alloc(sizeof(Seq_i64)); *_itv{id} = seq_i64_new(); size_t _it{id} = 0;"
7971 ));
7972 self.line(&format!(
7973 "for (size_t _oi = 0; _oi < _m{id}->ord_len; _oi++) {{ size_t _si = _m{id}->order[_oi]; if (!_m{id}->state[_si]) continue; int64_t *_kv = (int64_t*)lc_gc_calloc(2, sizeof(int64_t)); _kv[0] = {key}; _kv[1] = {val}; seq_i64_push(_itv{id}, (int64_t)(intptr_t)_kv); }}"
7974 ));
7975 self.iter_stack.push(IterFrame {
7976 seq_kind: self.seq_kind_for(Kind::Tuple(ti)),
7977 text_bytes: false,
7978 id,
7979 });
7980 } else if let Some(sk) = resolve_set(raw) {
7981 let (set_ty, infix) = (sk.set_c_type().unwrap(), sk.seq_infix().unwrap());
7983 let seq_kind = self.seq_kind_for(sk.elem());
7984 let seq_ty = seq_kind.seq_c_type().unwrap();
7985 self.line(&format!(
7986 "{set_ty} *_st{id} = ({set_ty}*)(intptr_t)r[{iterable}]; {seq_ty} *_itv{id} = ({seq_ty}*)lc_gc_alloc(sizeof({seq_ty})); *_itv{id} = seq_{infix}_new(); size_t _it{id} = 0;"
7987 ));
7988 self.line(&format!(
7989 "for (size_t _oi = 0; _oi < _st{id}->ord_len; _oi++) {{ size_t _si = _st{id}->order[_oi]; if (_st{id}->state[_si]) seq_{infix}_push(_itv{id}, _st{id}->keys[_si]); }}"
7990 ));
7991 self.iter_stack.push(IterFrame {
7992 seq_kind,
7993 text_bytes: false,
7994 id,
7995 });
7996 } else {
7997 let seq_kind = seq_kind_of(raw);
7998 let ty = seq_kind
7999 .seq_c_type()
8000 .ok_or_else(|| format!("iteration over a non-sequence kind {raw:?}"))?;
8001 self.line(&format!(
8002 "{ty} *_itv{id} = ({ty}*)(intptr_t)r[{iterable}]; size_t _it{id} = 0;"
8003 ));
8004 self.iter_stack.push(IterFrame {
8005 seq_kind,
8006 text_bytes: false,
8007 id,
8008 });
8009 }
8010 }
8011 Op::IterNext { dst, exit } => {
8012 let frame = self
8013 .iter_stack
8014 .last()
8015 .ok_or("IterNext with no active iterator")?;
8016 let (seq_kind, id) = (frame.seq_kind, frame.id);
8017 if frame.text_bytes {
8018 self.line(&format!(
8019 "if (!_itv{id}[_it{id}]) goto {}{exit};",
8020 self.label
8021 ));
8022 self.line(&format!(
8023 "r[{dst}] = (int64_t)_itv{id}[_it{id}]; _it{id}++;"
8024 ));
8025 } else if seq_kind == Kind::Text {
8026 self.line(&format!(
8028 "if (!_itv{id}[_it{id}]) goto {}{exit};",
8029 self.label
8030 ));
8031 self.line(&format!(
8032 "{{ size_t _n = lc_utf8_len((unsigned char)_itv{id}[_it{id}]); char *_c = (char*)lc_gc_alloc_atomic(_n + 1); memcpy(_c, _itv{id} + _it{id}, _n); _c[_n] = 0; r[{dst}] = (int64_t)(intptr_t)_c; _it{id} += _n; }}"
8033 ));
8034 } else {
8035 self.line(&format!(
8036 "if (_it{id} >= _itv{id}->len) goto {}{exit};",
8037 self.label
8038 ));
8039 let read = elem_to_reg(
8040 &format!("_itv{id}->data[_it{id}]"),
8041 self.elem_kind(seq_kind),
8042 );
8043 self.line(&format!("r[{dst}] = {read}; _it{id}++;"));
8044 }
8045 }
8046 Op::IterPop => {
8047 self.iter_stack.pop();
8048 }
8049 Op::Show { src } => match kind_at(kinds, *src) {
8051 Kind::Int => self.line(&format!("show_i64(r[{src}]);")),
8052 Kind::Bool => self.line(&format!("show_bool(r[{src}]);")),
8053 Kind::Float => self.line(&format!("show_f64(as_f64(r[{src}]));")),
8054 Kind::Text => self.line(&format!("show_str({});", as_cstr(*src))),
8055 Kind::BigInt => self.line(&format!("show_lint(lc_xget(r[{src}]));")),
8057 Kind::Word(_) => self.line(&format!(
8058 "printf(\"%\" PRIu64 \"\\n\", (uint64_t)r[{src}]);"
8059 )),
8060 Kind::Unknown => {
8061 return Err(
8062 "Show of a register whose kind is not yet modeled (Unknown)".to_string()
8063 )
8064 }
8065 k => {
8066 let text = self.render(&format!("r[{src}]"), k)?;
8067 self.line(&format!("show_str({text});"));
8068 }
8069 },
8070 Op::FailWith { msg } => {
8073 let m = match &self.prog.constants[*msg as usize] {
8074 Constant::Text(s) => c_escape(s),
8075 _ => "FailWith".to_string(),
8076 };
8077 self.line(&format!("logos_fatal(\"{m}\");"));
8078 }
8079 Op::RegionBoundsGuard { .. } | Op::Obs { .. } => {}
8083 Op::LoadToday { dst } => self.line(&format!("r[{dst}] = logos_today();")),
8085 Op::LoadNow { dst } => self.line(&format!("r[{dst}] = logos_now();")),
8086 Op::Args { dst } => {
8088 let n = self.fresh();
8089 self.line(&format!(
8090 "Seq_str *_av{n} = (Seq_str*)lc_gc_alloc(sizeof(Seq_str)); *_av{n} = seq_str_new();"
8091 ));
8092 self.line(&format!(
8093 "for (int _a = 0; _a < lc_argc; _a++) seq_str_push(_av{n}, lc_argv[_a]);"
8094 ));
8095 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_av{n};"));
8096 }
8097 Op::RevSwap { a, b } => {
8099 let t = self.fresh();
8100 self.line("/* ping-pong buffer swap */");
8101 self.line(&format!(
8102 "{{ int64_t _rs{t} = r[{a}]; r[{a}] = r[{b}]; r[{b}] = _rs{t}; }}"
8103 ));
8104 match (self.owned.contains(a), self.owned.contains(b)) {
8107 (true, true) => self.line(&format!("{{ unsigned char _rso{t} = own[{a}]; own[{a}] = own[{b}]; own[{b}] = _rso{t}; }}")),
8108 (true, false) => self.line(&format!("own[{a}] = 0;")),
8109 (false, true) => self.line(&format!("own[{b}] = 0;")),
8110 (false, false) => {}
8111 }
8112 for (side, from) in [(*a, *b), (*b, *a)] {
8114 if kind_at(after, side) == Kind::BigInt
8115 && matches!(kind_at(kinds, from), Kind::Int | Kind::Bool)
8116 {
8117 self.line(&format!("r[{side}] = lc_xof(r[{side}]);"));
8118 }
8119 }
8120 }
8121 Op::GlobalGet { dst, idx } => {
8126 self.line(&format!("r[{dst}] = g[{idx}];"));
8127 if self.takes_out_global(pc) {
8128 if self.owned.contains(dst) {
8129 self.line(&format!("own[{dst}] = gown[{idx}];"));
8130 }
8131 } else if !self.stays_an_inspection(*dst) {
8132 self.line(&format!("gown[{idx}] = 0;"));
8133 }
8134 }
8135 Op::GlobalSet { idx, src } => {
8136 let value = match (
8137 self.global_kinds
8138 .get(*idx as usize)
8139 .copied()
8140 .unwrap_or(Kind::Unknown),
8141 kind_at(kinds, *src),
8142 ) {
8143 (Kind::BigInt, Kind::Int | Kind::Bool) => format!("lc_xof(r[{src}])"),
8144 (Kind::Int, Kind::BigInt) => format!("lc_xnarrow(r[{src}])"),
8145 (Kind::Tower, k) if k != Kind::Tower => format!(
8146 "(int64_t)(intptr_t)({})",
8147 self.tower_of(&format!("r[{src}]"), k)?
8148 ),
8149 _ => format!("r[{src}]"),
8150 };
8151 self.line(&format!("g[{idx}] = {value};"));
8152 let sole = if self.owned.contains(src) && self.dead_after(pc, *src) {
8153 format!("own[{src}]")
8154 } else {
8155 "0".to_string()
8156 };
8157 self.line(&format!("gown[{idx}] = {sole};"));
8158 }
8159 Op::RevXor { place, delta } => self.line(&format!("r[{place}] ^= r[{delta}];")),
8160 Op::RevNeg { place } => self.line(&format!("r[{place}] = -r[{place}];")),
8161 Op::RevAdd { place, delta } => self.line(&format!("r[{place}] += r[{delta}];")),
8162 Op::RevSub { place, delta } => self.line(&format!("r[{place}] -= r[{delta}];")),
8163 Op::DeepClone { dst, src } => {
8165 let e = self
8166 .clone_collection_expr(*src, kind_at(kinds, *src))?
8167 .unwrap_or_else(|| format!("r[{src}]"));
8168 self.line(&format!("r[{dst}] = {e};"));
8169 }
8170 Op::Halt => {}
8171 Op::Return { src } => {
8172 if in_function {
8173 self.store_inout_params(kinds)?;
8174 let owner = self.owned_flag(*src);
8175 self.line(&format!("if (__ret_own) *__ret_own = {owner};"));
8176 self.release_owned_scalar_sequences(Some(*src));
8177 let exact = self
8180 .current_fn
8181 .is_some_and(|fi| self.fn_ret[fi] == Kind::BigInt);
8182 let word = matches!(kind_at(kinds, *src), Kind::Int | Kind::Bool);
8183 let tower = self
8184 .current_fn
8185 .is_some_and(|fi| self.fn_ret[fi] == Kind::Tower);
8186 let declared = self.current_fn.map(|fi| self.fn_ret[fi]).unwrap_or(Kind::Unknown);
8187 if tower && kind_at(kinds, *src) != Kind::Tower {
8188 let cell = self.tower_of(&format!("r[{src}]"), kind_at(kinds, *src))?;
8190 self.line(&format!("return (int64_t)(intptr_t)({cell});"));
8191 } else if self.widens_to(kind_at(kinds, *src), declared) {
8192 let v = self.convert_number(&format!("r[{src}]"), kind_at(kinds, *src), declared)?;
8194 self.line(&format!("return {v};"));
8195 } else if self.word_entry {
8196 self.line(&self.ret(&format!("r[{src}]"), exact && !word));
8197 } else if exact && word {
8198 self.line(&format!("return lc_xof(r[{src}]);"));
8199 } else {
8200 self.line(&format!("return r[{src}];"));
8201 }
8202 }
8203 }
8204 Op::ReturnNothing => {
8205 if in_function {
8206 self.store_inout_params(kinds)?;
8207 self.line("if (__ret_own) *__ret_own = 0;");
8208 self.release_owned_scalar_sequences(None);
8209 self.line(&self.ret("0", false));
8210 }
8211 }
8212 Op::NewCrdt { dst, kind } => {
8218 match kind {
8219 4 | 5 => {
8220 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)logos_crdt_new();"));
8221 }
8222 1 => {
8223 let t = self.fresh();
8224 self.line(&format!("Seq_i64 *_cr{t} = (Seq_i64*)lc_gc_alloc(sizeof(Seq_i64)); *_cr{t} = seq_i64_new();"));
8228 self.line(&format!("r[{dst}] = (int64_t)(intptr_t)_cr{t};"));
8229 }
8230 k => {
8231 return Err(format!(
8232 "NewCrdt of a non-counter/sequence CRDT kind {k} (set/register lattice)"
8233 ))
8234 }
8235 }
8236 }
8237 Op::CrdtBump {
8238 obj,
8239 field,
8240 amount,
8241 negate,
8242 } => {
8243 let ok = kind_at(kinds, *obj);
8248 let (cell, _) = self
8249 .resolve_field_any(ok, *field)
8250 .ok_or("CrdtBump of an unknown struct field")?;
8251 let is_counter = self
8252 .struct_name_of_kind(ok)
8253 .map(|n| self.counter_fields.contains(&(n, cell)))
8254 .unwrap_or(false);
8255 let cell_lv = format!("((int64_t*)(intptr_t)r[{obj}])[{cell}]");
8256 if is_counter {
8257 let amount = if kind_at(kinds, *amount) == Kind::BigInt {
8258 format!("lc_xnarrow(r[{amount}])")
8259 } else {
8260 format!("r[{amount}]")
8261 };
8262 self.line(&format!(
8263 "logos_crdt_bump((logos_crdt*)(intptr_t){cell_lv}, {amount}, {});",
8264 if *negate { 1 } else { 0 }
8265 ));
8266 } else {
8267 let op = if *negate { "lc_xsub" } else { "lc_xadd" };
8268 let amount = as_exact(*amount, kind_at(kinds, *amount))?;
8269 self.line(&format!("{cell_lv} = {op}({cell_lv}, {amount});"));
8270 }
8271 }
8272 Op::CrdtMerge { target, source } | Op::CrdtJoin { target, source } => {
8273 let is_join = matches!(op, Op::CrdtJoin { .. });
8278 let tk = kind_at(kinds, *target);
8279 let tname = self
8280 .struct_name_of_kind(tk)
8281 .ok_or("CRDT merge target is not a struct")?;
8282 let n = self
8283 .struct_cell_count(tk)
8284 .ok_or("CRDT merge of an unknown struct type")?;
8285 for cell in 0..n {
8286 let t_lv = format!("((int64_t*)(intptr_t)r[{target}])[{cell}]");
8287 let s_lv = format!("((int64_t*)(intptr_t)r[{source}])[{cell}]");
8288 if self.counter_fields.contains(&(tname.clone(), cell)) {
8289 self.line(&format!(
8290 "logos_crdt_merge((logos_crdt*)(intptr_t){t_lv}, (logos_crdt*)(intptr_t){s_lv});"
8291 ));
8292 } else if self.field_storage_kind(tk, cell) == Some(Kind::BigInt) {
8293 if is_join {
8294 self.line(&format!(
8295 "if (lc_xcmp({s_lv}, {t_lv}) > 0) {t_lv} = {s_lv};"
8296 ));
8297 } else {
8298 self.line(&format!("{t_lv} = lc_xadd({t_lv}, {s_lv});"));
8299 }
8300 } else if is_join {
8301 self.line(&format!("if ({s_lv} > {t_lv}) {t_lv} = {s_lv};"));
8302 } else {
8303 self.line(&format!("{t_lv} += {s_lv};"));
8304 }
8305 }
8306 }
8307 Op::CrdtResolve { obj, field, value } => {
8308 let ok = kind_at(kinds, *obj);
8311 let (cell, _) = self
8312 .resolve_field_any(ok, *field)
8313 .ok_or("CrdtResolve of an unknown struct field")?;
8314 let stored = self.field_store_value(ok, cell, *value, kind_at(kinds, *value))?;
8315 self.line(&format!(
8316 "((int64_t*)(intptr_t)r[{obj}])[{cell}] = {stored};"
8317 ));
8318 }
8319 Op::CrdtAppend { seq, value } => {
8320 let seq_kind = self.seq_kind_for(kind_at(kinds, *value));
8324 let infix = seq_kind.seq_infix().ok_or_else(|| {
8325 format!("CrdtAppend of an element of unknown kind {seq_kind:?}")
8326 })?;
8327 let ty = seq_kind.seq_c_type().unwrap();
8328 self.line(&format!(
8329 "seq_{infix}_push(({ty}*)(intptr_t)r[{seq}], {});",
8330 as_elem_push(*value, self.elem_kind(seq_kind))
8331 ));
8332 }
8333 Op::NetConnect { url, .. } => {
8337 let text = self.text_of(*url, kind_at(kinds, *url))?;
8338 self.line(&format!("logos_net_connect({text});"));
8339 }
8340 Op::NetListen { topic } => {
8341 let text = self.text_of(*topic, kind_at(kinds, *topic))?;
8342 self.line(&format!("logos_net_listen({text});"));
8343 }
8344 Op::NetSync { .. } => {}
8345 Op::NetMakePeer { dst, addr } => {
8347 let text = self.text_of(*addr, kind_at(kinds, *addr))?;
8348 self.line(&format!(
8349 "r[{dst}] = (int64_t)logos_net_makepeer((long long)(intptr_t)({text}));"
8350 ));
8351 }
8352 Op::NetSend { to, msg } => {
8355 if let Some(to) = self.peer_address(*to, kinds)? {
8356 self.line(&format!("logos_net_send({to}, (long long)r[{msg}]);"));
8357 }
8358 }
8359 Op::NetStream { to, values } => {
8360 if let Some(to) = self.peer_address(*to, kinds)? {
8361 self.line(&format!("logos_net_stream({to}, (long long)r[{values}]);"));
8362 }
8363 }
8364 Op::NetAwait { dst, from, stream } => {
8365 if let Some(from) = self.peer_address(*from, kinds)? {
8366 let take = if *stream {
8367 "logos_net_await_stream"
8368 } else {
8369 "logos_net_await"
8370 };
8371 self.line(&format!("r[{dst}] = (int64_t){take}({from});"));
8372 }
8373 }
8374 Op::Tcp {
8383 dst,
8384 kind,
8385 args_start,
8386 arg_count,
8387 } => {
8388 let a = |i: u16| args_start + i;
8389 let n = self.fresh();
8390 let err = format!("_tcperr{n}");
8391 self.line(&format!("char *{err} = NULL;"));
8392 let (ok_tag, err_tag) = (
8395 self.ctor_tag("Result", "Ok").unwrap(),
8396 self.ctor_tag("Result", "Err").unwrap(),
8397 );
8398 let box_result = |em: &mut Self, ok_expr: &str, cond: &str| {
8399 let b = em.fresh();
8400 em.line(&format!(
8401 "int64_t *_res{b} = (int64_t*)lc_gc_calloc(2, sizeof(int64_t));"
8402 ));
8403 em.line(&format!("if ({cond}) {{ _res{b}[0] = {err_tag}; _res{b}[1] = (int64_t)(intptr_t){err}; }}"));
8404 em.line(&format!(
8405 "else {{ _res{b}[0] = {ok_tag}; _res{b}[1] = (int64_t){ok_expr}; }}"
8406 ));
8407 em.line(&format!("r[{dst}] = (int64_t)(intptr_t)_res{b};"));
8408 };
8409 match *kind {
8410 0 => {
8412 let c = self.fresh();
8413 self.line(&format!(
8414 "long long _tc{c} = logos_tcp_connect({}, r[{}], &{err});",
8415 as_cstr(a(0)),
8416 a(1)
8417 ));
8418 box_result(self, &format!("_tc{c}"), &format!("_tc{c} < 0"));
8419 }
8420 1 => {
8422 let dptr = Self::seq_ptr(a(1), Kind::SeqInt)?;
8423 let c = self.fresh();
8424 self.line(&format!(
8425 "long long _tc{c} = logos_tcp_send(r[{}], {dptr}, &{err});",
8426 a(0)
8427 ));
8428 box_result(self, &format!("_tc{c}"), &format!("_tc{c} < 0"));
8429 }
8430 2 | 3 => {
8432 let f = if *kind == 2 {
8433 "logos_tcp_recv"
8434 } else {
8435 "logos_tcp_recv_exact"
8436 };
8437 let c = self.fresh();
8438 self.line(&format!(
8439 "Seq_i64 *_tc{c} = {f}(r[{}], r[{}], &{err});",
8440 a(0),
8441 a(1)
8442 ));
8443 box_result(
8444 self,
8445 &format!("(intptr_t)_tc{c}"),
8446 &format!("_tc{c} == NULL"),
8447 );
8448 }
8449 4 => {
8451 let c = self.fresh();
8452 self.line(&format!(
8453 "long long _tc{c} = logos_tcp_close(r[{}], &{err});",
8454 a(0)
8455 ));
8456 box_result(self, "0", &format!("_tc{c} < 0"));
8457 }
8458 6 => {
8460 let c = self.fresh();
8461 self.line(&format!(
8462 "long long _tc{c} = logos_tcp_listen(r[{}], &{err});",
8463 a(0)
8464 ));
8465 box_result(self, &format!("_tc{c}"), &format!("_tc{c} < 0"));
8466 }
8467 7 => {
8469 let c = self.fresh();
8470 self.line(&format!(
8471 "long long _tc{c} = logos_tcp_accept(r[{}], &{err});",
8472 a(0)
8473 ));
8474 box_result(self, &format!("_tc{c}"), &format!("_tc{c} < 0"));
8475 }
8476 8 => {
8478 let c = self.fresh();
8479 self.line(&format!(
8480 "long long _tc{c} = logos_tcp_listener_port(r[{}], &{err});",
8481 a(0)
8482 ));
8483 box_result(self, &format!("_tc{c}"), &format!("_tc{c} < 0"));
8484 }
8485 5 => {
8488 let c = self.fresh();
8489 self.line(&format!(
8490 "long long _tc{c} = logos_tcp_set_timeout(r[{}], r[{}], &{err});",
8491 a(0),
8492 a(1)
8493 ));
8494 box_result(self, "0", &format!("_tc{c} < 0"));
8495 }
8496 9 => {
8499 let c = self.fresh();
8500 self.line(&format!(
8501 "long long _tc{c} = logos_tcp_shutdown_write(r[{}], &{err});",
8502 a(0)
8503 ));
8504 box_result(self, "0", &format!("_tc{c} < 0"));
8505 }
8506 other => return Err(format!("Tcp op with unknown kind {other}")),
8507 }
8508 let _ = arg_count;
8509 }
8510 Op::CheckPolicy {
8515 subject,
8516 predicate,
8517 is_capability,
8518 object,
8519 source_text,
8520 } => {
8521 let src_msg: String = self
8522 .const_text(*source_text)
8523 .unwrap_or_default()
8524 .to_string();
8525 let msg_lit = c_escape(&src_msg);
8526 let trap = |em: &mut Self| {
8527 em.line("fflush(stdout);");
8528 em.line(&format!(
8529 "fprintf(stderr, \"Security Check Failed: {msg_lit}\\n\"); exit(1);"
8530 ));
8531 };
8532 let Some((interner, policies)) = self.policy_ctx else {
8533 trap(self);
8535 return Ok(());
8536 };
8537 let subj_kind = kind_at(kinds, *subject);
8538 let Some(subj_type) = self.struct_name_of_kind(subj_kind) else {
8539 trap(self); return Ok(());
8541 };
8542 let Some(type_sym) = interner.lookup(&subj_type) else {
8543 trap(self); return Ok(());
8545 };
8546 let obj_type = if *object != u16::MAX {
8547 self.struct_name_of_kind(kind_at(kinds, *object))
8548 } else {
8549 None
8550 };
8551 let cond = if *is_capability {
8552 policies.get_capabilities(type_sym).and_then(|caps| {
8553 caps.iter()
8554 .find(|c| c.action == *predicate)
8555 .map(|c| c.condition.clone())
8556 })
8557 } else {
8558 policies.get_predicates(type_sym).and_then(|preds| {
8559 preds
8560 .iter()
8561 .find(|p| p.predicate_name == *predicate)
8562 .map(|p| p.condition.clone())
8563 })
8564 };
8565 match cond {
8566 Some(c) => {
8567 let expr = self.policy_condition_expr(
8568 &c,
8569 *subject,
8570 &subj_type,
8571 *object,
8572 obj_type.as_deref(),
8573 interner,
8574 )?;
8575 self.line(&format!("if (!({expr})) {{"));
8576 trap(self);
8577 self.line("}");
8578 }
8579 None => trap(self), }
8581 }
8582 other => return Err(format!("op not yet lowered: {}", other.name())),
8583 }
8584 Ok(())
8585 }
8586
8587 fn word_arith(
8590 &mut self,
8591 dst: u16,
8592 lhs: u16,
8593 rhs: u16,
8594 cop: &str,
8595 lk: Kind,
8596 rk: Kind,
8597 ) -> Result<(), String> {
8598 let refused = |em: &mut Self| -> Result<(), String> {
8599 let (Some(l), Some(r)) = (sample_value(lk), sample_value(rk)) else {
8600 return Err(format!("`{cop}` over {lk:?} and {rk:?}"));
8601 };
8602 let result = match cop {
8603 "+" => crate::semantics::arith::add(l, r),
8604 "-" => crate::semantics::arith::subtract(l, r),
8605 "*" => crate::semantics::arith::multiply(l, r),
8606 "/" => crate::semantics::arith::divide(l, r),
8607 "%" => crate::semantics::arith::modulo(l, r),
8608 "&" => {
8609 crate::semantics::arith::binary_op(crate::ast::stmt::BinaryOpKind::BitAnd, l, r)
8610 }
8611 "|" => {
8612 crate::semantics::arith::binary_op(crate::ast::stmt::BinaryOpKind::BitOr, l, r)
8613 }
8614 _ => {
8615 crate::semantics::arith::binary_op(crate::ast::stmt::BinaryOpKind::BitXor, l, r)
8616 }
8617 };
8618 let sentence = match result {
8619 Err(sentence) => sentence,
8620 Ok(_) => return Err(format!("the VM combines {lk:?} and {rk:?} with `{cop}`")),
8621 };
8622 em.line(&format!("logos_fatalf({});", words::literal(&sentence)));
8623 Ok(())
8624 };
8625 let (Kind::Word(a), Kind::Word(b)) = (lk, rk) else {
8626 return refused(self);
8627 };
8628 if a != b {
8629 return refused(self);
8630 }
8631 let mask = if a == 64 {
8632 "UINT64_MAX".to_string()
8633 } else {
8634 format!("((1ULL << {a}) - 1)")
8635 };
8636 let (x, y) = (format!("(uint64_t)r[{lhs}]"), format!("(uint64_t)r[{rhs}]"));
8637 match cop {
8638 "/" | "%" => {
8639 let zero = if cop == "/" {
8640 words::division_by_zero()
8641 } else {
8642 words::modulo_by_zero()
8643 };
8644 self.line(&format!("if ({y} == 0) logos_fatalf({zero});"));
8645 self.line(&format!("r[{dst}] = (int64_t)({x} {cop} {y});"));
8646 }
8647 _ => self.line(&format!("r[{dst}] = (int64_t)(({x} {cop} {y}) & {mask});")),
8648 }
8649 Ok(())
8650 }
8651
8652 fn arith(
8654 &mut self,
8655 dst: u16,
8656 lhs: u16,
8657 rhs: u16,
8658 cop: &str,
8659 kinds: &[Kind],
8660 ) -> Result<(), String> {
8661 let (lk, rk) = (kind_at(kinds, lhs), kind_at(kinds, rhs));
8662 if [lk, rk].iter().any(|k| matches!(k, Kind::Date | Kind::Moment | Kind::Time | Kind::Span)) {
8665 return Err(format!("`{cop}` over {lk:?} and {rk:?} is not yet lowered"));
8666 }
8667 if lk == Kind::Text || rk == Kind::Text {
8672 if cop != "+" {
8673 return Err(format!("`{cop}` on a Text operand"));
8674 }
8675 let (l, r) = (self.text_of(lhs, lk)?, self.text_of(rhs, rk)?);
8676 self.line(&format!(
8677 "r[{dst}] = (int64_t)(intptr_t)str_concat({l}, {r});"
8678 ));
8679 return Ok(());
8680 }
8681 if matches!(lk, Kind::Word(_)) || matches!(rk, Kind::Word(_)) {
8682 return self.word_arith(dst, lhs, rhs, cop, lk, rk);
8683 }
8684 if lk == Kind::Rational || rk == Kind::Rational {
8688 if lk == Kind::Float || rk == Kind::Float {
8689 let (l, r) = (self.real_of(lhs, lk), self.real_of(rhs, rk));
8690 if cop == "/" {
8691 self.line(&format!("r[{dst}] = as_bits(logos_rdiv({l}, {r}));"));
8692 } else if matches!(cop, "+" | "-" | "*") {
8693 self.line(&format!("r[{dst}] = as_bits({l} {cop} {r});"));
8694 } else {
8695 return Err(format!("`{cop}` over {lk:?} and {rk:?} is not yet lowered"));
8696 }
8697 return Ok(());
8698 }
8699 let (l, r) = (as_ldec(lhs, lk)?, as_ldec(rhs, rk)?);
8700 if matches!(cop, "&" | "|" | "^") {
8701 self.line(&format!("r[{dst}] = ldec_bitwise({l}, {r}, '{cop}');"));
8702 return Ok(());
8703 }
8704 let f = match cop {
8705 "+" => "ldec_add",
8706 "-" => "ldec_sub",
8707 "*" => "ldec_mul",
8708 "/" => "ldec_quotient",
8709 _ => return Err(format!("`{cop}` over {lk:?} and {rk:?} is not yet lowered")),
8710 };
8711 self.box_ldec(dst, &format!("{f}({l}, {r})"));
8712 return Ok(());
8713 }
8714 if cop == "/" && (lk == Kind::Float || rk == Kind::Float) {
8715 self.line(&format!(
8716 "r[{dst}] = as_bits(logos_rdiv({}, {}));",
8717 as_double(lhs, lk),
8718 as_double(rhs, rk)
8719 ));
8720 } else if lk == Kind::Float || rk == Kind::Float {
8721 self.line(&format!(
8722 "r[{dst}] = as_bits({} {cop} {});",
8723 as_double(lhs, lk),
8724 as_double(rhs, rk)
8725 ));
8726 } else if cop == "/" {
8727 self.line(&format!("r[{dst}] = logos_idiv(r[{lhs}], r[{rhs}]);"));
8728 } else {
8729 self.line(&format!("r[{dst}] = r[{lhs}] {cop} r[{rhs}];"));
8730 }
8731 Ok(())
8732 }
8733
8734 fn format_spec_body(&self, src: u16, sk: Kind, spec: &str) -> Result<String, String> {
8739 if spec == "$" {
8740 return Ok(format!(
8741 "logos_dyn_sprintf(\"$%.2f\", {})",
8742 as_double(src, sk)
8743 ));
8744 }
8745 if let Some(rest) = spec.strip_prefix('.') {
8746 let prec: usize = rest
8747 .parse()
8748 .map_err(|_| format!("bad precision spec `{spec}`"))?;
8749 return Ok(format!(
8750 "logos_dyn_sprintf(\"%.{prec}f\", {})",
8751 as_double(src, sk)
8752 ));
8753 }
8754 if let Some(align) = spec.chars().next().filter(|c| matches!(c, '<' | '>' | '^')) {
8755 let width: usize = spec[1..]
8756 .parse()
8757 .map_err(|_| format!("bad width in spec `{spec}`"))?;
8758 if align == '^' {
8759 return match sk {
8760 Kind::Int => Ok(format!("logos_center_i64(r[{src}], {width})")),
8761 Kind::Text => Ok(format!("logos_center_str({}, {width})", as_cstr(src))),
8762 Kind::Word(_) => Ok(format!(
8763 "logos_center_str(logos_dyn_sprintf(\"%\" PRIu64, (uint64_t)r[{src}]), {width})"
8764 )),
8765 _ => Err(format!("center-format of kind {sk:?} unsupported")),
8766 };
8767 }
8768 let flag = if align == '<' { "-" } else { "" };
8769 return match sk {
8770 Kind::Int => Ok(format!(
8771 "logos_dyn_sprintf(\"%{flag}{width}\" PRId64, r[{src}])"
8772 )),
8773 Kind::Text => Ok(format!(
8774 "logos_dyn_sprintf(\"%{flag}{width}s\", {})",
8775 as_cstr(src)
8776 )),
8777 Kind::Float => Ok(format!(
8778 "logos_dyn_sprintf(\"%{flag}{width}g\", as_f64(r[{src}]))"
8779 )),
8780 Kind::Word(_) => Ok(format!(
8781 "logos_dyn_sprintf(\"%{flag}{width}\" PRIu64, (uint64_t)r[{src}])"
8782 )),
8783 _ => Err(format!("aligned-format of kind {sk:?} unsupported")),
8784 };
8785 }
8786 if let Ok(width) = spec.parse::<usize>() {
8787 return match sk {
8788 Kind::Int => Ok(format!("logos_dyn_sprintf(\"%{width}\" PRId64, r[{src}])")),
8789 Kind::Text => Ok(format!(
8790 "logos_dyn_sprintf(\"%{width}s\", {})",
8791 as_cstr(src)
8792 )),
8793 Kind::Float => Ok(format!(
8794 "logos_dyn_sprintf(\"%{width}g\", as_f64(r[{src}]))"
8795 )),
8796 Kind::Word(_) => Ok(format!(
8797 "logos_dyn_sprintf(\"%{width}\" PRIu64, (uint64_t)r[{src}])"
8798 )),
8799 _ => Err(format!("width-format of kind {sk:?} unsupported")),
8800 };
8801 }
8802 Err(format!("unsupported interpolation format spec `{spec}`"))
8803 }
8804
8805 fn cmp(&mut self, dst: u16, lhs: u16, rhs: u16, cop: &str, kinds: &[Kind]) -> Result<(), String> {
8807 let (lk, rk) = (kind_at(kinds, lhs), kind_at(kinds, rhs));
8808 let time = |k: Kind| matches!(k, Kind::Date | Kind::Moment | Kind::Time | Kind::Span);
8811 if (time(lk) || time(rk)) && (lk != rk || lk == Kind::Span) {
8812 return Err(format!("`{cop}` over {lk:?} and {rk:?} is not yet lowered"));
8813 }
8814 if let (Kind::Word(a), Kind::Word(b)) = (lk, rk) {
8815 if a == b {
8816 self.line(&format!(
8817 "r[{dst}] = ((uint64_t)r[{lhs}] {cop} (uint64_t)r[{rhs}]);"
8818 ));
8819 return Ok(());
8820 }
8821 }
8822 if matches!(lk, Kind::Word(_)) || matches!(rk, Kind::Word(_)) {
8824 if let (Some(l), Some(r)) = (sample_value(lk), sample_value(rk)) {
8825 use crate::ast::stmt::BinaryOpKind;
8826 let op = match cop {
8827 "<" => BinaryOpKind::Lt,
8828 ">" => BinaryOpKind::Gt,
8829 "<=" => BinaryOpKind::LtEq,
8830 _ => BinaryOpKind::GtEq,
8831 };
8832 if let Err(sentence) = crate::semantics::arith::binary_op(op, l, r) {
8833 self.line(&format!("logos_fatalf({});", words::literal(&sentence)));
8834 return Ok(());
8835 }
8836 }
8837 }
8838 if lk == Kind::Rational || rk == Kind::Rational {
8841 if lk == Kind::Float || rk == Kind::Float {
8842 let (x, f, flip) = if lk == Kind::Rational {
8843 (as_ldec(lhs, lk)?, as_double(rhs, rk), "")
8844 } else {
8845 (as_ldec(rhs, rk)?, as_double(lhs, lk), "-")
8846 };
8847 let n = self.fresh();
8848 let holds = if cop == "!=" {
8849 format!("_c{n} == 2 || _c{n} != 0")
8850 } else {
8851 format!("_c{n} != 2 && ({flip}_c{n}) {cop} 0")
8852 };
8853 self.line(&format!("{{ int _c{n} = ldec_cmp_f64({x}, {f}); r[{dst}] = ({holds}); }}"));
8854 } else {
8855 let (l, r) = (as_ldec(lhs, lk)?, as_ldec(rhs, rk)?);
8856 self.line(&format!("r[{dst}] = (ldec_cmp({l}, {r}) {cop} 0);"));
8857 }
8858 return Ok(());
8859 }
8860 if (lk == Kind::BigInt || rk == Kind::BigInt)
8861 && !matches!(lk, Kind::Float | Kind::Rational)
8862 && !matches!(rk, Kind::Float | Kind::Rational)
8863 {
8864 let tagged = |reg: u16, k: Kind| {
8866 if k == Kind::BigInt {
8867 format!("r[{reg}]")
8868 } else {
8869 format!("lc_xof(r[{reg}])")
8870 }
8871 };
8872 self.line(&format!(
8873 "r[{dst}] = (lc_xcmp({}, {}) {cop} 0);",
8874 tagged(lhs, lk),
8875 tagged(rhs, rk)
8876 ));
8877 } else if lk == Kind::Float || rk == Kind::Float {
8878 self.line(&format!(
8879 "r[{dst}] = ({} {cop} {});",
8880 as_double(lhs, lk),
8881 as_double(rhs, rk)
8882 ));
8883 } else {
8884 self.line(&format!("r[{dst}] = (r[{lhs}] {cop} r[{rhs}]);"));
8885 }
8886 Ok(())
8887 }
8888}
8889
8890fn ctor_tags(prog: &CompiledProgram) -> std::collections::HashMap<String, Vec<String>> {
8894 let mut tags: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
8895 tags.insert(
8896 "Option".to_string(),
8897 vec!["Some".to_string(), "None".to_string()],
8898 );
8899 tags.insert(
8900 "Result".to_string(),
8901 vec!["Ok".to_string(), "Err".to_string()],
8902 );
8903 let text = |idx: u32| match prog.constants.get(idx as usize) {
8904 Some(Constant::Text(t)) => Some(t.clone()),
8905 _ => None,
8906 };
8907 for op in &prog.code {
8908 if let Op::NewInductive {
8909 type_name, ctor, ..
8910 } = op
8911 {
8912 let (Some(ty), Some(c)) = (text(*type_name), text(*ctor)) else {
8913 continue;
8914 };
8915 if prog.enum_types.iter().any(|e| e.name == ty) {
8916 continue;
8917 }
8918 let ctors = tags.entry(ty).or_default();
8919 if !ctors.contains(&c) {
8920 ctors.push(c);
8921 }
8922 }
8923 }
8924 tags
8925}
8926
8927#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8929enum Mutation {
8930 Owned(u16),
8933 Shared,
8935}
8936
8937fn refused_for_kinds(op: &Op, kinds: &[Kind]) -> bool {
8941 use crate::ast::stmt::BinaryOpKind;
8942 let words_clash = |bop: BinaryOpKind, a: Kind, b: Kind| {
8946 if !matches!(a, Kind::Word(_)) && !matches!(b, Kind::Word(_)) {
8947 return false;
8948 }
8949 match (sample_value(a), sample_divisor(b)) {
8950 (Some(x), Some(y)) => crate::semantics::arith::binary_op(bop, x, y).is_err(),
8951 _ => false,
8952 }
8953 };
8954 let pair = |lhs: u16, rhs: u16| (kind_at(kinds, lhs), kind_at(kinds, rhs));
8955 match *op {
8956 Op::Add { lhs, rhs, .. } => {
8957 let (l, r) = pair(lhs, rhs);
8958 words_clash(BinaryOpKind::Add, l, r)
8959 }
8960 Op::Sub { lhs, rhs, .. } => {
8961 let (l, r) = pair(lhs, rhs);
8962 words_clash(BinaryOpKind::Subtract, l, r)
8963 }
8964 Op::Mul { lhs, rhs, .. } => {
8965 let (l, r) = pair(lhs, rhs);
8966 words_clash(BinaryOpKind::Multiply, l, r)
8967 }
8968 Op::Div { lhs, rhs, .. } => {
8969 let (l, r) = pair(lhs, rhs);
8970 words_clash(BinaryOpKind::Divide, l, r)
8971 }
8972 Op::BitAnd { lhs, rhs, .. } => {
8973 let (l, r) = pair(lhs, rhs);
8974 words_clash(BinaryOpKind::BitAnd, l, r)
8975 }
8976 Op::BitOr { lhs, rhs, .. } => {
8977 let (l, r) = pair(lhs, rhs);
8978 words_clash(BinaryOpKind::BitOr, l, r)
8979 }
8980 Op::BitXor { lhs, rhs, .. } => {
8981 let (l, r) = pair(lhs, rhs);
8982 words_clash(BinaryOpKind::BitXor, l, r)
8983 }
8984 Op::Mod { lhs, rhs, .. } => {
8985 let (l, r) = pair(lhs, rhs);
8986 words_clash(BinaryOpKind::Modulo, l, r) || l == Kind::Float || r == Kind::Float
8987 }
8988 Op::ApproxEq { lhs, rhs, .. } => {
8989 let numeric = |k: Kind| matches!(k, Kind::Int | Kind::Float | Kind::Unknown);
8990 !numeric(kind_at(kinds, lhs)) || !numeric(kind_at(kinds, rhs))
8991 }
8992 _ => false,
8993 }
8994}
8995
8996fn mutation(op: &Op) -> Option<Mutation> {
8999 match *op {
9000 Op::ListPush { list, .. } | Op::ListPop { list, .. } => Some(Mutation::Owned(list)),
9001 Op::SetIndex { collection, .. }
9002 | Op::SetIndexUnchecked { collection, .. }
9003 | Op::RemoveFrom { collection, .. } => Some(Mutation::Owned(collection)),
9004 Op::SetAdd { set, .. } => Some(Mutation::Owned(set)),
9005 Op::StructInsert { obj, .. } | Op::ListPushField { obj, .. } => Some(Mutation::Owned(obj)),
9006 Op::EnsureOwned { reg } => Some(Mutation::Owned(reg)),
9007 Op::CrdtBump { .. }
9008 | Op::CrdtMerge { .. }
9009 | Op::CrdtJoin { .. }
9010 | Op::CrdtAppend { .. }
9011 | Op::CrdtResolve { .. } => Some(Mutation::Shared),
9012 _ => None,
9013 }
9014}
9015
9016fn inspected_regs(op: &Op) -> Vec<u16> {
9019 match *op {
9020 Op::RegionBoundsGuard {
9021 array, bound, iv, ..
9022 } => vec![array, bound, iv],
9023 Op::Length { collection, .. } => vec![collection],
9024 Op::Index {
9025 collection, index, ..
9026 }
9027 | Op::IndexUnchecked {
9028 collection, index, ..
9029 } => vec![collection, index],
9030 Op::Contains {
9031 collection, value, ..
9032 } => vec![collection, value],
9033 Op::Show { src }
9034 | Op::FormatValue { src, .. }
9035 | Op::Not { src, .. }
9036 | Op::DeepClone { src, .. } => vec![src],
9037 Op::Concat { lhs, rhs, .. }
9038 | Op::Eq { lhs, rhs, .. }
9039 | Op::NotEq { lhs, rhs, .. }
9040 | Op::Lt { lhs, rhs, .. }
9041 | Op::Gt { lhs, rhs, .. }
9042 | Op::LtEq { lhs, rhs, .. }
9043 | Op::GtEq { lhs, rhs, .. }
9044 | Op::ApproxEq { lhs, rhs, .. }
9045 | Op::SeqConcat { lhs, rhs, .. }
9046 | Op::UnionOp { lhs, rhs, .. }
9047 | Op::IntersectOp { lhs, rhs, .. } => vec![lhs, rhs],
9048 Op::SliceOp {
9049 collection,
9050 start,
9051 end,
9052 ..
9053 } => vec![collection, start, end],
9054 Op::GetField { obj, .. } => vec![obj],
9055 Op::TestArm { target, .. } | Op::BindArm { target, .. } => vec![target],
9056 Op::JumpIfFalse { cond, .. } | Op::JumpIfTrue { cond, .. } => vec![cond],
9057 Op::AddAssign { src, .. } => vec![src],
9058 Op::CheckPolicy {
9059 subject, object, ..
9060 } => vec![subject, object],
9061 Op::NewEmptyListWithCapacity { capacity, .. }
9062 | Op::NewEmptyMapWithCapacity { capacity, .. } => vec![capacity],
9063 _ => Vec::new(),
9064 }
9065}
9066
9067fn carries_no_handle(k: Kind) -> bool {
9069 matches!(
9070 k,
9071 Kind::Int
9072 | Kind::Bool
9073 | Kind::Float
9074 | Kind::BigInt
9075 | Kind::Word(_)
9076 | Kind::Nothing
9077 | Kind::Never
9078 )
9079}
9080
9081fn fresh_regs(op: &Op, after: &[Kind]) -> Vec<u16> {
9083 use crate::semantics::builtins::BuiltinId;
9084 match *op {
9085 Op::NewList { dst, .. }
9086 | Op::NewEmptyList { dst }
9087 | Op::NewEmptyListWithCapacity { dst, .. }
9088 | Op::NewEmptyListFixed { dst, .. }
9089 | Op::NewEmptyListI32 { dst }
9090 | Op::NewEmptySet { dst }
9091 | Op::NewEmptyMap { dst }
9092 | Op::NewEmptyMapWithCapacity { dst, .. }
9093 | Op::NewRange { dst, .. }
9094 | Op::SeqConcat { dst, .. }
9095 | Op::SliceOp { dst, .. }
9096 | Op::UnionOp { dst, .. }
9097 | Op::IntersectOp { dst, .. }
9098 | Op::NewStruct { dst, .. }
9099 | Op::Args { dst } => vec![dst],
9100 Op::DeepClone { dst, .. } if !matches!(kind_at(after, dst), Kind::CrdtCounter) => vec![dst],
9101 Op::CallBuiltin {
9102 dst,
9103 builtin: BuiltinId::TextBytes,
9104 ..
9105 } => vec![dst],
9106 _ => Vec::new(),
9107 }
9108}
9109
9110fn jump_target(op: &Op) -> Option<usize> {
9111 match op {
9112 Op::Jump { target } | Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } => {
9113 Some(*target)
9114 }
9115 Op::IterNext { exit, .. } => Some(*exit),
9117 _ => None,
9118 }
9119}
9120
9121fn index_too_wide(
9124 op: &Op,
9125 kinds: &[Kind],
9126 reg: u16,
9127) -> Option<crate::semantics::failcode::FailCode> {
9128 use crate::semantics::failcode::FailCode;
9129 match *op {
9130 Op::Index {
9131 collection, index, ..
9132 }
9133 | Op::IndexUnchecked {
9134 collection, index, ..
9135 } if index == reg => match kind_at(kinds, collection) {
9136 Kind::Text => Some(FailCode::TextIndexTooWide),
9137 k if is_sequence(k) => Some(FailCode::ListIndexTooWide),
9138 _ => None,
9139 },
9140 Op::SetIndex {
9141 collection, index, ..
9142 }
9143 | Op::SetIndexUnchecked {
9144 collection, index, ..
9145 } if index == reg && is_sequence(kind_at(kinds, collection)) => {
9146 Some(FailCode::ListStoreIndexTooWide)
9147 }
9148 _ => None,
9149 }
9150}
9151
9152fn reads_exact_reg(op: &Op, kinds: &[Kind], reg: u16) -> bool {
9154 reads_exact(op, kinds)
9155 || matches!(op, Op::CrdtBump { amount, .. } if *amount == reg)
9156 || matches!(op, Op::StructInsert { value, .. } | Op::CrdtResolve { value, .. } if *value == reg)
9157 || matches!(op,
9158 Op::SetIndex { collection, value, .. }
9159 | Op::SetIndexUnchecked { collection, value, .. }
9160 if *value == reg && matches!(kind_at(kinds, *collection), Kind::Map(_) | Kind::MapUnknown | Kind::Struct(_) | Kind::StructSynth(_))
9161 )
9162}
9163
9164fn reads_exact(op: &Op, kinds: &[Kind]) -> bool {
9166 match op {
9167 Op::Shl { lhs, .. } | Op::Shr { lhs, .. } => !matches!(kind_at(kinds, *lhs), Kind::Word(_)),
9168 Op::FormatValue { spec, .. } => *spec == u32::MAX,
9169 Op::Move { .. }
9170 | Op::DeepClone { .. }
9171 | Op::Add { .. }
9172 | Op::Sub { .. }
9173 | Op::Mul { .. }
9174 | Op::Pow { .. }
9175 | Op::Div { .. }
9176 | Op::FloorDiv { .. }
9177 | Op::Mod { .. }
9178 | Op::DivPow2 { .. }
9179 | Op::MagicDivU { .. }
9180 | Op::AddAssign { .. }
9181 | Op::BitXor { .. }
9182 | Op::BitAnd { .. }
9183 | Op::BitOr { .. }
9184 | Op::Lt { .. }
9185 | Op::Gt { .. }
9186 | Op::LtEq { .. }
9187 | Op::GtEq { .. }
9188 | Op::Eq { .. }
9189 | Op::NotEq { .. }
9190 | Op::Show { .. }
9191 | Op::Concat { .. }
9192 | Op::Return { .. }
9193 | Op::Call { .. }
9194 | Op::EnsureOwned { .. }
9195 | Op::GlobalSet { .. }
9196 | Op::RevSwap { .. } => true,
9197 Op::CallBuiltin {
9199 builtin:
9200 crate::semantics::builtins::BuiltinId::Word8Make
9201 | crate::semantics::builtins::BuiltinId::Word16Make
9202 | crate::semantics::builtins::BuiltinId::Word32
9203 | crate::semantics::builtins::BuiltinId::Word64,
9204 ..
9205 } => true,
9206 _ => false,
9207 }
9208}
9209
9210fn product_domain(c: i128) -> (i128, i128) {
9212 let (min, max) = (i64::MIN as i128, i64::MAX as i128);
9213 let floor_div = |n: i128, d: i128| {
9214 let q = n / d;
9215 if n % d != 0 && ((n < 0) != (d < 0)) {
9216 q - 1
9217 } else {
9218 q
9219 }
9220 };
9221 let ceil_div = |n: i128, d: i128| -floor_div(-n, d);
9222 let (lo, hi) = match c {
9223 0 => (min, max),
9224 c if c > 0 => (ceil_div(min, c), floor_div(max, c)),
9225 c => (ceil_div(max, c), floor_div(min, c)),
9226 };
9227 (lo.max(min), hi.min(max))
9228}
9229
9230fn overflows(op: &Op) -> bool {
9232 matches!(
9233 op,
9234 Op::Add { .. }
9235 | Op::Sub { .. }
9236 | Op::Mul { .. }
9237 | Op::AddAssign { .. }
9238 | Op::Pow { .. }
9239 | Op::Div { .. }
9240 | Op::FloorDiv { .. }
9241 )
9242}
9243
9244fn makes_exact(op: &Op) -> bool {
9246 matches!(
9247 op,
9248 Op::Add { .. }
9249 | Op::Sub { .. }
9250 | Op::Mul { .. }
9251 | Op::Pow { .. }
9252 | Op::Div { .. }
9253 | Op::FloorDiv { .. }
9254 | Op::Mod { .. }
9255 | Op::DivPow2 { .. }
9256 | Op::MagicDivU { .. }
9257 | Op::AddAssign { .. }
9258 )
9259}
9260
9261fn op_is_crdt(op: &Op) -> bool {
9263 matches!(
9264 op,
9265 Op::NewCrdt { .. }
9266 | Op::CrdtBump { .. }
9267 | Op::CrdtMerge { .. }
9268 | Op::CrdtJoin { .. }
9269 | Op::CrdtResolve { .. }
9270 | Op::CrdtAppend { .. }
9271 )
9272}
9273
9274fn op_is_file(op: &Op) -> bool {
9276 use crate::semantics::builtins::BuiltinId;
9277 matches!(
9278 op,
9279 Op::CallBuiltin {
9280 builtin: BuiltinId::VfsReadFile | BuiltinId::VfsWriteFile | BuiltinId::VfsMount,
9281 ..
9282 }
9283 )
9284}
9285
9286fn op_is_net(op: &Op) -> bool {
9288 matches!(
9289 op,
9290 Op::NetConnect { .. }
9291 | Op::NetListen { .. }
9292 | Op::NetMakePeer { .. }
9293 | Op::NetSend { .. }
9294 | Op::NetStream { .. }
9295 | Op::NetAwait { .. }
9296 | Op::NetSync { .. }
9297 )
9298}
9299
9300fn op_is_tcp(op: &Op) -> bool {
9302 matches!(op, Op::Tcp { .. })
9303}
9304
9305#[cfg(test)]
9306#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_c/from_program/tests.rs"]
9307mod tests;