use crate::bytecode_compiler;
use crate::compile_cache;
use crate::core_ir::{Decl, ModuleIdentityReport, UnifiedModule};
use crate::core_ir_verifier;
use crate::in_lang_parse;
use crate::external_guard::{self, ExternalInvocationGuard};
use crate::native_backend;
use crate::native_emit::{self, NativeLinkage};
use crate::parser_registry::{self, ParserCli};
use crate::sil_to_bytecode;
use crate::vm::BytecodeVM;
use serde::Serialize;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum CompileTarget {
Bytecode,
Native,
}
#[derive(Debug, Clone)]
pub struct OwnedCompileRequest {
pub path: PathBuf,
pub module_id: String,
pub parser: ParserCli,
pub target: CompileTarget,
pub entry: Option<String>,
pub out: Option<PathBuf>,
pub linkage: NativeLinkage,
pub target_triple: Option<String>,
pub jobs: usize,
}
#[derive(Debug, Clone, Serialize, serde::Deserialize)]
pub struct OwnedCompileReport {
pub schema_version: u32,
pub owned: bool,
pub path: String,
pub module_id: String,
#[serde(default)]
pub package_name: Option<String>,
pub module_identity: Option<ModuleIdentityReport>,
pub target: String,
#[serde(default)]
pub target_triple: Option<String>,
pub entry: Option<String>,
#[serde(default = "default_linkage_label")]
pub linkage: String,
pub frontend_level: &'static str,
pub semantic_level: &'static str,
pub backend_level: &'static str,
pub runtime_level: &'static str,
pub external_invocations: Vec<String>,
pub reason_code: Option<String>,
pub reason: Option<String>,
pub success: bool,
pub artifact_path: Option<String>,
pub executable_path: Option<String>,
pub abi_path: Option<String>,
pub parsed_function_count: usize,
pub typed_function_count: usize,
pub call_edge_count: usize,
pub jobs: usize,
pub timing_micros: u128,
pub timing_waves_us: Option<Vec<u128>>,
pub cache_hit: bool,
pub frontend_hash: Option<String>,
pub eval_exit_code: Option<u8>,
pub error: Option<String>,
}
fn target_label(target: CompileTarget) -> &'static str {
match target {
CompileTarget::Bytecode => "bytecode",
CompileTarget::Native => "native",
}
}
fn linkage_label(linkage: NativeLinkage) -> &'static str {
match linkage {
NativeLinkage::Executable => "executable",
NativeLinkage::Dylib => "dylib",
NativeLinkage::StaticLib => "staticlib",
}
}
fn default_linkage_label() -> String {
linkage_label(NativeLinkage::Executable).to_string()
}
fn count_functions(module: &UnifiedModule) -> usize {
module
.decls
.iter()
.filter(|decl| matches!(decl, Decl::Function { .. }))
.count()
}
fn count_call_edges(module: &UnifiedModule, module_id: &str) -> usize {
let sil = crate::compiler::driver::lower_unified_module(
module,
module.effective_module_id(module_id),
);
let artifact = crate::hybrid_sil::parse_textual_sil(&sil);
let cleaned = crate::hybrid_sil::remove_debug_insts(&artifact);
crate::hybrid_sil::extract_call_graph(&cleaned)
.call_edges
.len()
}
fn jobs_for_request(request: &OwnedCompileRequest) -> usize {
request.jobs.max(1)
}
fn timing_waves_for_jobs(jobs: usize, total_micros: u128) -> Vec<u128> {
if jobs <= 1 {
return vec![total_micros];
}
if crate::v_native::v_native_available() {
let boundaries = crate::v_native::parallel::wave_plan(jobs, jobs, jobs);
let mut waves = Vec::with_capacity(boundaries.len());
for &boundary in &boundaries {
let share = (total_micros * boundary as u128) / jobs as u128;
waves.push(share);
}
if let Some((last, rest)) = waves.split_last_mut() {
let sum: u128 = rest.iter().sum();
*last = total_micros.saturating_sub(sum);
}
return waves;
}
let per = total_micros / jobs as u128;
let mut waves = vec![per; jobs];
if let Some(last) = waves.last_mut() {
*last = total_micros.saturating_sub(per.saturating_mul((jobs - 1) as u128));
}
waves
}
pub fn compile_owned(request: &OwnedCompileRequest) -> OwnedCompileReport {
let started = Instant::now();
let jobs = jobs_for_request(request);
let cwd = compile_cache::workspace_cwd_for_path(&request.path);
let source = match fs::read_to_string(&request.path) {
Ok(content) => content,
Err(err) => {
return OwnedCompileReport {
schema_version: 1,
owned: true,
path: request.path.display().to_string(),
module_id: request.module_id.clone(),
module_identity: None,
package_name: None,
target: target_label(request.target).to_string(),
target_triple: request.target_triple.clone(),
entry: request.entry.clone(),
linkage: linkage_label(request.linkage).to_string(),
frontend_level: "unsupported",
semantic_level: "failed",
backend_level: match request.target {
CompileTarget::Bytecode => "bytecode-vm-subset",
CompileTarget::Native => "contract-only",
},
runtime_level: match request.target {
CompileTarget::Bytecode => "inrt-bytecode",
CompileTarget::Native => "none",
},
external_invocations: Vec::new(),
reason_code: Some("frontend-read-failed".to_string()),
reason: Some(err.to_string()),
success: false,
artifact_path: None,
executable_path: None,
abi_path: None,
parsed_function_count: 0,
typed_function_count: 0,
call_edge_count: 0,
jobs,
timing_micros: started.elapsed().as_micros(),
timing_waves_us: None,
cache_hit: false,
frontend_hash: None,
eval_exit_code: None,
error: Some(err.to_string()),
};
}
};
let frontend_hash = compile_cache::source_frontend_hash(&request.path, &source);
if let Some(mut cached) = compile_cache::read_cached_report(&cwd, &frontend_hash) {
let requested_out = request.out.as_ref().map(|path| path.display().to_string());
let cached_out = cached
.executable_path
.clone()
.or_else(|| cached.artifact_path.clone());
if cached.target == target_label(request.target)
&& cached.entry == request.entry
&& cached.target_triple == request.target_triple
&& cached.module_id == request.module_id
&& cached.linkage == linkage_label(request.linkage)
&& requested_out == cached_out
{
cached.cache_hit = true;
cached.jobs = jobs;
cached.timing_micros = started.elapsed().as_micros();
cached.timing_waves_us = Some(timing_waves_for_jobs(jobs, cached.timing_micros));
cached.frontend_hash = Some(frontend_hash);
return cached;
}
}
let _guard = ExternalInvocationGuard::enter();
let mut report = OwnedCompileReport {
schema_version: 1,
owned: true,
path: request.path.display().to_string(),
module_id: request.module_id.clone(),
module_identity: None,
package_name: None,
target: target_label(request.target).to_string(),
target_triple: request.target_triple.clone(),
entry: request.entry.clone(),
linkage: linkage_label(request.linkage).to_string(),
frontend_level: "unsupported",
semantic_level: "failed",
backend_level: match request.target {
CompileTarget::Bytecode => "bytecode-vm-subset",
CompileTarget::Native => "contract-only",
},
runtime_level: match request.target {
CompileTarget::Bytecode => "inrt-bytecode",
CompileTarget::Native => "none",
},
external_invocations: Vec::new(),
reason_code: None,
reason: None,
success: false,
artifact_path: None,
executable_path: None,
abi_path: None,
parsed_function_count: 0,
typed_function_count: 0,
call_edge_count: 0,
jobs,
timing_micros: 0,
timing_waves_us: None,
cache_hit: false,
frontend_hash: Some(frontend_hash.clone()),
eval_exit_code: None,
error: None,
};
let resolved = parser_registry::resolve_parser_id(&request.path, request.parser);
let mut module = match parser_registry::parse_with_resolved(resolved, &request.path) {
Ok(Some(module)) => module,
Ok(None) => {
let reason = "owned compile requires a Core IR frontend. All languages now route through Core IR via Tree-sitter.".to_string();
report.reason_code = Some("frontend-parse-failed".to_string());
report.reason = Some(reason.clone());
report.error = Some(reason);
return finalize_report(&mut report, started, &cwd, &frontend_hash);
}
Err(err) => {
let err_str = err.to_string();
if err_str.contains("missing required `fn main`")
&& request.path.extension().is_some_and(|e| e == "in")
&& (request.linkage == NativeLinkage::StaticLib
|| request
.target_triple
.as_deref()
.is_some_and(|t| t.ends_with("-none")))
{
match in_lang_parse::parse_in_library_file(&request.path) {
Ok(module) => module,
Err(lib_err) => {
let reason = lib_err;
report.reason_code = Some("frontend-parse-failed".to_string());
report.reason = Some(reason.clone());
report.error = Some(reason);
return finalize_report(&mut report, started, &cwd, &frontend_hash);
}
}
} else {
let reason = err_str;
report.reason_code = Some("frontend-parse-failed".to_string());
report.reason = Some(reason.clone());
report.error = Some(reason);
return finalize_report(&mut report, started, &cwd, &frontend_hash);
}
}
};
let mut pkg_entry: Option<String> = None;
if request.path.extension().is_some_and(|e| e == "in") {
let source_dir = request.path.parent().map(PathBuf::from).unwrap_or_default();
let mut import_resolver = crate::module_resolver::ModuleResolver::new();
import_resolver.add_search_path(source_dir.clone());
import_resolver.add_search_path(PathBuf::from("."));
if let Some(pkg) = crate::package_manifest::compile_context_for_source(&request.path) {
report.package_name = Some(pkg.name);
pkg_entry = pkg.entry;
for dep in pkg.dependency_search_paths {
import_resolver.add_search_path(dep);
}
}
match import_resolver.resolve_imports(&source) {
Ok(imported) => {
for imp in imported {
module.decls.extend(imp.decls);
}
}
Err(e) => {
eprintln!("[import] warning: {e}");
}
}
}
crate::lower_core::desugar_module(&mut module);
if let parser_registry::ResolvedBuildParser::CoreIr(parser_id) = resolved
&& crate::family_typecheck::uses_family_typecheck(parser_id)
{
module = crate::family_typecheck::normalize_module(parser_id, &module);
}
report.frontend_level = "core-ir-direct";
report.module_identity = Some(module.identity_report(&request.module_id));
report.parsed_function_count = count_functions(&module);
let effective_entry = request.entry.clone().or(pkg_entry);
let semantic_module = if request.target == CompileTarget::Native {
effective_entry
.as_deref()
.map(|entry| native_entry_module(&module, entry))
.unwrap_or_else(|| module.clone())
} else {
module.clone()
};
let verify_opts = core_ir_verifier::VerifyOptions {
entry: effective_entry.clone(),
require_entry: effective_entry.as_deref() == Some("main"),
};
let verify_report = core_ir_verifier::verify_module(&semantic_module, &verify_opts);
if !verify_report.ok {
report.reason_code = Some(format!(
"verify-{}",
verify_report.reason_code.as_deref().unwrap_or("failed")
));
report.reason = verify_report.reason.clone();
report.error = verify_report.reason;
report.call_edge_count = verify_report.call_edges.len();
return finalize_report(&mut report, started, &cwd, &frontend_hash);
}
if request.target != CompileTarget::Native
&& let Err(err) = crate::family_typecheck::typecheck_resolved(&resolved, &semantic_module)
{
report.semantic_level = "failed";
report.reason_code = Some("semantic-typecheck-failed".to_string());
report.reason = Some(err.clone());
report.error = Some(err);
return finalize_report(&mut report, started, &cwd, &frontend_hash);
}
if std::env::var("IN_TYPECHECK").is_ok() {
let strict = std::env::var("IN_TYPECHECK").as_deref() == Ok("strict");
match crate::typecheck::TypeChecker::new().check_module(&module) {
Ok(()) => {}
Err(errors) => {
for err in &errors {
eprintln!("[typecheck] {:?}", err);
}
if strict {
report.success = false;
report.reason_code = Some("typecheck-failed".to_string());
}
}
}
}
report.semantic_level = "typed-subset";
report.typed_function_count = report.parsed_function_count;
report.call_edge_count = count_call_edges(&module, &request.module_id);
match request.target {
CompileTarget::Bytecode => {
report.backend_level = "bytecode-vm-subset";
report.runtime_level = "inrt-bytecode";
match compile_bytecode(&module, &request.module_id, request.out.as_deref()) {
Ok(artifact_path) => {
report.success = true;
report.artifact_path = artifact_path;
}
Err(err) => {
report.reason_code = Some("bytecode-lowering-failed".to_string());
report.reason = Some(err.clone());
report.error = Some(err);
}
}
}
CompileTarget::Native => match compile_native(&module, &request.module_id, request) {
Ok(native_result) => {
report.backend_level = native_result.backend_level;
report.runtime_level = native_result.runtime_level;
report.reason_code = Some(native_result.reason_code.to_string());
report.reason = Some(native_result.reason.to_string());
report.success = true;
report.eval_exit_code = native_result.eval_exit_code;
report.executable_path = if request.linkage == NativeLinkage::Executable {
Some(native_result.artifact_path.clone())
} else {
None
};
report.artifact_path = Some(native_result.artifact_path);
report.abi_path = native_result.abi_path;
}
Err(err) if err == "native-host-unsupported" => {
let status = native_backend::native_backend_status();
report.backend_level = "contract-only";
report.runtime_level = "none";
report.reason_code = Some(status.reason_code.to_string());
report.reason = Some(status.reason.to_string());
}
Err(err) if err.starts_with("native-target-not-implemented:") => {
report.backend_level = "contract-only";
report.runtime_level = "none";
report.reason_code = Some("native-target-not-implemented".to_string());
report.reason = Some(err.clone());
report.error = Some(err);
}
Err(err) if err.starts_with("native-package-not-implemented:") => {
report.backend_level = "contract-only";
report.runtime_level = "none";
report.reason_code = Some("native-package-not-implemented".to_string());
report.reason = Some(err.clone());
report.error = Some(err);
}
Err(err) => {
report.backend_level = "owned-native-subset";
report.runtime_level = "inrt-native";
report.reason_code = Some("native-lowering-failed".to_string());
report.reason = Some(err.clone());
report.error = Some(err);
}
},
}
finalize_report(&mut report, started, &cwd, &frontend_hash)
}
fn compile_native(
module: &UnifiedModule,
module_id: &str,
request: &OwnedCompileRequest,
) -> Result<NativeCompileResult, String> {
let entry = request
.entry
.as_deref()
.filter(|name| !name.is_empty())
.unwrap_or("answer");
let native_module = native_entry_module(module, entry);
let eval_exit = match request.linkage {
NativeLinkage::Executable => Some(const_eval_entry_exit_code(
&native_module,
module_id,
entry,
)?),
NativeLinkage::Dylib | NativeLinkage::StaticLib => None,
};
let out_path = request
.out
.as_ref()
.ok_or_else(|| "native compile requires --out executable path".to_string())?;
if let Some(target_triple) = request.target_triple.as_deref() {
let exit = match const_eval_entry_exit_code(&native_module, module_id, entry) {
Ok(code) => code,
Err(_) if request.linkage == NativeLinkage::StaticLib => 0,
Err(err) => return Err(err),
};
if request.linkage == NativeLinkage::Executable
&& target_triple == "aarch64-apple-darwin"
&& path_extension_is(out_path, "app")
{
emit_macos_app_bundle(&native_module, module_id, entry, out_path)?;
return Ok(NativeCompileResult {
artifact_path: out_path.display().to_string(),
eval_exit_code: Some(exit),
abi_path: None,
backend_level: "owned-native-subset-aarch64-app",
runtime_level: "macos-app-bundle",
reason_code: "native-aarch64-darwin-app-subset",
reason: "inauguration owns macOS .app bundle emission around its AArch64 Mach-O executable subset",
});
}
if request.linkage == NativeLinkage::Executable
&& target_triple == "x86_64-unknown-linux-gnu"
&& path_extension_is(out_path, "AppImage")
{
return Err("native-package-not-implemented: AppImage requires an owned AppImage runtime and SquashFS writer before this backend can claim .AppImage artifacts".to_string());
}
if request.linkage == NativeLinkage::Executable
&& target_triple == "x86_64-unknown-linux-gnu"
&& path_extension_is(out_path, "AppDir")
{
emit_linux_appdir(exit, out_path)?;
return Ok(NativeCompileResult {
artifact_path: out_path.display().to_string(),
eval_exit_code: Some(exit),
abi_path: None,
backend_level: "owned-native-subset-x86_64-appdir",
runtime_level: "linux-appdir",
reason_code: "native-x86_64-linux-appdir-subset",
reason: "inauguration owns Linux AppDir emission around its x86_64 ELF executable subset",
});
}
let object_request = native_emit::NativeObjectRequest {
target_triple,
linkage: request.linkage,
entry,
exit_code: exit,
module,
module_id,
};
if let Some(artifact) = native_emit::emit_native_object(&object_request) {
fs::write(out_path, artifact.bytes)
.map_err(|err| format!("native object write `{}`: {err}", out_path.display()))?;
set_native_artifact_permissions(out_path, request.linkage)?;
let abi_path = if let Some(manifest) = artifact.abi_manifest {
let abi_path = out_path.with_extension("abi.json");
fs::write(&abi_path, manifest)
.map_err(|err| format!("write abi manifest `{}`: {err}", abi_path.display()))?;
Some(abi_path.display().to_string())
} else {
None
};
let _meta_path = emit_component_metadata_sidecar(module, entry, out_path);
return Ok(NativeCompileResult {
artifact_path: out_path.display().to_string(),
eval_exit_code: if request.linkage == NativeLinkage::Executable {
Some(exit)
} else {
None
},
abi_path,
backend_level: artifact.backend_level,
runtime_level: artifact.runtime_level,
reason_code: artifact.reason_code,
reason: artifact.reason,
});
}
return Err(format!(
"native-target-not-implemented: target `{target_triple}` with linkage `{}` is not implemented by the owned backend",
linkage_label(request.linkage)
));
}
let abi_path = native_emit::compile_native_artifact_for_host(
&native_module,
module_id,
entry,
request.linkage,
out_path,
)?;
set_native_artifact_permissions(out_path, request.linkage)?;
let status = native_backend::native_backend_status();
Ok(NativeCompileResult {
artifact_path: out_path.display().to_string(),
eval_exit_code: eval_exit,
abi_path: abi_path.map(|path| path.display().to_string()),
backend_level: "owned-native-subset",
runtime_level: "inrt-native",
reason_code: status.reason_code,
reason: status.reason,
})
}
fn emit_component_metadata_sidecar(
module: &UnifiedModule,
entry: &str,
out_path: &Path,
) -> Option<String> {
use crate::boundary_emit::emit_component_metadata;
use crate::boundary_ir::{
CapabilityDecl, CodeSection, ComponentMetadata, MemoryRequirements, ObjectField,
ObjectSchema, Provenance, ServiceExport, ServiceImport,
};
let component = module.decls.iter().find_map(|d| match d {
Decl::Component {
name,
target,
deterministic,
checkpoint,
imports,
exports,
capabilities,
} => Some((
name.clone(),
target.clone(),
*deterministic,
checkpoint.clone(),
imports.clone(),
exports.clone(),
capabilities.clone(),
)),
_ => None,
})?;
let (name, target, deterministic, checkpoint, imports, exports, capabilities) = component;
let object_schemas: Vec<ObjectSchema> = module
.decls
.iter()
.filter_map(|d| match d {
Decl::Struct {
name: sn, fields, ..
} => {
let mut offset = 0u64;
let obj_fields: Vec<ObjectField> = fields
.iter()
.map(|(fn_, typ)| {
let size = schema_field_size(typ);
let off = offset;
offset += size;
ObjectField {
name: fn_.clone(),
typ: schema_type_name(typ),
offset: off,
size,
}
})
.collect();
let size = offset.next_multiple_of(8);
Some(ObjectSchema {
name: sn.clone(),
fields: obj_fields,
size,
align: 8,
})
}
_ => None,
})
.collect();
let component_name = format!("{}", name);
let comp = module
.identity
.package
.as_deref()
.map(|pkg| format!("{pkg}/{name}"))
.unwrap_or_else(|| component_name);
let metadata = ComponentMetadata {
component: comp,
target: target.clone(),
entry: Some(entry.to_string()),
code_sections: vec![CodeSection {
name: ".text".to_string(),
offset: 0,
size: 0, flags: "rx".to_string(),
}],
data_sections: Vec::new(),
imports: imports
.into_iter()
.map(|i: crate::core_ir::ComponentImport| ServiceImport {
name: i.name,
interface: i.interface,
})
.collect(),
exports: exports
.into_iter()
.map(|e: crate::core_ir::ComponentExport| ServiceExport {
name: e.name,
interface: e.interface,
})
.collect(),
capabilities_required: capabilities
.iter()
.map(|c: &crate::core_ir::ComponentCapability| CapabilityDecl {
name: c.name.clone(),
capability_type: c.capability_type.clone(),
args: c.args.clone(),
})
.collect(),
capabilities_exported: capabilities
.iter()
.map(|c: &crate::core_ir::ComponentCapability| CapabilityDecl {
name: c.name.clone(),
capability_type: c.capability_type.clone(),
args: c.args.clone(),
})
.collect(),
object_schemas,
memory: Some(MemoryRequirements {
stack: 16384,
heap: 0,
static_data: 0,
}),
checkpoint: checkpoint.clone(),
deterministic,
provenance: Provenance {
compiler: "inauguration".to_string(),
compiler_version: env!("CARGO_PKG_VERSION").to_string(),
source_hash: String::new(),
},
};
let json = emit_component_metadata(&metadata);
let meta_path = out_path.with_extension("component-metadata.json");
match std::fs::write(&meta_path, &json) {
Ok(()) => Some(meta_path.display().to_string()),
Err(_) => None,
}
}
fn schema_field_size(typ: &crate::core_ir::Typ) -> u64 {
match typ {
crate::core_ir::Typ::Int | crate::core_ir::Typ::Float | crate::core_ir::Typ::Bool => 8,
crate::core_ir::Typ::String => 16,
crate::core_ir::Typ::Void => 0,
crate::core_ir::Typ::Array(_) => 16,
crate::core_ir::Typ::Named(_) => 8,
crate::core_ir::Typ::Generic(_) => 8,
}
}
fn schema_type_name(typ: &crate::core_ir::Typ) -> String {
match typ {
crate::core_ir::Typ::Int => "Int".to_string(),
crate::core_ir::Typ::Float => "Float".to_string(),
crate::core_ir::Typ::String => "String".to_string(),
crate::core_ir::Typ::Bool => "Bool".to_string(),
crate::core_ir::Typ::Void => "void".to_string(),
crate::core_ir::Typ::Named(name) => name.clone(),
crate::core_ir::Typ::Generic(name) => name.clone(),
crate::core_ir::Typ::Array(elem) => format!("[{}]", schema_type_name(elem)),
}
}
fn path_extension_is(path: &Path, extension: &str) -> bool {
path.extension()
.and_then(|value| value.to_str())
.is_some_and(|value| value.eq_ignore_ascii_case(extension))
}
fn artifact_stem(path: &Path, fallback: &str) -> String {
path.file_stem()
.and_then(|value| value.to_str())
.filter(|value| !value.is_empty())
.unwrap_or(fallback)
.to_string()
}
fn emit_macos_app_bundle(
module: &UnifiedModule,
module_id: &str,
entry: &str,
out_path: &Path,
) -> Result<(), String> {
let name = artifact_stem(out_path, "App");
let contents = out_path.join("Contents");
let macos = contents.join("MacOS");
fs::create_dir_all(&macos)
.map_err(|err| format!("create app bundle `{}`: {err}", macos.display()))?;
let executable = macos.join(&name);
native_emit::compile_native_artifact(
module,
module_id,
entry,
NativeLinkage::Executable,
&executable,
)?;
set_native_artifact_permissions(&executable, NativeLinkage::Executable)?;
let plist = format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n<key>CFBundleExecutable</key>\n<string>{name}</string>\n<key>CFBundleIdentifier</key>\n<string>inauguration.{name}</string>\n<key>CFBundleName</key>\n<string>{name}</string>\n<key>CFBundlePackageType</key>\n<string>APPL</string>\n</dict>\n</plist>\n"
);
fs::write(contents.join("Info.plist"), plist)
.map_err(|err| format!("write app Info.plist `{}`: {err}", out_path.display()))?;
fs::write(contents.join("PkgInfo"), "APPL????")
.map_err(|err| format!("write app PkgInfo `{}`: {err}", out_path.display()))
}
fn emit_linux_appdir(exit: u8, out_path: &Path) -> Result<(), String> {
fs::create_dir_all(out_path)
.map_err(|err| format!("create AppDir `{}`: {err}", out_path.display()))?;
let app_run = out_path.join("AppRun");
let exe = native_emit::ElfExecutable {
code: native_emit::x86_64_linux_exit_code(exit),
entry_offset: 0,
};
let mut bytes = Vec::new();
native_emit::write_elf_executable(&exe, &mut bytes);
fs::write(&app_run, bytes)
.map_err(|err| format!("write AppRun `{}`: {err}", app_run.display()))?;
set_native_artifact_permissions(&app_run, NativeLinkage::Executable)?;
fs::write(
out_path.join("answer.desktop"),
"[Desktop Entry]\nType=Application\nName=answer\nExec=AppRun\n",
)
.map_err(|err| format!("write AppDir desktop file `{}`: {err}", out_path.display()))
}
#[cfg(unix)]
fn set_native_artifact_permissions(path: &Path, linkage: NativeLinkage) -> Result<(), String> {
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)
.map_err(|err| format!("native artifact metadata: {err}"))?
.permissions();
perms.set_mode(match linkage {
NativeLinkage::StaticLib => 0o644,
NativeLinkage::Executable | NativeLinkage::Dylib => 0o755,
});
fs::set_permissions(path, perms).map_err(|err| format!("chmod native artifact: {err}"))
}
#[cfg(not(unix))]
fn set_native_artifact_permissions(_path: &Path, _linkage: NativeLinkage) -> Result<(), String> {
Ok(())
}
struct NativeCompileResult {
artifact_path: String,
eval_exit_code: Option<u8>,
abi_path: Option<String>,
backend_level: &'static str,
runtime_level: &'static str,
reason_code: &'static str,
reason: &'static str,
}
fn native_entry_module(module: &UnifiedModule, entry: &str) -> UnifiedModule {
use crate::core_ir::{Expr, Stmt};
use std::collections::HashSet;
fn collect_expr_calls(expr: &Expr, out: &mut HashSet<String>) {
match expr {
Expr::Call { callee, args, ..} => {
if let Expr::Ident(name) = callee.as_ref() {
out.insert(name.clone());
} else {
collect_expr_calls(callee, out);
}
for arg in args {
collect_expr_calls(arg, out);
}
}
Expr::Unary { expr, .. } => collect_expr_calls(expr, out),
Expr::Binary { lhs, rhs, .. } => {
collect_expr_calls(lhs, out);
collect_expr_calls(rhs, out);
}
Expr::StructInit { fields, .. } => {
for (_, expr) in fields {
collect_expr_calls(expr, out);
}
}
Expr::Field { base, .. } => collect_expr_calls(base, out),
Expr::ArrayLit(items) => {
for item in items {
collect_expr_calls(item, out);
}
}
Expr::Index { base, index, ..} => {
collect_expr_calls(base, out);
collect_expr_calls(index, out);
}
Expr::Closure { body, .. } => collect_stmt_calls(body, out),
Expr::Ident(name) => {
out.insert(name.clone());
}
Expr::IntLit(_) | Expr::FloatLit(_) | Expr::StringLit(_) | Expr::BoolLit(_) => {}
}
}
fn collect_stmt_calls(stmts: &[Stmt], out: &mut HashSet<String>) {
for stmt in stmts {
match stmt {
Stmt::Let(_, _, expr)
| Stmt::Assign(_, expr)
| Stmt::Return(Some(expr))
| Stmt::Expr(expr)
| Stmt::Throw(expr) => collect_expr_calls(expr, out),
Stmt::IndexAssign { base, index, value, ..} => {
collect_expr_calls(base, out);
collect_expr_calls(index, out);
collect_expr_calls(value, out);
}
Stmt::If {
cond,
then_body,
else_body,
} => {
collect_expr_calls(cond, out);
collect_stmt_calls(then_body, out);
collect_stmt_calls(else_body, out);
}
Stmt::Loop { cond, body, .. } => {
if let Some(cond) = cond {
collect_expr_calls(cond, out);
}
collect_stmt_calls(body, out);
}
Stmt::Match { scrutinee, arms, ..} => {
collect_expr_calls(scrutinee, out);
for arm in arms {
collect_stmt_calls(&arm.body, out);
}
}
Stmt::Try { body, catches, ..} => {
collect_stmt_calls(body, out);
for catch in catches {
collect_stmt_calls(&catch.body, out);
}
}
Stmt::Return(None) => {}
Stmt::Break => {}
}
}
}
let mut reachable = HashSet::from([entry.to_string()]);
loop {
let mut next = reachable.clone();
for decl in &module.decls {
let Decl::Function { name, body, .. } = decl else {
continue;
};
if reachable.contains(name) {
collect_stmt_calls(body, &mut next);
}
}
if next.len() == reachable.len() {
break;
}
reachable = next;
}
UnifiedModule::new(
module
.decls
.iter()
.filter(|decl| match decl {
Decl::Function { name, .. } => reachable.contains(name),
_ => true,
})
.cloned()
.collect(),
)
}
fn try_const_answer_entry(module: &UnifiedModule, entry: &str) -> Option<u8> {
use crate::core_ir::{Expr, Stmt};
for decl in &module.decls {
if let Decl::Function {
name, body, ret, ..
} = decl
{
if name != entry {
continue;
}
if *ret != crate::core_ir::Typ::Int {
return None;
}
if body.len() != 1 {
return None;
}
if let Stmt::Return(Some(Expr::IntLit(val))) = &body[0] {
let code = crate::v_native::inrt::eval_answer(*val);
return Some(code);
}
}
}
None
}
fn const_eval_entry_exit_code(
module: &UnifiedModule,
module_id: &str,
entry: &str,
) -> Result<u8, String> {
if crate::v_native::v_native_available()
&& let Some(code) = try_const_answer_entry(module, entry)
{
return Ok(code);
}
let sil = crate::compiler::driver::lower_unified_module(
module,
module.effective_module_id(module_id),
);
let artifact = crate::hybrid_sil::parse_textual_sil(&sil);
let mut bytecode_module = sil_to_bytecode::lower_sil_to_bytecode(&artifact)?;
bytecode_module.entry_point = entry.to_string();
if bytecode_module.find_function(entry).is_none() {
return Err(format!("native compile missing entry function `{entry}`"));
}
let mut vm = BytecodeVM::new(bytecode_module);
let value = vm.run()?;
let code = value.to_int();
if !(0..=255).contains(&code) {
return Err(format!(
"native compile entry `{entry}` exit code {code} is outside 0..=255"
));
}
Ok(code as u8)
}
fn compile_bytecode(
module: &UnifiedModule,
module_id: &str,
out: Option<&Path>,
) -> Result<Option<String>, String> {
let sil = crate::compiler::driver::lower_unified_module(
module,
module.effective_module_id(module_id),
);
let artifact = crate::hybrid_sil::parse_textual_sil(&sil);
let bytecode_module = sil_to_bytecode::lower_sil_to_bytecode(&artifact)?;
let Some(out_path) = out else {
return Ok(None);
};
bytecode_compiler::write_bytecode_module(&bytecode_module, out_path)?;
Ok(Some(out_path.display().to_string()))
}
fn finalize_report(
report: &mut OwnedCompileReport,
started: Instant,
cwd: &Path,
frontend_hash: &str,
) -> OwnedCompileReport {
report.external_invocations = external_guard::ExternalInvocationGuard::active_invocations();
if let Err(reason) =
external_guard::assert_no_forbidden_invocations(&report.external_invocations)
{
report.success = false;
report.reason_code = Some("external-tool-invoked".to_string());
report.reason = Some(reason.clone());
report.error = Some(reason);
}
report.timing_micros = started.elapsed().as_micros();
report.timing_waves_us = Some(timing_waves_for_jobs(report.jobs, report.timing_micros));
if !report.cache_hit {
let _ = compile_cache::write_cached_report(cwd, frontend_hash, report);
}
report.clone()
}
pub fn report_to_json(report: &OwnedCompileReport) -> Result<String, String> {
serde_json::to_string_pretty(report)
.map_err(|err| format!("serialize owned compile report: {err}"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};
fn temp_path(name: &str) -> PathBuf {
std::env::temp_dir().join(format!(
"inauguration-owned-compile-{}-{}-{name}",
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
))
}
fn default_request(
path: PathBuf,
target: CompileTarget,
entry: Option<&str>,
out: Option<PathBuf>,
) -> OwnedCompileRequest {
OwnedCompileRequest {
path,
module_id: "App".to_string(),
parser: ParserCli::Auto,
target,
entry: entry.map(str::to_string),
out,
linkage: NativeLinkage::Executable,
target_triple: None,
jobs: 1,
}
}
#[test]
fn compiles_sample_in_bytecode_with_temp_out_file() {
let source_path = temp_path("sample.in");
let out_path = temp_path("sample.bca");
fs::write(
&source_path,
"fn helper(value: Int) -> Int { return value; }\nfn main() -> void { helper(1); return; }\n",
)
.unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
Some("main"),
Some(out_path.clone()),
));
assert!(report.success, "{:?}", report);
assert_eq!(report.frontend_level, "core-ir-direct");
assert_eq!(report.semantic_level, "typed-subset");
assert_eq!(report.backend_level, "bytecode-vm-subset");
assert_eq!(report.runtime_level, "inrt-bytecode");
assert_eq!(report.parsed_function_count, 2);
assert_eq!(report.typed_function_count, 2);
assert_eq!(
report.artifact_path.as_deref(),
Some(out_path.to_str().unwrap())
);
assert!(out_path.exists());
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
}
#[test]
fn native_target_reports_host_status() {
let source_path = temp_path("native.in");
fs::write(&source_path, "fn main() -> void { return; }\n").unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Native,
Some("main"),
None,
));
if native_backend::native_subset_host_available() {
assert!(!report.success);
assert!(
report
.reason
.as_deref()
.is_some_and(|reason| reason.contains("--out"))
);
} else {
assert!(!report.success);
assert_eq!(
report.reason_code.as_deref(),
Some(native_backend::NATIVE_BACKEND_NOT_IMPLEMENTED)
);
}
fs::remove_file(source_path).unwrap();
}
#[test]
fn native_staticlib_emits_x86_64_linux_object_file() {
let source_path = temp_path("x86-object.in");
let out_path = temp_path("x86-object.o");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::StaticLib;
request.target_triple = Some("x86_64-unknown-linux-gnu".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-object-subset");
assert_eq!(report.runtime_level, "none");
assert_eq!(report.reason_code.as_deref(), Some("native-object-subset"));
assert_eq!(
report.artifact_path.as_deref(),
Some(out_path.to_str().unwrap())
);
let bytes = fs::read(&out_path).expect("object bytes");
assert_eq!(&bytes[0..4], b"\x7FELF");
assert_eq!(u16::from_le_bytes([bytes[16], bytes[17]]), 1);
assert_eq!(u16::from_le_bytes([bytes[18], bytes[19]]), 62);
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
let _ = fs::remove_file(out_path.with_extension("abi.json"));
}
#[test]
fn native_staticlib_emits_aarch64_linux_object_file() {
let source_path = temp_path("aarch64-object.in");
let out_path = temp_path("aarch64-object.o");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::StaticLib;
request.target_triple = Some("aarch64-unknown-linux-gnu".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-object-subset");
assert_eq!(report.runtime_level, "none");
assert_eq!(report.reason_code.as_deref(), Some("native-object-subset"));
let bytes = fs::read(&out_path).expect("object bytes");
assert_eq!(&bytes[0..4], b"\x7FELF");
assert_eq!(u16::from_le_bytes([bytes[16], bytes[17]]), 1);
assert_eq!(u16::from_le_bytes([bytes[18], bytes[19]]), 183);
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
let _ = fs::remove_file(out_path.with_extension("abi.json"));
}
#[test]
fn native_staticlib_emits_arm32_linux_object_file() {
let source_path = temp_path("arm32-object.in");
let out_path = temp_path("arm32-object.o");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::StaticLib;
request.target_triple = Some("armv7-unknown-linux-gnueabihf".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-object-subset");
assert_eq!(report.runtime_level, "none");
assert_eq!(report.reason_code.as_deref(), Some("native-object-subset"));
let bytes = fs::read(&out_path).expect("object bytes");
assert_eq!(&bytes[0..4], b"\x7FELF");
assert_eq!(bytes[4], 1);
assert_eq!(u16::from_le_bytes([bytes[16], bytes[17]]), 1);
assert_eq!(u16::from_le_bytes([bytes[18], bytes[19]]), 40);
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
let _ = fs::remove_file(out_path.with_extension("abi.json"));
}
#[test]
fn native_staticlib_emits_aarch64_macho_archive() {
let source_path = temp_path("macho-staticlib.in");
let out_path = temp_path("macho-staticlib.a");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::StaticLib;
request.target_triple = Some("aarch64-apple-darwin".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-object-subset");
assert_eq!(report.runtime_level, "none");
assert_eq!(report.reason_code.as_deref(), Some("native-object-subset"));
let bytes = fs::read(&out_path).expect("archive bytes");
assert_eq!(&bytes[..8], b"!<arch>\n");
assert!(bytes.windows(7).any(|window| window == b"_answer"));
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
let _ = fs::remove_file(out_path.with_extension("abi.json"));
}
#[test]
fn native_executable_emits_x86_64_linux_elf_file() {
let source_path = temp_path("x86-executable.in");
let out_path = temp_path("x86-executable");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("x86_64-unknown-linux-gnu".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset-x86_64");
assert_eq!(report.runtime_level, "linux-syscall-exit");
assert_eq!(
report.reason_code.as_deref(),
Some("native-x86_64-linux-exit-subset")
);
let bytes = fs::read(&out_path).expect("executable bytes");
assert_eq!(&bytes[0..4], b"\x7FELF");
assert_eq!(u16::from_le_bytes([bytes[16], bytes[17]]), 2);
assert_eq!(u16::from_le_bytes([bytes[18], bytes[19]]), 62);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(
fs::metadata(&out_path).unwrap().permissions().mode() & 0o777,
0o755
);
}
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
}
#[test]
fn native_executable_emits_aarch64_linux_elf_file() {
let source_path = temp_path("aarch64-linux-executable.in");
let out_path = temp_path("aarch64-linux-executable");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("aarch64-unknown-linux-gnu".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset-aarch64");
assert_eq!(report.runtime_level, "linux-syscall-exit");
assert_eq!(
report.reason_code.as_deref(),
Some("native-aarch64-linux-exit-subset")
);
let bytes = fs::read(&out_path).expect("executable bytes");
assert_eq!(&bytes[0..4], b"\x7FELF");
assert_eq!(bytes[4], 2);
assert_eq!(u16::from_le_bytes([bytes[16], bytes[17]]), 2);
assert_eq!(u16::from_le_bytes([bytes[18], bytes[19]]), 183);
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
}
#[test]
fn native_executable_emits_arm32_linux_elf_file() {
let source_path = temp_path("arm32-linux-executable.in");
let out_path = temp_path("arm32-linux-executable");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("armv7-unknown-linux-gnueabihf".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset-arm32");
assert_eq!(report.runtime_level, "linux-syscall-exit");
assert_eq!(
report.reason_code.as_deref(),
Some("native-armv7-linux-exit-subset")
);
let bytes = fs::read(&out_path).expect("executable bytes");
assert_eq!(&bytes[0..4], b"\x7FELF");
assert_eq!(bytes[4], 1);
assert_eq!(u16::from_le_bytes([bytes[16], bytes[17]]), 2);
assert_eq!(u16::from_le_bytes([bytes[18], bytes[19]]), 40);
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
}
#[test]
fn native_executable_emits_windows_pe_exe_file() {
let source_path = temp_path("windows-executable.in");
let out_path = temp_path("windows-executable.exe");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("x86_64-pc-windows-msvc".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset-x86_64");
assert_eq!(report.runtime_level, "windows-exitprocess");
assert_eq!(
report.reason_code.as_deref(),
Some("native-x86_64-windows-exe-subset")
);
let bytes = fs::read(&out_path).expect("exe bytes");
assert_eq!(&bytes[0..2], b"MZ");
let pe_off = u32::from_le_bytes(bytes[0x3C..0x40].try_into().unwrap()) as usize;
assert_eq!(&bytes[pe_off..pe_off + 4], b"PE\0\0");
assert_eq!(
u16::from_le_bytes(bytes[pe_off + 4..pe_off + 6].try_into().unwrap()),
0x8664
);
assert!(bytes.windows(12).any(|window| window == b"KERNEL32.dll"));
assert!(bytes.windows(11).any(|window| window == b"ExitProcess"));
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
}
#[test]
fn native_executable_emits_aarch64_darwin_app_bundle() {
let source_path = temp_path("darwin-app.in");
let out_path = temp_path("Answer.app");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("aarch64-apple-darwin".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset-aarch64-app");
assert_eq!(report.runtime_level, "macos-app-bundle");
assert_eq!(
report.reason_code.as_deref(),
Some("native-aarch64-darwin-app-subset")
);
let executable = out_path
.join("Contents/MacOS")
.join(artifact_stem(&out_path, "App"));
assert!(executable.exists());
assert!(out_path.join("Contents/Info.plist").exists());
fs::remove_file(source_path).unwrap();
let _ = fs::remove_dir_all(&out_path);
}
#[test]
fn native_executable_emits_linux_appdir() {
let source_path = temp_path("linux-appdir.in");
let out_path = temp_path("Answer.AppDir");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("x86_64-unknown-linux-gnu".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset-x86_64-appdir");
assert_eq!(report.runtime_level, "linux-appdir");
assert_eq!(
report.reason_code.as_deref(),
Some("native-x86_64-linux-appdir-subset")
);
assert!(out_path.join("AppRun").exists());
assert!(out_path.join("answer.desktop").exists());
fs::remove_file(source_path).unwrap();
let _ = fs::remove_dir_all(&out_path);
}
#[test]
fn native_executable_appimage_fails_closed() {
let source_path = temp_path("linux-appimage.in");
let out_path = temp_path("Answer.AppImage");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("x86_64-unknown-linux-gnu".to_string());
let report = compile_owned(&request);
assert!(!report.success, "{:?}", report);
assert_eq!(report.backend_level, "contract-only");
assert_eq!(
report.reason_code.as_deref(),
Some("native-package-not-implemented")
);
assert!(!out_path.exists());
fs::remove_file(source_path).unwrap();
}
#[test]
fn explicit_unsupported_native_target_fails_closed() {
let source_path = temp_path("unsupported-target.in");
let out_path = temp_path("unsupported-target.o");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::StaticLib;
request.target_triple = Some("riscv64gc-unknown-none-elf".to_string());
let report = compile_owned(&request);
assert!(!report.success, "{:?}", report);
assert_eq!(report.backend_level, "contract-only");
assert_eq!(
report.reason_code.as_deref(),
Some("native-target-not-implemented")
);
assert!(!out_path.exists());
fs::remove_file(source_path).unwrap();
}
#[test]
fn explicit_aarch64_darwin_executable_target_fails_closed() {
let source_path = temp_path("unsupported-macho-executable.in");
let out_path = temp_path("unsupported-macho-executable");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::Executable;
request.target_triple = Some("aarch64-apple-darwin".to_string());
let report = compile_owned(&request);
assert!(!report.success, "{:?}", report);
assert_eq!(report.backend_level, "contract-only");
assert_eq!(
report.reason_code.as_deref(),
Some("native-target-not-implemented")
);
assert!(!out_path.exists());
fs::remove_file(source_path).unwrap();
}
#[test]
fn native_staticlib_emits_wasm32_module() {
let source_path = temp_path("wasm-object.in");
let out_path = temp_path("wasm-object.wasm");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let mut request = default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
);
request.linkage = NativeLinkage::StaticLib;
request.target_triple = Some("wasm32-unknown-unknown".to_string());
let report = compile_owned(&request);
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-object-subset");
assert_eq!(report.runtime_level, "none");
assert_eq!(report.reason_code.as_deref(), Some("native-object-subset"));
let bytes = fs::read(&out_path).expect("wasm bytes");
assert_eq!(&bytes[0..4], b"\0asm");
assert!(bytes.windows(6).any(|window| window == b"answer"));
fs::remove_file(source_path).unwrap();
fs::remove_file(&out_path).unwrap();
}
#[test]
fn native_answer_entry_compiles_on_aarch64_host() {
if !native_backend::native_subset_host_available() {
return;
}
let source_path = temp_path("answer.in");
let out_path = temp_path("answer.bin");
fs::write(
&source_path,
"fn answer() -> Int { return 42; }\nfn main() -> void { return; }\n",
)
.unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
));
assert!(report.success, "{:?}", report);
assert_eq!(report.backend_level, "owned-native-subset");
assert_eq!(report.runtime_level, "inrt-native");
assert_eq!(report.eval_exit_code, Some(42));
assert!(out_path.exists());
fs::remove_file(source_path).unwrap();
fs::remove_file(out_path).unwrap();
}
#[test]
fn native_polyglot_answer_entries_compile_on_aarch64_host() {
if !native_backend::native_subset_host_available() {
return;
}
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("repo root")
.to_path_buf();
let cases = [
"apps/polyglot-sample/sample.js",
"apps/polyglot-sample/sample.ts",
"apps/polyglot-sample/sample.py",
"apps/polyglot-sample/sample.rb",
"apps/polyglot-sample/sample.zig",
"apps/polyglot-sample/sample.php",
"apps/polyglot-sample/Sample.java",
];
for case in cases {
let source_path = root.join(case);
let out_path = temp_path(&format!(
"polyglot-{}.bin",
source_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("sample")
));
let report = compile_owned(&default_request(
source_path,
CompileTarget::Native,
Some("answer"),
Some(out_path.clone()),
));
assert!(report.success, "{case}: {report:?}");
assert_eq!(report.eval_exit_code, Some(42), "{case}");
assert!(out_path.exists(), "{case}");
fs::remove_file(out_path).unwrap();
}
}
#[test]
fn report_has_empty_external_invocations() {
let source_path = temp_path("external.in");
fs::write(&source_path, "fn main() -> void { return; }\n").unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
None,
None,
));
assert!(report.external_invocations.is_empty());
assert!(report.success);
fs::remove_file(source_path).unwrap();
}
#[test]
fn report_to_json_roundtrip_fields() {
let source_path = temp_path("json.in");
fs::write(&source_path, "fn main() -> void { return; }\n").unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
None,
None,
));
let json = report_to_json(&report).unwrap();
assert!(json.contains("\"schema_version\": 1"));
assert!(json.contains("\"owned\": true"));
assert!(json.contains("\"external_invocations\": []"));
fs::remove_file(source_path).unwrap();
}
#[test]
fn report_carries_core_identity_metadata() {
let source_path = temp_path("identity.in");
fs::write(
&source_path,
"package agents.video;\nmodule agents.video.main;\nfn main() -> Int { return 7; }\n",
)
.unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
None,
None,
));
assert!(report.success, "{:?}", report);
let identity = report.module_identity.as_ref().expect("module identity");
assert_eq!(identity.package.as_deref(), Some("agents.video"));
assert_eq!(identity.module.as_deref(), Some("agents.video.main"));
assert_eq!(identity.requested_module_id, "App");
assert_eq!(identity.effective_module_id, "agents.video.main");
fs::remove_file(source_path).unwrap();
}
#[test]
fn report_defaults_identity_metadata_without_source_identity() {
let source_path = temp_path("default-identity.in");
fs::write(&source_path, "fn main() -> void { return; }\n").unwrap();
let report = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
None,
None,
));
assert!(report.success, "{:?}", report);
let identity = report.module_identity.as_ref().expect("module identity");
assert_eq!(identity.package, None);
assert_eq!(identity.module, None);
assert_eq!(identity.requested_module_id, "App");
assert_eq!(identity.effective_module_id, "App");
fs::remove_file(source_path).unwrap();
}
#[test]
fn compile_cache_hit_on_second_run() {
let source_path = temp_path("cache.in");
fs::write(&source_path, "fn main() -> void { return; }\n").unwrap();
let first = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
None,
None,
));
assert!(!first.cache_hit);
let second = compile_owned(&default_request(
source_path.clone(),
CompileTarget::Bytecode,
None,
None,
));
assert!(second.cache_hit);
assert_eq!(first.success, second.success);
fs::remove_file(source_path).unwrap();
}
}