///|
// Built-in runtime for the SSA interpreter - the portable analogue of the
// symbol resolution layer an LLVM JIT wires up (LLVM ORC resolves external
// symbols against the host process; the interpreter resolves them against
// this table plus an optional user-provided hook). Everything is implemented
// in pure MoonBit. Text output is collected into a buffer that `run_module`
// returns alongside the result, so the library stays portable and side
// effect free.
///|
// Raised by the `exit` builtin to unwind interpretation.
priv suberror ExitSignal {
ExitSignal(Int64)
}
///|
// Heap allocation granularity (C malloc guarantees max_align_t).
const HEAP_ALIGN : UInt64 = 16UL
///|
// Resolve and execute a builtin. Raises QbeError for unknown symbols.
fn Interp::call_builtin(
self : Interp,
name : String,
args : Array[InterpValue],
) -> InterpValue raise {
match name {
"putchar" => {
let c = self.arg_of(args, 0)
self.out.write_string(c.to_int().unsafe_to_char().to_string())
VInt(c)
}
"puts" => {
let s = self.mem.load_cstr(self.arg_of(args, 0).reinterpret_as_uint64())
self.out.write_string(s)
self.out.write_string("\n")
VInt((s.length() + 1).to_int64())
}
"printf" => VInt(self.exec_printf(args))
"malloc" => {
let sz = self.arg_of(args, 0).reinterpret_as_uint64()
let base = (self.heap + HEAP_ALIGN - 1UL) / HEAP_ALIGN * HEAP_ALIGN
self.heap = base + (if sz > 0UL { sz } else { 1UL })
// the sparse memory is zero-initialized
VInt(base.reinterpret_as_int64())
}
"free" =>
// no-op: the interpreter heap is a bump allocator. This mirrors the
// common interpreter simplification and is safe for test programs.
VInt(0L)
"exit" => raise ExitSignal(self.arg_of(args, 0))
_ =>
raise @util.QbeError::CompileError(
"interpreter: unknown external function '\{name}'",
)
}
}
///|
// The raw word of argument `i`, or 0 when absent.
fn Interp::arg_of(self : Interp, args : Array[InterpValue], i : Int) -> Int64 {
ignore(self)
if i < args.length() {
args[i].bits()
} else {
0L
}
}
///|
// A minimal printf: %d %i %u %x %c %s %ld %li %lu %lx %f %lf %e %g and %%.
// Arguments are consumed from the pending call-argument list.
fn Interp::exec_printf(self : Interp, args : Array[InterpValue]) -> Int64 raise {
if args.length() == 0 {
raise @util.QbeError::CompileError("interpreter: printf without format")
}
let fmt_addr = self.arg_of(args, 0).reinterpret_as_uint64()
let fmt = self.mem.load_cstr(fmt_addr)
let sb = StringBuilder::StringBuilder()
let mut ai = 1
let mut i = 0
while i < fmt.length() {
let c = fmt[i]
if c != '%' {
sb.write_char(c.unsafe_to_char())
i = i + 1
continue
}
i = i + 1
if i >= fmt.length() {
break
}
let esc = fmt[i]
i = i + 1
// accept an optional length modifier (l / ll) before the conversion
let mut conv = esc
if esc == 'l' {
if i < fmt.length() {
conv = fmt[i]
i = i + 1
if conv == 'l' && i < fmt.length() {
conv = fmt[i]
i = i + 1
}
} else {
break
}
}
match conv {
'%' => sb.write_char('%')
'd' | 'i' => {
sb.write_string(self.arg_of(args, ai).to_string())
ai = ai + 1
}
'u' => {
sb.write_string(
self.arg_of(args, ai).reinterpret_as_uint64().to_string(),
)
ai = ai + 1
}
'x' => {
sb.write_string(
hex_string(self.arg_of(args, ai).reinterpret_as_uint64()),
)
ai = ai + 1
}
'c' => {
sb.write_char(self.arg_of(args, ai).to_int().unsafe_to_char())
ai = ai + 1
}
's' => {
sb.write_string(
self.mem.load_cstr(self.arg_of(args, ai).reinterpret_as_uint64()),
)
ai = ai + 1
}
'f' | 'e' | 'g' => {
// doubles are passed as VDouble arguments; bit patterns carried in
// VInt are also accepted
let d = match args.get(ai) {
Some(VDouble(d)) => d
Some(VFloat(f)) => f.to_double()
Some(v) => v.bits().reinterpret_as_double()
None => 0.0
}
sb.write_string(d.to_string())
ai = ai + 1
}
_ => {
// unknown conversion: emit it verbatim
sb.write_char('%')
sb.write_char(conv.unsafe_to_char())
}
}
}
self.out.write_string(sb.to_string())
sb.to_string().length().to_int64()
}
///|
// Lowercase hex without a prefix (C printf %x).
fn hex_string(v : UInt64) -> String {
if v == 0UL {
return "0"
}
let digits = "0123456789abcdef"
let sb = StringBuilder::StringBuilder()
let mut x = v
let buf : Array[Char] = []
while x > 0UL {
buf.push(char_code_at(digits, (x & 0xFUL).to_int()).unsafe_to_char())
x = x >> 4
}
for i in (buf.length() - 1)>=..0 {
sb.write_char(buf[i])
}
sb.to_string()
}
///|
fn char_code_at(s : String, i : Int) -> Int {
s[i].to_int()
}