10 comments

[ 5.0 ms ] story [ 32.7 ms ] thread
Spaceship: A JIT-compiled systems language that compiles .sh to LLVM

I’ve always felt that the gap between "one-off shell scripts" and "robust systems code" is too wide. Bash is ubiquitous but dangerous; Go is safe but can feel heavy for quick automation.

I’m building Spaceship to bridge that gap. It’s a Go-inspired systems language with a C++/Boost-based compiler that JIT-compiles everything—including legacy shell scripts—directly into native machine code via LLVM.

The highlights:

* @jit Directive: You can take an existing .sh file and run @jit("script.sh"). Instead of spawning a subshell, Spaceship parses the shell logic, translates it to POSIX-compliant AST nodes, and JIT-compiles it into the current execution path. * Zero-Trust JIT Sandbox: Security is enforced at the LLVM IR lowering phase. If your script doesn't explicitly allow a capability (like network.tcp or process.fork) in the security manifest, the JIT simply refuses to generate the machine code for those instructions. No runtime interceptor overhead. * Arbitrary Bit-Widths: Since it’s LLVM-native, you aren't stuck with i32 or i64. If you're interfacing with specific hardware or protocols, you can use i1, i23, i25, etc. * The !i32 Contract: All system calls return a success value or an i32 POSIX error code, handled via a check/except flow that mirrors C++ exception speed but keeps the simplicity of Go’s error handling. * Unified Backend: We use Boost (Asio, Process, Filesystem) as the high-performance standard library that the JIT links against, ensuring POSIX compatibility across Linux and macOS.

The parser is implemented in C++ and handles deferred execution pipelines—nothing runs until you call .run(), which allows the JIT to optimize the entire chain of operations.

I'd love to hear your thoughts on the "Security through Omission" model and the feasibility of replacing dash/bash with a JIT-ted environment for high-performance automation.

What on earth is the value of a “hypothetical benchmark” as shown in the readme?
Thing with LLMs, they'll tell you what a great idea and then output a design and tons of code for you which if lack the necessary knowledge will look coherent and correct. It's good to throw the design/code back in and tell them to review it and explicitly prompt them to tell you what is wrong.

So here it says your error handling maps directly to POSIX exit code. But then "On success, the function returns a non-zero value."

For the sh JIT: The slowness isn't due to the language per se but due to launching multiple processes. If performance is really the goal then you essentially need to replace every process launch with a built-in command. The benchmark is an hallucination unless can indeed be run. Hypothetical benchmarks with hypothetical results are nonsense. (Unless you've a mathematical model backing it up.)

Nice "functional programming synatx."
Hmmmm

  llvm::Value* JitDirectiveNode::CodeGen(Compiler& compiler) {
    // TODO: Implement the @jit shell-to-native translation engine.
    // 1. Read the content of the shell script at FilePath.
    // 2. Parse the shell script into a sequence of POSIX-equivalent commands.
    // 3. Translate these commands into LLVM IR, similar to ProcessCallNode.
    // 4. Inline the generated IR into the current function.
    // This is a major and complex part of the compiler.
    return nullptr;
  }
> designed to replace legacy shell scripting ... as arguments are passed as a structured array, not a raw string to be parsed by a shell

I find shell scripters prefer ubiquity and readability over raw performance. And making it mandatory to give arguments as arrays worsens the readabilty. However having both options would be good, your example doesn't actually require the shell escaping so it could have simpler way.

Here is equivalent in Deno for instance

    #!/usr/bin/env -S deno run --allow-all
    import $ from "jsr:@david/dax"; 
    const command = $`grep -r keyword .`.pipe($`wc -l`);
    const result = await command;
Deno (via library) and Bun both have $ that can also handle escaping, e.g.

    const dirName = "Dir with spaces";
    await $`mkdir ${dirName}`; // executes as: mkdir 'Dir with spaces'
I don't think syntax is your biggest hurdle though, biggest hurdle is that Bash is so common, Powershell was supposed to be better shell scripting, yet it takes nowhere outside Windows space.

    Process("grep", ["-r", "keyword", "."])
        .then(Process("wc", ["-l"]))
I see the author haven't figured out WHY people still use shell scripting.

Precisely because A|b|c is simple and clear to write. For anything more verbose (and far more saner) we have Python already, and for other stuff Go is there too

(comment deleted)
Strings are u8 arrays only? Gulp. Who needs Unicode right?