Phase 20.34: wire Host providers via externs (env.mirbuilder.emit, env.codegen.emit_object); implement MirBuilder provider (Program→MIR JSON) and ny-llvmc wrapper; update Hako boxes (MirBuilderBox, LLVMEmitProviderBox) to delegate; adjust canaries to PASS (MirBuilder PASS, LLVM SKIP on unresolved or missing ny-llvmc).
This commit is contained in:
@ -11,7 +11,7 @@
|
|||||||
// - Provider examples: ny-llvmc wrapper or llvmlite harness via a Plugin box `LLVMCodegenBox.emit_object/2`.
|
// - Provider examples: ny-llvmc wrapper or llvmlite harness via a Plugin box `LLVMCodegenBox.emit_object/2`.
|
||||||
// - This stub only validates inputs and reports provider availability via env.
|
// - This stub only validates inputs and reports provider availability via env.
|
||||||
|
|
||||||
static box LLVMEmitBox {
|
static box LLVMEmitProviderBox {
|
||||||
// Availability probe (for canaries)
|
// Availability probe (for canaries)
|
||||||
is_available() {
|
is_available() {
|
||||||
// Treat HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite as availability hint
|
// Treat HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite as availability hint
|
||||||
@ -22,7 +22,7 @@ static box LLVMEmitBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main entry
|
// Main entry
|
||||||
emit_object(mir_json, opts) {
|
method emit_object(mir_json, opts) {
|
||||||
if mir_json == null {
|
if mir_json == null {
|
||||||
print("[llvmemit/input/null] mir_json is null")
|
print("[llvmemit/input/null] mir_json is null")
|
||||||
return null
|
return null
|
||||||
@ -38,13 +38,18 @@ static box LLVMEmitBox {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
local pv = "" + p
|
local pv = "" + p
|
||||||
if pv != "ny-llvmc" && pv != "llvmlite" {
|
if pv == "ny-llvmc" {
|
||||||
print("[llvmemit/provider/unsupported] " + pv)
|
local args = new ArrayBox(); args.push(mir_json)
|
||||||
|
// env.codegen.emit_object(mir_json)
|
||||||
|
local ret = hostbridge.extern_invoke("env.codegen", "emit_object", args)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
if pv == "llvmlite" {
|
||||||
|
// Not wired yet for llvmlite provider
|
||||||
|
print("[llvmemit/skip] provider stub; implement llvmlite Plugin v2 call")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Provider path not wired yet in this stub
|
print("[llvmemit/provider/unsupported] " + pv)
|
||||||
print("[llvmemit/skip] provider stub; implement Plugin v2 call")
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ static box MirBuilderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main entry
|
// Main entry
|
||||||
emit_from_program_json_v0(program_json, opts) {
|
method emit_from_program_json_v0(program_json, opts) {
|
||||||
if program_json == null {
|
if program_json == null {
|
||||||
print("[mirbuilder/input/null] program_json is null")
|
print("[mirbuilder/input/null] program_json is null")
|
||||||
return null
|
return null
|
||||||
@ -35,12 +35,13 @@ static box MirBuilderBox {
|
|||||||
// Delegate-first policy (Phase 20.34 Milestone A)
|
// Delegate-first policy (Phase 20.34 Milestone A)
|
||||||
local d = env.get("HAKO_MIR_BUILDER_DELEGATE")
|
local d = env.get("HAKO_MIR_BUILDER_DELEGATE")
|
||||||
if d != null && ("" + d) == "1" {
|
if d != null && ("" + d) == "1" {
|
||||||
print("[mirbuilder/delegate] use Runner --program-json-to-mir")
|
// Call host provider via extern: env.mirbuilder.emit(program_json)
|
||||||
return null
|
local args = new ArrayBox(); args.push(program_json)
|
||||||
|
local ret = hostbridge.extern_invoke("env.mirbuilder", "emit", args)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
// Provider not wired yet
|
// Provider not wired → Fail‑Fast tag
|
||||||
print("[mirbuilder/delegate/missing] no provider; enable HAKO_MIR_BUILDER_DELEGATE=1")
|
print("[mirbuilder/delegate/missing] no provider; enable HAKO_MIR_BUILDER_DELEGATE=1")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
75
src/host_providers/llvm_codegen.rs
Normal file
75
src/host_providers/llvm_codegen.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub struct Opts {
|
||||||
|
pub out: Option<PathBuf>,
|
||||||
|
pub nyrt: Option<PathBuf>,
|
||||||
|
pub opt_level: Option<String>,
|
||||||
|
pub timeout_ms: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_ny_llvmc() -> PathBuf {
|
||||||
|
if let Ok(s) = std::env::var("NYASH_NY_LLVM_COMPILER") {
|
||||||
|
if !s.is_empty() { return PathBuf::from(s); }
|
||||||
|
}
|
||||||
|
if let Ok(p) = which::which("ny-llvmc") { return p; }
|
||||||
|
PathBuf::from("target/release/ny-llvmc")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile MIR(JSON v0) to an object file (.o) using ny-llvmc. Returns the output path.
|
||||||
|
/// Fail‑Fast: prints stable tags and returns Err with the same message.
|
||||||
|
pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String> {
|
||||||
|
// Basic shape check for MIR(JSON v0)
|
||||||
|
if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") {
|
||||||
|
let tag = "[llvmemit/input/invalid] missing functions/blocks keys";
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let ny_llvmc = resolve_ny_llvmc();
|
||||||
|
if !ny_llvmc.exists() {
|
||||||
|
let tag = format!("[llvmemit/ny-llvmc/not-found] path={}", ny_llvmc.display());
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write MIR JSON to temp
|
||||||
|
let tmp_dir = std::env::temp_dir();
|
||||||
|
let in_path = tmp_dir.join("hako_llvm_in.json");
|
||||||
|
{
|
||||||
|
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
|
||||||
|
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output path
|
||||||
|
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
|
||||||
|
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
|
||||||
|
|
||||||
|
// Build command: ny-llvmc --in <json> --emit obj --out <out>
|
||||||
|
let mut cmd = Command::new(&ny_llvmc);
|
||||||
|
cmd.arg("--in").arg(&in_path)
|
||||||
|
.arg("--emit").arg("obj")
|
||||||
|
.arg("--out").arg(&out_path);
|
||||||
|
if let Some(nyrt) = opts.nyrt.as_ref() { cmd.arg("--nyrt").arg(nyrt); }
|
||||||
|
if let Some(level) = opts.opt_level.as_ref() {
|
||||||
|
cmd.env("HAKO_LLVM_OPT_LEVEL", level);
|
||||||
|
cmd.env("NYASH_LLVM_OPT_LEVEL", level);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = cmd.status().map_err(|e| format!("[llvmemit/spawn/error] {}", e))?;
|
||||||
|
if !status.success() {
|
||||||
|
let code = status.code().unwrap_or(1);
|
||||||
|
let tag = format!("[llvmemit/ny-llvmc/failed status={}]", code);
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag);
|
||||||
|
}
|
||||||
|
if !out_path.exists() {
|
||||||
|
let tag = format!("[llvmemit/output/missing] {}", out_path.display());
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag);
|
||||||
|
}
|
||||||
|
Ok(out_path)
|
||||||
|
}
|
||||||
|
|
||||||
46
src/host_providers/mir_builder.rs
Normal file
46
src/host_providers/mir_builder.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::runner;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// Convert Program(JSON v0) to MIR(JSON v0) and return it as a String.
|
||||||
|
/// Fail-Fast: prints stable tags on stderr and returns Err with the same tag text.
|
||||||
|
pub fn program_json_to_mir_json(program_json: &str) -> Result<String, String> {
|
||||||
|
// Basic header check
|
||||||
|
if !program_json.contains("\"version\"") || !program_json.contains("\"kind\"") {
|
||||||
|
let tag = "[mirbuilder/input/invalid] missing version/kind keys";
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Program(JSON v0) into a MIR Module
|
||||||
|
let module = match runner::json_v0_bridge::parse_json_v0_to_module(program_json) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
let tag = format!("[mirbuilder/parse/error] {}", e);
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit MIR(JSON) to a temporary file (reuse existing emitter), then read back
|
||||||
|
let tmp_dir = std::env::temp_dir();
|
||||||
|
let tmp_path = tmp_dir.join("hako_mirbuilder_out.json");
|
||||||
|
if let Err(e) = runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &tmp_path) {
|
||||||
|
let tag = format!("[mirbuilder/emit/error] {}", e);
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
return Err(tag);
|
||||||
|
}
|
||||||
|
match fs::read_to_string(&tmp_path) {
|
||||||
|
Ok(s) => {
|
||||||
|
// Best-effort cleanup
|
||||||
|
let _ = fs::remove_file(&tmp_path);
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let tag = format!("[mirbuilder/read/error] {}", e);
|
||||||
|
eprintln!("{}", tag);
|
||||||
|
Err(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3
src/host_providers/mod.rs
Normal file
3
src/host_providers/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod mir_builder;
|
||||||
|
pub mod llvm_codegen;
|
||||||
|
|
||||||
@ -25,6 +25,8 @@ pub fn extern_call(
|
|||||||
"env.debug" => handle_debug(method_name, args),
|
"env.debug" => handle_debug(method_name, args),
|
||||||
"env.runtime" => handle_runtime(method_name, args),
|
"env.runtime" => handle_runtime(method_name, args),
|
||||||
"env.future" => handle_future(method_name, args),
|
"env.future" => handle_future(method_name, args),
|
||||||
|
"env.mirbuilder" => handle_mirbuilder(method_name, args),
|
||||||
|
"env.codegen" => handle_codegen(method_name, args),
|
||||||
_ => Err(BidError::PluginError),
|
_ => Err(BidError::PluginError),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,6 +189,39 @@ fn handle_future(method_name: &str, args: &[Box<dyn NyashBox>]) -> BidResult<Opt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle env.mirbuilder.* methods (Program(JSON v0) → MIR(JSON v0))
|
||||||
|
fn handle_mirbuilder(method_name: &str, args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||||
|
match method_name {
|
||||||
|
"emit" => {
|
||||||
|
let program_json = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default();
|
||||||
|
match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) {
|
||||||
|
Ok(s) => Ok(Some(Box::new(StringBox::new(&s)) as Box<dyn NyashBox>)),
|
||||||
|
Err(_e) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(BidError::PluginError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle env.codegen.* methods (MIR(JSON v0) → object via ny-llvmc)
|
||||||
|
fn handle_codegen(method_name: &str, args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||||
|
match method_name {
|
||||||
|
"emit_object" => {
|
||||||
|
let mir_json = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default();
|
||||||
|
// Collect minimal options from env (optional)
|
||||||
|
let opt_level = std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or_else(|| std::env::var("NYASH_LLVM_OPT_LEVEL").ok());
|
||||||
|
let out = None;
|
||||||
|
let nyrt = std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from);
|
||||||
|
let opts = crate::host_providers::llvm_codegen::Opts { out, nyrt, opt_level, timeout_ms: None };
|
||||||
|
match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
|
||||||
|
Ok(p) => Ok(Some(Box::new(StringBox::new(&p.to_string_lossy())) as Box<dyn NyashBox>)),
|
||||||
|
Err(_e) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(BidError::PluginError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle env.future.await method
|
/// Handle env.future.await method
|
||||||
fn handle_future_await(args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
|
fn handle_future_await(args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||||
if let Some(arg) = args.get(0) {
|
if let Some(arg) = args.get(0) {
|
||||||
|
|||||||
@ -14,15 +14,38 @@ require_env || exit 2
|
|||||||
tmp_hako="/tmp/llvmemit_canary_$$.hako"
|
tmp_hako="/tmp/llvmemit_canary_$$.hako"
|
||||||
cat > "$tmp_hako" <<'HAKO'
|
cat > "$tmp_hako" <<'HAKO'
|
||||||
include "lang/src/llvm_ir/emit/LLVMEmitBox.hako"
|
include "lang/src/llvm_ir/emit/LLVMEmitBox.hako"
|
||||||
static box Main { method main(args) { return 0; } }
|
static box Main { method main(args) {
|
||||||
|
// Minimal MIR(JSON v0)
|
||||||
|
local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"Int\",\"value\":0}},{\"op\":\"ret\",\"value\":1}] }] }},\"blocks\":1}";
|
||||||
|
// Call provider via extern directly to avoid name collision risks in this canary
|
||||||
|
local argsA = new ArrayBox(); argsA.push(mir)
|
||||||
|
local out = hostbridge.extern_invoke("env.codegen", "emit_object", argsA)
|
||||||
|
if out == null { return 0 }
|
||||||
|
// Print the path for the shell script to verify existence
|
||||||
|
print("" + out)
|
||||||
|
return 1;
|
||||||
|
} }
|
||||||
HAKO
|
HAKO
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
|
out="$(HAKO_LLVM_EMIT_PROVIDER=ny-llvmc NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
|
||||||
$NYASH_BIN --backend vm "$tmp_hako" 2>&1)"
|
$NYASH_BIN --backend vm "$tmp_hako" 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
set -e
|
set -e
|
||||||
rm -f "$tmp_hako" 2>/dev/null || true
|
rm -f "$tmp_hako" 2>/dev/null || true
|
||||||
|
|
||||||
echo "[SKIP] llvmemit_canary (provider not wired; box present)"
|
path="$(echo "$out" | tail -n1 | tr -d '\r')"
|
||||||
exit 0
|
if [ "$rc" -eq 1 ] && [ -n "$path" ] && [ -f "$path" ]; then
|
||||||
|
echo "[PASS] llvmemit_canary_vm"
|
||||||
|
rm -f "$path" || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if echo "$out" | grep -q "\[llvmemit/ny-llvmc/not-found\]"; then
|
||||||
|
echo "[SKIP] llvmemit_canary (ny-llvmc not found)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if echo "$out" | grep -qi "call unresolved: 'hostbridge\.extern_invoke/3'\|call unresolved: 'LLVMEmitProviderBox\.emit_object/2'"; then
|
||||||
|
echo "[SKIP] llvmemit_canary (provider reachable only via plugin; unresolved in this profile)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "[FAIL] llvmemit_canary_vm (rc=$rc)" >&2; exit 1
|
||||||
|
|||||||
@ -14,14 +14,25 @@ require_env || exit 2
|
|||||||
tmp_hako="/tmp/mirbuilder_canary_$$.hako"
|
tmp_hako="/tmp/mirbuilder_canary_$$.hako"
|
||||||
cat > "$tmp_hako" <<'HAKO'
|
cat > "$tmp_hako" <<'HAKO'
|
||||||
include "lang/src/mir/builder/MirBuilderBox.hako"
|
include "lang/src/mir/builder/MirBuilderBox.hako"
|
||||||
static box Main { method main(args) { return 0; } }
|
static box Main { method main(args) {
|
||||||
|
// Build minimal Program(JSON v0)
|
||||||
|
local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":42}}]}";
|
||||||
|
local out = MirBuilderBox.emit_from_program_json_v0(j, null);
|
||||||
|
if out == null { return 0 }
|
||||||
|
local s = "" + out
|
||||||
|
if s.indexOf("\"functions\"") >= 0 && s.indexOf("\"blocks\"") >= 0 { return 1 }
|
||||||
|
return 0
|
||||||
|
} }
|
||||||
HAKO
|
HAKO
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
|
out="$(HAKO_MIR_BUILDER_DELEGATE=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
|
||||||
$NYASH_BIN --backend vm "$tmp_hako" 2>&1)"; rc=$?
|
$NYASH_BIN --backend vm "$tmp_hako" 2>&1)"; rc=$?
|
||||||
set -e
|
set -e
|
||||||
rm -f "$tmp_hako" 2>/dev/null || true
|
rm -f "$tmp_hako" 2>/dev/null || true
|
||||||
|
|
||||||
echo "[SKIP] mirbuilder_canary (delegate/provider not wired; box present)"
|
if [ "$rc" -eq 1 ]; then
|
||||||
exit 0
|
echo "[PASS] mirbuilder_canary_vm"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "[FAIL] mirbuilder_canary_vm (rc=$rc)" >&2; exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user