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 }
96 }
97
98 pub fn set_comptime_mode(&mut self, enabled: bool) {
100 self.comptime_mode = enabled;
101 }
102
103 pub fn set_blob_cache(&mut self, cache: BlobCache) {
109 self.blob_cache = Some(cache);
110 }
111
112 pub(crate) fn finalize_current_blob(&mut self, func_idx: usize) {
118 let entry = self.program.functions[func_idx].entry_point;
120 let end = self.program.instructions.len();
121 self.program.functions[func_idx].body_length = end - entry;
122
123 if let Some(builder) = self.current_blob_builder.take() {
124 let instr_end = self.program.instructions.len();
125 let func = &self.program.functions[func_idx];
126 let blob = builder.finalize(&self.program, func, &self.blob_name_to_hash, instr_end);
127 self.blob_name_to_hash
128 .insert(blob.name.clone(), blob.content_hash);
129
130 if let Some(ref mut cache) = self.blob_cache {
132 cache.put_blob(&blob);
133 }
134
135 if self.function_hashes_by_id.len() <= func_idx {
136 self.function_hashes_by_id.resize(func_idx + 1, None);
137 }
138 self.function_hashes_by_id[func_idx] = Some(blob.content_hash);
139
140 self.completed_blobs.push(blob);
141 }
142 }
143
144 pub(crate) fn record_blob_call(&mut self, func_idx: u16) {
147 if let Some(ref mut blob) = self.current_blob_builder {
148 let callee_name = self.program.functions[func_idx as usize].name.clone();
149 blob.record_call(&callee_name);
150 }
151 }
152
153 pub(crate) fn record_blob_permissions(&mut self, module: &str, function: &str) {
156 if let Some(ref mut blob) = self.current_blob_builder {
157 let perms =
158 shape_runtime::stdlib::capability_tags::required_permissions(module, function);
159 if !perms.is_empty() {
160 blob.record_permissions(&perms);
161 }
162 }
163 }
164
165 pub(super) fn build_content_addressed_program(&mut self) {
168 use crate::bytecode::Function;
169
170 if let Some(main_builder) = self.current_blob_builder.take() {
174 let instr_end = self.program.instructions.len();
175
176 let main_func = Function {
178 name: "__main__".to_string(),
179 arity: 0,
180 param_names: Vec::new(),
181 locals_count: self.next_local,
182 entry_point: main_builder.instr_start,
183 body_length: instr_end - main_builder.instr_start,
184 is_closure: false,
185 captures_count: 0,
186 is_async: false,
187 ref_params: Vec::new(),
188 ref_mutates: Vec::new(),
189 mutable_captures: Vec::new(),
190 frame_descriptor: None,
191 osr_entry_points: Vec::new(),
192 };
193
194 let blob = main_builder.finalize(
195 &self.program,
196 &main_func,
197 &self.blob_name_to_hash,
198 instr_end,
199 );
200 self.blob_name_to_hash
201 .insert("__main__".to_string(), blob.content_hash);
202 let mut main_hash = blob.content_hash;
203
204 if let Some(ref mut cache) = self.blob_cache {
206 cache.put_blob(&blob);
207 }
208
209 self.completed_blobs.push(blob);
210
211 let mut function_store = HashMap::new();
213 for blob in &self.completed_blobs {
214 function_store.insert(blob.content_hash, blob.clone());
215 }
216
217 let mut call_edges: std::collections::HashSet<(String, String)> =
230 std::collections::HashSet::new();
231 for blob in function_store.values() {
232 for callee in &blob.callee_names {
233 if callee != &blob.name {
234 call_edges.insert((blob.name.clone(), callee.clone()));
235 }
236 }
237 }
238 let mut mutual_edges: std::collections::HashSet<(String, String)> =
239 std::collections::HashSet::new();
240 for (a, b) in &call_edges {
241 if call_edges.contains(&(b.clone(), a.clone())) {
242 mutual_edges.insert((a.clone(), b.clone()));
243 }
244 }
245
246 let max_iterations = 10;
247 for _iteration in 0..max_iterations {
248 let mut any_changed = false;
249 let mut recomputed: Vec<(FunctionHash, FunctionHash, FunctionBlob)> = Vec::new();
250
251 for blob in function_store.values() {
252 let mut updated = blob.clone();
253 let mut deps_changed = false;
254
255 for (i, dep) in updated.dependencies.iter_mut().enumerate() {
256 if let Some(name) = blob.callee_names.get(i) {
257 if name == &blob.name {
261 continue;
262 }
263 if mutual_edges.contains(&(blob.name.clone(), name.clone())) {
268 if *dep != FunctionHash::ZERO {
269 *dep = FunctionHash::ZERO;
270 deps_changed = true;
271 }
272 continue;
273 }
274 if let Some(¤t) = self.blob_name_to_hash.get(name) {
275 if *dep != current {
276 *dep = current;
277 deps_changed = true;
278 }
279 }
280 }
281 }
282
283 if deps_changed {
284 let old_hash = updated.content_hash;
285 updated.finalize();
286 if updated.content_hash != old_hash {
287 recomputed.push((old_hash, updated.content_hash, updated));
288 any_changed = true;
289 }
290 }
291 }
292
293 for (old_hash, new_hash, blob) in recomputed {
294 function_store.remove(&old_hash);
295 function_store.insert(new_hash, blob.clone());
296 self.blob_name_to_hash.insert(blob.name.clone(), new_hash);
297 for slot in &mut self.function_hashes_by_id {
298 if *slot == Some(old_hash) {
299 *slot = Some(new_hash);
300 }
301 }
302
303 if let Some(ref mut cache) = self.blob_cache {
305 cache.put_blob(&blob);
306 }
307 }
308
309 if !any_changed {
310 break;
311 }
312 }
313
314 for _perm_iter in 0..max_iterations {
319 let mut perm_changed = false;
320 let mut updates: Vec<(FunctionHash, shape_abi_v1::PermissionSet)> = Vec::new();
321
322 for (hash, blob) in function_store.iter() {
323 let mut accumulated = blob.required_permissions.clone();
324 for dep_hash in &blob.dependencies {
325 if let Some(dep_blob) = function_store.get(dep_hash) {
326 let unioned = accumulated.union(&dep_blob.required_permissions);
327 if unioned != accumulated {
328 accumulated = unioned;
329 }
330 }
331 }
332 if accumulated != blob.required_permissions {
333 updates.push((*hash, accumulated));
334 perm_changed = true;
335 }
336 }
337
338 let mut rehashed: Vec<(FunctionHash, FunctionBlob)> = Vec::new();
339 for (hash, perms) in updates {
340 if let Some(blob) = function_store.get_mut(&hash) {
341 blob.required_permissions = perms;
342 let old_hash = blob.content_hash;
343 blob.finalize();
344 if blob.content_hash != old_hash {
345 rehashed.push((old_hash, blob.clone()));
346 }
347 }
348 }
349
350 for (old_hash, blob) in rehashed {
351 function_store.remove(&old_hash);
352 self.blob_name_to_hash
353 .insert(blob.name.clone(), blob.content_hash);
354 for slot in &mut self.function_hashes_by_id {
355 if *slot == Some(old_hash) {
356 *slot = Some(blob.content_hash);
357 }
358 }
359 if let Some(ref mut cache) = self.blob_cache {
360 cache.put_blob(&blob);
361 }
362 function_store.insert(blob.content_hash, blob);
363 }
364
365 if !perm_changed {
366 break;
367 }
368 }
369
370 if let Some(&updated_main) = self.blob_name_to_hash.get("__main__") {
372 main_hash = updated_main;
373 }
374
375 let mut module_binding_names = vec![String::new(); self.module_bindings.len()];
377 for (name, &idx) in &self.module_bindings {
378 module_binding_names[idx as usize] = name.clone();
379 }
380
381 self.content_addressed_program = Some(ContentAddressedProgram {
382 entry: main_hash,
383 function_store,
384 top_level_locals_count: self.next_local,
385 top_level_local_storage_hints: self.program.top_level_local_storage_hints.clone(),
386 module_binding_names,
387 module_binding_storage_hints: self.program.module_binding_storage_hints.clone(),
388 function_local_storage_hints: self.program.function_local_storage_hints.clone(),
389 data_schema: self.program.data_schema.clone(),
390 type_schema_registry: self.type_tracker.schema_registry().clone(),
391 trait_method_symbols: self.program.trait_method_symbols.clone(),
392 foreign_functions: self.program.foreign_functions.clone(),
393 native_struct_layouts: self.program.native_struct_layouts.clone(),
394 debug_info: self.program.debug_info.clone(),
395 top_level_frame: None,
396 });
397 }
398 }
399
400 pub(crate) fn collect_comptime_helpers(&self) -> Vec<FunctionDef> {
402 let mut helpers: Vec<FunctionDef> = self
403 .function_defs
404 .values()
405 .filter(|def| def.is_comptime)
406 .cloned()
407 .collect();
408 helpers.sort_by(|a, b| a.name.cmp(&b.name));
409 helpers
410 }
411
412 pub fn register_known_export(&mut self, function_name: &str, module_path: &str) {
417 self.known_exports
418 .insert(function_name.to_string(), module_path.to_string());
419 }
420
421 pub fn register_known_exports(&mut self, exports: &HashMap<String, String>) {
423 for (name, path) in exports {
424 self.known_exports.insert(name.clone(), path.clone());
425 }
426 }
427
428 pub fn suggest_import(&self, function_name: &str) -> Option<&str> {
430 self.known_exports.get(function_name).map(|s| s.as_str())
431 }
432
433 pub fn set_source(&mut self, source: &str) {
435 self.source_text = Some(source.to_string());
436 self.source_lines = source.lines().map(|s| s.to_string()).collect();
437 }
438
439 pub fn set_source_with_file(&mut self, source: &str, file_name: &str) {
441 self.source_text = Some(source.to_string());
442 self.source_lines = source.lines().map(|s| s.to_string()).collect();
443 self.current_file_id = self
445 .program
446 .debug_info
447 .source_map
448 .add_file(file_name.to_string());
449 self.program
450 .debug_info
451 .source_map
452 .set_source_text(self.current_file_id, source.to_string());
453 }
454
455 pub fn set_line(&mut self, line: u32) {
457 self.current_line = line;
458 }
459
460 pub fn set_line_from_span(&mut self, span: shape_ast::ast::Span) {
462 if let Some(source) = &self.source_text {
463 let line = source[..span.start.min(source.len())]
465 .chars()
466 .filter(|c| *c == '\n')
467 .count() as u32
468 + 1;
469 self.current_line = line;
470 }
471 }
472
473 pub fn get_source_line(&self, line: usize) -> Option<&str> {
475 self.source_lines
476 .get(line.saturating_sub(1))
477 .map(|s| s.as_str())
478 }
479
480 pub(crate) fn span_to_source_location(
482 &self,
483 span: shape_ast::ast::Span,
484 ) -> shape_ast::error::SourceLocation {
485 let (line, column) = if let Some(source) = &self.source_text {
486 let clamped = span.start.min(source.len());
487 let line = source[..clamped].chars().filter(|c| *c == '\n').count() + 1;
488 let last_nl = source[..clamped].rfind('\n').map(|p| p + 1).unwrap_or(0);
489 let column = clamped - last_nl + 1;
490 (line, column)
491 } else {
492 (1, 1)
493 };
494 let source_line = self.source_lines.get(line.saturating_sub(1)).cloned();
495 let mut loc = shape_ast::error::SourceLocation::new(line, column);
496 if span.end > span.start {
497 loc = loc.with_length(span.end - span.start);
498 }
499 if let Some(sl) = source_line {
500 loc = loc.with_source_line(sl);
501 }
502 if let Some(file) = self
503 .program
504 .debug_info
505 .source_map
506 .get_file(self.current_file_id)
507 {
508 loc = loc.with_file(file.to_string());
509 }
510 loc
511 }
512
513 pub fn register_known_bindings(&mut self, names: &[String]) {
519 for name in names {
520 if !name.is_empty() && !self.module_bindings.contains_key(name) {
521 let idx = self.next_global;
522 self.module_bindings.insert(name.clone(), idx);
523 self.next_global += 1;
524 self.register_extension_module_schema(name);
525 let module_schema_name = format!("__mod_{}", name);
526 if self
527 .type_tracker
528 .schema_registry()
529 .get(&module_schema_name)
530 .is_some()
531 {
532 self.set_module_binding_type_info(idx, &module_schema_name);
533 self.module_namespace_bindings.insert(name.clone());
534 }
535 }
536 }
537 }
538
539 pub fn with_schema(schema: crate::bytecode::DataFrameSchema) -> Self {
542 let mut compiler = Self::new();
543 compiler.program.data_schema = Some(schema);
544 compiler
545 }
546
547 pub fn set_schema(&mut self, schema: crate::bytecode::DataFrameSchema) {
550 self.program.data_schema = Some(schema);
551 }
552
553 pub fn set_source_dir(&mut self, dir: std::path::PathBuf) {
555 self.source_dir = Some(dir);
556 }
557
558 pub fn with_extensions(
560 mut self,
561 extensions: Vec<shape_runtime::module_exports::ModuleExports>,
562 ) -> Self {
563 self.extension_registry = Some(Arc::new(extensions));
564 self
565 }
566
567 pub fn set_type_diagnostic_mode(&mut self, mode: TypeDiagnosticMode) {
569 self.type_diagnostic_mode = mode;
570 }
571
572 pub fn set_compile_diagnostic_mode(&mut self, mode: CompileDiagnosticMode) {
574 self.compile_diagnostic_mode = mode;
575 }
576
577 pub fn set_permission_set(&mut self, permissions: Option<shape_abi_v1::PermissionSet>) {
582 self.permission_set = permissions;
583 }
584
585 pub(crate) fn should_recover_compile_diagnostics(&self) -> bool {
586 matches!(
587 self.compile_diagnostic_mode,
588 CompileDiagnosticMode::RecoverAll
589 )
590 }
591
592 pub(super) fn type_error_with_location_to_shape(error: TypeErrorWithLocation) -> ShapeError {
593 let mut location = SourceLocation::new(error.line.max(1), error.column.max(1));
594 if let Some(file) = error.file {
595 location = location.with_file(file);
596 }
597 if let Some(source_line) = error.source_line {
598 location = location.with_source_line(source_line);
599 }
600
601 ShapeError::SemanticError {
602 message: error.error.to_string(),
603 location: Some(location),
604 }
605 }
606
607 pub(super) fn type_errors_to_shape(errors: Vec<TypeErrorWithLocation>) -> ShapeError {
608 let mut mapped: Vec<ShapeError> = errors
609 .into_iter()
610 .map(Self::type_error_with_location_to_shape)
611 .collect();
612 if mapped.len() == 1 {
613 return mapped.pop().unwrap_or_else(|| ShapeError::SemanticError {
614 message: "Type analysis failed".to_string(),
615 location: None,
616 });
617 }
618 ShapeError::MultiError(mapped)
619 }
620
621 pub(super) fn should_emit_type_diagnostic(error: &TypeError) -> bool {
622 matches!(error, TypeError::UnknownProperty(_, _))
623 }
624
625 pub(super) fn collect_program_functions(
626 program: &Program,
627 ) -> HashMap<String, shape_ast::ast::FunctionDef> {
628 let mut out = HashMap::new();
629 Self::collect_program_functions_recursive(&program.items, None, &mut out);
630 out
631 }
632
633 pub(super) fn collect_program_functions_recursive(
634 items: &[shape_ast::ast::Item],
635 module_prefix: Option<&str>,
636 out: &mut HashMap<String, shape_ast::ast::FunctionDef>,
637 ) {
638 for item in items {
639 match item {
640 shape_ast::ast::Item::Function(func, _) => {
641 let mut qualified = func.clone();
642 if let Some(prefix) = module_prefix {
643 qualified.name = format!("{}::{}", prefix, func.name);
644 }
645 out.insert(qualified.name.clone(), qualified);
646 }
647 shape_ast::ast::Item::Export(export, _) => {
648 if let shape_ast::ast::ExportItem::Function(func) = &export.item {
649 let mut qualified = func.clone();
650 if let Some(prefix) = module_prefix {
651 qualified.name = format!("{}::{}", prefix, func.name);
652 }
653 out.insert(qualified.name.clone(), qualified);
654 }
655 }
656 shape_ast::ast::Item::Module(module_def, _) => {
657 let prefix = if let Some(parent) = module_prefix {
658 format!("{}::{}", parent, module_def.name)
659 } else {
660 module_def.name.clone()
661 };
662 Self::collect_program_functions_recursive(
663 &module_def.items,
664 Some(prefix.as_str()),
665 out,
666 );
667 }
668 _ => {}
669 }
670 }
671 }
672
673 pub(super) fn is_primitive_value_type_name(name: &str) -> bool {
674 matches!(
675 name,
676 "int"
677 | "integer"
678 | "i64"
679 | "number"
680 | "float"
681 | "f64"
682 | "decimal"
683 | "bool"
684 | "boolean"
685 | "void"
686 | "unit"
687 | "none"
688 | "null"
689 | "undefined"
690 | "never"
691 )
692 }
693
694 pub(super) fn annotation_is_heap_like(ann: &TypeAnnotation) -> bool {
695 match ann {
696 TypeAnnotation::Basic(name) => !Self::is_primitive_value_type_name(name),
697 TypeAnnotation::Reference(name) => !Self::is_primitive_value_type_name(name),
698 TypeAnnotation::Array(_)
699 | TypeAnnotation::Tuple(_)
700 | TypeAnnotation::Object(_)
701 | TypeAnnotation::Function { .. }
702 | TypeAnnotation::Generic { .. }
703 | TypeAnnotation::Dyn(_) => true,
704 TypeAnnotation::Union(types) | TypeAnnotation::Intersection(types) => {
705 types.iter().any(Self::annotation_is_heap_like)
706 }
707 TypeAnnotation::Optional(inner) => Self::annotation_is_heap_like(inner),
708 TypeAnnotation::Void
709 | TypeAnnotation::Any
710 | TypeAnnotation::Never
711 | TypeAnnotation::Null
712 | TypeAnnotation::Undefined => false,
713 }
714 }
715
716 pub(super) fn type_is_heap_like(ty: &Type) -> bool {
717 match ty {
718 Type::Concrete(ann) => Self::annotation_is_heap_like(ann),
719 Type::Function { .. } => false,
720 Type::Generic { .. } => true,
721 Type::Variable(_) | Type::Constrained { .. } => false,
722 }
723 }
724
725 pub(crate) fn pass_mode_from_ref_flags(
726 ref_params: &[bool],
727 ref_mutates: &[bool],
728 idx: usize,
729 ) -> ParamPassMode {
730 if !ref_params.get(idx).copied().unwrap_or(false) {
731 ParamPassMode::ByValue
732 } else if ref_mutates.get(idx).copied().unwrap_or(false) {
733 ParamPassMode::ByRefExclusive
734 } else {
735 ParamPassMode::ByRefShared
736 }
737 }
738
739 pub(crate) fn pass_modes_from_ref_flags(
740 ref_params: &[bool],
741 ref_mutates: &[bool],
742 ) -> Vec<ParamPassMode> {
743 let len = ref_params.len().max(ref_mutates.len());
744 (0..len)
745 .map(|idx| Self::pass_mode_from_ref_flags(ref_params, ref_mutates, idx))
746 .collect()
747 }
748
749 pub(crate) fn build_param_pass_mode_map(
750 program: &Program,
751 inferred_ref_params: &HashMap<String, Vec<bool>>,
752 inferred_ref_mutates: &HashMap<String, Vec<bool>>,
753 ) -> HashMap<String, Vec<ParamPassMode>> {
754 let funcs = Self::collect_program_functions(program);
755 let mut by_function = HashMap::new();
756
757 for (name, func) in funcs {
758 let inferred_refs = inferred_ref_params.get(&name).cloned().unwrap_or_default();
759 let inferred_mutates = inferred_ref_mutates.get(&name).cloned().unwrap_or_default();
760 let mut modes = Vec::with_capacity(func.params.len());
761
762 for (idx, param) in func.params.iter().enumerate() {
763 let explicit_ref = param.is_reference;
764 let inferred_ref = inferred_refs.get(idx).copied().unwrap_or(false);
765 if !(explicit_ref || inferred_ref) {
766 modes.push(ParamPassMode::ByValue);
767 continue;
768 }
769
770 if inferred_mutates.get(idx).copied().unwrap_or(false) {
771 modes.push(ParamPassMode::ByRefExclusive);
772 } else {
773 modes.push(ParamPassMode::ByRefShared);
774 }
775 }
776
777 by_function.insert(name, modes);
778 }
779
780 by_function
781 }
782}