use anyhow::Result;
use std::process::Command;
pub struct Linker {
output_name: String,
}
impl Linker {
pub fn new(output_name: &str) -> Self {
Self {
output_name: output_name.to_string(),
}
}
pub fn link(&self, object_files: &[&str]) -> Result<Vec<u8>> {
#[cfg(target_os = "windows")]
{
self.link_windows(object_files)
}
#[cfg(target_os = "linux")]
{
self.link_linux(object_files)
}
#[cfg(target_os = "macos")]
{
self.link_macos(object_files)
}
}
#[cfg(target_os = "windows")]
fn link_windows(&self, object_files: &[&str]) -> Result<Vec<u8>> {
let jade_link = Self::find_jade_linker();
let mut link_args = vec![
"/NOLOGO".to_string(),
"/ENTRY:mainCRTStartup".to_string(),
"/SUBSYSTEM:CONSOLE".to_string(),
format!("/OUT:{}", self.output_name),
];
for obj in object_files {
link_args.push((*obj).to_string());
}
let crt_start_o = Self::find_crt_start();
if let Some(crt) = &crt_start_o {
link_args.push(crt.clone());
}
let ext_libs = Self::find_extension_libs();
if !ext_libs.is_empty() {
eprintln!("[link] Found {} extension libs", ext_libs.len());
}
for lib in &ext_libs {
link_args.push(lib.clone());
}
let linker = jade_link.clone().unwrap_or_else(|| "link.exe".to_string());
let mut cmd = Command::new(&linker);
cmd.args(&link_args);
if let Some(linker_path) = &jade_link {
if let Some(parent) = std::path::Path::new(linker_path).parent() {
let path_env = std::env::var("PATH").unwrap_or_default();
let new_path = format!("{};{}", parent.to_string_lossy(), path_env);
cmd.env("PATH", new_path);
}
}
let output = cmd
.output()
.map_err(|e| anyhow::anyhow!("Failed to execute linker '{}': {}", linker, e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
anyhow::bail!("Link failed:\n Linker: {}\n Args: {:?}\n Stderr: {}\n Stdout: {}",
linker, link_args, stderr, stdout);
}
std::fs::read(&self.output_name)
.map_err(|e| anyhow::anyhow!("Failed to read output: {}", e))
}
fn normalize_path(path: std::path::PathBuf) -> Option<String> {
let canonical = path.canonicalize().ok()?;
let path_str = canonical.to_string_lossy().to_string();
if path_str.starts_with("\\\\?\\") {
Some(path_str[4..].to_string())
} else {
Some(path_str)
}
}
fn find_jade_linker() -> Option<String> {
let cwd = std::env::current_dir().ok()?;
let candidates = vec![
cwd.join("javascript-web-to-rust-native/packages/jade/toolchain/win32-x64-msvc/link.exe"),
cwd.join("../javascript-web-to-rust-native/packages/jade/toolchain/win32-x64-msvc/link.exe"),
cwd.join("../../javascript-web-to-rust-native/packages/jade/toolchain/win32-x64-msvc/link.exe"),
std::path::PathBuf::from("E:/Administrator/Documents/codebuddy-projects/javascript-web-to-rust-native/packages/jade/toolchain/win32-x64-msvc/link.exe"),
];
for path in candidates {
if path.exists() {
return Self::normalize_path(path);
}
}
None
}
fn find_crt_start() -> Option<String> {
let cwd = std::env::current_dir().ok()?;
let candidates = vec![
cwd.join("ts-native-runtime/target/release/build/ts-native-runtime-022efd2cc526c363/out/ea708c7824d36062-crt_start.o"),
cwd.join("../ts-native-runtime/target/release/build/ts-native-runtime-022efd2cc526c363/out/ea708c7824d36062-crt_start.o"),
std::path::PathBuf::from("E:/Administrator/Documents/codebuddy-projects/ts-native-runtime/target/release/build/ts-native-runtime-022efd2cc526c363/out/ea708c7824d36062-crt_start.o"),
];
for path in candidates {
if path.exists() {
return Self::normalize_path(path);
}
}
None
}
fn find_extension_libs() -> Vec<String> {
let mut libs = Vec::new();
let cwd = if let Ok(c) = std::env::current_dir() {
c
} else {
return libs;
};
let candidates = vec![
cwd.join("ts-native-stdlib/target/release/ts_native_stdlib.lib"),
cwd.join("../ts-native-stdlib/target/release/ts_native_stdlib.lib"),
cwd.join("ts-native-extension-dom/target/release/ts_native_extension_dom.lib"),
cwd.join("../ts-native-extension-dom/target/release/ts_native_extension_dom.lib"),
];
for lib_path in candidates {
if lib_path.exists() {
eprintln!("[link] Found lib: {:?}", lib_path);
if let Some(normalized) = Self::normalize_path(lib_path) {
libs.push(normalized);
}
}
}
libs
}
#[cfg(target_os = "linux")]
fn link_linux(&self, object_files: &[&str]) -> Result<Vec<u8>> {
let mut ld_args = vec![
"-o".to_string(),
self.output_name.clone(),
];
for obj in object_files {
ld_args.push((*obj).to_string());
}
ld_args.push("-lc".to_string());
let output = Command::new("ld")
.args(&ld_args)
.output()?;
if !output.status.success() {
anyhow::bail!("Link failed: {}", String::from_utf8_lossy(&output.stderr));
}
std::fs::read(&self.output_name)
.map_err(|e| anyhow::anyhow!("Failed to read output: {}", e))
}
#[cfg(target_os = "macos")]
fn link_macos(&self, object_files: &[&str]) -> Result<Vec<u8>> {
let mut ld_args = vec![
"-o".to_string(),
self.output_name.clone(),
];
for obj in object_files {
ld_args.push((*obj).to_string());
}
ld_args.push("-lSystem".to_string());
let output = Command::new("ld")
.args(&ld_args)
.output()?;
if !output.status.success() {
anyhow::bail!("Link failed: {}", String::from_utf8_lossy(&output.stderr));
}
std::fs::read(&self.output_name)
.map_err(|e| anyhow::anyhow!("Failed to read output: {}", e))
}
pub fn link_with_gcc(&self, object_files: &[&str]) -> Result<Vec<u8>> {
let mut gcc_args = vec![
"-o".to_string(),
self.output_name.clone(),
];
for obj in object_files {
gcc_args.push((*obj).to_string());
}
let output = Command::new("gcc")
.args(&gcc_args)
.output()?;
if !output.status.success() {
anyhow::bail!("GCC link failed: {}", String::from_utf8_lossy(&output.stderr));
}
std::fs::read(&self.output_name)
.map_err(|e| anyhow::anyhow!("Failed to read output: {}", e))
}
pub fn link_with_clang(&self, object_files: &[&str]) -> Result<Vec<u8>> {
let clang_path = "C:\\Program Files\\Microsoft Visual Studio\\18\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang.exe";
let mut clang_args = vec![
"-Wl,/ENTRY:_start".to_string(),
"-o".to_string(),
self.output_name.clone(),
];
for obj in object_files {
clang_args.push((*obj).to_string());
}
clang_args.push("-lkernel32".to_string());
let output = Command::new(clang_path)
.args(&clang_args)
.output()?;
if !output.status.success() {
anyhow::bail!("Clang link failed: {}", String::from_utf8_lossy(&output.stderr));
}
std::fs::read(&self.output_name)
.map_err(|e| anyhow::anyhow!("Failed to read output: {}", e))
}
}
pub fn create_c_runtime() -> &'static str {
r#"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// NaN-boxing 常量
#define UNDEFINED 0x7FFF800000000001ULL
#define NULL_VAL 0x7FFF800000000002ULL
#define TRUE 0x7FFF000000000001ULL
#define FALSE 0x7FFF000000000000ULL
#define STRING_TAG 0x7FFC000000000000ULL
#define ARRAY_TAG 0x7FFB000000000000ULL
// 字符串结构
typedef struct {
uint32_t len;
uint32_t hash;
char data[];
} JsString;
// 数组结构
typedef struct {
uint32_t len;
uint32_t capacity;
uint64_t data[];
} JsArray;
// 创建字符串
uint64_t js_string_new(const char* s, uint32_t len) {
JsString* str = malloc(sizeof(JsString) + len);
str->len = len;
str->hash = 0;
for (uint32_t i = 0; i < len; i++) {
str->data[i] = s[i];
str->hash = str->hash * 31 + s[i];
}
return STRING_TAG | (uint64_t)str;
}
// 创建数组
uint64_t js_array_new(uint32_t capacity) {
if (capacity == 0) capacity = 8;
JsArray* arr = malloc(sizeof(JsArray) + capacity * sizeof(uint64_t));
arr->len = 0;
arr->capacity = capacity;
return ARRAY_TAG | (uint64_t)arr;
}
// 数组 push
void js_array_push(uint64_t arr_val, uint64_t value) {
JsArray* arr = (JsArray*)(arr_val & 0x0000FFFFFFFFFFFFULL);
if (arr->len >= arr->capacity) {
arr->capacity *= 2;
arr = realloc(arr, sizeof(JsArray) + arr->capacity * sizeof(uint64_t));
}
arr->data[arr->len++] = value;
}
// 打印值
void js_print(uint64_t val) {
if (val == UNDEFINED) {
printf("undefined\n");
} else if (val == NULL_VAL) {
printf("null\n");
} else if (val == TRUE) {
printf("true\n");
} else if (val == FALSE) {
printf("false\n");
} else if ((val >> 48) == (STRING_TAG >> 48)) {
JsString* str = (JsString*)(val & 0x0000FFFFFFFFFFFFULL);
printf("%.*s\n", str->len, str->data);
} else {
double d = *(double*)&val;
printf("%g\n", d);
}
}
// 数学函数
uint64_t js_add(uint64_t a, uint64_t b) {
double da = *(double*)&a;
double db = *(double*)&b;
double result = da + db;
return *(uint64_t*)&result;
}
uint64_t js_sub(uint64_t a, uint64_t b) {
double da = *(double*)&a;
double db = *(double*)&b;
double result = da - db;
return *(uint64_t*)&result;
}
uint64_t js_mul(uint64_t a, uint64_t b) {
double da = *(double*)&a;
double db = *(double*)&b;
double result = da * db;
return *(uint64_t*)&result;
}
uint64_t js_div(uint64_t a, uint64_t b) {
double da = *(double*)&a;
double db = *(double*)&b;
double result = da / db;
return *(uint64_t*)&result;
}
"#
}