Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
build/
dist/
node_modules/
/target/
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[workspace]
members = [
"packages/krad-clang",
"packages/krad-libs",
"packages/krad-musl",
"packages/krad-utils",
]
resolver = "3"

[workspace.package]
edition = "2024"
publish = false
rust-version = "1.85"
version = "0.1.0"

[profile.release]
codegen-units = 1
lto = true
opt-level = "s"
panic = "abort"
strip = "symbols"
60 changes: 42 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,52 @@
# krad

Krad is the source-pinned component graph for a compact client-side C/C++
toolchain. It keeps the compiler, libc, platform glue, and support libraries as
four independently replaceable packages instead of copying multi-gigabyte
source trees into this repository:
Krad is a Rust workspace that turns pinned local Cheerp research inputs into
real WebAssembly artifacts. It does not download a compiler or pretend to be a
new LLVM implementation.

The four packages have concrete jobs:

- `krad-clang`: a Rust driver that invokes one verified Cheerp `clang++` with a
fixed `-O3 -target cheerp-wasm` ES-module build.
- `krad-musl`: an allocator-free `no_std` memory/string ABI subset.
- `krad-libs`: small Linux/FreeBSD errno, page, and guest-copy helpers over
`krad-musl`.
- `krad-utils`: a `no_std` Wasm contract plus the Rust `krad` verification/build
CLI.

Build from this checkout with the sibling `../research/cheerp-*` repositories:

```sh
cargo run -p krad-utils -- verify ../research
cargo run -p krad-utils -- build ../research dist
```

The second command verifies every source remote/revision/license, verifies the
local Cheerp 3.0 archive SHA-256, extracts it with `brotli` and `tar`, builds the
Rust workspace for `wasm32v1-none`, and compiles `examples/add.cpp` with the
real pinned compiler. It creates:

```text
krad-clang
└── krad-utils
└── krad-musl
└── krad-libs
dist/krad-clang.mjs
dist/krad-clang.wasm
dist/krad-libs.wasm
dist/krad-musl.wasm
dist/krad-utils.wasm
```

The first boundary is reproducible source identity. Verify the local research
inputs without downloading or changing them:
The generated Cheerp module is directly executable:

```sh
node tools/krad.js plan
node tools/krad.js verify ../research
npm test
node --input-type=module -e \
"import init from './dist/krad-clang.mjs'; const api=await init({absPath:'./dist/krad-clang.wasm'}); if(api.krad_add(20,22)!==42) process.exit(1)"
```

Use `--json` with `plan` or `verify` for machine-readable output. The four
packages are private until compiler/sysroot artifacts and their browser runtime
contract are built and tested; they must not be published as empty toolchains.
No upstream source is vendored here, and each component records its own source
license and exact researched revision.
Run `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets -- -D warnings`,
and `cargo test --workspace` for the Rust boundary. The checked-in `dist/`
files are build products, not source-package placeholders.

Krad currently wraps Cheerp 3.0 rc.5 (embedded compiler revision
`29e4600563ed2153faf0221362ce4f0a5b93e1ce`). It does not yet build the full
Cheerp compiler from `cheerp-compiler`, provide a complete musl/sysroot, compile
inside a browser, boot Fedora/FreeBSD/Debian, or establish a performance or size
advantage over Cheerp. Those claims require separate comparable evidence.
1 change: 1 addition & 0 deletions dist/krad-clang.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"use strict";/*Compiled using Cheerp (R) by Leaning Technologies Ltd*/var m=Math.imul;var n=Math.fround;var oSlot=0;var nullArray=[null];var nullObj={d:nullArray,o:0};function M(p){return (typeof self==='object')?fetch(p).then(r=>r.arrayBuffer()):new Promise((y,n)=>{import('fs').then(r=>r.readFile(p,(e,d)=>{if(e)n(e);else y(d);}));});}function g(){}var __asm=null,__heap=null;function J(){throw new Error('this should be unreachable');};var i=null;var krad_add=J;export default function(j){return (j&&j.buffer)?Promise.resolve(j.buffer):M((j&&j.absPath)?j.absPath:new URL('krad-clang.wasm', import.meta.url)).then(j=>WebAssembly.instantiate(j,{i:{}})).then(j=>{__asm=j.instance.exports;__heap=__asm.N.buffer;i=__asm.i;krad_add=i;g();return{krad_add:krad_add,};});}function I(j){}
Binary file added dist/krad-clang.wasm
Binary file not shown.
Binary file added dist/krad-libs.wasm
Binary file not shown.
Binary file added dist/krad-musl.wasm
Binary file not shown.
Binary file added dist/krad-utils.wasm
Binary file not shown.
5 changes: 5 additions & 0 deletions examples/add.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern "C" {
[[cheerp::jsexport, cheerp::wasm]] int krad_add(int left, int right) {
return left + right;
}
}
18 changes: 0 additions & 18 deletions package.json

