Step 06 · Globals and the print runtime

MiniLang's top-level let bindings become module globals. We scan every function for LoadGlobal/StoreGlobal to discover the names, then create one GlobalVariable each:

new llvm::GlobalVariable(
    mod, i64, /*isConstant=*/false,
    llvm::GlobalValue::ExternalLinkage,
    llvm::ConstantInt::get(i64, 0), name);

External linkage keeps the symbol visible in the .o we'd emit with llc — necessary for any future linker-level integration.

printf shim

auto* ft = llvm::FunctionType::get(i32, {i8p}, /*isVarArg=*/true);
printfFn = llvm::Function::Create(ft, ExternalLinkage, "printf", mod);
fmtStr = b.CreateGlobalString("%lld\n", ".fmt", 0, &mod);

CreateGlobalString returns a pointer (i8*/ptr under opaque pointers) directly usable as the first argument to printf. Under LLVM 20 the textual form is ptr @.fmt.

Why printf and not a hand-rolled write(2) loop? Three reasons:

  • the C runtime is always available on a JIT or system linker;
  • %lld is portable and exactly matches our i64;
  • it lets lli execute the program with no extra plumbing.

cp-14 will replace this with a proper ml_print(Value) runtime that understands strings, booleans and closures.