1use super::*;
2
3impl BytecodeCompiler {
4 pub(super) fn collect_namespace_import_bindings(program: &Program) -> Vec<String> {
5 use shape_ast::ast::{ImportItems, Item};
6
7 let mut bindings = Vec::new();
8 for item in &program.items {
9 if let Item::Import(import_stmt, _) = item
10 && let ImportItems::Namespace { name, alias } = &import_stmt.items
11 {
12 bindings.push(alias.clone().unwrap_or_else(|| name.clone()));
13 }
14 }
15 bindings
16 }
17
18 pub fn new() -> Self {
19 Self {
20 program: BytecodeProgram::new(),
21 current_function: None,
22 locals: vec![HashMap::new()],
23 module_bindings: HashMap::new(),
24 next_local: 0,
25 next_global: 0,
26 loop_stack: Vec::new(),
27 closure_counter: 0,
28 closure_row_schema: None,
29 last_expr_type_info: None,
30 type_tracker: TypeTracker::with_stdlib(),
31 last_expr_schema: None,
32 last_expr_numeric_type: None,
33 type_inference: shape_runtime::type_system::inference::TypeInferenceEngine::new(),
34 type_aliases: HashMap::new(),
35 current_line: 1,
36 current_file_id: 0,
37 source_text: None,
38 source_lines: Vec::new(),
39 imported_names: HashMap::new(),
40 module_namespace_bindings: HashSet::new(),
41 module_scope_stack: Vec::new(),
42 known_exports: HashMap::new(),
43 function_arity_bounds: HashMap::new(),
44 function_const_params: HashMap::new(),
45 function_defs: HashMap::new(),
46 foreign_function_defs: HashMap::new(),
47 const_specializations: HashMap::new(),
48 next_const_specialization_id: 0,
49 specialization_const_bindings: HashMap::new(),
50 struct_types: HashMap::new(),
51 struct_generic_info: HashMap::new(),
52 native_layout_types: HashSet::new(),
53 generated_native_conversion_pairs: HashSet::new(),
54 current_function_is_async: false,
55 source_dir: None,
56 errors: Vec::new(),
57 hoisted_fields: HashMap::new(),
58 pending_variable_name: None,
59 known_traits: std::collections::HashSet::new(),
60 trait_defs: HashMap::new(),
61 extension_registry: None,
62 comptime_fields: HashMap::new(),
63 type_diagnostic_mode: TypeDiagnosticMode::ReliableOnly,
64 compile_diagnostic_mode: CompileDiagnosticMode::FailFast,
65 comptime_mode: false,
66 allow_internal_comptime_namespace: false,
67 method_table: MethodTable::new(),
68 borrow_checker: crate::borrow_checker::BorrowChecker::new(),
69 ref_locals: HashSet::new(),
70 exclusive_ref_locals: HashSet::new(),
71 const_locals: HashSet::new(),
72 const_module_bindings: HashSet::new(),
73 immutable_locals: HashSet::new(),
74 param_locals: HashSet::new(),
75 immutable_module_bindings: HashSet::new(),
76 in_call_args: false,
77 current_call_arg_borrow_mode: None,
78 call_arg_module_binding_ref_writebacks: Vec::new(),
79 inferred_ref_params: HashMap::new(),
80 inferred_ref_mutates: HashMap::new(),
81 inferred_param_pass_modes: HashMap::new(),
82 inferred_param_type_hints: HashMap::new(),
83 drop_locals: Vec::new(),
84 drop_type_info: HashMap::new(),
85 mutable_closure_captures: HashMap::new(),
86 boxed_locals: HashSet::new(),
87 permission_set: None,
88 current_blob_builder: None,
89 completed_blobs: Vec::new(),
90 blob_name_to_hash: HashMap::new(),
91 content_addressed_program: None,
92 function_hashes_by_id: Vec::new(),
93 blob_cache: None,
94 function_aliases: HashMap::new(),
95 current_function_params: Vec::new(),
96 }
97 }
98
99 pub fn set_comptime_mode(&mut self, enabled: bool) {
101 self.comptime_mode = enabled;
102 }
103
104 pub fn set_blob_cache(&mut self, cache: BlobCache) {
110 self.blob_cache = Some(cache);
111 }
112
113 pub(crate) fn finalize_current_blob(&mut self, func_idx: usize) {
119 let entry = self.program.functions[func_idx].entry_point;
121 let end = self.program.instructions.len();
122 self.program.functions[func_idx].body_length = end - entry;
123
124 if let Some(builder) = self.current_blob_builder.take() {
125 let instr_end = self.program.instructions.len();
126 let func = &self.program.functions[func_idx];
127 let blob = builder.finalize(&self.program, func, &self.blob_name_to_hash, instr_end);
128 self.blob_name_to_hash
129 .insert(blob.name.clone(), blob.content_hash);
130
131 if let Some(ref mut cache) = self.blob_cache {
133 cache.put_blob(&blob);
134 }
135
136 if self.function_hashes_by_id.len() <= func_idx {
137 self.function_hashes_by_id.resize(func_idx + 1, None);
138 }
139 self.function_hashes_by_id[func_idx] = Some(blob.content_hash);
140
141 self.completed_blobs.push(blob);
142 }
143 }
144
145 pub(crate) fn record_blob_call(&mut self, func_idx: u16) {
148 if let Some(ref mut blob) = self.current_blob_builder {
149 let callee_name = self.program.functions[func_idx as usize].name.clone();
150 blob.record_call(&callee_name);
151 }
152 }
153
154 pub(crate) fn record_blob_permissions(&mut self, module: &str, function: &str) {
157 if let Some(ref mut blob) = self.current_blob_builder {
158 let perms =
159 shape_runtime::stdlib::capability_tags::required_permissions(module, function);
160 if !perms.is_empty() {
161 blob.record_permissions(&perms);
162 }
163 }
164 }
165
166 pub(super) fn build_content_addressed_program(&mut self) {
169 use crate::bytecode::Function;
170
171 if let Some(main_builder) = self.current_blob_builder.take() {
175 let instr_end = self.program.instructions.len();
176
177 let main_func = Function {
179 name: "__main__".to_string(),
180 arity: 0,
181 param_names: Vec::new(),
182 locals_count: self.next_local,
183 entry_point: main_builder.instr_start,
184 body_length: instr_end - main_builder.instr_start,
185 is_closure: false,
186 captures_count: 0,
187 is_async: false,
188 ref_params: Vec::new(),
189 ref_mutates: Vec::new(),
190 mutable_captures: Vec::new(),
191 frame_descriptor: None,
192 osr_entry_points: Vec::new(),
193 };
194
195 let blob = main_builder.finalize(
196 &self.program,
197 &main_func,
198 &self.blob_name_to_hash,
199 instr_end,
200 );
201 self.blob_name_to_hash
202 .insert("__main__".to_string(), blob.content_hash);
203 let mut main_hash = blob.content_hash;
204
205 if let Some(ref mut cache) = self.blob_cache {
207 cache.put_blob(&blob);
208 }
209
210 self.completed_blobs.push(blob);
211
212 let mut function_store = HashMap::new();
214 for blob in &self.completed_blobs {
215 function_store.insert(blob.content_hash, blob.clone());
216 }
217
218 let mut call_edges: std::collections::HashSet<(String, String)> =
231 std::collections::HashSet::new();
232 for blob in function_store.values() {
233 for callee in &blob.callee_names {
234 if callee != &blob.name {
235 call_edges.insert((blob.name.clone(), callee.clone()));
236 }
237 }
238 }
239 let mut mutual_edges: std::collections::HashSet<(String, String)> =
240 std::collections::HashSet::new();
241 for (a, b) in &call_edges {
242 if call_edges.contains(&(b.clone(), a.clone())) {
243 mutual_edges.insert((a.clone(), b.clone()));
244 }
245 }
246
247 let max_iterations = 10;
248 for _iteration in 0..max_iterations {
249 let mut any_changed = false;
250 let mut recomputed: Vec<(FunctionHash, FunctionHash, FunctionBlob)> = Vec::new();
251
252 for blob in function_store.values() {
253 let mut updated = blob.clone();
254 let mut deps_changed = false;
255
256 for (i, dep) in updated.dependencies.iter_mut().enumerate() {
257 if let Some(name) = blob.callee_names.get(i) {
258 if name == &blob.name {
262 continue;
263 }
264 if mutual_edges.contains(&(blob.name.clone(), name.clone())) {
269 if *dep != FunctionHash::ZERO {
270 *dep = FunctionHash::ZERO;
271 deps_changed = true;
272 }
273 continue;
274 }
275 if let Some(¤t) = self.blob_name_to_hash.get(name) {
276 if *dep != current {
277 *dep = current;
278 deps_changed = true;
279 }
280 }
281 }
282 }
283
284 if deps_changed {
285 let old_hash = updated.content_hash;
286 updated.finalize();
287 if updated.content_hash != old_hash {
288 recomputed.push((old_hash, updated.content_hash, updated));
289 any_changed = true;
290 }
291 }
292 }
293
294 for (old_hash, new_hash, blob) in recomputed {
295 function_store.remove(&old_hash);
296 function_store.insert(new_hash, blob.clone());
297 self.blob_name_to_hash.insert(blob.name.clone(), new_hash);
298 for slot in &mut self.function_hashes_by_id {
299 if *slot == Some(old_hash) {
300 *slot = Some(new_hash);
301 }
302 }
303
304 if let Some(ref mut cache) = self.blob_cache {
306 cache.put_blob(&blob);
307 }
308 }
309
310 if !any_changed {
311 break;
312 }
313 }
314
315 for _perm_iter in 0..max_iterations {
320 let mut perm_changed = false;
321 let mut updates: Vec<(FunctionHash, shape_abi_v1::PermissionSet)> = Vec::new();
322
323 for (hash, blob) in function_store.iter() {
324 let mut accumulated = blob.required_permissions.clone();
325 for dep_hash in &blob.dependencies {
326 if let Some(dep_blob) = function_store.get(dep_hash) {
327 let unioned = accumulated.union(&dep_blob.required_permissions);
328 if unioned != accumulated {
329 accumulated = unioned;
330 }
331 }
332 }
333 if accumulated != blob.required_permissions {
334 updates.push((*hash, accumulated));
335 perm_changed = true;
336 }
337 }
338
339 let mut rehashed: Vec<(FunctionHash, FunctionBlob)> = Vec::new();
340 for (hash, perms) in updates {
341 if let Some(blob) = function_store.get_mut(&hash) {
342 blob.required_permissions = perms;
343 let old_hash = blob.content_hash;
344 blob.finalize();
345 if blob.content_hash != old_hash {
346 rehashed.push((old_hash, blob.clone()));
347 }
348 }
349 }
350
351 for (old_hash, blob) in rehashed {
352 function_store.remove(&old_hash);
353 self.blob_name_to_hash
354 .insert(blob.name.clone(), blob.content_hash);
355 for slot in &mut self.function_hashes_by_id {
356 if *slot == Some(old_hash) {
357 *slot = Some(blob.content_hash);
358 }
359 }
360 if let Some(ref mut cache) = self.blob_cache {
361 cache.put_blob(&blob);
362 }
363 function_store.insert(blob.content_hash, blob);
364 }
365
366 if !perm_changed {
367 break;
368 }
369 }
370
371 if let Some(&updated_main) = self.blob_name_to_hash.get("__main__") {
373 main_hash = updated_main;
374 }
375
376 let mut module_binding_names = vec![String::new(); self.module_bindings.len()];
378 for (name, &idx) in &self.module_bindings {
379 module_binding_names[idx as usize] = name.clone();
380 }
381
382 self.content_addressed_program = Some(ContentAddressedProgram {
383 entry: main_hash,
384 function_store,
385 top_level_locals_count: self.next_local,
386 top_level_local_storage_hints: self.program.top_level_local_storage_hints.clone(),
387 module_binding_names,
388 module_binding_storage_hints: self.program.module_binding_storage_hints.clone(),
389 function_local_storage_hints: self.program.function_local_storage_hints.clone(),
390 data_schema: self.program.data_schema.clone(),
391 type_schema_registry: self.type_tracker.schema_registry().clone(),
392 trait_method_symbols: self.program.trait_method_symbols.clone(),
393 foreign_functions: self.program.foreign_functions.clone(),
394 native_struct_layouts: self.program.native_struct_layouts.clone(),
395 debug_info: self.program.debug_info.clone(),
396 top_level_frame: None,
397 });
398 }
399 }
400
401 pub(crate) fn collect_comptime_helpers(&self) -> Vec<FunctionDef> {
403 let mut helpers: Vec<FunctionDef> = self
404 .function_defs
405 .values()
406 .filter(|def| def.is_comptime)
407 .cloned()
408 .collect();
409 helpers.sort_by(|a, b| a.name.cmp(&b.name));
410 helpers
411 }
412
413 pub fn register_known_export(&mut self, function_name: &str, module_path: &str) {
418 self.known_exports
419 .insert(function_name.to_string(), module_path.to_string());
420 }
421
422 pub fn register_known_exports(&mut self, exports: &HashMap<String, String>) {
424 for (name, path) in exports {
425 self.known_exports.insert(name.clone(), path.clone());
426 }
427 }
428
429 pub fn suggest_import(&self, function_name: &str) -> Option<&str> {
431 self.known_exports.get(function_name).map(|s| s.as_str())
432 }
433
434 pub fn set_source(&mut self, source: &str) {
436 self.source_text = Some(source.to_string());
437 self.source_lines = source.lines().map(|s| s.to_string()).collect();
438 }
439
440 pub fn set_source_with_file(&mut self, source: &str, file_name: &str) {
442 self.source_text = Some(source.to_string());
443 self.source_lines = source.lines().map(|s| s.to_string()).collect();
444 self.current_file_id = self
446 .program
447 .debug_info
448 .source_map
449 .add_file(file_name.to_string());
450 self.program
451 .debug_info
452 .source_map
453 .set_source_text(self.current_file_id, source.to_string());
454 }
455
456 pub fn set_line(&mut self, line: u32) {
458 self.current_line = line;
459 }
460
461 pub fn set_line_from_span(&mut self, span: shape_ast::ast::Span) {
463 if let Some(source) = &self.source_text {
464 let line = source[..span.start.min(source.len())]
466 .chars()
467 .filter(|c| *c == '\n')
468 .count() as u32
469 + 1;
470 self.current_line = line;
471 }
472 }
473
474 pub fn get_source_line(&self, line: usize) -> Option<&str> {
476 self.source_lines
477 .get(line.saturating_sub(1))
478 .map(|s| s.as_str())
479 }
480
481 pub(crate) fn span_to_source_location(
483 &self,
484 span: shape_ast::ast::Span,
485 ) -> shape_ast::error::SourceLocation {
486 let (line, column) = if let Some(source) = &self.source_text {
487 let clamped = span.start.min(source.len());
488 let line = source[..clamped].chars().filter(|c| *c == '\n').count() + 1;
489 let last_nl = source[..clamped].rfind('\n').map(|p| p + 1).unwrap_or(0);
490 let column = clamped - last_nl + 1;
491 (line, column)
492 } else {
493 (1, 1)
494 };
495 let source_line = self.source_lines.get(line.saturating_sub(1)).cloned();
496 let mut loc = shape_ast::error::SourceLocation::new(line, column);
497 if span.end > span.start {
498 loc = loc.with_length(span.end - span.start);
499 }
500 if let Some(sl) = source_line {
501 loc = loc.with_source_line(sl);
502 }
503 if let Some(file) = self
504 .program
505 .debug_info
506 .source_map
507 .get_file(self.current_file_id)
508 {
509 loc = loc.with_file(file.to_string());
510 }
511 loc
512 }
513
514 pub fn register_known_bindings(&mut self, names: &[String]) {
520 for name in names {
521 if !name.is_empty() && !self.module_bindings.contains_key(name) {
522 let idx = self.next_global;
523 self.module_bindings.insert(name.clone(), idx);
524 self.next_global += 1;
525 self.register_extension_module_schema(name);
526 let module_schema_name = format!("__mod_{}", name);
527 if self
528 .type_tracker
529 .schema_registry()
530 .get(&module_schema_name)
531 .is_some()
532 {
533 self.set_module_binding_type_info(idx, &module_schema_name);
534 self.module_namespace_bindings.insert(name.clone());
535 }
536 }
537 }
538 }
539
540 pub fn with_schema(schema: crate::bytecode::DataFrameSchema) -> Self {
543 let mut compiler = Self::new();
544 compiler.program.data_schema = Some(schema);
545 compiler
546 }
547
548 pub fn set_schema(&mut self, schema: crate::bytecode::DataFrameSchema) {
551 self.program.data_schema = Some(schema);
552 }
553
554 pub fn set_source_dir(&mut self, dir: std::path::PathBuf) {
556 self.source_dir = Some(dir);
557 }
558
559 pub fn with_extensions(
561 mut self,
562 extensions: Vec<shape_runtime::module_exports::ModuleExports>,
563 ) -> Self {
564 self.extension_registry = Some(Arc::new(extensions));
565 self
566 }
567
568 pub fn set_type_diagnostic_mode(&mut self, mode: TypeDiagnosticMode) {
570 self.type_diagnostic_mode = mode;
571 }
572
573 pub fn set_compile_diagnostic_mode(&mut self, mode: CompileDiagnosticMode) {
575 self.compile_diagnostic_mode = mode;
576 }
577
578 pub fn set_permission_set(&mut self, permissions: Option<shape_abi_v1::PermissionSet>) {
583 self.permission_set = permissions;
584 }
585
586 pub(crate) fn should_recover_compile_diagnostics(&self) -> bool {
587 matches!(
588 self.compile_diagnostic_mode,
589 CompileDiagnosticMode::RecoverAll
590 )
591 }
592
593 pub(super) fn type_error_with_location_to_shape(error: TypeErrorWithLocation) -> ShapeError {
594 let mut location = SourceLocation::new(error.line.max(1), error.column.max(1));
595 if let Some(file) = error.file {
596 location = location.with_file(file);
597 }
598 if let Some(source_line) = error.source_line {
599 location = location.with_source_line(source_line);
600 }
601
602 ShapeError::SemanticError {
603 message: error.error.to_string(),
604 location: Some(location),
605 }
606 }
607
608 pub(super) fn type_errors_to_shape(errors: Vec<TypeErrorWithLocation>) -> ShapeError {
609 let mut mapped: Vec<ShapeError> = errors
610 .into_iter()
611 .map(Self::type_error_with_location_to_shape)
612 .collect();
613 if mapped.len() == 1 {
614 return mapped.pop().unwrap_or_else(|| ShapeError::SemanticError {
615 message: "Type analysis failed".to_string(),
616 location: None,
617 });
618 }
619 ShapeError::MultiError(mapped)
620 }
621
622 pub(super) fn should_emit_type_diagnostic(error: &TypeError) -> bool {
623 matches!(error, TypeError::UnknownProperty(_, _))
624 }
625
626 pub(super) fn collect_program_functions(
627 program: &Program,
628 ) -> HashMap<String, shape_ast::ast::FunctionDef> {
629 let mut out = HashMap::new();
630 Self::collect_program_functions_recursive(&program.items, None, &mut out);
631 out
632 }
633
634 pub(super) fn collect_program_functions_recursive(
635 items: &[shape_ast::ast::Item],
636 module_prefix: Option<&str>,
637 out: &mut HashMap<String, shape_ast::ast::FunctionDef>,
638 ) {
639 for item in items {
640 match item {
641 shape_ast::ast::Item::Function(func, _) => {
642 let mut qualified = func.clone();
643 if let Some(prefix) = module_prefix {
644 qualified.name = format!("{}::{}", prefix, func.name);
645 }
646 out.insert(qualified.name.clone(), qualified);
647 }
648 shape_ast::ast::Item::Export(export, _) => {
649 if let shape_ast::ast::ExportItem::Function(func) = &export.item {
650 let mut qualified = func.clone();
651 if let Some(prefix) = module_prefix {
652 qualified.name = format!("{}::{}", prefix, func.name);
653 }
654 out.insert(qualified.name.clone(), qualified);
655 }
656 }
657 shape_ast::ast::Item::Module(module_def, _) => {
658 let prefix = if let Some(parent) = module_prefix {
659 format!("{}::{}", parent, module_def.name)
660 } else {
661 module_def.name.clone()
662 };
663 Self::collect_program_functions_recursive(
664 &module_def.items,
665 Some(prefix.as_str()),
666 out,
667 );
668 }
669 _ => {}
670 }
671 }
672 }
673
674 pub(super) fn is_primitive_value_type_name(name: &str) -> bool {
675 matches!(
676 name,
677 "int"
678 | "integer"
679 | "i64"
680 | "number"
681 | "float"
682 | "f64"
683 | "decimal"
684 | "bool"
685 | "boolean"
686 | "void"
687 | "unit"
688 | "none"
689 | "null"
690 | "undefined"
691 | "never"
692 )
693 }
694
695 pub(super) fn annotation_is_heap_like(ann: &TypeAnnotation) -> bool {
696 match ann {
697 TypeAnnotation::Basic(name) => !Self::is_primitive_value_type_name(name),
698 TypeAnnotation::Reference(name) => !Self::is_primitive_value_type_name(name),
699 TypeAnnotation::Array(_)
700 | TypeAnnotation::Tuple(_)
701 | TypeAnnotation::Object(_)
702 | TypeAnnotation::Function { .. }
703 | TypeAnnotation::Generic { .. }
704 | TypeAnnotation::Dyn(_) => true,
705 TypeAnnotation::Union(types) | TypeAnnotation::Intersection(types) => {
706 types.iter().any(Self::annotation_is_heap_like)
707 }
708 TypeAnnotation::Optional(inner) => Self::annotation_is_heap_like(inner),
709 TypeAnnotation::Void
710 | TypeAnnotation::Any
711 | TypeAnnotation::Never
712 | TypeAnnotation::Null
713 | TypeAnnotation::Undefined => false,
714 }
715 }
716
717 pub(super) fn type_is_heap_like(ty: &Type) -> bool {
718 match ty {
719 Type::Concrete(ann) => Self::annotation_is_heap_like(ann),
720 Type::Function { .. } => false,
721 Type::Generic { .. } => true,
722 Type::Variable(_) | Type::Constrained { .. } => false,
723 }
724 }
725
726 pub(crate) fn pass_mode_from_ref_flags(
727 ref_params: &[bool],
728 ref_mutates: &[bool],
729 idx: usize,
730 ) -> ParamPassMode {
731 if !ref_params.get(idx).copied().unwrap_or(false) {
732 ParamPassMode::ByValue
733 } else if ref_mutates.get(idx).copied().unwrap_or(false) {
734 ParamPassMode::ByRefExclusive
735 } else {
736 ParamPassMode::ByRefShared
737 }
738 }
739
740 pub(crate) fn pass_modes_from_ref_flags(
741 ref_params: &[bool],
742 ref_mutates: &[bool],
743 ) -> Vec<ParamPassMode> {
744 let len = ref_params.len().max(ref_mutates.len());
745 (0..len)
746 .map(|idx| Self::pass_mode_from_ref_flags(ref_params, ref_mutates, idx))
747 .collect()
748 }
749
750 pub(crate) fn build_param_pass_mode_map(
751 program: &Program,
752 inferred_ref_params: &HashMap<String, Vec<bool>>,
753 inferred_ref_mutates: &HashMap<String, Vec<bool>>,
754 ) -> HashMap<String, Vec<ParamPassMode>> {
755 let funcs = Self::collect_program_functions(program);
756 let mut by_function = HashMap::new();
757
758 for (name, func) in funcs {
759 let inferred_refs = inferred_ref_params.get(&name).cloned().unwrap_or_default();
760 let inferred_mutates = inferred_ref_mutates.get(&name).cloned().unwrap_or_default();
761 let mut modes = Vec::with_capacity(func.params.len());
762
763 for (idx, param) in func.params.iter().enumerate() {
764 let explicit_ref = param.is_reference;
765 let inferred_ref = inferred_refs.get(idx).copied().unwrap_or(false);
766 if !(explicit_ref || inferred_ref) {
767 modes.push(ParamPassMode::ByValue);
768 continue;
769 }
770
771 if inferred_mutates.get(idx).copied().unwrap_or(false) {
772 modes.push(ParamPassMode::ByRefExclusive);
773 } else {
774 modes.push(ParamPassMode::ByRefShared);
775 }
776 }
777
778 by_function.insert(name, modes);
779 }
780
781 by_function
782 }
783}