This file was deleted.

9 changes: 9 additions & 0 deletions packages/krad-clang/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "krad-clang"
version.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true

[lib]
path = "src/lib.rs"
13 changes: 0 additions & 13 deletions packages/krad-clang/index.js

This file was deleted.

7 changes: 0 additions & 7 deletions packages/krad-clang/package.json

This file was deleted.

100 changes: 100 additions & 0 deletions packages/krad-clang/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

pub const SOURCE_DIRECTORY: &str = "cheerp-compiler";
pub const SOURCE_REPOSITORY: &str = "https://github.com/leaningtech/cheerp-compiler";
pub const SOURCE_REVISION: &str = "0596bd128750d43569d17d56e443a95066d3461f";
pub const SOURCE_LICENSE: &str = "Apache-2.0 WITH LLVM-exception";
pub const SOURCE_LICENSE_FILE: &str = "LICENSE.TXT";
pub const SOURCE_FOCUS: &[&str] = &[
"llvm/include/llvm/Cheerp",
"llvm/lib/CheerpUtils",
"llvm/lib/CheerpWriter",
"clang/test/Cheerp",
];
pub const BINARY_REVISION: &str = "29e4600563ed2153faf0221362ce4f0a5b93e1ce";

#[derive(Debug, Clone)]
pub struct Compiler {
executable: PathBuf,
}

impl Compiler {
pub fn new(executable: impl Into<PathBuf>) -> Result<Self, String> {
let executable = executable.into();
if !executable.is_file() {
return Err(format!("compiler is not a file: {}", executable.display()));
}
Ok(Self { executable })
}

pub fn version(&self) -> Result<String, String> {
let output = Command::new(&self.executable)
.arg("--version")
.output()
.map_err(|error| format!("could not execute {}: {error}", self.executable.display()))?;
if !output.status.success() {
return Err(format!("{} --version failed", self.executable.display()));
}
String::from_utf8(output.stdout).map_err(|_| "compiler version is not UTF-8".to_owned())
}

pub fn compile(&self, source: &Path, javascript: &Path, wasm: &Path) -> Result<(), String> {
if !source.is_file() {
return Err(format!("source is not a file: {}", source.display()));
}
if javascript == wasm {
return Err("JavaScript and WebAssembly outputs must differ".to_owned());
}
for output in [javascript, wasm] {
if let Some(parent) = output.parent() {
fs::create_dir_all(parent)
.map_err(|error| format!("could not create {}: {error}", parent.display()))?;
}
if output.exists() {
fs::remove_file(output)
.map_err(|error| format!("could not replace {}: {error}", output.display()))?;
}
}
let mut secondary = OsString::from("-cheerp-secondary-output-file=");
secondary.push(wasm);
let status = Command::new(&self.executable)
.args(["-O3", "-target", "cheerp-wasm", "-cheerp-make-module=es6"])
.arg(secondary)
.arg(source)
.arg("-o")
.arg(javascript)
.status()
.map_err(|error| format!("could not execute {}: {error}", self.executable.display()))?;
if !status.success() {
return Err(format!("Cheerp compilation failed with {status}"));
}
if !javascript.is_file() {
return Err("Cheerp did not create the JavaScript output".to_owned());
}
let header = fs::read(wasm)
.map_err(|error| format!("could not read {}: {error}", wasm.display()))?;
if !valid_wasm(&header) {
return Err("Cheerp did not create a valid WebAssembly module".to_owned());
}
Ok(())
}
}

