pr 65
This commit is contained in:
parent
6cf1e71b99
commit
45696b99e7
@ -17,7 +17,6 @@ use crate::lowering::core_to_vm;
|
|||||||
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
|
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct ExportKey {
|
pub struct ExportKey {
|
||||||
@ -61,6 +60,11 @@ pub struct CompiledModule {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CompileError {
|
pub enum CompileError {
|
||||||
Frontend(crate::common::diagnostics::DiagnosticBundle),
|
Frontend(crate::common::diagnostics::DiagnosticBundle),
|
||||||
|
DuplicateExport {
|
||||||
|
symbol: String,
|
||||||
|
first_dep: String,
|
||||||
|
second_dep: String,
|
||||||
|
},
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
Internal(String),
|
Internal(String),
|
||||||
}
|
}
|
||||||
@ -69,6 +73,9 @@ impl std::fmt::Display for CompileError {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
CompileError::Frontend(d) => write!(f, "Frontend error: {:?}", d),
|
CompileError::Frontend(d) => write!(f, "Frontend error: {:?}", d),
|
||||||
|
CompileError::DuplicateExport { symbol, first_dep, second_dep } => {
|
||||||
|
write!(f, "duplicate export: symbol `{}`\n first defined in dependency `{}`\n again defined in dependency `{}`", symbol, first_dep, second_dep)
|
||||||
|
}
|
||||||
CompileError::Io(e) => write!(f, "IO error: {}", e),
|
CompileError::Io(e) => write!(f, "IO error: {}", e),
|
||||||
CompileError::Internal(s) => write!(f, "Internal error: {}", s),
|
CompileError::Internal(s) => write!(f, "Internal error: {}", s),
|
||||||
}
|
}
|
||||||
@ -107,7 +114,7 @@ pub fn compile_project(
|
|||||||
|
|
||||||
// 1. Parse all files and group symbols by module
|
// 1. Parse all files and group symbols by module
|
||||||
let mut module_symbols_map: HashMap<String, ModuleSymbols> = HashMap::new();
|
let mut module_symbols_map: HashMap<String, ModuleSymbols> = HashMap::new();
|
||||||
let mut parsed_files: Vec<(String, FileNode, String)> = Vec::new(); // (module_path, ast, file_stem)
|
let mut parsed_files: Vec<(String, FileNode)> = Vec::new(); // (module_path, ast)
|
||||||
|
|
||||||
for source_rel in &step.sources {
|
for source_rel in &step.sources {
|
||||||
let source_abs = step.project_dir.join(source_rel);
|
let source_abs = step.project_dir.join(source_rel);
|
||||||
@ -120,10 +127,19 @@ pub fn compile_project(
|
|||||||
let mut collector = SymbolCollector::new();
|
let mut collector = SymbolCollector::new();
|
||||||
let (ts, vs) = collector.collect(&ast)?;
|
let (ts, vs) = collector.collect(&ast)?;
|
||||||
|
|
||||||
let module_path = source_rel.parent()
|
let full_path = source_rel.to_string_lossy().replace('\\', "/");
|
||||||
.unwrap_or(Path::new(""))
|
let logical_module_path = if let Some(stripped) = full_path.strip_prefix("src/main/modules/") {
|
||||||
.to_string_lossy()
|
stripped
|
||||||
.replace('\\', "/");
|
} else if let Some(stripped) = full_path.strip_prefix("src/test/modules/") {
|
||||||
|
stripped
|
||||||
|
} else {
|
||||||
|
&full_path
|
||||||
|
};
|
||||||
|
|
||||||
|
let module_path = std::path::Path::new(logical_module_path)
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_string_lossy().replace('\\', "/"))
|
||||||
|
.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new);
|
let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new);
|
||||||
|
|
||||||
@ -145,8 +161,7 @@ pub fn compile_project(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_stem = source_abs.file_stem().unwrap().to_string_lossy().to_string();
|
parsed_files.push((module_path, ast));
|
||||||
parsed_files.push((module_path, ast, file_stem));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Synthesize ModuleSymbols for dependencies
|
// 2. Synthesize ModuleSymbols for dependencies
|
||||||
@ -155,7 +170,7 @@ pub fn compile_project(
|
|||||||
if let Some(compiled) = dep_modules.get(project_id) {
|
if let Some(compiled) = dep_modules.get(project_id) {
|
||||||
for (key, meta) in &compiled.exports {
|
for (key, meta) in &compiled.exports {
|
||||||
// Support syntax: "alias/module" and "@alias:module"
|
// Support syntax: "alias/module" and "@alias:module"
|
||||||
let key_module_path = key.module_path.replace("src/main/modules/", "");
|
let key_module_path = &key.module_path;
|
||||||
let synthetic_paths = [
|
let synthetic_paths = [
|
||||||
format!("{}/{}", alias, key_module_path),
|
format!("{}/{}", alias, key_module_path),
|
||||||
format!("@{}:{}", alias, key_module_path),
|
format!("@{}:{}", alias, key_module_path),
|
||||||
@ -185,9 +200,21 @@ pub fn compile_project(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if sym.namespace == Namespace::Type {
|
if sym.namespace == Namespace::Type {
|
||||||
ms.type_symbols.insert(sym).ok();
|
if let Err(existing) = ms.type_symbols.insert(sym.clone()) {
|
||||||
|
return Err(CompileError::DuplicateExport {
|
||||||
|
symbol: sym.name,
|
||||||
|
first_dep: existing.origin.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ms.value_symbols.insert(sym).ok();
|
if let Err(existing) = ms.value_symbols.insert(sym.clone()) {
|
||||||
|
return Err(CompileError::DuplicateExport {
|
||||||
|
symbol: sym.name,
|
||||||
|
first_dep: existing.origin.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +229,7 @@ pub fn compile_project(
|
|||||||
// We need to collect imported symbols for Lowerer
|
// We need to collect imported symbols for Lowerer
|
||||||
let mut file_imported_symbols: HashMap<String, ModuleSymbols> = HashMap::new(); // keyed by module_path
|
let mut file_imported_symbols: HashMap<String, ModuleSymbols> = HashMap::new(); // keyed by module_path
|
||||||
|
|
||||||
for (module_path, ast, _) in &parsed_files {
|
for (module_path, ast) in &parsed_files {
|
||||||
let ms = module_symbols_map.get(module_path).unwrap();
|
let ms = module_symbols_map.get(module_path).unwrap();
|
||||||
let mut resolver = Resolver::new(ms, &module_provider);
|
let mut resolver = Resolver::new(ms, &module_provider);
|
||||||
resolver.resolve(ast)?;
|
resolver.resolve(ast)?;
|
||||||
@ -225,11 +252,11 @@ pub fn compile_project(
|
|||||||
field_types: HashMap::new(),
|
field_types: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (module_path, ast, file_stem) in &parsed_files {
|
for (module_path, ast) in &parsed_files {
|
||||||
let ms = module_symbols_map.get(module_path).unwrap();
|
let ms = module_symbols_map.get(module_path).unwrap();
|
||||||
let imported = file_imported_symbols.get(module_path).unwrap();
|
let imported = file_imported_symbols.get(module_path).unwrap();
|
||||||
let lowerer = Lowerer::new(ms, imported);
|
let lowerer = Lowerer::new(ms, imported);
|
||||||
let program = lowerer.lower_file(ast, &file_stem)?;
|
let program = lowerer.lower_file(ast, module_path)?;
|
||||||
|
|
||||||
// Combine program into combined_program
|
// Combine program into combined_program
|
||||||
if combined_program.modules.is_empty() {
|
if combined_program.modules.is_empty() {
|
||||||
@ -367,7 +394,7 @@ mod tests {
|
|||||||
|
|
||||||
// Vec2 should be exported
|
// Vec2 should be exported
|
||||||
let vec2_key = ExportKey {
|
let vec2_key = ExportKey {
|
||||||
module_path: "src/main/modules".to_string(),
|
module_path: "".to_string(),
|
||||||
symbol_name: "Vec2".to_string(),
|
symbol_name: "Vec2".to_string(),
|
||||||
kind: ExportSurfaceKind::DeclareType,
|
kind: ExportSurfaceKind::DeclareType,
|
||||||
};
|
};
|
||||||
|
|||||||
259
crates/prometeu-compiler/tests/export_conflicts.rs
Normal file
259
crates/prometeu-compiler/tests/export_conflicts.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata};
|
||||||
|
use prometeu_compiler::building::plan::{BuildStep, BuildTarget};
|
||||||
|
use prometeu_compiler::deps::resolver::ProjectId;
|
||||||
|
use prometeu_compiler::semantics::export_surface::ExportSurfaceKind;
|
||||||
|
use prometeu_compiler::building::output::CompiledModule;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_local_vs_dependency_conflict() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let project_dir = dir.path().to_path_buf();
|
||||||
|
|
||||||
|
// Dependency: sdk
|
||||||
|
let dep_id = ProjectId { name: "sdk-impl".to_string(), version: "1.0.0".to_string() };
|
||||||
|
let mut dep_exports = BTreeMap::new();
|
||||||
|
dep_exports.insert(ExportKey {
|
||||||
|
module_path: "math".to_string(), // normalized path
|
||||||
|
symbol_name: "Vector".to_string(),
|
||||||
|
kind: ExportSurfaceKind::DeclareType,
|
||||||
|
}, ExportMetadata {
|
||||||
|
func_idx: None,
|
||||||
|
is_host: false,
|
||||||
|
ty: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let dep_module = CompiledModule {
|
||||||
|
project_id: dep_id.clone(),
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
exports: dep_exports,
|
||||||
|
imports: vec![],
|
||||||
|
const_pool: vec![],
|
||||||
|
code: vec![],
|
||||||
|
function_metas: vec![],
|
||||||
|
debug_info: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dep_modules = HashMap::new();
|
||||||
|
dep_modules.insert(dep_id.clone(), dep_module);
|
||||||
|
|
||||||
|
// Main project has a LOCAL module named "sdk/math"
|
||||||
|
// By creating a file in src/main/modules/sdk/math/, the module path becomes "sdk/math"
|
||||||
|
fs::create_dir_all(project_dir.join("src/main/modules/sdk/math")).unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/sdk/math/local.pbs"), "pub declare struct Vector(x: int)").unwrap();
|
||||||
|
|
||||||
|
let main_id = ProjectId { name: "main".to_string(), version: "0.1.0".to_string() };
|
||||||
|
let mut deps = BTreeMap::new();
|
||||||
|
deps.insert("sdk".to_string(), dep_id.clone());
|
||||||
|
|
||||||
|
let step = BuildStep {
|
||||||
|
project_id: main_id,
|
||||||
|
project_dir,
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
sources: vec![PathBuf::from("src/main/modules/sdk/math/local.pbs")],
|
||||||
|
deps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = compile_project(step, &dep_modules);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(CompileError::DuplicateExport { symbol, .. }) => {
|
||||||
|
assert_eq!(symbol, "Vector");
|
||||||
|
},
|
||||||
|
_ => panic!("Expected DuplicateExport error, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aliased_dependency_conflict() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let project_dir = dir.path().to_path_buf();
|
||||||
|
|
||||||
|
// Dependency 1: exports "b/c:Vector"
|
||||||
|
let dep1_id = ProjectId { name: "p1".to_string(), version: "1.0.0".to_string() };
|
||||||
|
let mut dep1_exports = BTreeMap::new();
|
||||||
|
dep1_exports.insert(ExportKey {
|
||||||
|
module_path: "b/c".to_string(),
|
||||||
|
symbol_name: "Vector".to_string(),
|
||||||
|
kind: ExportSurfaceKind::DeclareType,
|
||||||
|
}, ExportMetadata {
|
||||||
|
func_idx: None,
|
||||||
|
is_host: false,
|
||||||
|
ty: None,
|
||||||
|
});
|
||||||
|
let dep1_module = CompiledModule {
|
||||||
|
project_id: dep1_id.clone(),
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
exports: dep1_exports,
|
||||||
|
imports: vec![],
|
||||||
|
const_pool: vec![],
|
||||||
|
code: vec![],
|
||||||
|
function_metas: vec![],
|
||||||
|
debug_info: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dependency 2: exports "c:Vector"
|
||||||
|
let dep2_id = ProjectId { name: "p2".to_string(), version: "1.0.0".to_string() };
|
||||||
|
let mut dep2_exports = BTreeMap::new();
|
||||||
|
dep2_exports.insert(ExportKey {
|
||||||
|
module_path: "c".to_string(),
|
||||||
|
symbol_name: "Vector".to_string(),
|
||||||
|
kind: ExportSurfaceKind::DeclareType,
|
||||||
|
}, ExportMetadata {
|
||||||
|
func_idx: None,
|
||||||
|
is_host: false,
|
||||||
|
ty: None,
|
||||||
|
});
|
||||||
|
let dep2_module = CompiledModule {
|
||||||
|
project_id: dep2_id.clone(),
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
exports: dep2_exports,
|
||||||
|
imports: vec![],
|
||||||
|
const_pool: vec![],
|
||||||
|
code: vec![],
|
||||||
|
function_metas: vec![],
|
||||||
|
debug_info: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dep_modules = HashMap::new();
|
||||||
|
dep_modules.insert(dep1_id.clone(), dep1_module);
|
||||||
|
dep_modules.insert(dep2_id.clone(), dep2_module);
|
||||||
|
|
||||||
|
let main_id = ProjectId { name: "main".to_string(), version: "0.1.0".to_string() };
|
||||||
|
let mut deps = BTreeMap::new();
|
||||||
|
deps.insert("a".to_string(), dep1_id.clone());
|
||||||
|
deps.insert("a/b".to_string(), dep2_id.clone());
|
||||||
|
|
||||||
|
let step = BuildStep {
|
||||||
|
project_id: main_id,
|
||||||
|
project_dir,
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
sources: vec![],
|
||||||
|
deps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = compile_project(step, &dep_modules);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(CompileError::DuplicateExport { symbol, .. }) => {
|
||||||
|
assert_eq!(symbol, "Vector");
|
||||||
|
},
|
||||||
|
_ => panic!("Expected DuplicateExport error, got {:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mixed_main_test_modules() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let project_dir = dir.path().to_path_buf();
|
||||||
|
|
||||||
|
fs::create_dir_all(project_dir.join("src/main/modules/math")).unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/math/Vector.pbs"), "pub declare struct Vector(x: int)").unwrap();
|
||||||
|
|
||||||
|
fs::create_dir_all(project_dir.join("src/test/modules/foo")).unwrap();
|
||||||
|
fs::write(project_dir.join("src/test/modules/foo/Test.pbs"), "pub declare struct Test(x: int)").unwrap();
|
||||||
|
|
||||||
|
let project_id = ProjectId { name: "mixed".to_string(), version: "0.1.0".to_string() };
|
||||||
|
let step = BuildStep {
|
||||||
|
project_id,
|
||||||
|
project_dir,
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
sources: vec![
|
||||||
|
PathBuf::from("src/main/modules/math/Vector.pbs"),
|
||||||
|
PathBuf::from("src/test/modules/foo/Test.pbs"),
|
||||||
|
],
|
||||||
|
deps: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let compiled = compile_project(step, &HashMap::new()).unwrap();
|
||||||
|
|
||||||
|
// Both should be in exports with normalized paths
|
||||||
|
assert!(compiled.exports.keys().any(|k| k.module_path == "math"));
|
||||||
|
assert!(compiled.exports.keys().any(|k| k.module_path == "foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_module_merging_same_directory() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let project_dir = dir.path().to_path_buf();
|
||||||
|
|
||||||
|
fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/gfx/api.pbs"), "pub declare struct Gfx(id: int)").unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/gfx/colors.pbs"), "pub declare struct Color(r: int)").unwrap();
|
||||||
|
|
||||||
|
let project_id = ProjectId { name: "merge".to_string(), version: "0.1.0".to_string() };
|
||||||
|
let step = BuildStep {
|
||||||
|
project_id,
|
||||||
|
project_dir,
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
sources: vec![
|
||||||
|
PathBuf::from("src/main/modules/gfx/api.pbs"),
|
||||||
|
PathBuf::from("src/main/modules/gfx/colors.pbs"),
|
||||||
|
],
|
||||||
|
deps: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let compiled = compile_project(step, &HashMap::new()).unwrap();
|
||||||
|
|
||||||
|
// Both should be in the same module "gfx"
|
||||||
|
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && k.symbol_name == "Gfx"));
|
||||||
|
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && k.symbol_name == "Color"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_duplicate_symbol_in_same_module_different_files() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let project_dir = dir.path().to_path_buf();
|
||||||
|
|
||||||
|
fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/gfx/a.pbs"), "pub declare struct Gfx(id: int)").unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/gfx/b.pbs"), "pub declare struct Gfx(id: int)").unwrap();
|
||||||
|
|
||||||
|
let project_id = ProjectId { name: "dup".to_string(), version: "0.1.0".to_string() };
|
||||||
|
let step = BuildStep {
|
||||||
|
project_id,
|
||||||
|
project_dir,
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
sources: vec![
|
||||||
|
PathBuf::from("src/main/modules/gfx/a.pbs"),
|
||||||
|
PathBuf::from("src/main/modules/gfx/b.pbs"),
|
||||||
|
],
|
||||||
|
deps: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = compile_project(step, &HashMap::new());
|
||||||
|
assert!(result.is_err());
|
||||||
|
// Should be a frontend error (duplicate symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_module_merging() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let project_dir = dir.path().to_path_buf();
|
||||||
|
|
||||||
|
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/main.pbs"), "pub declare struct Main(id: int)").unwrap();
|
||||||
|
fs::write(project_dir.join("src/main/modules/utils.pbs"), "pub declare struct Utils(id: int)").unwrap();
|
||||||
|
|
||||||
|
let project_id = ProjectId { name: "root-merge".to_string(), version: "0.1.0".to_string() };
|
||||||
|
let step = BuildStep {
|
||||||
|
project_id,
|
||||||
|
project_dir,
|
||||||
|
target: BuildTarget::Main,
|
||||||
|
sources: vec![
|
||||||
|
PathBuf::from("src/main/modules/main.pbs"),
|
||||||
|
PathBuf::from("src/main/modules/utils.pbs"),
|
||||||
|
],
|
||||||
|
deps: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let compiled = compile_project(step, &HashMap::new()).unwrap();
|
||||||
|
|
||||||
|
// Both should be in the root module ""
|
||||||
|
assert!(compiled.exports.keys().any(|k| k.module_path == "" && k.symbol_name == "Main"));
|
||||||
|
assert!(compiled.exports.keys().any(|k| k.module_path == "" && k.symbol_name == "Utils"));
|
||||||
|
}
|
||||||
@ -215,14 +215,15 @@ State:
|
|||||||
|
|
||||||
### 6.6 Functions
|
### 6.6 Functions
|
||||||
|
|
||||||
| Instruction | Cycles | Description |
|
| Instruction | Cycles | Description |
|
||||||
|----------------| ------ |--------------------------------------------|
|
|----------------------| ------ |-----------------------------------------------|
|
||||||
| `CALL addr` | 5 | Saves PC and creates a new call frame |
|
| `CALL <u32 func_id>` | 5 | Saves PC and creates a new call frame |
|
||||||
| `RET` | 4 | Returns from function, restoring PC |
|
| `RET` | 4 | Returns from function, restoring PC |
|
||||||
| `PUSH_SCOPE` | 3 | Creates a scope within the current function |
|
| `PUSH_SCOPE` | 3 | Creates a scope within the current function |
|
||||||
| `POP_SCOPE` | 3 | Removes current scope and its local variables |
|
| `POP_SCOPE` | 3 | Removes current scope and its local variables |
|
||||||
|
|
||||||
**ABI Rules for Functions:**
|
**ABI Rules for Functions:**
|
||||||
|
* **`func_id`:** A 32-bit index into the **final FunctionTable**, assigned by the compiler linker at build time.
|
||||||
* **Mandatory Return Value:** Every function MUST leave exactly one value on the stack before `RET`. If the function logic doesn't return a value, it must push `null`.
|
* **Mandatory Return Value:** Every function MUST leave exactly one value on the stack before `RET`. If the function logic doesn't return a value, it must push `null`.
|
||||||
* **Stack Cleanup:** `RET` automatically clears all local variables (based on `stack_base`) and re-pushes the return value.
|
* **Stack Cleanup:** `RET` automatically clears all local variables (based on `stack_base`) and re-pushes the return value.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user