pub fn valid_wasm(bytes: &[u8]) -> bool {
bytes.starts_with(b"\0asm\x01\0\0\0")
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn validates_wasm_headers_and_compiler_paths() {
assert!(valid_wasm(b"\0asm\x01\0\0\0"));
assert!(!valid_wasm(b"not wasm"));
assert!(Compiler::new("/definitely/missing/krad-clang").is_err());
}
}
13 changes: 13 additions & 0 deletions packages/krad-libs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "krad-libs"
version.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"

[dependencies]
krad-musl = { path = "../krad-musl" }
14 changes: 0 additions & 14 deletions packages/krad-libs/index.js

This file was deleted.

7 changes: 0 additions & 7 deletions packages/krad-libs/package.json

This file was deleted.

59 changes: 59 additions & 0 deletions packages/krad-libs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![cfg_attr(target_arch = "wasm32", no_std)]

pub const SOURCE_DIRECTORY: &str = "cheerp-libs";
pub const SOURCE_REPOSITORY: &str = "https://github.com/leaningtech/cheerp-libs";
pub const SOURCE_REVISION: &str = "a578cfaa28bde51f1ae8b17980e6bf7727333be7";
pub const SOURCE_LICENSE: &str = "Apache-2.0 WITH LLVM-exception";
pub const SOURCE_LICENSE_FILE: &str = "LICENSE.TXT";
pub const SOURCE_FOCUS: &[&str] = &["system", "stdlibs", "wasm"];

pub const ABI_LINUX: u32 = 0;
pub const ABI_FREEBSD: u32 = 1;
pub const ERROR_NO_ENTRY: u32 = 1;
pub const ERROR_NO_MEMORY: u32 = 2;
pub const ERROR_INVALID: u32 = 3;
pub const ERROR_NOT_IMPLEMENTED: u32 = 4;

#[unsafe(no_mangle)]
pub extern "C" fn krad_errno(abi: u32, error: u32) -> u32 {
match (abi, error) {
(ABI_LINUX | ABI_FREEBSD, ERROR_NO_ENTRY) => 2,
(ABI_LINUX | ABI_FREEBSD, ERROR_NO_MEMORY) => 12,
(ABI_LINUX | ABI_FREEBSD, ERROR_INVALID) => 22,
(ABI_LINUX, ERROR_NOT_IMPLEMENTED) => 38,
(ABI_FREEBSD, ERROR_NOT_IMPLEMENTED) => 78,
_ => 0,
}
}

#[unsafe(no_mangle)]
pub extern "C" fn krad_page_align(value: u32) -> u32 {
value.checked_add(4095).map_or(0, |value| value & !4095)
}

/// Copies guest bytes through the `krad-musl` memory primitive.
///
/// # Safety
/// `destination` and `source` must be valid for `length` non-overlapping bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn krad_copy(
destination: *mut u8,
source: *const u8,
length: usize,
) -> *mut u8 {
unsafe { krad_musl::krad_memcpy(destination, source, length) }
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn maps_abi_errors_and_pages() {
assert_eq!(krad_errno(ABI_LINUX, ERROR_NOT_IMPLEMENTED), 38);
assert_eq!(krad_errno(ABI_FREEBSD, ERROR_NOT_IMPLEMENTED), 78);
assert_eq!(krad_errno(99, ERROR_INVALID), 0);
assert_eq!(krad_page_align(4097), 8192);
assert_eq!(krad_page_align(u32::MAX), 0);
}
}
10 changes: 10 additions & 0 deletions packages/krad-musl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "krad-musl"
version.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
Loading