chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
#ifndef ZERO_C_AARCH64_DIRECT_H
|
||||
#define ZERO_C_AARCH64_DIRECT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef struct ZAArch64DirectContext ZAArch64DirectContext;
|
||||
|
||||
typedef enum {
|
||||
A64_DIRECT_RUNTIME_JSON_PARSE_BYTES,
|
||||
A64_DIRECT_RUNTIME_JSON_DIAGNOSTIC,
|
||||
A64_DIRECT_RUNTIME_JSON_FIELD,
|
||||
A64_DIRECT_RUNTIME_JSON_LOOKUP_SCALAR,
|
||||
A64_DIRECT_RUNTIME_JSON_STRING_DECODE,
|
||||
A64_DIRECT_RUNTIME_JSON_STRING_FIELD,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_STRING,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_RAW,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_STRING,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_U32,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_BOOL,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_STRING,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_U32,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_BOOL,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_FIELDS,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_STRING_FIELD,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_U32_FIELD,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_BOOL_FIELD,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_STRINGS,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_U32,
|
||||
A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_BOOLS,
|
||||
A64_DIRECT_RUNTIME_STR_BUFFER_OP,
|
||||
A64_DIRECT_RUNTIME_STR_CONCAT,
|
||||
A64_DIRECT_RUNTIME_STR_REPEAT,
|
||||
A64_DIRECT_RUNTIME_STR_TRIM_OP,
|
||||
A64_DIRECT_RUNTIME_STR_PAIR_OP,
|
||||
A64_DIRECT_RUNTIME_STR_COUNT_BYTE,
|
||||
A64_DIRECT_RUNTIME_STR_WORD_COUNT_ASCII,
|
||||
A64_DIRECT_RUNTIME_CRYPTO_DIGEST,
|
||||
A64_DIRECT_RUNTIME_CRYPTO_HMAC_SHA256,
|
||||
A64_DIRECT_RUNTIME_CRYPTO_HMAC_SHA256_HEX,
|
||||
A64_DIRECT_RUNTIME_ASCII_OP,
|
||||
A64_DIRECT_RUNTIME_TEXT_OP,
|
||||
A64_DIRECT_RUNTIME_PARSE_OP,
|
||||
A64_DIRECT_RUNTIME_PARSE_USIZE,
|
||||
A64_DIRECT_RUNTIME_PARSE_I32,
|
||||
A64_DIRECT_RUNTIME_PARSE_U32,
|
||||
A64_DIRECT_RUNTIME_FMT_BOOL,
|
||||
A64_DIRECT_RUNTIME_FMT_HEX_U32,
|
||||
A64_DIRECT_RUNTIME_FMT_I32,
|
||||
A64_DIRECT_RUNTIME_FMT_U32,
|
||||
A64_DIRECT_RUNTIME_FMT_USIZE,
|
||||
A64_DIRECT_RUNTIME_TIME_OP,
|
||||
A64_DIRECT_RUNTIME_TERM_OP,
|
||||
A64_DIRECT_RUNTIME_TERM_READ_INPUT,
|
||||
A64_DIRECT_RUNTIME_MATH_OP,
|
||||
A64_DIRECT_RUNTIME_MATH_USIZE_OP,
|
||||
A64_DIRECT_RUNTIME_SEARCH_OP,
|
||||
A64_DIRECT_RUNTIME_SORT_OP,
|
||||
A64_DIRECT_RUNTIME_SORT_IS_SORTED_OP,
|
||||
A64_DIRECT_RUNTIME_HTTP_REQUEST_METHOD_NAME,
|
||||
A64_DIRECT_RUNTIME_HTTP_REQUEST_PATH,
|
||||
A64_DIRECT_RUNTIME_PROC_SPAWN_INHERIT,
|
||||
A64_DIRECT_RUNTIME_PROC_SPAWN_INHERIT_ARGS,
|
||||
A64_DIRECT_RUNTIME_PROC_CAPTURE,
|
||||
A64_DIRECT_RUNTIME_PROC_CAPTURE_ARGS,
|
||||
A64_DIRECT_RUNTIME_PROC_CAPTURE_FILES,
|
||||
A64_DIRECT_RUNTIME_PROC_CAPTURE_FILES_ARGS,
|
||||
A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD,
|
||||
A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_IN,
|
||||
A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_IN_ENV,
|
||||
A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_ARGS,
|
||||
A64_DIRECT_RUNTIME_PROC_CHILD_OP,
|
||||
A64_DIRECT_RUNTIME_PROC_CHILD_IO,
|
||||
A64_DIRECT_RUNTIME_PTY_SPAWN,
|
||||
A64_DIRECT_RUNTIME_PTY_SPAWN_IN,
|
||||
A64_DIRECT_RUNTIME_PTY_SPAWN_IN_ENV,
|
||||
A64_DIRECT_RUNTIME_PTY_SPAWN_ARGS,
|
||||
A64_DIRECT_RUNTIME_PTY_RESIZE,
|
||||
A64_DIRECT_RUNTIME_HELPER_COUNT
|
||||
} ZAArch64DirectRuntimeHelper;
|
||||
|
||||
typedef bool (*ZAArch64DirectDataPatchFn)(void *user, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag);
|
||||
typedef bool (*ZAArch64DirectCallPatchFn)(void *user, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag);
|
||||
typedef bool (*ZAArch64DirectRuntimePatchFn)(void *user, size_t patch_offset, ZAArch64DirectRuntimeHelper helper, const IrValue *value, ZDiag *diag);
|
||||
typedef bool (*ZAArch64DirectWorldWriteFn)(ZBuf *text, const IrInstr *instr, ZAArch64DirectContext *ctx, ZDiag *diag);
|
||||
|
||||
struct ZAArch64DirectContext {
|
||||
const IrProgram *program;
|
||||
size_t *function_offsets;
|
||||
size_t function_count;
|
||||
unsigned rodata_base_offset;
|
||||
void *patch_user;
|
||||
ZAArch64DirectDataPatchFn record_data_patch;
|
||||
ZAArch64DirectCallPatchFn record_call_patch;
|
||||
ZAArch64DirectRuntimePatchFn record_runtime_patch;
|
||||
ZAArch64DirectWorldWriteFn emit_world_write;
|
||||
ZDirectTrapMessages trap_messages;
|
||||
ZDirectTrapBranchList trap_branches[Z_DIRECT_TRAP_KIND_COUNT];
|
||||
ZDirectLoopFrame *loop;
|
||||
};
|
||||
|
||||
bool z_aarch64_direct_emit_function_text(ZBuf *text, const IrFunction *fun, ZAArch64DirectContext *ctx, ZDiag *diag);
|
||||
bool z_aarch64_direct_validate_function(const IrFunction *fun, ZDiag *diag);
|
||||
const IrFunction *z_aarch64_direct_find_main(const IrProgram *program, unsigned *out_index, ZDiag *diag);
|
||||
unsigned z_aarch64_direct_rodata_base_offset(const IrProgram *program);
|
||||
void z_aarch64_direct_append_trap_messages(ZBuf *rodata, unsigned base_offset, ZDirectTrapMessages *messages);
|
||||
bool z_aarch64_direct_emit_trap_stubs(ZBuf *text, ZAArch64DirectContext *ctx, ZDiag *diag);
|
||||
void z_aarch64_direct_append_rodata(ZBuf *rodata, const IrProgram *program, unsigned base_offset);
|
||||
size_t z_aarch64_direct_stack_bytes_from_ir(const IrProgram *program);
|
||||
size_t z_aarch64_direct_max_frame_bytes_from_ir(const IrProgram *program);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,548 @@
|
||||
#include "aarch64_emit.h"
|
||||
|
||||
size_t z_aarch64_align(size_t value, size_t alignment) {
|
||||
size_t remainder = alignment ? value % alignment : 0;
|
||||
return remainder == 0 ? value : value + (alignment - remainder);
|
||||
}
|
||||
|
||||
void z_aarch64_append_u8(ZBuf *buf, unsigned value) {
|
||||
zbuf_append_char(buf, (char)(value & 0xffu));
|
||||
}
|
||||
|
||||
void z_aarch64_append_u32(ZBuf *buf, uint32_t value) {
|
||||
z_aarch64_append_u8(buf, value);
|
||||
z_aarch64_append_u8(buf, value >> 8);
|
||||
z_aarch64_append_u8(buf, value >> 16);
|
||||
z_aarch64_append_u8(buf, value >> 24);
|
||||
}
|
||||
|
||||
void z_aarch64_append_u64(ZBuf *buf, uint64_t value) {
|
||||
z_aarch64_append_u32(buf, (uint32_t)value);
|
||||
z_aarch64_append_u32(buf, (uint32_t)(value >> 32));
|
||||
}
|
||||
|
||||
void z_aarch64_append_zeros(ZBuf *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) z_aarch64_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_aarch64_pad_to(ZBuf *buf, size_t offset) {
|
||||
while (buf->len < offset) z_aarch64_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_aarch64_patch_u32(ZBuf *buf, size_t offset, uint32_t value) {
|
||||
buf->data[offset + 0] = (char)(value & 0xffu);
|
||||
buf->data[offset + 1] = (char)((value >> 8) & 0xffu);
|
||||
buf->data[offset + 2] = (char)((value >> 16) & 0xffu);
|
||||
buf->data[offset + 3] = (char)((value >> 24) & 0xffu);
|
||||
}
|
||||
|
||||
void z_aarch64_patch_u64(ZBuf *buf, size_t offset, uint64_t value) {
|
||||
z_aarch64_patch_u32(buf, offset, (uint32_t)value);
|
||||
z_aarch64_patch_u32(buf, offset + 4, (uint32_t)(value >> 32));
|
||||
}
|
||||
|
||||
static uint32_t z_aarch64_read_u32(const ZBuf *buf, size_t offset) {
|
||||
const unsigned char *data = (const unsigned char *)&buf->data[offset];
|
||||
return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_ret(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xd65f03c0u);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_blr_x(ZBuf *text, unsigned reg) {
|
||||
z_aarch64_append_u32(text, 0xd63f0000u | ((reg & 31u) << 5));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_nop(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xd503201fu);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_brk(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xd4200000u);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_svc(ZBuf *text, unsigned imm16) {
|
||||
z_aarch64_append_u32(text, 0xd4000001u | ((imm16 & 0xffffu) << 5));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_literal_return(ZBuf *text, uint32_t literal) {
|
||||
z_aarch64_emit_movz_w(text, 0, literal);
|
||||
z_aarch64_emit_ret(text);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_movz_w(ZBuf *text, unsigned reg, uint32_t literal) {
|
||||
z_aarch64_append_u32(text, 0x52800000u | ((literal & 0xffffu) << 5) | (reg & 31u));
|
||||
if (literal > 0xffffu) {
|
||||
z_aarch64_append_u32(text, 0x72a00000u | (((literal >> 16) & 0xffffu) << 5) | (reg & 31u));
|
||||
}
|
||||
}
|
||||
|
||||
void z_aarch64_emit_movz_x(ZBuf *text, unsigned reg, uint64_t literal) {
|
||||
z_aarch64_append_u32(text, 0xd2800000u | ((uint32_t)(literal & 0xffffu) << 5) | (reg & 31u));
|
||||
if (literal > 0xffffu) {
|
||||
z_aarch64_append_u32(text, 0xf2a00000u | ((uint32_t)((literal >> 16) & 0xffffu) << 5) | (reg & 31u));
|
||||
}
|
||||
if (literal > 0xffffffffu) {
|
||||
z_aarch64_append_u32(text, 0xf2c00000u | ((uint32_t)((literal >> 32) & 0xffffu) << 5) | (reg & 31u));
|
||||
}
|
||||
if (literal > 0xffffffffffffu) {
|
||||
z_aarch64_append_u32(text, 0xf2e00000u | ((uint32_t)((literal >> 48) & 0xffffu) << 5) | (reg & 31u));
|
||||
}
|
||||
}
|
||||
|
||||
void z_aarch64_emit_mov_w(ZBuf *text, unsigned dst, unsigned src) {
|
||||
z_aarch64_append_u32(text, 0x2a0003e0u | ((src & 31u) << 16) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_mov_x(ZBuf *text, unsigned dst, unsigned src) {
|
||||
z_aarch64_append_u32(text, 0xaa0003e0u | ((src & 31u) << 16) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_uxtb_w(ZBuf *text, unsigned dst, unsigned src) {
|
||||
z_aarch64_append_u32(text, 0x53000000u | (7u << 10) | ((src & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_uxth_w(ZBuf *text, unsigned dst, unsigned src) {
|
||||
z_aarch64_append_u32(text, 0x53000000u | (15u << 10) | ((src & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_sxtw_x(ZBuf *text, unsigned dst, unsigned src) {
|
||||
z_aarch64_append_u32(text, 0x93400000u | (31u << 10) | ((src & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_mov_x29_sp(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0x910003fdu);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_stp_x20_x21_sp_pre16(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xa9bf57f4u);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_stp_x29_x30_sp_pre16(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xa9bf7bfdu);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_ldp_x20_x21_sp_post16(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xa8c157f4u);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_ldp_x29_x30_sp_post16(ZBuf *text) {
|
||||
z_aarch64_append_u32(text, 0xa8c17bfdu);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_add_sp_imm(ZBuf *text, unsigned imm) {
|
||||
while (imm > 0) {
|
||||
unsigned chunk = imm > 4080u ? 4080u : imm;
|
||||
z_aarch64_append_u32(text, 0x910003ffu | ((chunk & 0xfffu) << 10));
|
||||
imm -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
void z_aarch64_emit_sub_sp_imm(ZBuf *text, unsigned imm) {
|
||||
bool probe = imm > 4080u;
|
||||
while (imm > 0) {
|
||||
unsigned chunk = imm > 4080u ? 4080u : imm;
|
||||
z_aarch64_append_u32(text, 0xd10003ffu | ((chunk & 0xfffu) << 10));
|
||||
imm -= chunk;
|
||||
if (probe) z_aarch64_append_u32(text, 0xf90003ffu);
|
||||
}
|
||||
}
|
||||
|
||||
static void z_aarch64_emit_add_x_base_imm(ZBuf *text, unsigned dst, unsigned base, unsigned imm) {
|
||||
if (imm > 8190u && (dst & 31u) != (base & 31u) && (dst & 31u) != 31u) {
|
||||
z_aarch64_emit_movz_x(text, dst, imm);
|
||||
z_aarch64_append_u32(text, 0x8b206000u | ((dst & 31u) << 16) | ((base & 31u) << 5) | (dst & 31u));
|
||||
return;
|
||||
}
|
||||
do {
|
||||
unsigned chunk = imm > 4095u ? 4095u : imm;
|
||||
z_aarch64_append_u32(text, 0x91000000u | ((chunk & 0xfffu) << 10) | ((base & 31u) << 5) | (dst & 31u));
|
||||
imm -= chunk;
|
||||
base = dst;
|
||||
} while (imm > 0);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_add_x_sp_imm(ZBuf *text, unsigned dst, unsigned imm) { z_aarch64_emit_add_x_base_imm(text, dst, 31u, imm); }
|
||||
void z_aarch64_emit_add_x_imm(ZBuf *text, unsigned dst, unsigned src, unsigned imm) { z_aarch64_emit_add_x_base_imm(text, dst, src, imm); }
|
||||
|
||||
void z_aarch64_emit_add_w_imm(ZBuf *text, unsigned dst, unsigned src, unsigned imm) {
|
||||
do {
|
||||
unsigned chunk = imm > 4095u ? 4095u : imm;
|
||||
z_aarch64_append_u32(text, 0x11000000u | ((chunk & 0xfffu) << 10) | ((src & 31u) << 5) | (dst & 31u));
|
||||
imm -= chunk;
|
||||
src = dst;
|
||||
} while (imm > 0);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_sub_w_imm(ZBuf *text, unsigned dst, unsigned src, unsigned imm) {
|
||||
do {
|
||||
unsigned chunk = imm > 4095u ? 4095u : imm;
|
||||
z_aarch64_append_u32(text, 0x51000000u | ((chunk & 0xfffu) << 10) | ((src & 31u) << 5) | (dst & 31u));
|
||||
imm -= chunk;
|
||||
src = dst;
|
||||
} while (imm > 0);
|
||||
}
|
||||
|
||||
static void z_aarch64_emit_mem_imm_or_reg(ZBuf *text, uint32_t imm_opcode, uint32_t reg_opcode, unsigned scale, unsigned reg, unsigned base, unsigned offset) {
|
||||
if (offset % scale == 0 && offset / scale <= 4095u) {
|
||||
z_aarch64_append_u32(text, imm_opcode | ((offset / scale) << 10) | ((base & 31u) << 5) | (reg & 31u));
|
||||
return;
|
||||
}
|
||||
unsigned scratch = (reg & 31u) == 17u ? 16u : 17u;
|
||||
z_aarch64_emit_movz_x(text, scratch, offset);
|
||||
z_aarch64_append_u32(text, reg_opcode | (scratch << 16) | (3u << 13) | ((base & 31u) << 5) | (reg & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_w_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0xb9400000u, 0xb8600800u, 4u, dst, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_x_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0xf9400000u, 0xf8600800u, 8u, dst, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_b_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0x39400000u, 0x38600800u, 1u, dst, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_h_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0x79400000u, 0x78600800u, 2u, dst, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_w_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0xb9000000u, 0xb8200800u, 4u, src, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_x_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0xf9000000u, 0xf8200800u, 8u, src, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_b_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0x39000000u, 0x38200800u, 1u, src, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_h_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset) {
|
||||
z_aarch64_emit_mem_imm_or_reg(text, 0x79000000u, 0x78200800u, 2u, src, base, byte_offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_w_sp(ZBuf *text, unsigned reg, unsigned offset) {
|
||||
z_aarch64_emit_load_w_imm(text, reg, 31u, offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_x_sp(ZBuf *text, unsigned reg, unsigned offset) {
|
||||
z_aarch64_emit_load_x_imm(text, reg, 31u, offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_load_b_sp(ZBuf *text, unsigned reg, unsigned offset) {
|
||||
z_aarch64_emit_load_b_imm(text, reg, 31u, offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_w_sp(ZBuf *text, unsigned reg, unsigned offset) {
|
||||
z_aarch64_emit_store_w_imm(text, reg, 31u, offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_x_sp(ZBuf *text, unsigned reg, unsigned offset) {
|
||||
z_aarch64_emit_store_x_imm(text, reg, 31u, offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_store_b_sp(ZBuf *text, unsigned reg, unsigned offset) {
|
||||
z_aarch64_emit_store_b_imm(text, reg, 31u, offset);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_add_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x0b000000u | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_add_x_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x8b000000u | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_add_x_reg_lsl(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs, unsigned shift) {
|
||||
z_aarch64_append_u32(text, 0x8b000000u | ((rhs & 31u) << 16) | ((shift & 0x3fu) << 10) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_and_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x0a000000u | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_eor_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x4a000000u | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_lsr_x_imm(ZBuf *text, unsigned dst, unsigned src, unsigned shift) {
|
||||
z_aarch64_append_u32(text, 0xd340fc00u | ((shift & 0x3fu) << 16) | ((src & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_lsr_w_imm(ZBuf *text, unsigned dst, unsigned src, unsigned shift) {
|
||||
z_aarch64_append_u32(text, 0x53007c00u | ((shift & 0x1fu) << 16) | ((src & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_neg_w(ZBuf *text, unsigned dst, unsigned src) {
|
||||
z_aarch64_append_u32(text, 0x4b0003e0u | ((src & 31u) << 16) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_sub_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x4b000000u | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_sub_x_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0xcb000000u | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_mul_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x1b000000u | ((rhs & 31u) << 16) | (31u << 10) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_mul_x_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x9b000000u | ((rhs & 31u) << 16) | (31u << 10) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_div_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs, bool is_unsigned, bool wide) {
|
||||
uint32_t sf = wide ? 0x80000000u : 0;
|
||||
z_aarch64_append_u32(text, sf | (is_unsigned ? 0x1ac00800u : 0x1ac00c00u) | ((rhs & 31u) << 16) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_msub_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs, unsigned acc, bool wide) {
|
||||
uint32_t sf = wide ? 0x80000000u : 0;
|
||||
z_aarch64_append_u32(text, sf | 0x1b008000u | ((rhs & 31u) << 16) | ((acc & 31u) << 10) | ((lhs & 31u) << 5) | (dst & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_cmp_w(ZBuf *text, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0x6b00001fu | ((rhs & 31u) << 16) | ((lhs & 31u) << 5));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_cmp_x(ZBuf *text, unsigned lhs, unsigned rhs) {
|
||||
z_aarch64_append_u32(text, 0xeb00001fu | ((rhs & 31u) << 16) | ((lhs & 31u) << 5));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_adrp_add_placeholder(ZBuf *text, unsigned reg) {
|
||||
z_aarch64_append_u32(text, 0x90000000u | (reg & 31u));
|
||||
z_aarch64_append_u32(text, 0x91000000u | ((reg & 31u) << 5) | (reg & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_ldr_x_literal8(ZBuf *text, unsigned reg) {
|
||||
z_aarch64_append_u32(text, 0x58000000u | (2u << 5) | (reg & 31u));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_b_offset_words(ZBuf *text, int32_t words) {
|
||||
z_aarch64_append_u32(text, 0x14000000u | ((uint32_t)words & 0x03ffffffu));
|
||||
}
|
||||
|
||||
void z_aarch64_emit_byte_copy_min_loop(ZBuf *text, unsigned result_reg) {
|
||||
z_aarch64_emit_mov_w(text, 14, 13);
|
||||
z_aarch64_emit_cmp_w(text, 13, 10);
|
||||
size_t keep_dst_len = z_aarch64_emit_b_cond_placeholder(text, 9);
|
||||
z_aarch64_emit_mov_w(text, 14, 10);
|
||||
z_aarch64_patch_cond19(text, keep_dst_len, text->len);
|
||||
z_aarch64_emit_movz_w(text, 9, 0);
|
||||
size_t loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 9, 14);
|
||||
size_t done = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 15, 11, 9);
|
||||
z_aarch64_emit_load_b_imm(text, 15, 15, 0);
|
||||
z_aarch64_emit_add_x_reg(text, 13, 12, 9);
|
||||
z_aarch64_emit_store_b_imm(text, 15, 13, 0);
|
||||
z_aarch64_emit_add_w_imm(text, 9, 9, 1);
|
||||
size_t back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, back, loop);
|
||||
z_aarch64_patch_cond19(text, done, text->len);
|
||||
z_aarch64_emit_mov_w(text, result_reg, 14);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_byte_fill_loop(ZBuf *text, unsigned result_reg) {
|
||||
z_aarch64_emit_movz_w(text, 9, 0);
|
||||
size_t loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 9, 10);
|
||||
size_t done = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 12, 11, 9);
|
||||
z_aarch64_emit_store_b_imm(text, 8, 12, 0);
|
||||
z_aarch64_emit_add_w_imm(text, 9, 9, 1);
|
||||
size_t back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, back, loop);
|
||||
z_aarch64_patch_cond19(text, done, text->len);
|
||||
z_aarch64_emit_mov_w(text, result_reg, 10);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_byte_eq_loop(ZBuf *text, unsigned result_reg) {
|
||||
z_aarch64_emit_movz_w(text, 9, 0);
|
||||
size_t loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 9, 10);
|
||||
size_t equal = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 13, 11, 9);
|
||||
z_aarch64_emit_load_b_imm(text, 13, 13, 0);
|
||||
z_aarch64_emit_add_x_reg(text, 14, 12, 9);
|
||||
z_aarch64_emit_load_b_imm(text, 14, 14, 0);
|
||||
z_aarch64_emit_cmp_w(text, 13, 14);
|
||||
size_t mismatch = z_aarch64_emit_b_cond_placeholder(text, 1);
|
||||
z_aarch64_emit_add_w_imm(text, 9, 9, 1);
|
||||
size_t back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, back, loop);
|
||||
z_aarch64_patch_cond19(text, mismatch, text->len);
|
||||
z_aarch64_emit_movz_w(text, result_reg, 0);
|
||||
size_t after_false = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_cond19(text, equal, text->len);
|
||||
z_aarch64_emit_movz_w(text, result_reg, 1);
|
||||
z_aarch64_patch_branch26(text, after_false, text->len);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_vec_lookup_loop(ZBuf *text, unsigned result_reg, bool contains) {
|
||||
// Inputs: x10=base, w9=len, w12=value. Output: result_reg=index/len or Bool.
|
||||
z_aarch64_emit_movz_w(text, 8, 0);
|
||||
size_t loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 8, 9);
|
||||
size_t absent = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 11, 10, 8);
|
||||
z_aarch64_emit_load_b_imm(text, 13, 11, 0);
|
||||
z_aarch64_emit_cmp_w(text, 13, 12);
|
||||
size_t found = z_aarch64_emit_b_cond_placeholder(text, 0);
|
||||
z_aarch64_emit_add_w_imm(text, 8, 8, 1);
|
||||
size_t back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, back, loop);
|
||||
z_aarch64_patch_cond19(text, absent, text->len);
|
||||
if (contains) z_aarch64_emit_movz_w(text, result_reg, 0);
|
||||
else if (result_reg != 9) z_aarch64_emit_mov_w(text, result_reg, 9);
|
||||
size_t end = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_cond19(text, found, text->len);
|
||||
if (contains) z_aarch64_emit_movz_w(text, result_reg, 1);
|
||||
else if (result_reg != 8) z_aarch64_emit_mov_w(text, result_reg, 8);
|
||||
z_aarch64_patch_branch26(text, end, text->len);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_vec_insert_unique_loop(ZBuf *text, unsigned result_reg) {
|
||||
// Inputs: x10=base, w9=len, w12=value, w13=capacity. Output: result_reg=Bool, w9=final len.
|
||||
z_aarch64_emit_movz_w(text, 8, 0);
|
||||
size_t loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 8, 9);
|
||||
size_t absent = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 11, 10, 8);
|
||||
z_aarch64_emit_load_b_imm(text, 14, 11, 0);
|
||||
z_aarch64_emit_cmp_w(text, 14, 12);
|
||||
size_t duplicate = z_aarch64_emit_b_cond_placeholder(text, 0);
|
||||
z_aarch64_emit_add_w_imm(text, 8, 8, 1);
|
||||
size_t back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, back, loop);
|
||||
z_aarch64_patch_cond19(text, absent, text->len);
|
||||
z_aarch64_emit_cmp_w(text, 9, 13);
|
||||
size_t has_capacity = z_aarch64_emit_b_cond_placeholder(text, 3);
|
||||
z_aarch64_patch_cond19(text, duplicate, text->len);
|
||||
z_aarch64_emit_movz_w(text, result_reg, 0);
|
||||
size_t end = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_cond19(text, has_capacity, text->len);
|
||||
z_aarch64_emit_add_x_reg(text, 11, 10, 9);
|
||||
z_aarch64_emit_store_b_imm(text, 12, 11, 0);
|
||||
z_aarch64_emit_add_w_imm(text, 9, 9, 1);
|
||||
z_aarch64_emit_movz_w(text, result_reg, 1);
|
||||
z_aarch64_patch_branch26(text, end, text->len);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_vec_remove_value_loop(ZBuf *text, unsigned result_reg) {
|
||||
// Inputs: x10=base, w9=len, w12=value. Output: result_reg=Bool, w9=final len.
|
||||
z_aarch64_emit_movz_w(text, 8, 0);
|
||||
size_t loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 8, 9);
|
||||
size_t absent = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 11, 10, 8);
|
||||
z_aarch64_emit_load_b_imm(text, 13, 11, 0);
|
||||
z_aarch64_emit_cmp_w(text, 13, 12);
|
||||
size_t found = z_aarch64_emit_b_cond_placeholder(text, 0);
|
||||
z_aarch64_emit_add_w_imm(text, 8, 8, 1);
|
||||
size_t back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, back, loop);
|
||||
z_aarch64_patch_cond19(text, absent, text->len);
|
||||
z_aarch64_emit_movz_w(text, result_reg, 0);
|
||||
size_t end = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_cond19(text, found, text->len);
|
||||
z_aarch64_emit_sub_w_imm(text, 9, 9, 1);
|
||||
z_aarch64_emit_add_x_reg(text, 11, 10, 9);
|
||||
z_aarch64_emit_load_b_imm(text, 13, 11, 0);
|
||||
z_aarch64_emit_add_x_reg(text, 11, 10, 8);
|
||||
z_aarch64_emit_store_b_imm(text, 13, 11, 0);
|
||||
z_aarch64_emit_movz_w(text, result_reg, 1);
|
||||
z_aarch64_patch_branch26(text, end, text->len);
|
||||
}
|
||||
|
||||
void z_aarch64_emit_crc32_bytes_loop(ZBuf *text, unsigned result_reg) {
|
||||
z_aarch64_emit_movz_w(text, 8, 0xffffffffu);
|
||||
z_aarch64_emit_movz_w(text, 9, 0);
|
||||
size_t byte_loop = text->len;
|
||||
z_aarch64_emit_cmp_w(text, 9, 10);
|
||||
size_t done = z_aarch64_emit_b_cond_placeholder(text, 2);
|
||||
z_aarch64_emit_add_x_reg(text, 12, 11, 9);
|
||||
z_aarch64_emit_load_b_imm(text, 12, 12, 0);
|
||||
z_aarch64_emit_eor_w_reg(text, 8, 8, 12);
|
||||
z_aarch64_emit_movz_w(text, 13, 8);
|
||||
size_t bit_loop = text->len;
|
||||
z_aarch64_emit_mov_w(text, 14, 8);
|
||||
z_aarch64_emit_movz_w(text, 15, 1);
|
||||
z_aarch64_emit_and_w_reg(text, 14, 14, 15);
|
||||
z_aarch64_emit_neg_w(text, 14, 14);
|
||||
z_aarch64_emit_movz_w(text, 15, 0xedb88320u);
|
||||
z_aarch64_emit_and_w_reg(text, 14, 14, 15);
|
||||
z_aarch64_emit_lsr_w_imm(text, 8, 8, 1);
|
||||
z_aarch64_emit_eor_w_reg(text, 8, 8, 14);
|
||||
z_aarch64_emit_sub_w_imm(text, 13, 13, 1);
|
||||
size_t byte_next = z_aarch64_emit_cbz_w_placeholder(text, 13);
|
||||
size_t bit_back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, bit_back, bit_loop);
|
||||
z_aarch64_patch_cond19(text, byte_next, text->len);
|
||||
z_aarch64_emit_add_w_imm(text, 9, 9, 1);
|
||||
size_t byte_back = z_aarch64_emit_b_placeholder(text);
|
||||
z_aarch64_patch_branch26(text, byte_back, byte_loop);
|
||||
z_aarch64_patch_cond19(text, done, text->len);
|
||||
z_aarch64_emit_movz_w(text, 14, 0xffffffffu);
|
||||
z_aarch64_emit_eor_w_reg(text, 8, 8, 14);
|
||||
if (result_reg != 8) z_aarch64_emit_mov_w(text, result_reg, 8);
|
||||
}
|
||||
|
||||
size_t z_aarch64_emit_bl_placeholder(ZBuf *text) {
|
||||
size_t patch = text->len;
|
||||
z_aarch64_append_u32(text, 0x94000000u);
|
||||
return patch;
|
||||
}
|
||||
|
||||
size_t z_aarch64_emit_b_placeholder(ZBuf *text) {
|
||||
size_t patch = text->len;
|
||||
z_aarch64_append_u32(text, 0x14000000u);
|
||||
return patch;
|
||||
}
|
||||
|
||||
size_t z_aarch64_emit_b_cond_placeholder(ZBuf *text, unsigned cond) {
|
||||
size_t patch = text->len;
|
||||
z_aarch64_append_u32(text, 0x54000000u | (cond & 15u));
|
||||
return patch;
|
||||
}
|
||||
|
||||
size_t z_aarch64_emit_cbz_w_placeholder(ZBuf *text, unsigned reg) {
|
||||
size_t patch = text->len;
|
||||
z_aarch64_append_u32(text, 0x34000000u | (reg & 31u));
|
||||
return patch;
|
||||
}
|
||||
|
||||
void z_aarch64_patch_branch26(ZBuf *text, size_t patch_offset, size_t target_offset) {
|
||||
uint32_t old_instr = z_aarch64_read_u32(text, patch_offset);
|
||||
int64_t delta = (int64_t)target_offset - (int64_t)patch_offset;
|
||||
int64_t words = delta / 4;
|
||||
z_aarch64_patch_u32(text, patch_offset, (old_instr & 0xfc000000u) | ((uint32_t)words & 0x03ffffffu));
|
||||
}
|
||||
|
||||
void z_aarch64_patch_cond19(ZBuf *text, size_t patch_offset, size_t target_offset) {
|
||||
uint32_t instr = z_aarch64_read_u32(text, patch_offset);
|
||||
int64_t delta = (int64_t)target_offset - (int64_t)patch_offset;
|
||||
int64_t words = delta / 4;
|
||||
z_aarch64_patch_u32(text, patch_offset, (instr & 0xff00001fu) | (((uint32_t)words & 0x7ffffu) << 5));
|
||||
}
|
||||
|
||||
void z_aarch64_patch_adrp_add(ZBuf *text, size_t patch_offset, uint64_t instr_addr, uint64_t target_addr) {
|
||||
uint32_t adrp = z_aarch64_read_u32(text, patch_offset);
|
||||
unsigned reg = adrp & 31u;
|
||||
int64_t instr_page = (int64_t)(instr_addr & ~0xfffull);
|
||||
int64_t target_page = (int64_t)(target_addr & ~0xfffull);
|
||||
int64_t pages = (target_page - instr_page) / 4096;
|
||||
uint32_t immlo = (uint32_t)pages & 0x3u;
|
||||
uint32_t immhi = ((uint32_t)pages >> 2) & 0x7ffffu;
|
||||
uint32_t patched_adrp = 0x90000000u | (immlo << 29) | (immhi << 5) | reg;
|
||||
uint32_t pageoff = (uint32_t)(target_addr & 0xfffu);
|
||||
uint32_t patched_add = 0x91000000u | ((pageoff & 0xfffu) << 10) | (reg << 5) | reg;
|
||||
z_aarch64_patch_u64(text, patch_offset, ((uint64_t)patched_add << 32) | patched_adrp);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#ifndef ZERO_C_AARCH64_EMIT_H
|
||||
#define ZERO_C_AARCH64_EMIT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
size_t z_aarch64_align(size_t value, size_t alignment);
|
||||
void z_aarch64_append_u8(ZBuf *buf, unsigned value);
|
||||
void z_aarch64_append_u32(ZBuf *buf, uint32_t value);
|
||||
void z_aarch64_append_u64(ZBuf *buf, uint64_t value);
|
||||
void z_aarch64_append_zeros(ZBuf *buf, size_t len);
|
||||
void z_aarch64_pad_to(ZBuf *buf, size_t offset);
|
||||
void z_aarch64_patch_u32(ZBuf *buf, size_t offset, uint32_t value);
|
||||
void z_aarch64_patch_u64(ZBuf *buf, size_t offset, uint64_t value);
|
||||
|
||||
void z_aarch64_emit_ret(ZBuf *text);
|
||||
void z_aarch64_emit_blr_x(ZBuf *text, unsigned reg);
|
||||
void z_aarch64_emit_nop(ZBuf *text);
|
||||
void z_aarch64_emit_brk(ZBuf *text);
|
||||
void z_aarch64_emit_svc(ZBuf *text, unsigned imm16);
|
||||
void z_aarch64_emit_literal_return(ZBuf *text, uint32_t literal);
|
||||
void z_aarch64_emit_movz_w(ZBuf *text, unsigned reg, uint32_t literal);
|
||||
void z_aarch64_emit_movz_x(ZBuf *text, unsigned reg, uint64_t literal);
|
||||
void z_aarch64_emit_mov_w(ZBuf *text, unsigned dst, unsigned src);
|
||||
void z_aarch64_emit_mov_x(ZBuf *text, unsigned dst, unsigned src);
|
||||
void z_aarch64_emit_uxtb_w(ZBuf *text, unsigned dst, unsigned src);
|
||||
void z_aarch64_emit_uxth_w(ZBuf *text, unsigned dst, unsigned src);
|
||||
void z_aarch64_emit_sxtw_x(ZBuf *text, unsigned dst, unsigned src);
|
||||
void z_aarch64_emit_mov_x29_sp(ZBuf *text);
|
||||
void z_aarch64_emit_stp_x20_x21_sp_pre16(ZBuf *text);
|
||||
void z_aarch64_emit_stp_x29_x30_sp_pre16(ZBuf *text);
|
||||
void z_aarch64_emit_ldp_x20_x21_sp_post16(ZBuf *text);
|
||||
void z_aarch64_emit_ldp_x29_x30_sp_post16(ZBuf *text);
|
||||
void z_aarch64_emit_add_sp_imm(ZBuf *text, unsigned imm);
|
||||
void z_aarch64_emit_sub_sp_imm(ZBuf *text, unsigned imm);
|
||||
void z_aarch64_emit_add_x_sp_imm(ZBuf *text, unsigned dst, unsigned imm);
|
||||
void z_aarch64_emit_add_x_imm(ZBuf *text, unsigned dst, unsigned src, unsigned imm);
|
||||
void z_aarch64_emit_add_w_imm(ZBuf *text, unsigned dst, unsigned src, unsigned imm);
|
||||
void z_aarch64_emit_sub_w_imm(ZBuf *text, unsigned dst, unsigned src, unsigned imm);
|
||||
void z_aarch64_emit_load_w_sp(ZBuf *text, unsigned reg, unsigned offset);
|
||||
void z_aarch64_emit_load_x_sp(ZBuf *text, unsigned reg, unsigned offset);
|
||||
void z_aarch64_emit_load_b_sp(ZBuf *text, unsigned reg, unsigned offset);
|
||||
void z_aarch64_emit_store_w_sp(ZBuf *text, unsigned reg, unsigned offset);
|
||||
void z_aarch64_emit_store_x_sp(ZBuf *text, unsigned reg, unsigned offset);
|
||||
void z_aarch64_emit_store_b_sp(ZBuf *text, unsigned reg, unsigned offset);
|
||||
void z_aarch64_emit_load_w_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_load_x_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_load_b_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_load_h_imm(ZBuf *text, unsigned dst, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_store_w_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_store_x_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_store_b_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_store_h_imm(ZBuf *text, unsigned src, unsigned base, unsigned byte_offset);
|
||||
void z_aarch64_emit_add_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_add_x_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_add_x_reg_lsl(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs, unsigned shift);
|
||||
void z_aarch64_emit_and_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_eor_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_lsr_x_imm(ZBuf *text, unsigned dst, unsigned src, unsigned shift);
|
||||
void z_aarch64_emit_lsr_w_imm(ZBuf *text, unsigned dst, unsigned src, unsigned shift);
|
||||
void z_aarch64_emit_neg_w(ZBuf *text, unsigned dst, unsigned src);
|
||||
void z_aarch64_emit_sub_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_sub_x_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_mul_w_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_mul_x_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_div_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs, bool is_unsigned, bool wide);
|
||||
void z_aarch64_emit_msub_reg(ZBuf *text, unsigned dst, unsigned lhs, unsigned rhs, unsigned acc, bool wide);
|
||||
void z_aarch64_emit_cmp_w(ZBuf *text, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_cmp_x(ZBuf *text, unsigned lhs, unsigned rhs);
|
||||
void z_aarch64_emit_adrp_add_placeholder(ZBuf *text, unsigned reg);
|
||||
void z_aarch64_emit_ldr_x_literal8(ZBuf *text, unsigned reg);
|
||||
void z_aarch64_emit_b_offset_words(ZBuf *text, int32_t words);
|
||||
void z_aarch64_emit_byte_copy_min_loop(ZBuf *text, unsigned result_reg);
|
||||
void z_aarch64_emit_byte_fill_loop(ZBuf *text, unsigned result_reg);
|
||||
void z_aarch64_emit_byte_eq_loop(ZBuf *text, unsigned result_reg);
|
||||
void z_aarch64_emit_vec_lookup_loop(ZBuf *text, unsigned result_reg, bool contains);
|
||||
void z_aarch64_emit_vec_insert_unique_loop(ZBuf *text, unsigned result_reg);
|
||||
void z_aarch64_emit_vec_remove_value_loop(ZBuf *text, unsigned result_reg);
|
||||
void z_aarch64_emit_crc32_bytes_loop(ZBuf *text, unsigned result_reg);
|
||||
|
||||
size_t z_aarch64_emit_bl_placeholder(ZBuf *text);
|
||||
size_t z_aarch64_emit_b_placeholder(ZBuf *text);
|
||||
size_t z_aarch64_emit_b_cond_placeholder(ZBuf *text, unsigned cond);
|
||||
size_t z_aarch64_emit_cbz_w_placeholder(ZBuf *text, unsigned reg);
|
||||
void z_aarch64_patch_branch26(ZBuf *text, size_t patch_offset, size_t target_offset);
|
||||
void z_aarch64_patch_cond19(ZBuf *text, size_t patch_offset, size_t target_offset);
|
||||
void z_aarch64_patch_adrp_add(ZBuf *text, size_t patch_offset, uint64_t instr_addr, uint64_t target_addr);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "abi_report.h"
|
||||
|
||||
#include "program_graph_query_internal.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static void abi_append_json_string(ZBuf *buf, const char *s) {
|
||||
zbuf_append_char(buf, '"');
|
||||
const unsigned char *p = (const unsigned char *)(s ? s : "");
|
||||
while (*p) {
|
||||
unsigned char ch = *p++;
|
||||
switch (ch) {
|
||||
case '"': zbuf_append(buf, "\\\""); break;
|
||||
case '\\': zbuf_append(buf, "\\\\"); break;
|
||||
case '\n': zbuf_append(buf, "\\n"); break;
|
||||
case '\r': zbuf_append(buf, "\\r"); break;
|
||||
case '\t': zbuf_append(buf, "\\t"); break;
|
||||
default:
|
||||
if (ch < 0x20) zbuf_appendf(buf, "\\u%04x", ch);
|
||||
else zbuf_append_char(buf, (char)ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
zbuf_append_char(buf, '"');
|
||||
}
|
||||
|
||||
static int abi_report_pointer_size(const ZTargetInfo *target) {
|
||||
(void)target;
|
||||
return 8;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *zero_type;
|
||||
const char *c_type;
|
||||
int fixed_size;
|
||||
bool pointer_sized;
|
||||
bool include_in_layout;
|
||||
} ZAbiPrimitive;
|
||||
|
||||
static const ZAbiPrimitive abi_primitives[] = {
|
||||
{"Void", "void", 0, false, false},
|
||||
{"Bool", "bool", 1, false, true},
|
||||
{"u8", "uint8_t", 1, false, true},
|
||||
{"i8", "int8_t", 1, false, true},
|
||||
{"char", "void *", 1, false, false},
|
||||
{"u16", "uint16_t", 2, false, true},
|
||||
{"i16", "int16_t", 2, false, true},
|
||||
{"u32", "uint32_t", 4, false, true},
|
||||
{"i32", "int32_t", 4, false, true},
|
||||
{"u64", "uint64_t", 8, false, true},
|
||||
{"i64", "int64_t", 8, false, true},
|
||||
{"usize", "uintptr_t", 0, true, true},
|
||||
{"isize", "intptr_t", 0, true, true},
|
||||
{"f32", "float", 4, false, true},
|
||||
{"f64", "double", 8, false, true},
|
||||
};
|
||||
|
||||
static const size_t abi_primitives_len = sizeof(abi_primitives) / sizeof(abi_primitives[0]);
|
||||
|
||||
static const ZAbiPrimitive *abi_report_primitive_for_type(const char *type) {
|
||||
if (!type) return NULL;
|
||||
for (size_t i = 0; i < abi_primitives_len; i++) {
|
||||
if (strcmp(abi_primitives[i].zero_type, type) == 0) return &abi_primitives[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool abi_report_type_has_prefix(const char *type, const char *prefix) {
|
||||
if (!type || !prefix) return false;
|
||||
size_t prefix_len = strlen(prefix);
|
||||
return strncmp(type, prefix, prefix_len) == 0;
|
||||
}
|
||||
|
||||
static bool abi_report_type_is_ref_like(const char *type) {
|
||||
return abi_report_type_has_prefix(type, "ref<") || abi_report_type_has_prefix(type, "mutref<");
|
||||
}
|
||||
|
||||
static int abi_report_type_size(const char *type, const ZTargetInfo *target) {
|
||||
if (!type) return 0;
|
||||
const ZAbiPrimitive *primitive = abi_report_primitive_for_type(type);
|
||||
if (primitive) {
|
||||
return primitive->pointer_sized ? abi_report_pointer_size(target) : primitive->fixed_size;
|
||||
}
|
||||
if (abi_report_type_is_ref_like(type)) return abi_report_pointer_size(target);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int abi_report_type_align(const char *type, const ZTargetInfo *target) {
|
||||
int size = abi_report_type_size(type, target);
|
||||
int pointer = abi_report_pointer_size(target);
|
||||
if (size > pointer) return pointer;
|
||||
return size > 0 ? size : 1;
|
||||
}
|
||||
|
||||
static int abi_report_align_to_int(int value, int align) {
|
||||
if (align <= 1) return value;
|
||||
int remainder = value % align;
|
||||
return remainder == 0 ? value : value + (align - remainder);
|
||||
}
|
||||
|
||||
static const char *abi_report_c_type_name(const char *zero_type) {
|
||||
if (!zero_type) return "void";
|
||||
const ZAbiPrimitive *primitive = abi_report_primitive_for_type(zero_type);
|
||||
if (primitive) return primitive->c_type;
|
||||
return "void *";
|
||||
}
|
||||
|
||||
static void append_abi_primitives_json(ZBuf *buf, const ZTargetInfo *target) {
|
||||
zbuf_append(buf, "[");
|
||||
bool wrote = false;
|
||||
for (size_t i = 0; i < abi_primitives_len; i++) {
|
||||
const ZAbiPrimitive *primitive = &abi_primitives[i];
|
||||
if (!primitive->include_in_layout) continue;
|
||||
if (wrote) zbuf_append(buf, ",");
|
||||
wrote = true;
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, primitive->zero_type);
|
||||
zbuf_appendf(buf, ",\"size\":%d,\"align\":%d}", abi_report_type_size(primitive->zero_type, target), abi_report_type_align(primitive->zero_type, target));
|
||||
}
|
||||
zbuf_append(buf, "]");
|
||||
}
|
||||
|
||||
static void append_abi_shapes_json(ZBuf *buf, const Program *program, const ZTargetInfo *target) {
|
||||
zbuf_append(buf, "[");
|
||||
bool wrote = false;
|
||||
for (size_t i = 0; program && i < program->shapes.len; i++) {
|
||||
const Shape *shape = &program->shapes.items[i];
|
||||
if (!shape->layout || strcmp(shape->layout, "extern") != 0) continue;
|
||||
if (wrote) zbuf_append(buf, ",");
|
||||
wrote = true;
|
||||
int offset = 0;
|
||||
int max_align = 1;
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, shape->name);
|
||||
zbuf_append(buf, ",\"layout\":\"extern\",\"fields\":[");
|
||||
for (size_t field_index = 0; field_index < shape->fields.len; field_index++) {
|
||||
const Param *field = &shape->fields.items[field_index];
|
||||
int align = abi_report_type_align(field->type, target);
|
||||
int size = abi_report_type_size(field->type, target);
|
||||
offset = abi_report_align_to_int(offset, align);
|
||||
if (align > max_align) max_align = align;
|
||||
if (field_index > 0) zbuf_append(buf, ",");
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, field->name);
|
||||
zbuf_append(buf, ",\"type\":");
|
||||
abi_append_json_string(buf, field->type);
|
||||
zbuf_appendf(buf, ",\"offset\":%d,\"size\":%d,\"align\":%d}", offset, size, align);
|
||||
offset += size;
|
||||
}
|
||||
zbuf_appendf(buf, "],\"size\":%d,\"align\":%d}", abi_report_align_to_int(offset, max_align), max_align);
|
||||
}
|
||||
zbuf_append(buf, "]");
|
||||
}
|
||||
|
||||
static void append_abi_enums_json(ZBuf *buf, const Program *program) {
|
||||
zbuf_append(buf, "[");
|
||||
for (size_t i = 0; program && i < program->enums.len; i++) {
|
||||
if (i > 0) zbuf_append(buf, ",");
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, program->enums.items[i].name);
|
||||
zbuf_appendf(buf, ",\"size\":4,\"align\":4,\"cases\":%zu}", program->enums.items[i].cases.len);
|
||||
}
|
||||
zbuf_append(buf, "]");
|
||||
}
|
||||
|
||||
static void append_c_exports_json(ZBuf *buf, const Program *program) {
|
||||
zbuf_append(buf, "[");
|
||||
bool wrote = false;
|
||||
for (size_t i = 0; program && i < program->functions.len; i++) {
|
||||
const Function *fun = &program->functions.items[i];
|
||||
if (!fun->export_c) continue;
|
||||
if (wrote) zbuf_append(buf, ",");
|
||||
wrote = true;
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, fun->name);
|
||||
zbuf_append(buf, ",\"returnType\":");
|
||||
abi_append_json_string(buf, fun->return_type ? fun->return_type : "Void");
|
||||
zbuf_append(buf, ",\"cReturnType\":");
|
||||
abi_append_json_string(buf, abi_report_c_type_name(fun->return_type));
|
||||
zbuf_append(buf, ",\"raises\":");
|
||||
zbuf_append(buf, fun->raises ? "true" : "false");
|
||||
zbuf_append(buf, ",\"params\":[");
|
||||
for (size_t param_index = 0; param_index < fun->params.len; param_index++) {
|
||||
const Param *param = &fun->params.items[param_index];
|
||||
if (param_index > 0) zbuf_append(buf, ",");
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, param->name);
|
||||
zbuf_append(buf, ",\"type\":");
|
||||
abi_append_json_string(buf, param->type);
|
||||
zbuf_append(buf, ",\"cType\":");
|
||||
abi_append_json_string(buf, abi_report_c_type_name(param->type));
|
||||
zbuf_append(buf, "}");
|
||||
}
|
||||
zbuf_append(buf, "]}");
|
||||
}
|
||||
zbuf_append(buf, "]");
|
||||
}
|
||||
|
||||
static size_t graph_c_export_count(const ZProgramGraph *graph) {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
if (graph->nodes[i].kind == Z_PROGRAM_GRAPH_NODE_FUNCTION && graph->nodes[i].export_c) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void append_c_exports_json_from_graph(ZBuf *buf, const ZProgramGraph *graph) {
|
||||
zbuf_append(buf, "[");
|
||||
bool wrote = false;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *fun = &graph->nodes[i];
|
||||
if (fun->kind != Z_PROGRAM_GRAPH_NODE_FUNCTION || !fun->export_c) continue;
|
||||
if (wrote) zbuf_append(buf, ",");
|
||||
wrote = true;
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, fun->name);
|
||||
zbuf_append(buf, ",\"returnType\":");
|
||||
abi_append_json_string(buf, fun->type && fun->type[0] ? fun->type : "Void");
|
||||
zbuf_append(buf, ",\"cReturnType\":");
|
||||
abi_append_json_string(buf, abi_report_c_type_name(fun->type));
|
||||
zbuf_append(buf, ",\"raises\":");
|
||||
zbuf_append(buf, fun->fallible ? "true" : "false");
|
||||
zbuf_append(buf, ",\"params\":[");
|
||||
size_t param_count = z_program_graph_query_child_count(graph, fun->id, "param");
|
||||
bool wrote_param = false;
|
||||
for (size_t param_order = 0; param_order < param_count; param_order++) {
|
||||
const ZProgramGraphNode *param = z_program_graph_query_child_node(graph, fun->id, "param", param_order);
|
||||
if (!param) continue;
|
||||
if (wrote_param) zbuf_append(buf, ",");
|
||||
wrote_param = true;
|
||||
zbuf_append(buf, "{\"name\":");
|
||||
abi_append_json_string(buf, param->name);
|
||||
zbuf_append(buf, ",\"type\":");
|
||||
abi_append_json_string(buf, param->type);
|
||||
zbuf_append(buf, ",\"cType\":");
|
||||
abi_append_json_string(buf, abi_report_c_type_name(param->type));
|
||||
zbuf_append(buf, "}");
|
||||
}
|
||||
zbuf_append(buf, "]}");
|
||||
}
|
||||
zbuf_append(buf, "]");
|
||||
}
|
||||
|
||||
static void append_c_export_header_text(ZBuf *header, const Program *program) {
|
||||
zbuf_append(header, "#pragma once\n#include <stdbool.h>\n#include <stdint.h>\n\n");
|
||||
for (size_t i = 0; program && i < program->functions.len; i++) {
|
||||
const Function *fun = &program->functions.items[i];
|
||||
if (!fun->export_c) continue;
|
||||
zbuf_append(header, abi_report_c_type_name(fun->return_type));
|
||||
zbuf_append_char(header, ' ');
|
||||
zbuf_append(header, fun->name);
|
||||
zbuf_append_char(header, '(');
|
||||
if (fun->params.len == 0) {
|
||||
zbuf_append(header, "void");
|
||||
} else {
|
||||
for (size_t param_index = 0; param_index < fun->params.len; param_index++) {
|
||||
const Param *param = &fun->params.items[param_index];
|
||||
if (param_index > 0) zbuf_append(header, ", ");
|
||||
zbuf_append(header, abi_report_c_type_name(param->type));
|
||||
zbuf_append_char(header, ' ');
|
||||
zbuf_append(header, param->name);
|
||||
}
|
||||
}
|
||||
zbuf_append(header, ");\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void append_c_export_header_text_from_graph(ZBuf *header, const ZProgramGraph *graph) {
|
||||
zbuf_append(header, "#pragma once\n#include <stdbool.h>\n#include <stdint.h>\n\n");
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *fun = &graph->nodes[i];
|
||||
if (fun->kind != Z_PROGRAM_GRAPH_NODE_FUNCTION || !fun->export_c) continue;
|
||||
zbuf_append(header, abi_report_c_type_name(fun->type));
|
||||
zbuf_append_char(header, ' ');
|
||||
zbuf_append(header, fun->name ? fun->name : "");
|
||||
zbuf_append_char(header, '(');
|
||||
size_t param_count = z_program_graph_query_child_count(graph, fun->id, "param");
|
||||
if (param_count == 0) {
|
||||
zbuf_append(header, "void");
|
||||
} else {
|
||||
bool wrote_param = false;
|
||||
for (size_t param_order = 0; param_order < param_count; param_order++) {
|
||||
const ZProgramGraphNode *param = z_program_graph_query_child_node(graph, fun->id, "param", param_order);
|
||||
if (!param) continue;
|
||||
if (wrote_param) zbuf_append(header, ", ");
|
||||
wrote_param = true;
|
||||
zbuf_append(header, abi_report_c_type_name(param->type));
|
||||
zbuf_append_char(header, ' ');
|
||||
zbuf_append(header, param->name ? param->name : "");
|
||||
}
|
||||
if (!wrote_param) zbuf_append(header, "void");
|
||||
}
|
||||
zbuf_append(header, ");\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void append_c_export_header_json(ZBuf *buf, const Program *program) {
|
||||
ZBuf header;
|
||||
zbuf_init(&header);
|
||||
append_c_export_header_text(&header, program);
|
||||
size_t export_count = 0;
|
||||
for (size_t i = 0; program && i < program->functions.len; i++) {
|
||||
if (program->functions.items[i].export_c) export_count++;
|
||||
}
|
||||
zbuf_appendf(buf, "{\"available\":%s,\"format\":\"c-header\",\"exportCount\":%zu,\"text\":", export_count > 0 ? "true" : "false", export_count);
|
||||
abi_append_json_string(buf, header.data ? header.data : "");
|
||||
zbuf_append(buf, "}");
|
||||
zbuf_free(&header);
|
||||
}
|
||||
|
||||
static void append_c_export_header_json_from_graph(ZBuf *buf, const ZProgramGraph *graph) {
|
||||
ZBuf header;
|
||||
zbuf_init(&header);
|
||||
append_c_export_header_text_from_graph(&header, graph);
|
||||
size_t export_count = graph_c_export_count(graph);
|
||||
zbuf_appendf(buf, "{\"available\":%s,\"format\":\"c-header\",\"exportCount\":%zu,\"text\":", export_count > 0 ? "true" : "false", export_count);
|
||||
abi_append_json_string(buf, header.data ? header.data : "");
|
||||
zbuf_append(buf, "}");
|
||||
zbuf_free(&header);
|
||||
}
|
||||
|
||||
void z_append_abi_dump_json(
|
||||
ZBuf *buf,
|
||||
const SourceInput *input,
|
||||
const Program *program,
|
||||
const ZTargetInfo *target,
|
||||
const ZProgramGraph *graph,
|
||||
ZAbiCImportsJsonFn append_c_imports_json
|
||||
) {
|
||||
zbuf_append(buf, "{\n \"schemaVersion\": 1,\n \"sourceFile\": ");
|
||||
abi_append_json_string(buf, input ? input->source_file : "");
|
||||
zbuf_append(buf, ",\n \"target\": ");
|
||||
abi_append_json_string(buf, target ? target->name : "host");
|
||||
zbuf_append(buf, ",\n \"pointerSize\": ");
|
||||
zbuf_appendf(buf, "%d", abi_report_pointer_size(target));
|
||||
zbuf_append(buf, ",\n \"objectFormat\": ");
|
||||
abi_append_json_string(buf, target && target->object_format ? target->object_format : "unknown");
|
||||
zbuf_append(buf, ",\n \"callingConvention\": ");
|
||||
abi_append_json_string(buf, target && target->abi ? target->abi : "host");
|
||||
zbuf_append(buf, ",\n \"primitiveLayouts\": ");
|
||||
append_abi_primitives_json(buf, target);
|
||||
zbuf_append(buf, ",\n \"externShapes\": ");
|
||||
append_abi_shapes_json(buf, program, target);
|
||||
zbuf_append(buf, ",\n \"enums\": ");
|
||||
append_abi_enums_json(buf, program);
|
||||
zbuf_append(buf, ",\n \"cImports\": ");
|
||||
if (append_c_imports_json) append_c_imports_json(buf, program, target);
|
||||
else zbuf_append(buf, "[]");
|
||||
zbuf_append(buf, ",\n \"cExports\": ");
|
||||
if (graph) append_c_exports_json_from_graph(buf, graph);
|
||||
else append_c_exports_json(buf, program);
|
||||
zbuf_append(buf, ",\n \"generatedHeader\": ");
|
||||
if (graph) append_c_export_header_json_from_graph(buf, graph);
|
||||
else append_c_export_header_json(buf, program);
|
||||
zbuf_append(buf, "\n}\n");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef ZERO_C_ABI_REPORT_H
|
||||
#define ZERO_C_ABI_REPORT_H
|
||||
|
||||
#include "program_graph.h"
|
||||
#include "zero.h"
|
||||
|
||||
typedef void (*ZAbiCImportsJsonFn)(ZBuf *buf, const Program *program, const ZTargetInfo *target);
|
||||
|
||||
void z_append_abi_dump_json(
|
||||
ZBuf *buf,
|
||||
const SourceInput *input,
|
||||
const Program *program,
|
||||
const ZTargetInfo *target,
|
||||
const ZProgramGraph *graph,
|
||||
ZAbiCImportsJsonFn append_c_imports_json
|
||||
);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void free_expr(Expr *expr) {
|
||||
if (!expr) return;
|
||||
free(expr->text);
|
||||
free(expr->resolved_type);
|
||||
free_expr(expr->left);
|
||||
free_expr(expr->right);
|
||||
for (size_t i = 0; i < expr->args.len; i++) free_expr(expr->args.items[i]);
|
||||
free(expr->args.items);
|
||||
for (size_t i = 0; i < expr->type_args.len; i++) free(expr->type_args.items[i].type);
|
||||
free(expr->type_args.items);
|
||||
for (size_t i = 0; i < expr->checked_type_args.len; i++) free(expr->checked_type_args.items[i].type);
|
||||
free(expr->checked_type_args.items);
|
||||
for (size_t i = 0; i < expr->fields.len; i++) {
|
||||
free(expr->fields.items[i].name);
|
||||
free_expr(expr->fields.items[i].value);
|
||||
}
|
||||
free(expr->fields.items);
|
||||
free(expr);
|
||||
}
|
||||
|
||||
static void free_stmt_vec(StmtVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
Stmt *stmt = vec->items[i];
|
||||
free(stmt->name);
|
||||
free(stmt->type);
|
||||
free(stmt->resolved_type);
|
||||
free(stmt->graph_id);
|
||||
free(stmt->then_graph_id);
|
||||
free(stmt->else_graph_id);
|
||||
free_expr(stmt->target);
|
||||
free_expr(stmt->expr);
|
||||
free_expr(stmt->range_end);
|
||||
free_stmt_vec(&stmt->then_body);
|
||||
free_stmt_vec(&stmt->else_body);
|
||||
for (size_t arm_index = 0; arm_index < stmt->match_arms.len; arm_index++) {
|
||||
free(stmt->match_arms.items[arm_index].case_name);
|
||||
free(stmt->match_arms.items[arm_index].range_end);
|
||||
free(stmt->match_arms.items[arm_index].payload_name);
|
||||
free(stmt->match_arms.items[arm_index].body_graph_id);
|
||||
free_expr(stmt->match_arms.items[arm_index].guard);
|
||||
free_stmt_vec(&stmt->match_arms.items[arm_index].body);
|
||||
}
|
||||
free(stmt->match_arms.items);
|
||||
free(stmt);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_param_vec(ParamVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free(vec->items[i].type);
|
||||
free_expr(vec->items[i].default_value);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_function(Function *fun) {
|
||||
free(fun->name);
|
||||
free(fun->test_name);
|
||||
free(fun->return_type);
|
||||
free_param_vec(&fun->type_params);
|
||||
free_param_vec(&fun->errors);
|
||||
free_param_vec(&fun->params);
|
||||
free_stmt_vec(&fun->body);
|
||||
}
|
||||
|
||||
static void free_function_vec(FunctionVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) free_function(&vec->items[i]);
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_use_import_vec(UseImportVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].module);
|
||||
free(vec->items[i].alias);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_c_import_vec(CImportVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].header);
|
||||
free(vec->items[i].resolved_header);
|
||||
free(vec->items[i].alias);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_const_vec(ConstVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free(vec->items[i].type);
|
||||
free_expr(vec->items[i].expr);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_alias_vec(TypeAliasVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free(vec->items[i].target);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_interface_vec(InterfaceVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free_param_vec(&vec->items[i].type_params);
|
||||
free_function_vec(&vec->items[i].methods);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_shape_vec(ShapeVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free(vec->items[i].layout);
|
||||
free_param_vec(&vec->items[i].type_params);
|
||||
free_param_vec(&vec->items[i].fields);
|
||||
free_function_vec(&vec->items[i].methods);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_enum_vec(EnumVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free(vec->items[i].type);
|
||||
free_param_vec(&vec->items[i].cases);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
static void free_choice_vec(ChoiceVec *vec) {
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
free(vec->items[i].name);
|
||||
free_param_vec(&vec->items[i].cases);
|
||||
}
|
||||
free(vec->items);
|
||||
}
|
||||
|
||||
void z_free_program(Program *program) {
|
||||
if (!program) return;
|
||||
free_use_import_vec(&program->use_imports);
|
||||
free_c_import_vec(&program->c_imports);
|
||||
free_const_vec(&program->consts);
|
||||
free_alias_vec(&program->aliases);
|
||||
free_interface_vec(&program->interfaces);
|
||||
free_shape_vec(&program->shapes);
|
||||
free_enum_vec(&program->enums);
|
||||
free_choice_vec(&program->choices);
|
||||
free_function_vec(&program->functions);
|
||||
memset(program, 0, sizeof(*program));
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
ZBackendFamily z_backend_family_from_request(const char *requested_backend, const char *emit_kind) {
|
||||
if (requested_backend && requested_backend[0]) {
|
||||
if (strcmp(requested_backend, "direct") == 0) return Z_BACKEND_FAMILY_DIRECT;
|
||||
if (strcmp(requested_backend, "llvm") == 0) return Z_BACKEND_FAMILY_LLVM;
|
||||
if (z_direct_backend_is_request_name(requested_backend)) return Z_BACKEND_FAMILY_DIRECT;
|
||||
return Z_BACKEND_FAMILY_UNKNOWN;
|
||||
}
|
||||
(void)emit_kind;
|
||||
return Z_BACKEND_FAMILY_DIRECT;
|
||||
}
|
||||
|
||||
const char *z_backend_family_name(ZBackendFamily family) {
|
||||
switch (family) {
|
||||
case Z_BACKEND_FAMILY_DIRECT: return "direct";
|
||||
case Z_BACKEND_FAMILY_LLVM: return "llvm";
|
||||
case Z_BACKEND_FAMILY_UNKNOWN:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool z_backend_request_is_known(const char *requested_backend, const char *emit_kind) {
|
||||
return z_backend_family_from_request(requested_backend, emit_kind) != Z_BACKEND_FAMILY_UNKNOWN;
|
||||
}
|
||||
|
||||
bool z_backend_request_is_llvm(const char *requested_backend, const char *emit_kind) {
|
||||
return z_backend_family_from_request(requested_backend, emit_kind) == Z_BACKEND_FAMILY_LLVM;
|
||||
}
|
||||
|
||||
const char *z_backend_direct_request_name(const char *requested_backend) {
|
||||
return z_direct_backend_is_request_name(requested_backend) ? requested_backend : NULL;
|
||||
}
|
||||
|
||||
const char *z_backend_request_expected(void) {
|
||||
return "one of direct, llvm, or a direct emitter name";
|
||||
}
|
||||
|
||||
void z_backend_init_unknown_diag(ZDiag *diag, const char *requested_backend, const char *path) {
|
||||
memset(diag, 0, sizeof(*diag));
|
||||
diag->code = 2002;
|
||||
diag->path = path;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "unknown backend '%s'", requested_backend ? requested_backend : "");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", z_backend_request_expected());
|
||||
snprintf(diag->actual, sizeof(diag->actual), "--backend %s", requested_backend ? requested_backend : "");
|
||||
snprintf(diag->help, sizeof(diag->help), "omit --backend for direct output, use --backend direct, or request the explicit llvm backend");
|
||||
}
|
||||
|
||||
void z_backend_init_llvm_unavailable_diag(ZDiag *diag, const ZTargetInfo *target, const char *emit_kind, const char *path) {
|
||||
memset(diag, 0, sizeof(*diag));
|
||||
diag->code = 2004;
|
||||
diag->path = path;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "LLVM backend does not support --emit %s for this target yet", emit_kind ? emit_kind : "exe");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "LLVM IR output or native host executable output");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "backend=llvm emit=%s", emit_kind ? emit_kind : "exe");
|
||||
snprintf(diag->help, sizeof(diag->help), "use --backend llvm --emit llvm-ir for textual IR, use host --backend llvm --emit exe when clang is ready, or use --backend direct");
|
||||
ZBackendBlocker blocker;
|
||||
z_backend_blocker_set(&blocker,
|
||||
target && target->name ? target->name : "unknown",
|
||||
target && target->object_format ? target->object_format : "unknown",
|
||||
"llvm",
|
||||
"buildability",
|
||||
"unsupported LLVM artifact kind");
|
||||
z_diag_set_backend_blocker(diag, &blocker);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "buildability_internal.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
bool z_build_check_value(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, bool local_set_value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (!value) return z_build_diag(ctx, diag, "direct backend buildability found a missing expression", 1, 1, "missing expression");
|
||||
if (!z_build_value_supported(ctx, value, local_set_value)) {
|
||||
return z_build_diag(ctx, diag, "direct backend buildability does not support this MIR value", value->line, value->column, z_build_value_kind_name(value->kind));
|
||||
}
|
||||
bool skip_left = false;
|
||||
unsigned right_slot = scratch_slot;
|
||||
if (!z_build_check_target_value(ctx, fun, value, scratch_slot, &skip_left, &right_slot, diag)) return false;
|
||||
if (value->kind == IR_VALUE_LOCAL && fun && value->local_index < fun->local_len && fun->locals[value->local_index].is_array) {
|
||||
return z_build_diag(ctx, diag, "direct backend buildability cannot use fixed array locals as scalar values", value->line, value->column, "array local");
|
||||
}
|
||||
if (value->index && !z_build_check_value(ctx, fun, value->index, false, scratch_slot, diag)) return false;
|
||||
if (value->left && !skip_left && !z_build_check_value(ctx, fun, value->left, false, scratch_slot, diag)) return false;
|
||||
if (value->right && !z_build_check_value(ctx, fun, value->right, false, right_slot, diag)) return false;
|
||||
unsigned arg_slot = z_build_target_call_arg_slot(ctx, value, scratch_slot);
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_value(ctx, fun, value->args[i], false, arg_slot, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_instrs(const ZBuildability *ctx, const IrFunction *fun, const IrInstr *instrs, size_t len, ZDiag *diag);
|
||||
|
||||
static bool build_check_instr(const ZBuildability *ctx, const IrFunction *fun, const IrInstr *instr, ZDiag *diag) {
|
||||
if (!ctx || !instr) return z_build_diag(ctx, diag, "direct backend buildability found a missing instruction", 1, 1, "missing instruction");
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && instr->kind == IR_INSTR_WORLD_WRITE) {
|
||||
if (!ctx->executable) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 object buildability does not support World write instructions", instr->line, instr->column, "IR_INSTR_WORLD_WRITE");
|
||||
}
|
||||
if (instr->value && !z_build_check_aarch64_world_write_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
if (instr->index && !z_build_check_value(ctx, fun, instr->index, false, 0, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && instr->kind == IR_INSTR_RAISE) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 buildability does not support this instruction yet", instr->line, instr->column, "unsupported instruction");
|
||||
}
|
||||
switch (instr->kind) {
|
||||
case IR_INSTR_LOCAL_SET:
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_COFF_X64 && fun && instr->local_index < fun->local_len && fun->locals[instr->local_index].type == IR_TYPE_BYTE_VIEW) {
|
||||
return !instr->value || z_build_check_coff_byte_view(ctx, fun, instr->value, diag);
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 && fun && instr->local_index < fun->local_len && fun->locals[instr->local_index].type == IR_TYPE_BYTE_VIEW) {
|
||||
return !instr->value || z_build_check_macho_x64_byte_view(ctx, fun, instr->value, diag);
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64 && fun && instr->local_index < fun->local_len && fun->locals[instr->local_index].type == IR_TYPE_BYTE_VIEW) {
|
||||
if (instr->value && !z_build_check_macho_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
}
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && fun && instr->local_index < fun->local_len && fun->locals[instr->local_index].type == IR_TYPE_BYTE_VIEW) {
|
||||
if (instr->value && !z_build_check_aarch64_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
}
|
||||
if (instr->value && !z_build_check_value(ctx, fun, instr->value, true, 0, diag)) return false;
|
||||
return true;
|
||||
case IR_INSTR_ARRAY_FILL: {
|
||||
if (!fun || instr->array_index >= fun->local_len) {
|
||||
return z_build_diag(ctx, diag, "direct backend buildability found an array fill outside the local table", instr->line, instr->column, "invalid array local");
|
||||
}
|
||||
const IrLocal *local = &fun->locals[instr->array_index];
|
||||
if (!local->is_array || local->array_len == 0 || local->type == IR_TYPE_BYTE_VIEW) {
|
||||
return z_build_diag(ctx, diag, "direct backend buildability found an unsupported array fill target", instr->line, instr->column, local->name ? local->name : "array local");
|
||||
}
|
||||
if (!instr->value || (instr->value->kind != IR_VALUE_INT && instr->value->kind != IR_VALUE_BOOL) || instr->value->type != local->element_type) {
|
||||
return z_build_diag(ctx, diag, "direct backend buildability found an unsupported array fill value", instr->line, instr->column, "non-literal fill");
|
||||
}
|
||||
if (!z_build_check_value(ctx, fun, instr->value, false, 0, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
case IR_INSTR_INDEX_STORE:
|
||||
case IR_INSTR_FIELD_STORE: {
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && instr->kind == IR_INSTR_FIELD_STORE && instr->value && instr->value->type == IR_TYPE_BYTE_VIEW) {
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
}
|
||||
if (instr->value && !z_build_check_value(ctx, fun, instr->value, false, 0, diag)) return false;
|
||||
unsigned index_scratch_slot = instr->kind == IR_INSTR_INDEX_STORE && (ctx->backend == Z_DIRECT_BACKEND_MACHO64 || z_build_backend_is_aarch64_direct(ctx->backend)) ? 1 : 0;
|
||||
if (instr->index && !z_build_check_value(ctx, fun, instr->index, false, index_scratch_slot, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
case IR_INSTR_WORLD_WRITE:
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_COFF_X64 && instr->value && !z_build_check_coff_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 && instr->value && !z_build_check_macho_x64_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64 && instr->value && !z_build_check_macho_byte_view(ctx, fun, instr->value, diag)) return false;
|
||||
if (ctx->backend != Z_DIRECT_BACKEND_COFF_X64 && instr->value && !z_build_check_value(ctx, fun, instr->value, false, 0, diag)) return false;
|
||||
if (instr->index && !z_build_check_value(ctx, fun, instr->index, false, 0, diag)) return false;
|
||||
return true;
|
||||
case IR_INSTR_EXPR:
|
||||
case IR_INSTR_RETURN:
|
||||
if (instr->value && !z_build_check_value(ctx, fun, instr->value, false, 0, diag)) return false;
|
||||
if (instr->index && !z_build_check_value(ctx, fun, instr->index, false, 0, diag)) return false;
|
||||
return true;
|
||||
case IR_INSTR_IF:
|
||||
case IR_INSTR_WHILE:
|
||||
if (instr->value && !z_build_check_value(ctx, fun, instr->value, false, 0, diag)) return false;
|
||||
if (!build_check_instrs(ctx, fun, instr->then_instrs, instr->then_len, diag)) return false;
|
||||
return build_check_instrs(ctx, fun, instr->else_instrs, instr->else_len, diag);
|
||||
case IR_INSTR_BREAK:
|
||||
case IR_INSTR_CONTINUE:
|
||||
return true;
|
||||
case IR_INSTR_RAISE:
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64 || ctx->backend == Z_DIRECT_BACKEND_MACHO_X64) return true;
|
||||
return z_build_diag(ctx, diag, "direct backend buildability does not support raise instructions for this emitter", instr->line, instr->column, "IR_INSTR_RAISE");
|
||||
}
|
||||
return z_build_diag(ctx, diag, "direct backend buildability does not support this instruction", instr->line, instr->column, "unsupported instruction");
|
||||
}
|
||||
|
||||
static bool build_check_instrs(const ZBuildability *ctx, const IrFunction *fun, const IrInstr *instrs, size_t len, ZDiag *diag) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!build_check_instr(ctx, fun, &instrs[i], diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_function_shape(const ZBuildability *ctx, const IrFunction *fun, ZDiag *diag) {
|
||||
if (!fun) return z_build_diag(ctx, diag, "direct backend buildability found a missing function", 1, 1, "missing function");
|
||||
size_t abi_slots = 0;
|
||||
for (size_t i = 0; i < fun->param_count; i++) abi_slots += fun->locals[i].type == IR_TYPE_BYTE_VIEW ? 2 : 1;
|
||||
size_t max_slots = (ctx->backend == Z_DIRECT_BACKEND_ELF64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_COFF_X64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_MACHO64) ? 8 : 6;
|
||||
if (!z_build_backend_is_aarch64_direct(ctx->backend) && abi_slots > max_slots) {
|
||||
return z_build_diag(ctx, diag, "direct backend object buildability has too many ABI argument slots", fun->line, fun->column, fun->name);
|
||||
}
|
||||
size_t frame_bytes = fun->frame_bytes ? fun->frame_bytes : fun->local_len * 8u;
|
||||
if (frame_bytes > Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES) {
|
||||
return z_build_diag(ctx, diag, "direct backend stack frame exceeds the supported per-function locals limit", fun->line, fun->column, fun->name);
|
||||
}
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend)) return z_build_check_aarch64_function_shape(ctx, fun, diag);
|
||||
bool wide_scalars = ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 || ctx->backend == Z_DIRECT_BACKEND_COFF_X64;
|
||||
bool return_ok = wide_scalars ? (fun->return_type == IR_TYPE_VOID || z_build_is_elf_scalar(fun->return_type))
|
||||
: (fun->return_type == IR_TYPE_VOID || z_build_is_scalar32(fun->return_type));
|
||||
if (fun->return_type == IR_TYPE_BYTE_VIEW || fun->return_type == IR_TYPE_MAYBE_BYTE_VIEW || fun->return_type == IR_TYPE_MAYBE_SCALAR) return_ok = true;
|
||||
if (!return_ok) return z_build_diag(ctx, diag, "direct backend object buildability does not support this return type", fun->line, fun->column, z_build_type_name(fun->return_type));
|
||||
for (size_t i = 0; i < fun->local_len; i++) {
|
||||
const IrLocal *local = &fun->locals[i];
|
||||
if (local->type == IR_TYPE_BYTE_VIEW && local->is_param && ctx->backend != Z_DIRECT_BACKEND_ELF64 && ctx->backend != Z_DIRECT_BACKEND_MACHO64 && ctx->backend != Z_DIRECT_BACKEND_MACHO_X64 && ctx->backend != Z_DIRECT_BACKEND_COFF_X64) return z_build_diag(ctx, diag, "direct backend object buildability does not support byte-view parameters", local->line, local->column, local->name);
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 && (local->type == IR_TYPE_ALLOC || local->type == IR_TYPE_VEC)) {
|
||||
return z_build_diag(ctx, diag, "direct x86_64 Mach-O object buildability does not support this local type", local->line, local->column, z_build_type_name(local->type));
|
||||
}
|
||||
if (local->is_record || local->type == IR_TYPE_BYTE_VIEW || local->type == IR_TYPE_ALLOC || local->type == IR_TYPE_VEC || local->type == IR_TYPE_MAYBE_BYTE_VIEW || local->type == IR_TYPE_MAYBE_SCALAR) continue;
|
||||
if (local->is_array) {
|
||||
bool array_ok = local->element_type == IR_TYPE_BOOL || local->element_type == IR_TYPE_U8 || local->element_type == IR_TYPE_U16 ||
|
||||
local->element_type == IR_TYPE_I32 || local->element_type == IR_TYPE_U32 || local->element_type == IR_TYPE_USIZE ||
|
||||
local->element_type == IR_TYPE_I64 || local->element_type == IR_TYPE_U64;
|
||||
if (!array_ok) return z_build_diag(ctx, diag, "direct backend object buildability does not support this fixed-array local", local->line, local->column, z_build_type_name(local->element_type));
|
||||
continue;
|
||||
}
|
||||
bool local_ok = wide_scalars ? z_build_is_elf_scalar(local->type) : z_build_is_scalar32(local->type);
|
||||
if (!local_ok) return z_build_diag(ctx, diag, "direct backend object buildability does not support this local type", local->line, local->column, z_build_type_name(local->type));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static const IrFunction *build_find_main(const ZBuildability *ctx, const IrProgram *ir, ZDiag *diag) {
|
||||
const IrFunction *main_fun = NULL;
|
||||
for (size_t i = 0; ir && i < ir->function_len; i++) {
|
||||
if (!ir->functions[i].is_exported || !ir->functions[i].name || strcmp(ir->functions[i].name, "main") != 0) continue;
|
||||
if (main_fun) {
|
||||
z_build_diag(ctx, diag, "direct executable buildability requires exactly one exported main function", ir->functions[i].line, ir->functions[i].column, ir->functions[i].name);
|
||||
return NULL;
|
||||
}
|
||||
main_fun = &ir->functions[i];
|
||||
}
|
||||
if (!main_fun) z_build_diag(ctx, diag, "direct executable buildability requires an exported main function", 1, 1, "missing main");
|
||||
return main_fun;
|
||||
}
|
||||
|
||||
static bool build_check_executable_shape(const ZBuildability *ctx, const IrProgram *ir, ZDiag *diag) {
|
||||
const IrFunction *main_fun = build_find_main(ctx, ir, diag);
|
||||
if (!main_fun) return false;
|
||||
if (main_fun->param_count != 0) {
|
||||
const char *message = ctx->backend == Z_DIRECT_BACKEND_ELF64 ? "direct ELF64 executable main must not take parameters" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_ELF_AARCH64 ? "direct AArch64 ELF executable main must not take parameters" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_COFF_AARCH64 ? "direct COFF AArch64 executable main must not take parameters" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_COFF_X64 ? "direct COFF x64 executable main must not take parameters" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 ? "direct x86_64 Mach-O executable main must not take parameters" :
|
||||
"direct AArch64 Mach-O executable main must not take parameters"))));
|
||||
return z_build_diag(ctx, diag, message, main_fun->line, main_fun->column, main_fun->name);
|
||||
}
|
||||
bool return_ok = main_fun->return_type == IR_TYPE_VOID || z_build_is_elf_scalar(main_fun->return_type);
|
||||
if (!return_ok) {
|
||||
const char *message = ctx->backend == Z_DIRECT_BACKEND_ELF64 ? "direct ELF64 executable main must return Void or a primitive scalar" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_ELF_AARCH64 ? "direct AArch64 ELF executable main must return Void or a primitive scalar" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_COFF_AARCH64 ? "direct COFF AArch64 executable main must return Void or a primitive scalar" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_COFF_X64 ? "direct COFF x64 executable main must return Void or a primitive scalar" :
|
||||
(ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 ? "direct x86_64 Mach-O executable main must return Void or a primitive scalar" :
|
||||
"direct AArch64 Mach-O executable main must return Void or a primitive scalar"))));
|
||||
return z_build_diag(ctx, diag, message, main_fun->line, main_fun->column, z_build_type_name(main_fun->return_type));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_direct_buildability_check(const IrProgram *ir, const ZTargetInfo *target, const char *emit_kind, ZDiag *diag) {
|
||||
ZBuildability ctx;
|
||||
if (!z_build_select(ir, target, emit_kind, &ctx, diag)) return false;
|
||||
if (!ir->mir_valid) {
|
||||
return z_build_diag(&ctx, diag, ir->mir_message[0] ? ir->mir_message : "direct backend lowering failed", ir->mir_line, ir->mir_column, ir->mir_actual);
|
||||
}
|
||||
if (ir->function_len == 0) return z_build_diag(&ctx, diag, "direct backend buildability requires at least one exported function", 1, 1, "empty program");
|
||||
bool has_export = false;
|
||||
for (size_t i = 0; i < ir->function_len; i++) {
|
||||
if (ir->functions[i].is_exported) has_export = true;
|
||||
if (!build_check_function_shape(&ctx, &ir->functions[i], diag)) return false;
|
||||
if (!build_check_instrs(&ctx, &ir->functions[i], ir->functions[i].instrs, ir->functions[i].instr_len, diag)) return false;
|
||||
}
|
||||
if (!has_export) return z_build_diag(&ctx, diag, "direct backend buildability requires at least one exported function", 1, 1, "no exported function");
|
||||
if (ctx.executable && !build_check_executable_shape(&ctx, ir, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef ZERO_C_BUILDABILITY_H
|
||||
#define ZERO_C_BUILDABILITY_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
bool z_direct_buildability_check(const IrProgram *ir, const ZTargetInfo *target, const char *emit_kind, ZDiag *diag);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "buildability_internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
const char *z_build_type_name(IrTypeKind type) {
|
||||
switch (type) {
|
||||
case IR_TYPE_VOID: return "Void";
|
||||
case IR_TYPE_BOOL: return "Bool";
|
||||
case IR_TYPE_U8: return "u8";
|
||||
case IR_TYPE_U16: return "u16";
|
||||
case IR_TYPE_USIZE: return "usize";
|
||||
case IR_TYPE_I32: return "i32";
|
||||
case IR_TYPE_U32: return "u32";
|
||||
case IR_TYPE_I64: return "i64";
|
||||
case IR_TYPE_U64: return "u64";
|
||||
case IR_TYPE_BYTE_VIEW: return "Span<u8>";
|
||||
case IR_TYPE_ALLOC: return "FixedBufAlloc";
|
||||
case IR_TYPE_VEC: return "Vec";
|
||||
case IR_TYPE_MAYBE_BYTE_VIEW: return "Maybe<MutSpan<u8>>";
|
||||
case IR_TYPE_MAYBE_SCALAR: return "Maybe<usize>";
|
||||
case IR_TYPE_RECORD: return "record";
|
||||
default: return "unsupported";
|
||||
}
|
||||
}
|
||||
|
||||
const char *z_build_value_kind_name(IrValueKind kind) {
|
||||
switch (kind) {
|
||||
case IR_VALUE_INT: return "IR_VALUE_INT";
|
||||
case IR_VALUE_BOOL: return "IR_VALUE_BOOL";
|
||||
case IR_VALUE_LOCAL: return "IR_VALUE_LOCAL";
|
||||
case IR_VALUE_CAST: return "IR_VALUE_CAST";
|
||||
case IR_VALUE_BINARY: return "IR_VALUE_BINARY";
|
||||
case IR_VALUE_COMPARE: return "IR_VALUE_COMPARE";
|
||||
case IR_VALUE_CALL: return "IR_VALUE_CALL";
|
||||
case IR_VALUE_INDEX_LOAD: return "IR_VALUE_INDEX_LOAD";
|
||||
case IR_VALUE_STRING_LITERAL: return "IR_VALUE_STRING_LITERAL";
|
||||
case IR_VALUE_ARRAY_BYTE_VIEW: return "IR_VALUE_ARRAY_BYTE_VIEW";
|
||||
case IR_VALUE_BYTE_SLICE: return "IR_VALUE_BYTE_SLICE";
|
||||
case IR_VALUE_BYTE_VIEW_LEN: return "IR_VALUE_BYTE_VIEW_LEN";
|
||||
case IR_VALUE_BYTE_VIEW_REMAINING: return "IR_VALUE_BYTE_VIEW_REMAINING";
|
||||
case IR_VALUE_BYTE_VIEW_INDEX_LOAD: return "IR_VALUE_BYTE_VIEW_INDEX_LOAD";
|
||||
case IR_VALUE_BYTE_VIEW_EQ: return "IR_VALUE_BYTE_VIEW_EQ";
|
||||
case IR_VALUE_STR_CONTAINS: return "IR_VALUE_STR_CONTAINS";
|
||||
case IR_VALUE_STR_RUNTIME: return "IR_VALUE_STR_RUNTIME";
|
||||
case IR_VALUE_ASCII_RUNTIME: return "IR_VALUE_ASCII_RUNTIME";
|
||||
case IR_VALUE_TEXT_RUNTIME: return "IR_VALUE_TEXT_RUNTIME";
|
||||
case IR_VALUE_PARSE_RUNTIME: return "IR_VALUE_PARSE_RUNTIME";
|
||||
case IR_VALUE_TIME_RUNTIME: return "IR_VALUE_TIME_RUNTIME";
|
||||
case IR_VALUE_TERM_RUNTIME: return "IR_VALUE_TERM_RUNTIME";
|
||||
case IR_VALUE_MATH_RUNTIME: return "IR_VALUE_MATH_RUNTIME";
|
||||
case IR_VALUE_SEARCH_RUNTIME: return "IR_VALUE_SEARCH_RUNTIME";
|
||||
case IR_VALUE_SORT_RUNTIME: return "IR_VALUE_SORT_RUNTIME";
|
||||
case IR_VALUE_PARSE_I32: return "IR_VALUE_PARSE_I32";
|
||||
case IR_VALUE_PARSE_U32: return "IR_VALUE_PARSE_U32";
|
||||
case IR_VALUE_ARGS_PARSE_U32: return "IR_VALUE_ARGS_PARSE_U32";
|
||||
case IR_VALUE_FMT_BOOL: return "IR_VALUE_FMT_BOOL";
|
||||
case IR_VALUE_FMT_HEX_U32: return "IR_VALUE_FMT_HEX_U32";
|
||||
case IR_VALUE_FMT_I32: return "IR_VALUE_FMT_I32";
|
||||
case IR_VALUE_FMT_U32: return "IR_VALUE_FMT_U32";
|
||||
case IR_VALUE_FMT_USIZE: return "IR_VALUE_FMT_USIZE";
|
||||
case IR_VALUE_BYTE_COPY: return "IR_VALUE_BYTE_COPY";
|
||||
case IR_VALUE_BYTE_FILL: return "IR_VALUE_BYTE_FILL";
|
||||
case IR_VALUE_ITEM_COPY: return "IR_VALUE_ITEM_COPY";
|
||||
case IR_VALUE_ITEM_FILL: return "IR_VALUE_ITEM_FILL";
|
||||
case IR_VALUE_ITEM_CONTAINS: return "IR_VALUE_ITEM_CONTAINS";
|
||||
case IR_VALUE_CRC32_BYTES: return "IR_VALUE_CRC32_BYTES";
|
||||
case IR_VALUE_FIXED_BUF_ALLOC: return "IR_VALUE_FIXED_BUF_ALLOC";
|
||||
case IR_VALUE_VEC_INIT: return "IR_VALUE_VEC_INIT";
|
||||
case IR_VALUE_VEC_PUSH: return "IR_VALUE_VEC_PUSH";
|
||||
case IR_VALUE_VEC_LEN: return "IR_VALUE_VEC_LEN";
|
||||
case IR_VALUE_VEC_CAPACITY: return "IR_VALUE_VEC_CAPACITY";
|
||||
case IR_VALUE_VEC_BYTES: return "IR_VALUE_VEC_BYTES";
|
||||
case IR_VALUE_VEC_GET: return "IR_VALUE_VEC_GET";
|
||||
case IR_VALUE_VEC_SET: return "IR_VALUE_VEC_SET";
|
||||
case IR_VALUE_VEC_CLEAR: return "IR_VALUE_VEC_CLEAR";
|
||||
case IR_VALUE_VEC_POP: return "IR_VALUE_VEC_POP";
|
||||
case IR_VALUE_VEC_TRUNCATE: return "IR_VALUE_VEC_TRUNCATE";
|
||||
case IR_VALUE_VEC_REMOVE_SWAP: return "IR_VALUE_VEC_REMOVE_SWAP";
|
||||
case IR_VALUE_VEC_INDEX: return "IR_VALUE_VEC_INDEX";
|
||||
case IR_VALUE_VEC_CONTAINS: return "IR_VALUE_VEC_CONTAINS";
|
||||
case IR_VALUE_VEC_INSERT_UNIQUE: return "IR_VALUE_VEC_INSERT_UNIQUE";
|
||||
case IR_VALUE_VEC_REMOVE_VALUE: return "IR_VALUE_VEC_REMOVE_VALUE";
|
||||
case IR_VALUE_ALLOC_BYTES: return "IR_VALUE_ALLOC_BYTES";
|
||||
case IR_VALUE_MAYBE_HAS: return "IR_VALUE_MAYBE_HAS";
|
||||
case IR_VALUE_MAYBE_VALUE: return "IR_VALUE_MAYBE_VALUE";
|
||||
case IR_VALUE_MAYBE_BYTE_VIEW_LITERAL: return "IR_VALUE_MAYBE_BYTE_VIEW_LITERAL";
|
||||
case IR_VALUE_MAYBE_SCALAR_LITERAL: return "IR_VALUE_MAYBE_SCALAR_LITERAL";
|
||||
case IR_VALUE_ARGS_LEN: return "IR_VALUE_ARGS_LEN";
|
||||
case IR_VALUE_ARGS_GET: return "IR_VALUE_ARGS_GET";
|
||||
case IR_VALUE_ARGS_EQ: return "IR_VALUE_ARGS_EQ";
|
||||
case IR_VALUE_ARGS_GET_OR: return "IR_VALUE_ARGS_GET_OR";
|
||||
case IR_VALUE_ARGS_FIND: return "IR_VALUE_ARGS_FIND";
|
||||
case IR_VALUE_ARGS_CONTAINS: return "IR_VALUE_ARGS_CONTAINS";
|
||||
case IR_VALUE_ARGS_VALUE_AFTER: return "IR_VALUE_ARGS_VALUE_AFTER";
|
||||
case IR_VALUE_ARGS_VALUE_AFTER_OR: return "IR_VALUE_ARGS_VALUE_AFTER_OR";
|
||||
case IR_VALUE_ARGS_VALUE_AFTER_PARSE_U32: return "IR_VALUE_ARGS_VALUE_AFTER_PARSE_U32";
|
||||
case IR_VALUE_ENV_GET: return "IR_VALUE_ENV_GET";
|
||||
case IR_VALUE_TIME_WALL_SECONDS: return "IR_VALUE_TIME_WALL_SECONDS";
|
||||
case IR_VALUE_TIME_MONOTONIC: return "IR_VALUE_TIME_MONOTONIC";
|
||||
case IR_VALUE_TIME_AS_MS: return "IR_VALUE_TIME_AS_MS";
|
||||
case IR_VALUE_RAND_NEXT_U32: return "IR_VALUE_RAND_NEXT_U32";
|
||||
case IR_VALUE_RAND_NEXT_BELOW: return "IR_VALUE_RAND_NEXT_BELOW";
|
||||
case IR_VALUE_RAND_RANGE_U32: return "IR_VALUE_RAND_RANGE_U32";
|
||||
case IR_VALUE_RAND_ENTROPY_U32: return "IR_VALUE_RAND_ENTROPY_U32";
|
||||
case IR_VALUE_FS_HOST: return "IR_VALUE_FS_HOST";
|
||||
case IR_VALUE_FS_OPEN: return "IR_VALUE_FS_OPEN";
|
||||
case IR_VALUE_FS_CREATE: return "IR_VALUE_FS_CREATE";
|
||||
case IR_VALUE_FS_READ_PATH: return "IR_VALUE_FS_READ_PATH";
|
||||
case IR_VALUE_FS_WRITE_PATH: return "IR_VALUE_FS_WRITE_PATH";
|
||||
case IR_VALUE_FS_READ_BYTES_PATH: return "IR_VALUE_FS_READ_BYTES_PATH";
|
||||
case IR_VALUE_FS_READ_BYTES_AT_PATH: return "IR_VALUE_FS_READ_BYTES_AT_PATH";
|
||||
case IR_VALUE_FS_WRITE_BYTES_PATH: return "IR_VALUE_FS_WRITE_BYTES_PATH";
|
||||
case IR_VALUE_FS_APPEND_BYTES_PATH: return "IR_VALUE_FS_APPEND_BYTES_PATH";
|
||||
case IR_VALUE_PROC_SPAWN_INHERIT: return "IR_VALUE_PROC_SPAWN_INHERIT";
|
||||
case IR_VALUE_PROC_CAPTURE: return "IR_VALUE_PROC_CAPTURE";
|
||||
case IR_VALUE_PROC_CAPTURE_FILES: return "IR_VALUE_PROC_CAPTURE_FILES";
|
||||
case IR_VALUE_PROC_CHILD_SPAWN: return "IR_VALUE_PROC_CHILD_SPAWN";
|
||||
case IR_VALUE_PROC_CHILD_OP: return "IR_VALUE_PROC_CHILD_OP";
|
||||
case IR_VALUE_PROC_CHILD_IO: return "IR_VALUE_PROC_CHILD_IO";
|
||||
case IR_VALUE_PROC_PTY_RESIZE: return "IR_VALUE_PROC_PTY_RESIZE";
|
||||
case IR_VALUE_FS_READ_ALL: return "IR_VALUE_FS_READ_ALL";
|
||||
case IR_VALUE_FS_READ_FILE: return "IR_VALUE_FS_READ_FILE";
|
||||
case IR_VALUE_FS_WRITE_ALL_FILE: return "IR_VALUE_FS_WRITE_ALL_FILE";
|
||||
case IR_VALUE_FS_CLOSE_FILE: return "IR_VALUE_FS_CLOSE_FILE";
|
||||
case IR_VALUE_FS_EXISTS: return "IR_VALUE_FS_EXISTS";
|
||||
case IR_VALUE_FS_REMOVE: return "IR_VALUE_FS_REMOVE";
|
||||
case IR_VALUE_FS_RENAME: return "IR_VALUE_FS_RENAME";
|
||||
case IR_VALUE_FS_FILE_LEN: return "IR_VALUE_FS_FILE_LEN";
|
||||
case IR_VALUE_FS_MAKE_DIR: return "IR_VALUE_FS_MAKE_DIR";
|
||||
case IR_VALUE_FS_REMOVE_DIR: return "IR_VALUE_FS_REMOVE_DIR";
|
||||
case IR_VALUE_FS_IS_DIR: return "IR_VALUE_FS_IS_DIR";
|
||||
case IR_VALUE_FS_DIR_ENTRY_COUNT: return "IR_VALUE_FS_DIR_ENTRY_COUNT";
|
||||
case IR_VALUE_FS_DIR_ENTRY_NAME: return "IR_VALUE_FS_DIR_ENTRY_NAME";
|
||||
case IR_VALUE_FS_TEMP_NAME: return "IR_VALUE_FS_TEMP_NAME";
|
||||
case IR_VALUE_FS_ATOMIC_WRITE: return "IR_VALUE_FS_ATOMIC_WRITE";
|
||||
case IR_VALUE_JSON_PARSE_BYTES: return "IR_VALUE_JSON_PARSE_BYTES";
|
||||
case IR_VALUE_JSON_VALIDATE_BYTES: return "IR_VALUE_JSON_VALIDATE_BYTES";
|
||||
case IR_VALUE_JSON_STREAM_TOKENS_BYTES: return "IR_VALUE_JSON_STREAM_TOKENS_BYTES";
|
||||
case IR_VALUE_JSON_DIAGNOSTIC_BYTES: return "IR_VALUE_JSON_DIAGNOSTIC_BYTES";
|
||||
case IR_VALUE_JSON_FIELD: return "IR_VALUE_JSON_FIELD";
|
||||
case IR_VALUE_JSON_LOOKUP_SCALAR: return "IR_VALUE_JSON_LOOKUP_SCALAR";
|
||||
case IR_VALUE_JSON_STRING_DECODE: return "IR_VALUE_JSON_STRING_DECODE";
|
||||
case IR_VALUE_JSON_STRING_FIELD: return "IR_VALUE_JSON_STRING_FIELD";
|
||||
case IR_VALUE_JSON_WRITE_STRING: return "IR_VALUE_JSON_WRITE_STRING";
|
||||
case IR_VALUE_JSON_WRITE_RUNTIME: return "IR_VALUE_JSON_WRITE_RUNTIME";
|
||||
case IR_VALUE_JSON_ERROR_LABEL: return "IR_VALUE_JSON_ERROR_LABEL";
|
||||
case IR_VALUE_HTTP_FETCH: return "IR_VALUE_HTTP_FETCH";
|
||||
case IR_VALUE_HTTP_RESULT_OK: return "IR_VALUE_HTTP_RESULT_OK";
|
||||
case IR_VALUE_HTTP_RESULT_STATUS: return "IR_VALUE_HTTP_RESULT_STATUS";
|
||||
case IR_VALUE_HTTP_RESULT_BODY_LEN: return "IR_VALUE_HTTP_RESULT_BODY_LEN";
|
||||
case IR_VALUE_HTTP_RESULT_ERROR: return "IR_VALUE_HTTP_RESULT_ERROR";
|
||||
case IR_VALUE_HTTP_RESPONSE_LEN: return "IR_VALUE_HTTP_RESPONSE_LEN";
|
||||
case IR_VALUE_HTTP_RESPONSE_HEADERS_LEN: return "IR_VALUE_HTTP_RESPONSE_HEADERS_LEN";
|
||||
case IR_VALUE_HTTP_RESPONSE_BODY_OFFSET: return "IR_VALUE_HTTP_RESPONSE_BODY_OFFSET";
|
||||
case IR_VALUE_HTTP_HEADER_VALUE: return "IR_VALUE_HTTP_HEADER_VALUE";
|
||||
case IR_VALUE_HTTP_HEADER_FOUND: return "IR_VALUE_HTTP_HEADER_FOUND";
|
||||
case IR_VALUE_HTTP_HEADER_OFFSET: return "IR_VALUE_HTTP_HEADER_OFFSET";
|
||||
case IR_VALUE_HTTP_HEADER_LEN: return "IR_VALUE_HTTP_HEADER_LEN";
|
||||
case IR_VALUE_HTTP_WRITE_JSON_RESPONSE: return "IR_VALUE_HTTP_WRITE_JSON_RESPONSE";
|
||||
case IR_VALUE_HTTP_REQUEST_METHOD_NAME: return "IR_VALUE_HTTP_REQUEST_METHOD_NAME";
|
||||
case IR_VALUE_HTTP_REQUEST_PATH: return "IR_VALUE_HTTP_REQUEST_PATH";
|
||||
case IR_VALUE_HTTP_REQUEST_MATCHES: return "IR_VALUE_HTTP_REQUEST_MATCHES";
|
||||
case IR_VALUE_HTTP_REQUEST_BODY_WITHIN: return "IR_VALUE_HTTP_REQUEST_BODY_WITHIN";
|
||||
case IR_VALUE_HTTP_STATUS_CLASS: return "IR_VALUE_HTTP_STATUS_CLASS";
|
||||
case IR_VALUE_FIELD_LOAD: return "IR_VALUE_FIELD_LOAD";
|
||||
case IR_VALUE_CHECK: return "IR_VALUE_CHECK";
|
||||
case IR_VALUE_RESCUE: return "IR_VALUE_RESCUE";
|
||||
case IR_VALUE_RECORD_ADDR: return "IR_VALUE_RECORD_ADDR";
|
||||
}
|
||||
return "IR_VALUE_UNKNOWN";
|
||||
}
|
||||
|
||||
bool z_build_is_elf_scalar(IrTypeKind type) {
|
||||
return type == IR_TYPE_BOOL || type == IR_TYPE_U8 || type == IR_TYPE_U16 || type == IR_TYPE_USIZE ||
|
||||
type == IR_TYPE_I32 || type == IR_TYPE_U32 || type == IR_TYPE_I64 || type == IR_TYPE_U64;
|
||||
}
|
||||
|
||||
bool z_build_is_scalar32(IrTypeKind type) {
|
||||
return type == IR_TYPE_BOOL || type == IR_TYPE_U8 || type == IR_TYPE_U16 ||
|
||||
type == IR_TYPE_I32 || type == IR_TYPE_U32;
|
||||
}
|
||||
|
||||
bool z_build_diag(const ZBuildability *ctx, ZDiag *diag, const char *message, int line, int column, const char *actual) {
|
||||
if (!diag) return false;
|
||||
memset(diag, 0, sizeof(*diag));
|
||||
diag->code = 2004;
|
||||
diag->line = line > 0 ? line : 1;
|
||||
diag->column = column > 0 ? column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message ? message : "direct backend buildability check failed");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", ctx && ctx->expected ? ctx->expected : "direct backend buildability subset");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual && actual[0] ? actual : "unsupported construct");
|
||||
snprintf(diag->help, sizeof(diag->help), "%s", ctx && ctx->help ? ctx->help : "choose a supported direct target or simplify the program for this backend");
|
||||
ZBackendBlocker blocker;
|
||||
z_backend_blocker_set(&blocker,
|
||||
ctx && ctx->target && ctx->target->name ? ctx->target->name : "unknown",
|
||||
ctx && ctx->target && ctx->target->object_format ? ctx->target->object_format : "unknown",
|
||||
ctx && ctx->backend_name ? ctx->backend_name : "unknown",
|
||||
"buildability",
|
||||
diag->actual);
|
||||
z_diag_set_backend_blocker(diag, &blocker);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *build_expected_for_backend(ZDirectBackend backend, bool executable) {
|
||||
switch (backend) {
|
||||
case Z_DIRECT_BACKEND_ELF64: return executable ? "direct ELF64 executable buildability subset" : "direct ELF64 object buildability subset";
|
||||
case Z_DIRECT_BACKEND_ELF_AARCH64: return executable ? "direct AArch64 ELF executable subset" : "direct AArch64 ELF object subset";
|
||||
case Z_DIRECT_BACKEND_MACHO64: return executable ? "direct AArch64 Mach-O executable buildability subset" : "direct AArch64 Mach-O object buildability subset";
|
||||
case Z_DIRECT_BACKEND_MACHO_X64: return executable ? "direct x86_64 Mach-O executable buildability subset" : "direct x86_64 Mach-O object buildability subset";
|
||||
case Z_DIRECT_BACKEND_COFF_X64: return executable ? "direct COFF x64 executable buildability subset" : "direct COFF x64 object buildability subset";
|
||||
case Z_DIRECT_BACKEND_COFF_AARCH64: return executable ? "direct COFF AArch64 executable subset" : "direct COFF AArch64 object subset";
|
||||
default: return "direct backend buildability subset";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *build_help_for_backend(ZDirectBackend backend) {
|
||||
switch (backend) {
|
||||
case Z_DIRECT_BACKEND_ELF_AARCH64:
|
||||
return "choose a supported direct target or reduce the program to AArch64 ELF supported direct-backend constructs";
|
||||
case Z_DIRECT_BACKEND_COFF_X64:
|
||||
return "reduce the program to primitive direct-backend constructs or choose a supported direct target";
|
||||
case Z_DIRECT_BACKEND_COFF_AARCH64:
|
||||
return "choose a supported direct target or reduce the program to AArch64 COFF supported direct-backend constructs";
|
||||
case Z_DIRECT_BACKEND_MACHO_X64:
|
||||
return "choose a supported direct target or reduce the program to x86_64 Mach-O supported direct-backend constructs";
|
||||
case Z_DIRECT_BACKEND_MACHO64:
|
||||
return "choose a supported direct target or reduce the program to Mach-O supported direct-backend constructs";
|
||||
default:
|
||||
return "choose a supported direct target or restrict this program to the ELF64 direct-backend subset";
|
||||
}
|
||||
}
|
||||
|
||||
bool z_build_select(const IrProgram *ir, const ZTargetInfo *target, const char *emit_kind, ZBuildability *ctx, ZDiag *diag) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
bool executable = emit_kind && strcmp(emit_kind, "exe") == 0;
|
||||
ctx->target = target;
|
||||
ctx->emit_kind = executable ? "exe" : "obj";
|
||||
ctx->executable = executable;
|
||||
ctx->backend = executable ? z_direct_exe_backend(target) : z_direct_object_backend(target);
|
||||
ctx->backend_name = executable ? z_direct_backend_exe_emitter(ctx->backend) : z_direct_backend_object_emitter(ctx->backend);
|
||||
ctx->expected = build_expected_for_backend(ctx->backend, executable);
|
||||
ctx->help = build_help_for_backend(ctx->backend);
|
||||
if (!ir) return z_build_diag(ctx, diag, "direct backend buildability requires MIR", 1, 1, "missing MIR");
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_NONE) {
|
||||
return z_build_diag(ctx, diag, "direct backend does not support this target and artifact kind", 1, 1, target && target->name ? target->name : "unknown target");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef ZERO_C_BUILDABILITY_INTERNAL_H
|
||||
#define ZERO_C_BUILDABILITY_INTERNAL_H
|
||||
|
||||
#include "buildability.h"
|
||||
|
||||
typedef struct {
|
||||
const ZTargetInfo *target;
|
||||
const char *emit_kind;
|
||||
const char *backend_name;
|
||||
const char *expected;
|
||||
const char *help;
|
||||
ZDirectBackend backend;
|
||||
bool executable;
|
||||
} ZBuildability;
|
||||
|
||||
enum { BUILD_MACHO_SCRATCH_SLOT_COUNT = 32u, BUILD_AARCH64_SCRATCH_SLOT_COUNT = 32u };
|
||||
|
||||
const char *z_build_type_name(IrTypeKind type);
|
||||
const char *z_build_value_kind_name(IrValueKind kind);
|
||||
bool z_build_is_elf_scalar(IrTypeKind type);
|
||||
bool z_build_is_scalar32(IrTypeKind type);
|
||||
bool z_build_diag(const ZBuildability *ctx, ZDiag *diag, const char *message, int line, int column, const char *actual);
|
||||
bool z_build_select(const IrProgram *ir, const ZTargetInfo *target, const char *emit_kind, ZBuildability *ctx, ZDiag *diag);
|
||||
bool z_build_backend_is_aarch64_direct(ZDirectBackend backend);
|
||||
bool z_build_check_coff_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_coff_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_macho_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_macho_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_macho_x64_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_macho_x64_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_aarch64_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_aarch64_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_aarch64_world_write_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
bool z_build_check_aarch64_function_shape(const ZBuildability *ctx, const IrFunction *fun, ZDiag *diag);
|
||||
bool z_build_value_supported(const ZBuildability *ctx, const IrValue *value, bool local_set_value);
|
||||
bool z_build_check_value(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, bool local_set_value, unsigned scratch_slot, ZDiag *diag);
|
||||
bool z_build_check_target_value(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, bool *skip_left, unsigned *right_slot, ZDiag *diag);
|
||||
unsigned z_build_target_call_arg_slot(const ZBuildability *ctx, const IrValue *value, unsigned scratch_slot);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "buildability_internal.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum {
|
||||
BUILD_AARCH64_IMM12_MAX = 4095u
|
||||
};
|
||||
|
||||
static bool build_const_u32_value(const IrValue *value, unsigned *out) {
|
||||
if (!value || value->kind != IR_VALUE_INT || value->int_value > UINT32_MAX) return false;
|
||||
if (out) *out = (unsigned)value->int_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static IrTypeKind build_view_element_type(const IrValue *view) {
|
||||
return view && view->element_type != IR_TYPE_UNSUPPORTED ? view->element_type : IR_TYPE_U8;
|
||||
}
|
||||
|
||||
static unsigned build_type_index_shift(IrTypeKind type) {
|
||||
if (type == IR_TYPE_U8 || type == IR_TYPE_BOOL) return 0;
|
||||
if (type == IR_TYPE_U16) return 1;
|
||||
if (type == IR_TYPE_I64 || type == IR_TYPE_U64) return 3;
|
||||
return 2;
|
||||
}
|
||||
|
||||
static bool build_scaled_index_exceeds_imm12(unsigned start, IrTypeKind element_type) {
|
||||
return start > ((unsigned)BUILD_AARCH64_IMM12_MAX >> build_type_index_shift(element_type));
|
||||
}
|
||||
|
||||
static bool build_byte_view_const_len(const IrValue *view, unsigned *out) {
|
||||
if (!view) return false;
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL || view->kind == IR_VALUE_ARRAY_BYTE_VIEW) {
|
||||
if (out) *out = view->data_len;
|
||||
return true;
|
||||
}
|
||||
if (view->kind == IR_VALUE_BYTE_SLICE) {
|
||||
unsigned base_len = 0;
|
||||
if (!build_byte_view_const_len(view->left, &base_len)) return false;
|
||||
unsigned start = 0;
|
||||
unsigned end = base_len;
|
||||
if (view->index && !build_const_u32_value(view->index, &start)) return false;
|
||||
if (view->right && !build_const_u32_value(view->right, &end)) return false;
|
||||
if (start > end || end > base_len) return false;
|
||||
if (out) *out = end - start;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool build_array_byte_view_has_storage(const IrFunction *fun, const IrValue *view) {
|
||||
if (!view || view->kind != IR_VALUE_ARRAY_BYTE_VIEW || !fun || view->array_index >= fun->local_len) return false;
|
||||
const IrLocal *local = &fun->locals[view->array_index];
|
||||
return (local->is_array && view->field_offset == 0) || local->is_record || local->is_record_ref;
|
||||
}
|
||||
|
||||
static bool build_runtime_byte_view_result(const IrValue *view) {
|
||||
return view && (view->kind == IR_VALUE_STR_RUNTIME || view->kind == IR_VALUE_ARGS_GET_OR || view->kind == IR_VALUE_ARGS_VALUE_AFTER_OR) && view->type == IR_TYPE_BYTE_VIEW;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *missing_view;
|
||||
const char *array_requires_storage;
|
||||
const char *unsupported_view;
|
||||
const char *unsupported_len;
|
||||
} BuildX64ByteViewDiagText;
|
||||
|
||||
static const BuildX64ByteViewDiagText BUILD_COFF_BYTE_VIEW_DIAG = {
|
||||
"direct COFF byte view is missing",
|
||||
"direct COFF byte-view array requires a fixed array or record array field",
|
||||
"direct COFF value is not a supported byte view",
|
||||
"direct COFF byte-view length currently requires a literal, constant slice, or byte-view local",
|
||||
};
|
||||
|
||||
static const BuildX64ByteViewDiagText BUILD_MACHO_X64_BYTE_VIEW_DIAG = {
|
||||
"direct x86_64 Mach-O byte view is missing",
|
||||
"direct x86_64 Mach-O byte-view array requires a fixed array or record array field",
|
||||
"direct x86_64 Mach-O value is not a supported byte view",
|
||||
"direct x86_64 Mach-O byte-view length currently requires a literal, constant slice, or byte-view local",
|
||||
};
|
||||
|
||||
static bool build_byte_view_direct_result(const IrFunction *fun, const IrValue *view) {
|
||||
if (!view) return false;
|
||||
if (view->kind == IR_VALUE_LOCAL && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_VEC_BYTES && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_VEC) return true;
|
||||
if (view->kind == IR_VALUE_MAYBE_VALUE && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_MAYBE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_CALL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_FIELD_LOAD && view->type == IR_TYPE_BYTE_VIEW && fun && view->local_index < fun->local_len &&
|
||||
(fun->locals[view->local_index].is_record || fun->locals[view->local_index].is_record_ref)) return true;
|
||||
if (view->kind == IR_VALUE_JSON_ERROR_LABEL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (build_runtime_byte_view_result(view)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool build_check_x64_byte_view_ptr(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, const BuildX64ByteViewDiagText *text, ZDiag *diag) {
|
||||
if (!view) return z_build_diag(ctx, diag, text->missing_view, 1, 1, "missing byte view");
|
||||
if (build_byte_view_direct_result(fun, view)) return true;
|
||||
if (view->kind == IR_VALUE_ARRAY_BYTE_VIEW && fun && view->array_index < fun->local_len) {
|
||||
if (!build_array_byte_view_has_storage(fun, view)) return z_build_diag(ctx, diag, text->array_requires_storage, view->line, view->column, "unsupported array view");
|
||||
return true;
|
||||
}
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL) return true;
|
||||
if (view->kind == IR_VALUE_BYTE_SLICE) {
|
||||
return build_check_x64_byte_view_ptr(ctx, fun, view->left, text, diag);
|
||||
}
|
||||
return z_build_diag(ctx, diag, text->unsupported_view, view->line, view->column, "unsupported byte view");
|
||||
}
|
||||
|
||||
static bool build_check_x64_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, const BuildX64ByteViewDiagText *text, ZDiag *diag) {
|
||||
unsigned len = 0;
|
||||
if (build_byte_view_const_len(view, &len)) return true;
|
||||
if (build_byte_view_direct_result(fun, view)) return true;
|
||||
if (view && view->kind == IR_VALUE_BYTE_SLICE) return true;
|
||||
return z_build_diag(ctx, diag, text->unsupported_len, view ? view->line : 1, view ? view->column : 1, "unsupported byte view length");
|
||||
}
|
||||
|
||||
bool z_build_check_coff_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
return build_check_x64_byte_view_len(ctx, fun, view, &BUILD_COFF_BYTE_VIEW_DIAG, diag);
|
||||
}
|
||||
|
||||
bool z_build_check_coff_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
return build_check_x64_byte_view_ptr(ctx, fun, view, &BUILD_COFF_BYTE_VIEW_DIAG, diag) && z_build_check_coff_byte_view_len(ctx, fun, view, diag);
|
||||
}
|
||||
|
||||
bool z_build_check_macho_x64_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
return build_check_x64_byte_view_len(ctx, fun, view, &BUILD_MACHO_X64_BYTE_VIEW_DIAG, diag);
|
||||
}
|
||||
|
||||
bool z_build_check_macho_x64_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
return build_check_x64_byte_view_ptr(ctx, fun, view, &BUILD_MACHO_X64_BYTE_VIEW_DIAG, diag) && z_build_check_macho_x64_byte_view_len(ctx, fun, view, diag);
|
||||
}
|
||||
|
||||
static bool build_check_macho_byte_view_ptr(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
if (!view) return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte view is missing", 1, 1, "missing byte view");
|
||||
if (view->kind == IR_VALUE_LOCAL && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_VEC_BYTES && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_VEC) return true;
|
||||
if (view->kind == IR_VALUE_MAYBE_VALUE && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_MAYBE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_CALL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_FIELD_LOAD && view->type == IR_TYPE_BYTE_VIEW && fun && view->local_index < fun->local_len &&
|
||||
(fun->locals[view->local_index].is_record || fun->locals[view->local_index].is_record_ref)) return true;
|
||||
if (view->kind == IR_VALUE_JSON_ERROR_LABEL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (build_runtime_byte_view_result(view)) return true;
|
||||
if (view->kind == IR_VALUE_ARRAY_BYTE_VIEW && fun && view->array_index < fun->local_len) {
|
||||
if (!build_array_byte_view_has_storage(fun, view)) return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte-view array requires a fixed array or record array field", view->line, view->column, "unsupported array view");
|
||||
return true;
|
||||
}
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL) return true;
|
||||
if (view->kind == IR_VALUE_BYTE_SLICE) {
|
||||
unsigned start = 0;
|
||||
if (!build_check_macho_byte_view_ptr(ctx, fun, view->left, diag)) return false;
|
||||
if (build_const_u32_value(view->index, &start) && build_scaled_index_exceeds_imm12(start, build_view_element_type(view))) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte slice constant start is too large", view->line, view->column, "unsupported byte slice");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O value is not a supported byte view", view->line, view->column, "unsupported byte view");
|
||||
}
|
||||
|
||||
bool z_build_check_macho_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
unsigned len = 0;
|
||||
if (build_byte_view_const_len(view, &len)) {
|
||||
if (len > Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte-view length is too large for the this backend", view->line, view->column, "large byte view");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!view) return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte view is missing", 1, 1, "missing byte view");
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL || view->kind == IR_VALUE_ARRAY_BYTE_VIEW) {
|
||||
if (view->data_len > Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte-view length is too large for the this backend", view->line, view->column, "large byte view");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (view->kind == IR_VALUE_LOCAL && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_VEC_BYTES && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_VEC) return true;
|
||||
if (view->kind == IR_VALUE_MAYBE_VALUE && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_MAYBE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_CALL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_FIELD_LOAD && view->type == IR_TYPE_BYTE_VIEW && fun && view->local_index < fun->local_len &&
|
||||
(fun->locals[view->local_index].is_record || fun->locals[view->local_index].is_record_ref)) return true;
|
||||
if (view->kind == IR_VALUE_JSON_ERROR_LABEL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (build_runtime_byte_view_result(view)) return true;
|
||||
if (view->kind == IR_VALUE_BYTE_SLICE) {
|
||||
return z_build_check_macho_byte_view_len(ctx, fun, view->left, diag);
|
||||
}
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte-view length currently requires a literal, constant slice, or byte-view local", view->line, view->column, "unsupported byte view length");
|
||||
}
|
||||
|
||||
bool z_build_check_macho_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
return build_check_macho_byte_view_ptr(ctx, fun, view, diag) && z_build_check_macho_byte_view_len(ctx, fun, view, diag);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
#include "buildability_internal.h"
|
||||
|
||||
static bool build_backend_is_native_graph_runtime(ZDirectBackend backend) {
|
||||
return backend == Z_DIRECT_BACKEND_ELF64 || backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
}
|
||||
|
||||
static bool build_backend_has_byte_runtime(ZDirectBackend backend) {
|
||||
return build_backend_is_native_graph_runtime(backend) ||
|
||||
backend == Z_DIRECT_BACKEND_MACHO_X64 ||
|
||||
backend == Z_DIRECT_BACKEND_COFF_X64;
|
||||
}
|
||||
|
||||
static bool build_backend_supports_hosted_runtime(const ZBuildability *ctx) {
|
||||
return ctx && (build_backend_is_native_graph_runtime(ctx->backend) ||
|
||||
(ctx->backend == Z_DIRECT_BACKEND_COFF_X64 && !ctx->executable));
|
||||
}
|
||||
|
||||
static bool build_backend_supports_json_parse(const ZBuildability *ctx) {
|
||||
if (!ctx) return false;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64) return !ctx->executable;
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
}
|
||||
|
||||
static bool build_backend_supports_json_validate(const ZBuildability *ctx) {
|
||||
if (!ctx) return false;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 || ctx->backend == Z_DIRECT_BACKEND_COFF_X64) return !ctx->executable;
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
}
|
||||
|
||||
static bool build_backend_supports_aarch64_hosted_runtime(ZDirectBackend backend) {
|
||||
return backend == Z_DIRECT_BACKEND_ELF_AARCH64 || backend == Z_DIRECT_BACKEND_COFF_AARCH64;
|
||||
}
|
||||
|
||||
static bool build_value_supported_aarch64(ZDirectBackend backend, IrValueKind kind) {
|
||||
switch (kind) {
|
||||
case IR_VALUE_INT: case IR_VALUE_BOOL: case IR_VALUE_LOCAL: case IR_VALUE_CAST: case IR_VALUE_BINARY: case IR_VALUE_COMPARE: case IR_VALUE_CALL:
|
||||
case IR_VALUE_STRING_LITERAL: case IR_VALUE_ARRAY_BYTE_VIEW: case IR_VALUE_BYTE_SLICE: case IR_VALUE_BYTE_VIEW_LEN: case IR_VALUE_BYTE_VIEW_REMAINING:
|
||||
case IR_VALUE_JSON_ERROR_LABEL:
|
||||
case IR_VALUE_BYTE_VIEW_INDEX_LOAD: case IR_VALUE_BYTE_COPY: case IR_VALUE_BYTE_FILL:
|
||||
case IR_VALUE_ITEM_COPY: case IR_VALUE_ITEM_FILL: case IR_VALUE_ITEM_CONTAINS:
|
||||
case IR_VALUE_BYTE_VIEW_EQ:
|
||||
case IR_VALUE_INDEX_LOAD: case IR_VALUE_FIELD_LOAD: case IR_VALUE_RECORD_ADDR:
|
||||
case IR_VALUE_MAYBE_HAS: case IR_VALUE_MAYBE_VALUE: case IR_VALUE_MAYBE_BYTE_VIEW_LITERAL:
|
||||
case IR_VALUE_MAYBE_SCALAR_LITERAL: case IR_VALUE_RAND_NEXT_U32: case IR_VALUE_RAND_NEXT_BELOW: case IR_VALUE_RAND_RANGE_U32: case IR_VALUE_CRC32_BYTES:
|
||||
return true;
|
||||
case IR_VALUE_ASCII_RUNTIME:
|
||||
case IR_VALUE_TEXT_RUNTIME:
|
||||
case IR_VALUE_PARSE_RUNTIME:
|
||||
case IR_VALUE_STR_RUNTIME:
|
||||
case IR_VALUE_PARSE_I32:
|
||||
case IR_VALUE_PARSE_U32:
|
||||
case IR_VALUE_FMT_BOOL:
|
||||
case IR_VALUE_FMT_HEX_U32:
|
||||
case IR_VALUE_FMT_I32:
|
||||
case IR_VALUE_FMT_U32:
|
||||
case IR_VALUE_FMT_USIZE:
|
||||
case IR_VALUE_TIME_RUNTIME:
|
||||
case IR_VALUE_TERM_RUNTIME:
|
||||
case IR_VALUE_MATH_RUNTIME:
|
||||
case IR_VALUE_SEARCH_RUNTIME:
|
||||
case IR_VALUE_SORT_RUNTIME:
|
||||
case IR_VALUE_JSON_VALIDATE_BYTES:
|
||||
case IR_VALUE_JSON_STREAM_TOKENS_BYTES:
|
||||
case IR_VALUE_JSON_DIAGNOSTIC_BYTES:
|
||||
case IR_VALUE_JSON_FIELD:
|
||||
case IR_VALUE_JSON_LOOKUP_SCALAR:
|
||||
case IR_VALUE_JSON_STRING_DECODE:
|
||||
case IR_VALUE_JSON_STRING_FIELD:
|
||||
case IR_VALUE_JSON_WRITE_STRING:
|
||||
case IR_VALUE_JSON_WRITE_RUNTIME:
|
||||
case IR_VALUE_HTTP_REQUEST_METHOD_NAME:
|
||||
case IR_VALUE_HTTP_REQUEST_PATH:
|
||||
case IR_VALUE_PROC_SPAWN_INHERIT:
|
||||
case IR_VALUE_PROC_CAPTURE:
|
||||
case IR_VALUE_PROC_CAPTURE_FILES:
|
||||
case IR_VALUE_PROC_CHILD_SPAWN:
|
||||
case IR_VALUE_PROC_CHILD_OP:
|
||||
case IR_VALUE_PROC_CHILD_IO:
|
||||
case IR_VALUE_PROC_PTY_RESIZE:
|
||||
return build_backend_supports_aarch64_hosted_runtime(backend);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool build_value_supported_macho_x64(IrValueKind kind) {
|
||||
switch (kind) {
|
||||
case IR_VALUE_INT: case IR_VALUE_BOOL: case IR_VALUE_LOCAL: case IR_VALUE_CAST: case IR_VALUE_BINARY: case IR_VALUE_COMPARE: case IR_VALUE_CALL:
|
||||
case IR_VALUE_STRING_LITERAL: case IR_VALUE_ARRAY_BYTE_VIEW: case IR_VALUE_BYTE_SLICE: case IR_VALUE_BYTE_VIEW_LEN: case IR_VALUE_BYTE_VIEW_REMAINING:
|
||||
case IR_VALUE_JSON_ERROR_LABEL:
|
||||
case IR_VALUE_BYTE_VIEW_INDEX_LOAD: case IR_VALUE_BYTE_COPY: case IR_VALUE_BYTE_FILL:
|
||||
case IR_VALUE_ITEM_COPY: case IR_VALUE_ITEM_FILL: case IR_VALUE_ITEM_CONTAINS:
|
||||
case IR_VALUE_BYTE_VIEW_EQ:
|
||||
case IR_VALUE_INDEX_LOAD: case IR_VALUE_FIELD_LOAD: case IR_VALUE_RECORD_ADDR: case IR_VALUE_CHECK:
|
||||
case IR_VALUE_MAYBE_HAS: case IR_VALUE_MAYBE_VALUE: case IR_VALUE_MAYBE_BYTE_VIEW_LITERAL: case IR_VALUE_MAYBE_SCALAR_LITERAL:
|
||||
case IR_VALUE_RAND_NEXT_U32: case IR_VALUE_RAND_NEXT_BELOW: case IR_VALUE_RAND_RANGE_U32: case IR_VALUE_CRC32_BYTES:
|
||||
case IR_VALUE_ASCII_RUNTIME: case IR_VALUE_TEXT_RUNTIME: case IR_VALUE_PARSE_RUNTIME:
|
||||
case IR_VALUE_PARSE_I32: case IR_VALUE_PARSE_U32:
|
||||
case IR_VALUE_FMT_BOOL: case IR_VALUE_FMT_HEX_U32: case IR_VALUE_FMT_I32: case IR_VALUE_FMT_U32: case IR_VALUE_FMT_USIZE:
|
||||
case IR_VALUE_STR_RUNTIME: case IR_VALUE_TIME_RUNTIME: case IR_VALUE_TERM_RUNTIME: case IR_VALUE_MATH_RUNTIME:
|
||||
case IR_VALUE_SEARCH_RUNTIME: case IR_VALUE_SORT_RUNTIME:
|
||||
case IR_VALUE_JSON_PARSE_BYTES: case IR_VALUE_JSON_VALIDATE_BYTES: case IR_VALUE_JSON_STREAM_TOKENS_BYTES: case IR_VALUE_JSON_DIAGNOSTIC_BYTES: case IR_VALUE_JSON_FIELD: case IR_VALUE_JSON_LOOKUP_SCALAR: case IR_VALUE_JSON_STRING_DECODE: case IR_VALUE_JSON_STRING_FIELD: case IR_VALUE_JSON_WRITE_STRING: case IR_VALUE_JSON_WRITE_RUNTIME:
|
||||
case IR_VALUE_HTTP_REQUEST_METHOD_NAME: case IR_VALUE_HTTP_REQUEST_PATH:
|
||||
case IR_VALUE_PROC_SPAWN_INHERIT:
|
||||
case IR_VALUE_PROC_CAPTURE:
|
||||
case IR_VALUE_PROC_CAPTURE_FILES:
|
||||
case IR_VALUE_PROC_CHILD_SPAWN:
|
||||
case IR_VALUE_PROC_CHILD_OP:
|
||||
case IR_VALUE_PROC_CHILD_IO:
|
||||
case IR_VALUE_PROC_PTY_RESIZE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool build_value_supported_generic(const ZBuildability *ctx, const IrValue *value, bool local_set_value) {
|
||||
switch (value->kind) {
|
||||
case IR_VALUE_INT: case IR_VALUE_BOOL: case IR_VALUE_LOCAL: case IR_VALUE_CAST: case IR_VALUE_BINARY: case IR_VALUE_COMPARE: case IR_VALUE_CALL:
|
||||
case IR_VALUE_STRING_LITERAL: case IR_VALUE_ARRAY_BYTE_VIEW: case IR_VALUE_BYTE_SLICE: case IR_VALUE_BYTE_VIEW_LEN: case IR_VALUE_BYTE_VIEW_REMAINING:
|
||||
case IR_VALUE_JSON_ERROR_LABEL:
|
||||
case IR_VALUE_BYTE_VIEW_INDEX_LOAD: case IR_VALUE_INDEX_LOAD: case IR_VALUE_FIELD_LOAD: case IR_VALUE_RECORD_ADDR:
|
||||
return true;
|
||||
case IR_VALUE_MAYBE_BYTE_VIEW_LITERAL:
|
||||
return true;
|
||||
case IR_VALUE_MAYBE_SCALAR_LITERAL:
|
||||
return true;
|
||||
case IR_VALUE_FIXED_BUF_ALLOC: case IR_VALUE_VEC_INIT: case IR_VALUE_ALLOC_BYTES:
|
||||
return local_set_value;
|
||||
case IR_VALUE_VEC_PUSH: case IR_VALUE_VEC_LEN: case IR_VALUE_VEC_CAPACITY:
|
||||
case IR_VALUE_VEC_BYTES: case IR_VALUE_VEC_GET: case IR_VALUE_VEC_SET: case IR_VALUE_VEC_CLEAR: case IR_VALUE_VEC_POP: case IR_VALUE_VEC_TRUNCATE: case IR_VALUE_VEC_REMOVE_SWAP:
|
||||
case IR_VALUE_VEC_INDEX: case IR_VALUE_VEC_CONTAINS: case IR_VALUE_VEC_INSERT_UNIQUE: case IR_VALUE_VEC_REMOVE_VALUE: case IR_VALUE_MAYBE_HAS:
|
||||
return true;
|
||||
case IR_VALUE_ARGS_GET:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend) ? local_set_value : false;
|
||||
case IR_VALUE_ARGS_EQ:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
case IR_VALUE_ARGS_GET_OR:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
case IR_VALUE_ARGS_VALUE_AFTER:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend) ? local_set_value : false;
|
||||
case IR_VALUE_ARGS_VALUE_AFTER_OR:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
case IR_VALUE_ARGS_LEN:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
case IR_VALUE_ENV_GET:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend) && local_set_value;
|
||||
case IR_VALUE_TIME_WALL_SECONDS: case IR_VALUE_TIME_MONOTONIC: case IR_VALUE_TIME_AS_MS:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64;
|
||||
case IR_VALUE_RAND_ENTROPY_U32:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
case IR_VALUE_RAND_NEXT_U32:
|
||||
return build_backend_has_byte_runtime(ctx->backend);
|
||||
case IR_VALUE_RAND_NEXT_BELOW:
|
||||
case IR_VALUE_RAND_RANGE_U32:
|
||||
return build_backend_has_byte_runtime(ctx->backend);
|
||||
case IR_VALUE_FS_HOST:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend);
|
||||
case IR_VALUE_FS_READ_BYTES_PATH: case IR_VALUE_FS_READ_BYTES_AT_PATH: case IR_VALUE_FS_WRITE_BYTES_PATH: case IR_VALUE_FS_APPEND_BYTES_PATH:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
case IR_VALUE_FS_EXISTS: case IR_VALUE_FS_REMOVE: case IR_VALUE_FS_RENAME: case IR_VALUE_FS_ATOMIC_WRITE:
|
||||
case IR_VALUE_FS_MAKE_DIR: case IR_VALUE_FS_REMOVE_DIR: case IR_VALUE_FS_IS_DIR:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
case IR_VALUE_FS_OPEN: case IR_VALUE_FS_CREATE: case IR_VALUE_FS_READ_PATH:
|
||||
case IR_VALUE_FS_WRITE_PATH:
|
||||
case IR_VALUE_FS_READ_ALL: case IR_VALUE_FS_READ_FILE: case IR_VALUE_FS_WRITE_ALL_FILE:
|
||||
case IR_VALUE_FS_CLOSE_FILE:
|
||||
case IR_VALUE_FS_FILE_LEN:
|
||||
case IR_VALUE_FS_TEMP_NAME:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64;
|
||||
case IR_VALUE_FS_DIR_ENTRY_COUNT:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
case IR_VALUE_FS_DIR_ENTRY_NAME:
|
||||
return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
case IR_VALUE_CRC32_BYTES:
|
||||
return build_backend_has_byte_runtime(ctx->backend) || z_build_backend_is_aarch64_direct(ctx->backend);
|
||||
case IR_VALUE_BYTE_COPY: case IR_VALUE_BYTE_FILL:
|
||||
case IR_VALUE_ITEM_COPY: case IR_VALUE_ITEM_FILL: case IR_VALUE_ITEM_CONTAINS:
|
||||
return build_backend_has_byte_runtime(ctx->backend);
|
||||
case IR_VALUE_BYTE_VIEW_EQ:
|
||||
return build_backend_has_byte_runtime(ctx->backend);
|
||||
case IR_VALUE_STR_RUNTIME:
|
||||
return build_backend_supports_hosted_runtime(ctx);
|
||||
case IR_VALUE_ASCII_RUNTIME:
|
||||
case IR_VALUE_TEXT_RUNTIME:
|
||||
case IR_VALUE_PARSE_RUNTIME:
|
||||
case IR_VALUE_PARSE_I32:
|
||||
case IR_VALUE_PARSE_U32:
|
||||
case IR_VALUE_FMT_BOOL:
|
||||
case IR_VALUE_FMT_HEX_U32:
|
||||
case IR_VALUE_FMT_I32:
|
||||
case IR_VALUE_FMT_U32:
|
||||
case IR_VALUE_FMT_USIZE:
|
||||
case IR_VALUE_PROC_SPAWN_INHERIT:
|
||||
case IR_VALUE_PROC_CAPTURE:
|
||||
case IR_VALUE_PROC_CAPTURE_FILES:
|
||||
case IR_VALUE_PROC_CHILD_SPAWN:
|
||||
case IR_VALUE_PROC_CHILD_OP:
|
||||
case IR_VALUE_PROC_CHILD_IO:
|
||||
case IR_VALUE_PROC_PTY_RESIZE:
|
||||
return build_backend_supports_hosted_runtime(ctx);
|
||||
case IR_VALUE_TIME_RUNTIME:
|
||||
return build_backend_supports_hosted_runtime(ctx);
|
||||
case IR_VALUE_TERM_RUNTIME:
|
||||
return build_backend_supports_hosted_runtime(ctx);
|
||||
case IR_VALUE_MATH_RUNTIME:
|
||||
return build_backend_supports_hosted_runtime(ctx);
|
||||
case IR_VALUE_STR_CONTAINS:
|
||||
case IR_VALUE_SEARCH_RUNTIME: case IR_VALUE_SORT_RUNTIME:
|
||||
case IR_VALUE_ARGS_PARSE_U32: case IR_VALUE_ARGS_FIND: case IR_VALUE_ARGS_CONTAINS:
|
||||
case IR_VALUE_ARGS_VALUE_AFTER_PARSE_U32:
|
||||
return build_backend_supports_hosted_runtime(ctx);
|
||||
case IR_VALUE_CHECK: return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64 || ctx->backend == Z_DIRECT_BACKEND_MACHO_X64;
|
||||
case IR_VALUE_RESCUE: return ctx->backend == Z_DIRECT_BACKEND_ELF64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64;
|
||||
case IR_VALUE_MAYBE_VALUE:
|
||||
return build_backend_has_byte_runtime(ctx->backend);
|
||||
case IR_VALUE_JSON_PARSE_BYTES:
|
||||
return build_backend_supports_json_parse(ctx);
|
||||
case IR_VALUE_JSON_VALIDATE_BYTES: case IR_VALUE_JSON_STREAM_TOKENS_BYTES: case IR_VALUE_JSON_DIAGNOSTIC_BYTES: case IR_VALUE_JSON_FIELD: case IR_VALUE_JSON_LOOKUP_SCALAR: case IR_VALUE_JSON_STRING_DECODE: case IR_VALUE_JSON_STRING_FIELD: case IR_VALUE_JSON_WRITE_STRING: case IR_VALUE_JSON_WRITE_RUNTIME:
|
||||
return build_backend_supports_json_validate(ctx);
|
||||
case IR_VALUE_HTTP_FETCH: case IR_VALUE_HTTP_RESULT_OK: case IR_VALUE_HTTP_RESULT_STATUS: case IR_VALUE_HTTP_RESULT_BODY_LEN:
|
||||
case IR_VALUE_HTTP_RESULT_ERROR: case IR_VALUE_HTTP_RESPONSE_LEN: case IR_VALUE_HTTP_RESPONSE_HEADERS_LEN:
|
||||
case IR_VALUE_HTTP_RESPONSE_BODY_OFFSET: case IR_VALUE_HTTP_HEADER_VALUE: case IR_VALUE_HTTP_HEADER_FOUND:
|
||||
case IR_VALUE_HTTP_HEADER_OFFSET: case IR_VALUE_HTTP_HEADER_LEN: case IR_VALUE_HTTP_WRITE_JSON_RESPONSE:
|
||||
case IR_VALUE_HTTP_REQUEST_METHOD_NAME: case IR_VALUE_HTTP_REQUEST_PATH: case IR_VALUE_HTTP_REQUEST_MATCHES:
|
||||
case IR_VALUE_HTTP_REQUEST_BODY_WITHIN:
|
||||
case IR_VALUE_HTTP_STATUS_CLASS:
|
||||
return build_backend_is_native_graph_runtime(ctx->backend) ||
|
||||
((value->kind == IR_VALUE_HTTP_REQUEST_METHOD_NAME || value->kind == IR_VALUE_HTTP_REQUEST_PATH ||
|
||||
value->kind == IR_VALUE_HTTP_REQUEST_BODY_WITHIN) &&
|
||||
ctx->backend == Z_DIRECT_BACKEND_COFF_X64 && !ctx->executable);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_build_value_supported(const ZBuildability *ctx, const IrValue *value, bool local_set_value) {
|
||||
if (!ctx || !value) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend)) return build_value_supported_aarch64(ctx->backend, value->kind);
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64) return build_value_supported_macho_x64(value->kind);
|
||||
return build_value_supported_generic(ctx, value, local_set_value);
|
||||
}
|
||||
@@ -0,0 +1,745 @@
|
||||
#include "buildability_internal.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
enum { BUILD_AARCH64_IMM12_MAX = 4095u };
|
||||
|
||||
bool z_build_backend_is_aarch64_direct(ZDirectBackend backend) { return backend == Z_DIRECT_BACKEND_ELF_AARCH64 || backend == Z_DIRECT_BACKEND_COFF_AARCH64; }
|
||||
|
||||
static bool build_const_u32_value(const IrValue *value, unsigned *out) {
|
||||
if (!value || value->kind != IR_VALUE_INT || value->int_value > UINT32_MAX) return false;
|
||||
if (out) *out = (unsigned)value->int_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_byte_view_const_len(const IrValue *view, unsigned *out) {
|
||||
if (!view) return false;
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL || view->kind == IR_VALUE_ARRAY_BYTE_VIEW) {
|
||||
if (out) *out = view->data_len;
|
||||
return true;
|
||||
}
|
||||
if (view->kind != IR_VALUE_BYTE_SLICE) return false;
|
||||
unsigned base_len = 0, start = 0, end = 0;
|
||||
if (!build_byte_view_const_len(view->left, &base_len)) return false;
|
||||
end = base_len;
|
||||
if ((view->index && !build_const_u32_value(view->index, &start)) || (view->right && !build_const_u32_value(view->right, &end))) return false;
|
||||
if (start > end || end > base_len) return false;
|
||||
if (out) *out = end - start;
|
||||
return true;
|
||||
}
|
||||
|
||||
static IrTypeKind build_view_element_type(const IrValue *view) {
|
||||
return view && view->element_type != IR_TYPE_UNSUPPORTED ? view->element_type : IR_TYPE_U8;
|
||||
}
|
||||
|
||||
static bool build_check_json_error_label(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (!value || value->kind != IR_VALUE_JSON_ERROR_LABEL) return true;
|
||||
if (!value->left) return z_build_diag(ctx, diag, "direct backend JSON error label is missing a status code", value->line, value->column, "missing JSON status");
|
||||
if (value->arg_len != 4) return z_build_diag(ctx, diag, "direct backend JSON error label requires four label literals", value->line, value->column, "invalid JSON error labels");
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!value->args[i] || value->args[i]->kind != IR_VALUE_STRING_LITERAL || value->args[i]->type != IR_TYPE_BYTE_VIEW) {
|
||||
return z_build_diag(ctx, diag, "direct backend JSON error label requires string literal labels", value->line, value->column, "invalid JSON error label");
|
||||
}
|
||||
}
|
||||
return z_build_check_value(ctx, fun, value->left, false, scratch_slot, diag);
|
||||
}
|
||||
|
||||
static unsigned build_type_index_shift(IrTypeKind type) {
|
||||
if (type == IR_TYPE_U8 || type == IR_TYPE_BOOL) return 0;
|
||||
if (type == IR_TYPE_U16) return 1;
|
||||
if (type == IR_TYPE_I64 || type == IR_TYPE_U64) return 3;
|
||||
return 2;
|
||||
}
|
||||
|
||||
static bool build_scaled_index_exceeds_imm12(unsigned start, IrTypeKind element_type) {
|
||||
return start > ((unsigned)BUILD_AARCH64_IMM12_MAX >> build_type_index_shift(element_type));
|
||||
}
|
||||
|
||||
static bool build_aarch64_index_load_uses_scratch(const IrFunction *fun, const IrValue *value) {
|
||||
if (!fun || value->kind != IR_VALUE_INDEX_LOAD || value->array_index >= fun->local_len) return true;
|
||||
const IrLocal *local = &fun->locals[value->array_index];
|
||||
unsigned const_index = 0;
|
||||
return !(local->is_array && (local->element_type == IR_TYPE_U32 || local->element_type == IR_TYPE_I32 || local->element_type == IR_TYPE_USIZE) &&
|
||||
build_const_u32_value(value->index, &const_index) && const_index < local->array_len);
|
||||
}
|
||||
|
||||
static bool build_check_aarch64_byte_view_ptr_spill(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, unsigned scratch_slot, unsigned slot_count, const char *message, ZDiag *diag);
|
||||
|
||||
static bool build_check_aarch64_byte_view_len_spill(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, unsigned scratch_slot, unsigned slot_count, const char *message, ZDiag *diag) {
|
||||
if (!view || view->kind != IR_VALUE_BYTE_SLICE) return true;
|
||||
unsigned len = 0;
|
||||
if (build_byte_view_const_len(view, &len) && len <= Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES) return true;
|
||||
if (!view->index && !view->right) {
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, view->left, scratch_slot, slot_count, message, diag)) return false;
|
||||
return build_check_aarch64_byte_view_len_spill(ctx, fun, view->left, scratch_slot, slot_count, message, diag);
|
||||
}
|
||||
unsigned required_slot = view->right ? 3u : 2u;
|
||||
if (scratch_slot + required_slot >= slot_count) return z_build_diag(ctx, diag, message, view->line, view->column, "expression too deep");
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, view->left, scratch_slot, slot_count, message, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, view->left, scratch_slot, slot_count, message, diag)) return false;
|
||||
return (!view->index || z_build_check_value(ctx, fun, view->index, false, scratch_slot + 2, diag)) &&
|
||||
(!view->right || z_build_check_value(ctx, fun, view->right, false, scratch_slot + 3, diag));
|
||||
}
|
||||
|
||||
static bool build_check_aarch64_byte_view_ptr_spill(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, unsigned scratch_slot, unsigned slot_count, const char *message, ZDiag *diag) {
|
||||
if (!view || view->kind != IR_VALUE_BYTE_SLICE) return true;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, view->left, scratch_slot, slot_count, message, diag)) return false;
|
||||
unsigned start = 0;
|
||||
if (view->index && !build_const_u32_value(view->index, &start)) {
|
||||
if (scratch_slot >= slot_count) return z_build_diag(ctx, diag, message, view->line, view->column, "expression too deep");
|
||||
if (!z_build_check_value(ctx, fun, view->index, false, scratch_slot + 1, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t build_value_abi_slots(const IrValue *value) {
|
||||
size_t slots = 0;
|
||||
for (size_t i = 0; value && i < value->arg_len; i++) {
|
||||
slots += value->args[i] && value->args[i]->type == IR_TYPE_BYTE_VIEW ? 2u : 1u;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
static bool build_array_byte_view_has_storage(const IrFunction *fun, const IrValue *view) {
|
||||
if (!view || view->kind != IR_VALUE_ARRAY_BYTE_VIEW || !fun || view->array_index >= fun->local_len) return false;
|
||||
const IrLocal *local = &fun->locals[view->array_index];
|
||||
return (local->is_array && view->field_offset == 0) || local->is_record;
|
||||
}
|
||||
|
||||
static bool build_aarch64_byte_view_ptr(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
if (!view) return z_build_diag(ctx, diag, "direct AArch64 byte view is missing", 1, 1, "missing byte view");
|
||||
if (view->kind == IR_VALUE_LOCAL && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_VEC_BYTES && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_VEC) return true;
|
||||
if (view->kind == IR_VALUE_MAYBE_VALUE && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_MAYBE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_CALL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_FIELD_LOAD && view->type == IR_TYPE_BYTE_VIEW && fun && view->local_index < fun->local_len &&
|
||||
(fun->locals[view->local_index].is_record || fun->locals[view->local_index].is_record_ref)) return true;
|
||||
if (view->kind == IR_VALUE_STR_RUNTIME && view->type == IR_TYPE_BYTE_VIEW &&
|
||||
(ctx->backend == Z_DIRECT_BACKEND_ELF_AARCH64 || ctx->backend == Z_DIRECT_BACKEND_COFF_AARCH64)) return true;
|
||||
if (view->kind == IR_VALUE_JSON_ERROR_LABEL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_ARRAY_BYTE_VIEW && fun && view->array_index < fun->local_len) {
|
||||
if (!build_array_byte_view_has_storage(fun, view)) return z_build_diag(ctx, diag, "direct AArch64 byte-view array requires a fixed array or record array field", view->line, view->column, "unsupported array view");
|
||||
return true;
|
||||
}
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL) return true;
|
||||
if (view->kind == IR_VALUE_BYTE_SLICE) {
|
||||
unsigned start = 0;
|
||||
if (!build_aarch64_byte_view_ptr(ctx, fun, view->left, diag)) return false;
|
||||
if (build_const_u32_value(view->index, &start) && build_scaled_index_exceeds_imm12(start, build_view_element_type(view))) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 byte slice constant start is too large", view->line, view->column, "unsupported byte slice");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return z_build_diag(ctx, diag, "direct AArch64 value is not a supported byte view", view->line, view->column, "unsupported byte view");
|
||||
}
|
||||
|
||||
bool z_build_check_aarch64_byte_view_len(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
unsigned len = 0;
|
||||
if (build_byte_view_const_len(view, &len)) return len <= Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES || z_build_diag(ctx, diag, "direct AArch64 byte-view length is too large for this backend", view->line, view->column, "large byte view");
|
||||
if (!view) return z_build_diag(ctx, diag, "direct AArch64 byte view is missing", 1, 1, "missing byte view");
|
||||
if (view->kind == IR_VALUE_STRING_LITERAL || view->kind == IR_VALUE_ARRAY_BYTE_VIEW) {
|
||||
if (view->data_len > Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES) return z_build_diag(ctx, diag, "direct AArch64 byte-view length is too large for this backend", view->line, view->column, "large byte view");
|
||||
return true;
|
||||
}
|
||||
if (view->kind == IR_VALUE_LOCAL && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_VEC_BYTES && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_VEC) return true;
|
||||
if (view->kind == IR_VALUE_MAYBE_VALUE && fun && view->local_index < fun->local_len && fun->locals[view->local_index].type == IR_TYPE_MAYBE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_CALL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_FIELD_LOAD && view->type == IR_TYPE_BYTE_VIEW && fun && view->local_index < fun->local_len &&
|
||||
(fun->locals[view->local_index].is_record || fun->locals[view->local_index].is_record_ref)) return true;
|
||||
if (view->kind == IR_VALUE_STR_RUNTIME && view->type == IR_TYPE_BYTE_VIEW &&
|
||||
(ctx->backend == Z_DIRECT_BACKEND_ELF_AARCH64 || ctx->backend == Z_DIRECT_BACKEND_COFF_AARCH64)) return true;
|
||||
if (view->kind == IR_VALUE_JSON_ERROR_LABEL && view->type == IR_TYPE_BYTE_VIEW) return true;
|
||||
if (view->kind == IR_VALUE_BYTE_SLICE) {
|
||||
return z_build_check_aarch64_byte_view_len(ctx, fun, view->left, diag);
|
||||
}
|
||||
return z_build_diag(ctx, diag, "direct AArch64 byte-view length currently requires a literal, constant slice, or byte-view local", view->line, view->column, "unsupported byte view length");
|
||||
}
|
||||
|
||||
bool z_build_check_aarch64_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) { return build_aarch64_byte_view_ptr(ctx, fun, view, diag) && z_build_check_aarch64_byte_view_len(ctx, fun, view, diag); }
|
||||
|
||||
bool z_build_check_aarch64_world_write_byte_view(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag) {
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, view, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, view, 0, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 World write exceeds scratch register spill capacity", diag)) return false;
|
||||
return build_check_aarch64_byte_view_len_spill(ctx, fun, view, 0, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 World write exceeds scratch register spill capacity", diag);
|
||||
}
|
||||
|
||||
static bool build_aarch64_byte_operation(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, bool *skip_left, ZDiag *diag) {
|
||||
if (value->kind == IR_VALUE_BYTE_COPY) {
|
||||
if (scratch_slot + 3 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 byte copy exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte copy exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte copy exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte copy exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 3, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte copy exceeds scratch register spill capacity", diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_BYTE_FILL) {
|
||||
if (scratch_slot + 2 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 byte fill exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte fill exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte fill exceeds scratch register spill capacity", diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_EQ) {
|
||||
if (scratch_slot + 3 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 byte-view equality exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view equality exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view equality exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view equality exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view equality exceeds scratch register spill capacity", diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_LEN && !build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view length exceeds scratch register spill capacity", diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_LEN && !z_build_check_aarch64_byte_view_len(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_REMAINING) {
|
||||
if (scratch_slot + 1 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 byte-view remaining exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view remaining exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_aarch64_byte_view_len(ctx, fun, value->left, diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_INDEX_LOAD && build_aarch64_index_load_uses_scratch(fun, value) && scratch_slot >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 indexed load exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && scratch_slot >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 byte-view indexed load exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && !build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 byte-view indexed load exceeds scratch register spill capacity", diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && !z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_JSON_ERROR_LABEL && !build_check_json_error_label(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (value->kind == IR_VALUE_CRC32_BYTES) {
|
||||
if (scratch_slot + 3 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 CRC32 exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 CRC32 exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 CRC32 exceeds scratch register spill capacity", diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_JSON_VALIDATE_BYTES || value->kind == IR_VALUE_JSON_STREAM_TOKENS_BYTES || value->kind == IR_VALUE_JSON_DIAGNOSTIC_BYTES ||
|
||||
value->kind == IR_VALUE_JSON_FIELD || value->kind == IR_VALUE_JSON_LOOKUP_SCALAR || value->kind == IR_VALUE_JSON_STRING_DECODE || value->kind == IR_VALUE_JSON_STRING_FIELD ||
|
||||
value->kind == IR_VALUE_JSON_WRITE_STRING) {
|
||||
bool two_views = value->kind == IR_VALUE_JSON_FIELD || value->kind == IR_VALUE_JSON_LOOKUP_SCALAR || value->kind == IR_VALUE_JSON_STRING_DECODE || value->kind == IR_VALUE_JSON_WRITE_STRING;
|
||||
bool three_views = value->kind == IR_VALUE_JSON_STRING_FIELD;
|
||||
unsigned needed_slots = three_views ? 8u : (two_views ? 6u : 2u);
|
||||
if (scratch_slot + needed_slots - 1u >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 JSON helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((two_views || three_views) && !z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (three_views && !z_build_check_aarch64_byte_view(ctx, fun, value->index, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if ((two_views || three_views) && !build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if ((two_views || three_views) && !build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 3, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if (three_views && !build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->index, scratch_slot + 4, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if (three_views && !build_check_aarch64_byte_view_len_spill(ctx, fun, value->index, scratch_slot + 5, BUILD_AARCH64_SCRATCH_SLOT_COUNT, "direct AArch64 JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_LEN || value->kind == IR_VALUE_BYTE_VIEW_REMAINING || value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD) *skip_left = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_str_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_STR_RUNTIME) return true;
|
||||
if (ctx->backend != Z_DIRECT_BACKEND_ELF_AARCH64 && ctx->backend != Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.str runtime helpers require an object target with runtime relocations", value->line, value->column, "unsupported std.str runtime");
|
||||
}
|
||||
if (scratch_slot + 8 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.str helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
const IrValue *arg = value->args[i];
|
||||
if (arg && arg->type == IR_TYPE_BYTE_VIEW && !z_build_check_aarch64_byte_view(ctx, fun, arg, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_math_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_MATH_RUNTIME) return true;
|
||||
if (ctx->backend != Z_DIRECT_BACKEND_ELF_AARCH64 && ctx->backend != Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.math runtime helpers require an object target with runtime relocations", value->line, value->column, "unsupported std.math runtime");
|
||||
}
|
||||
if (value->arg_len > 3) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.math helper supports at most three scalar arguments", value->line, value->column, "invalid std.math arity");
|
||||
}
|
||||
if (scratch_slot + 5 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.math helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_value(ctx, fun, value->args[i], false, scratch_slot + 3 + (unsigned)i, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_time_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_TIME_RUNTIME) return true;
|
||||
if (ctx->backend != Z_DIRECT_BACKEND_ELF_AARCH64 && ctx->backend != Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.time runtime helpers require an object target with runtime relocations", value->line, value->column, "unsupported std.time runtime");
|
||||
}
|
||||
if (value->arg_len > 3) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.time helper supports at most three scalar arguments", value->line, value->column, "invalid std.time arity");
|
||||
}
|
||||
if (scratch_slot + 5 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.time helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_value(ctx, fun, value->args[i], false, scratch_slot + 3 + (unsigned)i, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_term_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_TERM_RUNTIME) return true;
|
||||
if (ctx->backend != Z_DIRECT_BACKEND_ELF_AARCH64 && ctx->backend != Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.term runtime helpers require an object target with runtime relocations", value->line, value->column, "unsupported std.term runtime");
|
||||
}
|
||||
if (value->arg_len > 1) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.term helper supports at most one fallback argument", value->line, value->column, "invalid std.term arity");
|
||||
}
|
||||
if (scratch_slot + 1 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.term helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (value->arg_len == 1 && !z_build_check_value(ctx, fun, value->args[0], false, scratch_slot, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_text_parse_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind == IR_VALUE_TEXT_RUNTIME) {
|
||||
if (value->arg_len != 1) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.text helper requires one byte-view argument", value->line, value->column, "invalid std.text arity");
|
||||
}
|
||||
if (scratch_slot + 2 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.text helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!value->args[0] || !z_build_check_aarch64_byte_view(ctx, fun, value->args[0], diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_PARSE_RUNTIME) {
|
||||
if (value->arg_len < 1 || value->arg_len > 2) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.parse helper requires one byte-view argument and optional byte argument", value->line, value->column, "invalid std.parse arity");
|
||||
}
|
||||
if (scratch_slot + 3 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.parse helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!value->args[0] || !z_build_check_aarch64_byte_view(ctx, fun, value->args[0], diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_PARSE_I32 || value->kind == IR_VALUE_PARSE_U32) {
|
||||
if (scratch_slot + 1 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 parse helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!value->left || !z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_fmt_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_FMT_BOOL && value->kind != IR_VALUE_FMT_HEX_U32 && value->kind != IR_VALUE_FMT_I32 &&
|
||||
value->kind != IR_VALUE_FMT_U32 && value->kind != IR_VALUE_FMT_USIZE) return true;
|
||||
if (ctx->backend != Z_DIRECT_BACKEND_ELF_AARCH64 && ctx->backend != Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.fmt helpers require an object target with runtime relocations", value->line, value->column, "unsupported std.fmt runtime");
|
||||
}
|
||||
if (scratch_slot + 2 >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 std.fmt helper exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (!value->left || !z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_aarch64_proc_runtime(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_PROC_CAPTURE &&
|
||||
value->kind != IR_VALUE_PROC_SPAWN_INHERIT &&
|
||||
value->kind != IR_VALUE_PROC_CAPTURE_FILES &&
|
||||
value->kind != IR_VALUE_PROC_CHILD_SPAWN &&
|
||||
value->kind != IR_VALUE_PROC_CHILD_OP &&
|
||||
value->kind != IR_VALUE_PROC_CHILD_IO &&
|
||||
value->kind != IR_VALUE_PROC_PTY_RESIZE) return true;
|
||||
unsigned required = 2;
|
||||
const char *message = "direct AArch64 std.proc helper exceeds scratch register spill capacity";
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE) required = value->arg_len == 2 ? 6 : 3;
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_IO) required = 3;
|
||||
if (value->kind == IR_VALUE_PROC_PTY_RESIZE) required = 3;
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE_FILES) required = value->arg_len == 2 ? 8 : 5;
|
||||
if (value->kind == IR_VALUE_PROC_SPAWN_INHERIT && value->arg_len == 4) required = 8;
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->arg_len == 4) required = 8;
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->right) required = 4;
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->index) required = 6;
|
||||
if (scratch_slot + required >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, message, value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_SPAWN_INHERIT && value->arg_len == 4) {
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->args[i], diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE && value->arg_len == 2) {
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->args[0], diag)) return false;
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->args[1], diag)) return false;
|
||||
return z_build_check_aarch64_byte_view(ctx, fun, value->right, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE_FILES && value->arg_len == 2) {
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->args[0], diag)) return false;
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->args[1], diag)) return false;
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
return z_build_check_aarch64_byte_view(ctx, fun, value->index, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->arg_len == 4) {
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_aarch64_byte_view(ctx, fun, value->args[i], diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ((value->kind == IR_VALUE_PROC_SPAWN_INHERIT ||
|
||||
value->kind == IR_VALUE_PROC_CAPTURE ||
|
||||
value->kind == IR_VALUE_PROC_CAPTURE_FILES ||
|
||||
value->kind == IR_VALUE_PROC_CHILD_SPAWN) &&
|
||||
!z_build_check_aarch64_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_PROC_CAPTURE ||
|
||||
value->kind == IR_VALUE_PROC_CAPTURE_FILES ||
|
||||
value->kind == IR_VALUE_PROC_CHILD_IO) &&
|
||||
!z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->right && !z_build_check_aarch64_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->index && !z_build_check_aarch64_byte_view(ctx, fun, value->index, diag)) return false;
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE_FILES && !z_build_check_aarch64_byte_view(ctx, fun, value->index, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_binary_operator(const ZBuildability *ctx, const IrValue *value, unsigned scratch_slot, unsigned *right_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_BINARY) return true;
|
||||
bool supported = true;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_COFF_X64) {
|
||||
supported = value->binary_op == IR_BIN_ADD || value->binary_op == IR_BIN_SUB || value->binary_op == IR_BIN_MUL ||
|
||||
value->binary_op == IR_BIN_DIV || value->binary_op == IR_BIN_MOD ||
|
||||
value->binary_op == IR_BIN_AND || value->binary_op == IR_BIN_OR;
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 || ctx->backend == Z_DIRECT_BACKEND_MACHO64 || z_build_backend_is_aarch64_direct(ctx->backend)) {
|
||||
supported = value->binary_op == IR_BIN_ADD || value->binary_op == IR_BIN_SUB || value->binary_op == IR_BIN_MUL ||
|
||||
value->binary_op == IR_BIN_DIV || value->binary_op == IR_BIN_MOD ||
|
||||
value->binary_op == IR_BIN_AND || value->binary_op == IR_BIN_OR;
|
||||
}
|
||||
if (!supported) return z_build_diag(ctx, diag, "direct backend buildability does not support this binary operator", value->line, value->column, "unsupported operator");
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64 && value->binary_op != IR_BIN_AND && value->binary_op != IR_BIN_OR && scratch_slot >= BUILD_MACHO_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O expression nesting exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && value->binary_op != IR_BIN_AND && value->binary_op != IR_BIN_OR && scratch_slot >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 expression nesting exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if ((ctx->backend == Z_DIRECT_BACKEND_MACHO64 || z_build_backend_is_aarch64_direct(ctx->backend)) &&
|
||||
value->binary_op != IR_BIN_AND && value->binary_op != IR_BIN_OR) {
|
||||
*right_slot = scratch_slot + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_compare(const ZBuildability *ctx, const IrValue *value, unsigned scratch_slot, unsigned *right_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_COMPARE) return true;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64 && scratch_slot >= BUILD_MACHO_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 Mach-O expression nesting exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && scratch_slot >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 expression nesting exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64 || z_build_backend_is_aarch64_direct(ctx->backend)) *right_slot = scratch_slot + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_call_shape(const ZBuildability *ctx, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind != IR_VALUE_CALL) return true;
|
||||
size_t max_args = (ctx->backend == Z_DIRECT_BACKEND_ELF64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_MACHO_X64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_COFF_X64 ||
|
||||
ctx->backend == Z_DIRECT_BACKEND_MACHO64) ? 8 : 6;
|
||||
size_t abi_slots = build_value_abi_slots(value);
|
||||
if (!z_build_backend_is_aarch64_direct(ctx->backend) && abi_slots > max_args) {
|
||||
char actual[80];
|
||||
snprintf(actual, sizeof(actual), "%zu ABI argument slot(s)", abi_slots);
|
||||
return z_build_diag(ctx, diag, "direct backend buildability found a call with too many arguments", value->line, value->column, actual);
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64 && scratch_slot + abi_slots >= BUILD_MACHO_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 Mach-O call argument nesting exceeds scratch spill capacity", value->line, value->column, "too many nested call arguments");
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && scratch_slot + abi_slots >= BUILD_AARCH64_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 call argument nesting exceeds scratch spill capacity", value->line, value->column, "too many nested call arguments");
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*BuildByteViewCheck)(const ZBuildability *ctx, const IrFunction *fun, const IrValue *view, ZDiag *diag);
|
||||
|
||||
static bool build_check_two_byte_views(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, BuildByteViewCheck check, ZDiag *diag) {
|
||||
return check(ctx, fun, value->left, diag) && check(ctx, fun, value->right, diag);
|
||||
}
|
||||
|
||||
static bool build_kind_is_fmt_buffer_use(IrValueKind kind) {
|
||||
return kind == IR_VALUE_FMT_BOOL || kind == IR_VALUE_FMT_HEX_U32 || kind == IR_VALUE_FMT_I32 ||
|
||||
kind == IR_VALUE_FMT_U32 || kind == IR_VALUE_FMT_USIZE;
|
||||
}
|
||||
|
||||
static bool build_kind_is_skip_left_view(IrValueKind kind) {
|
||||
return kind == IR_VALUE_BYTE_VIEW_LEN || kind == IR_VALUE_BYTE_VIEW_REMAINING || kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD ||
|
||||
kind == IR_VALUE_VEC_BYTES;
|
||||
}
|
||||
|
||||
static bool build_check_linear_byte_view_target(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, BuildByteViewCheck check_view, BuildByteViewCheck check_len, bool include_json_parse, bool include_alloc_skip_left, bool *skip_left, ZDiag *diag) {
|
||||
if (value->kind == IR_VALUE_BYTE_COPY && !build_check_two_byte_views(ctx, fun, value, check_view, diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_FILL && !check_view(ctx, fun, value->right, diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_EQ && !build_check_two_byte_views(ctx, fun, value, check_view, diag)) return false;
|
||||
if (value->kind == IR_VALUE_CRC32_BYTES && !check_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_JSON_VALIDATE_BYTES || value->kind == IR_VALUE_JSON_STREAM_TOKENS_BYTES || value->kind == IR_VALUE_JSON_DIAGNOSTIC_BYTES ||
|
||||
(include_json_parse && value->kind == IR_VALUE_JSON_PARSE_BYTES)) &&
|
||||
!check_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_JSON_FIELD || value->kind == IR_VALUE_JSON_LOOKUP_SCALAR || value->kind == IR_VALUE_JSON_STRING_DECODE || value->kind == IR_VALUE_JSON_WRITE_STRING) && !build_check_two_byte_views(ctx, fun, value, check_view, diag)) return false;
|
||||
if (value->kind == IR_VALUE_JSON_STRING_FIELD && !(check_view(ctx, fun, value->left, diag) && check_view(ctx, fun, value->right, diag) && check_view(ctx, fun, value->index, diag))) return false;
|
||||
if (value->kind == IR_VALUE_JSON_WRITE_RUNTIME) {
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (value->args[i] && value->args[i]->type == IR_TYPE_BYTE_VIEW && !check_view(ctx, fun, value->args[i], diag)) return false;
|
||||
}
|
||||
}
|
||||
if (build_kind_is_fmt_buffer_use(value->kind) && !check_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_BYTE_VIEW_LEN || value->kind == IR_VALUE_BYTE_VIEW_REMAINING) && !check_len(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && !check_view(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_JSON_ERROR_LABEL && !build_check_json_error_label(ctx, fun, value, 0, diag)) return false;
|
||||
if (include_alloc_skip_left && (value->kind == IR_VALUE_FIXED_BUF_ALLOC || value->kind == IR_VALUE_VEC_INIT) && !check_view(ctx, fun, value->left, diag)) return false;
|
||||
if (build_kind_is_skip_left_view(value->kind) || (include_alloc_skip_left && (value->kind == IR_VALUE_FIXED_BUF_ALLOC || value->kind == IR_VALUE_VEC_INIT))) {
|
||||
if (skip_left) *skip_left = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_macho64_capacity(const ZBuildability *ctx, const IrValue *value, unsigned scratch_slot, unsigned required, const char *message, ZDiag *diag) {
|
||||
if (scratch_slot + required >= BUILD_MACHO_SCRATCH_SLOT_COUNT) {
|
||||
return z_build_diag(ctx, diag, message, value->line, value->column, "expression too deep");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_macho64_byte_operation(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind == IR_VALUE_BYTE_COPY) {
|
||||
const char *message = "direct AArch64 Mach-O byte copy exceeds scratch register spill capacity";
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 3, message, diag)) return false;
|
||||
if (!build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
return build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 3, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_BYTE_FILL) {
|
||||
const char *message = "direct AArch64 Mach-O byte fill exceeds scratch register spill capacity";
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 2, message, diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
return build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_EQ) {
|
||||
const char *message = "direct AArch64 Mach-O byte-view equality exceeds scratch register spill capacity";
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 3, message, diag)) return false;
|
||||
if (!build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->right, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
return build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->right, scratch_slot + 2, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_macho64_text_args_fmt(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind == IR_VALUE_STR_CONTAINS) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 3, "direct AArch64 Mach-O string contains exceeds scratch register spill capacity", diag)) return false;
|
||||
return build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PARSE_I32 || value->kind == IR_VALUE_PARSE_U32) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 1, "direct AArch64 Mach-O parse helper exceeds scratch register spill capacity", diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->left, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PARSE_RUNTIME) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 3, "direct AArch64 Mach-O parse runtime helper exceeds scratch register spill capacity", diag)) return false;
|
||||
return value->arg_len && z_build_check_macho_byte_view(ctx, fun, value->args[0], diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_ARGS_FIND || value->kind == IR_VALUE_ARGS_CONTAINS || value->kind == IR_VALUE_ARGS_VALUE_AFTER || value->kind == IR_VALUE_ARGS_VALUE_AFTER_PARSE_U32) {
|
||||
const char *message = (value->kind == IR_VALUE_ARGS_FIND || value->kind == IR_VALUE_ARGS_CONTAINS) ?
|
||||
"direct AArch64 Mach-O args find exceeds scratch register spill capacity" :
|
||||
"direct AArch64 Mach-O args option lookup exceeds scratch register spill capacity";
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 1, message, diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->left, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_ARGS_VALUE_AFTER_OR) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 2, "direct AArch64 Mach-O args option fallback exceeds scratch register spill capacity", diag)) return false;
|
||||
return build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag);
|
||||
}
|
||||
if (build_kind_is_fmt_buffer_use(value->kind)) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 2, "direct AArch64 Mach-O fmt helper exceeds scratch register spill capacity", diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->left, diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_macho64_view_operation(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_LEN && !build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot, BUILD_MACHO_SCRATCH_SLOT_COUNT, "direct AArch64 Mach-O byte-view length exceeds scratch register spill capacity", diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_LEN && !z_build_check_macho_byte_view_len(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_REMAINING) {
|
||||
const char *message = "direct AArch64 Mach-O byte-view remaining exceeds scratch register spill capacity";
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 1, message, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
if (!z_build_check_macho_byte_view_len(ctx, fun, value->left, diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_INDEX_LOAD && build_aarch64_index_load_uses_scratch(fun, value) && scratch_slot >= BUILD_MACHO_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 Mach-O indexed load exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && scratch_slot >= BUILD_MACHO_SCRATCH_SLOT_COUNT) return z_build_diag(ctx, diag, "direct AArch64 Mach-O byte-view indexed load exceeds scratch register spill capacity", value->line, value->column, "expression too deep");
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && !build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, "direct AArch64 Mach-O byte-view indexed load exceeds scratch register spill capacity", diag)) return false;
|
||||
if (value->kind == IR_VALUE_BYTE_VIEW_INDEX_LOAD && !z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_JSON_ERROR_LABEL && !build_check_json_error_label(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (value->kind == IR_VALUE_CRC32_BYTES) {
|
||||
const char *message = "direct AArch64 Mach-O CRC32 exceeds scratch register spill capacity";
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 3, message, diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!build_check_aarch64_byte_view_ptr_spill(ctx, fun, value->left, scratch_slot, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag)) return false;
|
||||
return build_check_aarch64_byte_view_len_spill(ctx, fun, value->left, scratch_slot + 1, BUILD_MACHO_SCRATCH_SLOT_COUNT, message, diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_macho64_json_http(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, ZDiag *diag) {
|
||||
if ((value->kind == IR_VALUE_FIXED_BUF_ALLOC || value->kind == IR_VALUE_VEC_INIT) && !z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_JSON_PARSE_BYTES || value->kind == IR_VALUE_JSON_VALIDATE_BYTES || value->kind == IR_VALUE_JSON_STREAM_TOKENS_BYTES || value->kind == IR_VALUE_JSON_DIAGNOSTIC_BYTES) && !z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_JSON_FIELD || value->kind == IR_VALUE_JSON_LOOKUP_SCALAR || value->kind == IR_VALUE_JSON_STRING_DECODE || value->kind == IR_VALUE_JSON_WRITE_STRING) && !build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag)) return false;
|
||||
if (value->kind == IR_VALUE_JSON_STRING_FIELD) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 8, "direct AArch64 Mach-O JSON helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->index, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_JSON_WRITE_RUNTIME) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 8, "direct AArch64 Mach-O JSON writer exceeds scratch register spill capacity", diag)) return false;
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (value->args[i] && value->args[i]->type == IR_TYPE_BYTE_VIEW && !z_build_check_macho_byte_view(ctx, fun, value->args[i], diag)) return false;
|
||||
}
|
||||
}
|
||||
if (value->kind == IR_VALUE_HTTP_FETCH && !build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_HTTP_RESPONSE_LEN || value->kind == IR_VALUE_HTTP_RESPONSE_HEADERS_LEN || value->kind == IR_VALUE_HTTP_RESPONSE_BODY_OFFSET) && !z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_HTTP_HEADER_VALUE && !build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag)) return false;
|
||||
if ((value->kind == IR_VALUE_HTTP_REQUEST_METHOD_NAME || value->kind == IR_VALUE_HTTP_REQUEST_PATH) && !z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (value->kind == IR_VALUE_HTTP_REQUEST_MATCHES) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 6, "direct AArch64 Mach-O HTTP request matcher exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->index, diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->right, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_HTTP_REQUEST_BODY_WITHIN) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 4, "direct AArch64 Mach-O HTTP request body helper exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
}
|
||||
if (value->kind == IR_VALUE_HTTP_WRITE_JSON_RESPONSE) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 4, "direct AArch64 Mach-O HTTP JSON response helper exceeds scratch register spill capacity", diag)) return false;
|
||||
return build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE) {
|
||||
if (value->arg_len == 2) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 6, "direct AArch64 Mach-O std.proc.captureArgs exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->args[0], diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->args[1], diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->right, diag);
|
||||
}
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 4, "direct AArch64 Mach-O std.proc.capture exceeds scratch register spill capacity", diag)) return false;
|
||||
return build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CAPTURE_FILES) {
|
||||
if (value->arg_len == 2) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 8, "direct AArch64 Mach-O std.proc.captureFilesArgs exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->args[0], diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->args[1], diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->index, diag);
|
||||
}
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 6, "direct AArch64 Mach-O std.proc.captureFiles exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->left, diag)) return false;
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->right, diag)) return false;
|
||||
return z_build_check_macho_byte_view(ctx, fun, value->index, diag);
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_SPAWN_INHERIT && value->arg_len == 4) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 8, "direct AArch64 Mach-O std.proc.spawnInheritArgs exceeds scratch register spill capacity", diag)) return false;
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->args[i], diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->arg_len == 4) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, 8, "direct AArch64 Mach-O std.proc.spawnChildArgs exceeds scratch register spill capacity", diag)) return false;
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (!z_build_check_macho_byte_view(ctx, fun, value->args[i], diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_CHILD_SPAWN && value->right) {
|
||||
if (!build_check_macho64_capacity(ctx, value, scratch_slot, value->index ? 6 : 4, value->index ? "direct AArch64 Mach-O std.proc.spawnChildInEnv exceeds scratch register spill capacity" : "direct AArch64 Mach-O std.proc.spawnChildIn exceeds scratch register spill capacity", diag)) return false;
|
||||
if (!build_check_two_byte_views(ctx, fun, value, z_build_check_macho_byte_view, diag)) return false;
|
||||
if (value->index && !z_build_check_macho_byte_view(ctx, fun, value->index, diag)) return false;
|
||||
return true;
|
||||
}
|
||||
if (value->kind == IR_VALUE_PROC_PTY_RESIZE) {
|
||||
return build_check_macho64_capacity(ctx, value, scratch_slot, 3, "direct AArch64 Mach-O std.pty.resize exceeds scratch register spill capacity", diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_check_macho64_target_value(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, bool *skip_left, ZDiag *diag) {
|
||||
if (!build_check_macho64_byte_operation(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (!build_check_macho64_text_args_fmt(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (!build_check_macho64_view_operation(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (!build_check_macho64_json_http(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (build_kind_is_skip_left_view(value->kind) && skip_left) *skip_left = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_build_check_target_value(const ZBuildability *ctx, const IrFunction *fun, const IrValue *value, unsigned scratch_slot, bool *skip_left, unsigned *right_slot, ZDiag *diag) {
|
||||
if (skip_left) *skip_left = false;
|
||||
if (right_slot) *right_slot = scratch_slot;
|
||||
if (!ctx || !value) return true;
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_COFF_X64) {
|
||||
if (!build_check_linear_byte_view_target(ctx, fun, value, z_build_check_coff_byte_view, z_build_check_coff_byte_view_len, false, true, skip_left, diag)) return false;
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO_X64) {
|
||||
if (!build_check_linear_byte_view_target(ctx, fun, value, z_build_check_macho_x64_byte_view, z_build_check_macho_x64_byte_view_len, true, false, skip_left, diag)) return false;
|
||||
}
|
||||
if (ctx->backend == Z_DIRECT_BACKEND_MACHO64) {
|
||||
if (!build_check_macho64_target_value(ctx, fun, value, scratch_slot, skip_left, diag)) return false;
|
||||
}
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_byte_operation(ctx, fun, value, scratch_slot, skip_left, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_str_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_text_parse_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_fmt_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_proc_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_time_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_term_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
if (z_build_backend_is_aarch64_direct(ctx->backend) && !build_aarch64_math_runtime(ctx, fun, value, scratch_slot, diag)) return false;
|
||||
return build_check_binary_operator(ctx, value, scratch_slot, right_slot, diag) &&
|
||||
build_check_compare(ctx, value, scratch_slot, right_slot, diag) &&
|
||||
build_check_call_shape(ctx, value, scratch_slot, diag);
|
||||
}
|
||||
|
||||
unsigned z_build_target_call_arg_slot(const ZBuildability *ctx, const IrValue *value, unsigned scratch_slot) {
|
||||
if (ctx && value && (ctx->backend == Z_DIRECT_BACKEND_MACHO64 || z_build_backend_is_aarch64_direct(ctx->backend)) && value->kind == IR_VALUE_CALL) {
|
||||
return scratch_slot + (unsigned)build_value_abi_slots(value);
|
||||
}
|
||||
return scratch_slot;
|
||||
}
|
||||
|
||||
bool z_build_check_aarch64_function_shape(const ZBuildability *ctx, const IrFunction *fun, ZDiag *diag) {
|
||||
size_t abi_slots = 0;
|
||||
for (size_t i = 0; i < fun->param_count; i++) abi_slots += fun->locals[i].type == IR_TYPE_BYTE_VIEW ? 2u : 1u;
|
||||
if (abi_slots > 8) return z_build_diag(ctx, diag, "direct AArch64 buildability found too many ABI argument slots", fun->line, fun->column, fun->name);
|
||||
if (fun->return_type != IR_TYPE_VOID && !z_build_is_elf_scalar(fun->return_type) &&
|
||||
fun->return_type != IR_TYPE_BYTE_VIEW && fun->return_type != IR_TYPE_MAYBE_BYTE_VIEW && fun->return_type != IR_TYPE_MAYBE_SCALAR) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 buildability currently supports Void, primitive scalars, byte views, Maybe byte views, and Maybe scalar returns", fun->line, fun->column, z_build_type_name(fun->return_type));
|
||||
}
|
||||
size_t frame_bytes = fun->frame_bytes ? fun->frame_bytes : fun->local_len * 8u;
|
||||
if (frame_bytes > Z_DIRECT_FRAME_LOCAL_LIMIT_BYTES) return z_build_diag(ctx, diag, "direct AArch64 stack frame exceeds the supported per-function locals limit", fun->line, fun->column, fun->name);
|
||||
for (size_t i = 0; i < fun->local_len; i++) {
|
||||
const IrLocal *local = &fun->locals[i];
|
||||
if (local->type == IR_TYPE_BYTE_VIEW) continue;
|
||||
if (local->is_record || local->is_record_ref) continue;
|
||||
if (local->is_array) {
|
||||
bool array_ok = local->element_type == IR_TYPE_U8 || local->element_type == IR_TYPE_BOOL || local->element_type == IR_TYPE_U16 ||
|
||||
local->element_type == IR_TYPE_U32 || local->element_type == IR_TYPE_I32 || local->element_type == IR_TYPE_USIZE ||
|
||||
local->element_type == IR_TYPE_I64 || local->element_type == IR_TYPE_U64;
|
||||
if (!array_ok) return z_build_diag(ctx, diag, "direct AArch64 object buildability does not support this fixed-array local", local->line, local->column, z_build_type_name(local->element_type));
|
||||
continue;
|
||||
}
|
||||
if (local->type == IR_TYPE_MAYBE_BYTE_VIEW || local->type == IR_TYPE_MAYBE_SCALAR) continue;
|
||||
if (!z_build_is_elf_scalar(local->type)) {
|
||||
return z_build_diag(ctx, diag, "direct AArch64 object buildability does not support this local type", local->line, local->column, z_build_type_name(local->type));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,750 @@
|
||||
#include "c_import.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool c_ident_char(char ch) {
|
||||
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_';
|
||||
}
|
||||
|
||||
static char *trim_span_copy(const char *start, const char *end) {
|
||||
if (!start || !end || end < start) return z_strdup("");
|
||||
while (start < end && isspace((unsigned char)*start)) start++;
|
||||
while (end > start && isspace((unsigned char)end[-1])) end--;
|
||||
return z_strndup(start, (size_t)(end - start));
|
||||
}
|
||||
|
||||
char *z_c_header_strip_comments(const char *header) {
|
||||
ZBuf out;
|
||||
zbuf_init(&out);
|
||||
const char *cursor = header ? header : "";
|
||||
while (*cursor) {
|
||||
if (cursor[0] == '/' && cursor[1] == '/') {
|
||||
cursor += 2;
|
||||
while (*cursor && *cursor != '\n') cursor++;
|
||||
if (*cursor == '\n') zbuf_append_char(&out, *cursor++);
|
||||
continue;
|
||||
}
|
||||
if (cursor[0] == '/' && cursor[1] == '*') {
|
||||
zbuf_append_char(&out, ' ');
|
||||
cursor += 2;
|
||||
while (*cursor) {
|
||||
if (cursor[0] == '*' && cursor[1] == '/') {
|
||||
cursor += 2;
|
||||
break;
|
||||
}
|
||||
if (*cursor == '\n') zbuf_append_char(&out, '\n');
|
||||
cursor++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (*cursor == '"' || *cursor == '\'') {
|
||||
char quote = *cursor;
|
||||
zbuf_append_char(&out, *cursor++);
|
||||
while (*cursor) {
|
||||
char ch = *cursor++;
|
||||
zbuf_append_char(&out, ch);
|
||||
if (ch == '\\' && *cursor) {
|
||||
zbuf_append_char(&out, *cursor++);
|
||||
continue;
|
||||
}
|
||||
if (ch == quote) break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
zbuf_append_char(&out, *cursor++);
|
||||
}
|
||||
return out.data ? out.data : z_strdup("");
|
||||
}
|
||||
|
||||
static const char *last_ident_start_before(const char *start, const char *end) {
|
||||
const char *cursor = end;
|
||||
while (cursor > start && !c_ident_char(cursor[-1])) cursor--;
|
||||
const char *ident_end = cursor;
|
||||
while (cursor > start && c_ident_char(cursor[-1])) cursor--;
|
||||
return ident_end > cursor ? cursor : NULL;
|
||||
}
|
||||
|
||||
static void c_import_function_push_param(ZCImportFunction *function, ZCImportParam param) {
|
||||
if (function->param_len == function->param_cap) {
|
||||
function->param_cap = z_grow_capacity(function->param_cap, function->param_len + 1, 4);
|
||||
function->params = z_checked_reallocarray(function->params, function->param_cap, sizeof(ZCImportParam));
|
||||
}
|
||||
function->params[function->param_len++] = param;
|
||||
}
|
||||
|
||||
static void c_import_function_vec_push(ZCImportFunctionVec *vec, ZCImportFunction function) {
|
||||
if (vec->len == vec->cap) {
|
||||
vec->cap = z_grow_capacity(vec->cap, vec->len + 1, 4);
|
||||
vec->items = z_checked_reallocarray(vec->items, vec->cap, sizeof(ZCImportFunction));
|
||||
}
|
||||
vec->items[vec->len++] = function;
|
||||
}
|
||||
|
||||
void z_c_import_function_free(ZCImportFunction *function) {
|
||||
if (!function) return;
|
||||
free(function->name);
|
||||
free(function->import_header);
|
||||
free(function->import_resolved_header);
|
||||
free(function->return_c_type);
|
||||
free(function->return_zero_type);
|
||||
for (size_t i = 0; i < function->param_len; i++) {
|
||||
free(function->params[i].name);
|
||||
free(function->params[i].c_type);
|
||||
free(function->params[i].zero_type);
|
||||
}
|
||||
free(function->params);
|
||||
*function = (ZCImportFunction){0};
|
||||
}
|
||||
|
||||
void z_c_import_function_vec_free(ZCImportFunctionVec *vec) {
|
||||
if (!vec) return;
|
||||
for (size_t i = 0; i < vec->len; i++) z_c_import_function_free(&vec->items[i]);
|
||||
free(vec->items);
|
||||
*vec = (ZCImportFunctionVec){0};
|
||||
}
|
||||
|
||||
static bool normalized_c_type(const char *c_type, char *out, size_t out_len) {
|
||||
if (!c_type || !out || out_len == 0) return false;
|
||||
out[0] = 0;
|
||||
if (strchr(c_type, '*') || strchr(c_type, '[') || strchr(c_type, ']')) return false;
|
||||
char *copy = trim_span_copy(c_type, c_type + strlen(c_type));
|
||||
size_t used = 0;
|
||||
const char *cursor = copy;
|
||||
while (*cursor) {
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
if (!*cursor) break;
|
||||
const char *word_start = cursor;
|
||||
while (*cursor && !isspace((unsigned char)*cursor)) cursor++;
|
||||
char *word = z_strndup(word_start, (size_t)(cursor - word_start));
|
||||
bool skip = strcmp(word, "const") == 0 || strcmp(word, "volatile") == 0 ||
|
||||
strcmp(word, "register") == 0 || strcmp(word, "extern") == 0;
|
||||
if (!skip) {
|
||||
if (used > 0 && used + 1 < out_len) out[used++] = ' ';
|
||||
for (size_t i = 0; word[i] && used + 1 < out_len; i++) out[used++] = word[i];
|
||||
}
|
||||
free(word);
|
||||
}
|
||||
out[used < out_len ? used : out_len - 1] = 0;
|
||||
free(copy);
|
||||
return out[0] != 0;
|
||||
}
|
||||
|
||||
bool z_c_type_to_zero(const char *c_type, char *out, size_t out_len) {
|
||||
if (!out || out_len == 0) return false;
|
||||
out[0] = 0;
|
||||
char normalized[128];
|
||||
if (!normalized_c_type(c_type, normalized, sizeof(normalized))) return false;
|
||||
if (strcmp(normalized, "void") == 0) snprintf(out, out_len, "Void");
|
||||
else if (strcmp(normalized, "bool") == 0 || strcmp(normalized, "_Bool") == 0) snprintf(out, out_len, "Bool");
|
||||
else if (strcmp(normalized, "char") == 0 || strcmp(normalized, "unsigned char") == 0 || strcmp(normalized, "uint8_t") == 0) snprintf(out, out_len, "u8");
|
||||
else if (strcmp(normalized, "unsigned short") == 0 || strcmp(normalized, "uint16_t") == 0) snprintf(out, out_len, "u16");
|
||||
else if (strcmp(normalized, "int") == 0 || strcmp(normalized, "signed int") == 0 || strcmp(normalized, "int32_t") == 0) snprintf(out, out_len, "i32");
|
||||
else if (strcmp(normalized, "unsigned") == 0 || strcmp(normalized, "unsigned int") == 0 || strcmp(normalized, "uint32_t") == 0) snprintf(out, out_len, "u32");
|
||||
else if (strcmp(normalized, "long long") == 0 || strcmp(normalized, "signed long long") == 0 || strcmp(normalized, "int64_t") == 0) snprintf(out, out_len, "i64");
|
||||
else if (strcmp(normalized, "unsigned long long") == 0 || strcmp(normalized, "uint64_t") == 0) snprintf(out, out_len, "u64");
|
||||
else if (strcmp(normalized, "size_t") == 0) snprintf(out, out_len, "usize");
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool c_import_parse_param(ZCImportFunction *function, const char *start, const char *end) {
|
||||
char *param_text = trim_span_copy(start, end);
|
||||
if (!param_text[0] || strcmp(param_text, "void") == 0) {
|
||||
free(param_text);
|
||||
return true;
|
||||
}
|
||||
char zero_type[128];
|
||||
if (z_c_type_to_zero(param_text, zero_type, sizeof(zero_type))) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "arg%zu", function->param_len);
|
||||
c_import_function_push_param(function, (ZCImportParam){.name = z_strdup(name), .c_type = z_strdup(param_text), .zero_type = z_strdup(zero_type)});
|
||||
free(param_text);
|
||||
return true;
|
||||
}
|
||||
const char *param_end = param_text + strlen(param_text);
|
||||
const char *name_start = last_ident_start_before(param_text, param_end);
|
||||
if (!name_start) {
|
||||
free(param_text);
|
||||
return false;
|
||||
}
|
||||
const char *name_end = name_start;
|
||||
while (*name_end && c_ident_char(*name_end)) name_end++;
|
||||
char *name = z_strndup(name_start, (size_t)(name_end - name_start));
|
||||
char *c_type = trim_span_copy(param_text, name_start);
|
||||
char *mapped = z_c_type_to_zero(c_type, zero_type, sizeof(zero_type)) ? z_strdup(zero_type) : z_strdup("Unknown");
|
||||
c_import_function_push_param(function, (ZCImportParam){.name = name, .c_type = c_type, .zero_type = mapped});
|
||||
free(param_text);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void c_import_parse_params(ZCImportFunction *function, const char *start, const char *end) {
|
||||
const char *cursor = start;
|
||||
while (cursor && cursor < end) {
|
||||
const char *comma = cursor;
|
||||
while (comma < end && *comma != ',') comma++;
|
||||
if (!c_import_parse_param(function, cursor, comma)) break;
|
||||
cursor = comma < end ? comma + 1 : end;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *c_import_function_decl_close(const char *paren) {
|
||||
if (!paren) return NULL;
|
||||
int depth = 0;
|
||||
for (const char *cursor = paren; *cursor; cursor++) {
|
||||
if (*cursor == '(') depth++;
|
||||
else if (*cursor == ')' && depth > 0) {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
const char *after = cursor + 1;
|
||||
while (isspace((unsigned char)*after)) after++;
|
||||
if (*after == ';') return cursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool c_import_parse_function_line(const char *line, ZCImportFunction *out) {
|
||||
const char *paren = strchr(line, '(');
|
||||
const char *close = c_import_function_decl_close(paren);
|
||||
const char *name_start = paren ? last_ident_start_before(line, paren) : NULL;
|
||||
if (!paren || !close || !name_start || strstr(line, "typedef")) return false;
|
||||
const char *name_end = name_start;
|
||||
while (*name_end && c_ident_char(*name_end)) name_end++;
|
||||
char *name = z_strndup(name_start, (size_t)(name_end - name_start));
|
||||
char *return_c_type = trim_span_copy(line, name_start);
|
||||
char return_zero_type[128];
|
||||
char *mapped_return = z_c_type_to_zero(return_c_type, return_zero_type, sizeof(return_zero_type)) ? z_strdup(return_zero_type) : z_strdup("Unknown");
|
||||
*out = (ZCImportFunction){
|
||||
.name = name,
|
||||
.return_c_type = return_c_type,
|
||||
.return_zero_type = mapped_return,
|
||||
};
|
||||
char *params_text = trim_span_copy(paren + 1, close);
|
||||
out->old_style_params = params_text[0] == 0;
|
||||
free(params_text);
|
||||
if (!out->old_style_params) c_import_parse_params(out, paren + 1, close);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void c_import_append_declaration_fragment(ZBuf *decl, const char *line) {
|
||||
if (!decl || !line) return;
|
||||
const char *cursor = line;
|
||||
bool wrote_space = decl->len > 0;
|
||||
while (*cursor) {
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
if (!*cursor) break;
|
||||
const char *start = cursor;
|
||||
while (*cursor && !isspace((unsigned char)*cursor)) cursor++;
|
||||
if (wrote_space) zbuf_append_char(decl, ' ');
|
||||
char *word = z_strndup(start, (size_t)(cursor - start));
|
||||
zbuf_append(decl, word);
|
||||
free(word);
|
||||
wrote_space = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void c_import_consume_complete_declarations(ZBuf *decl, ZCImportFunctionVec *out) {
|
||||
if (!decl || !decl->data || !out) return;
|
||||
const char *start = decl->data;
|
||||
const char *cursor = decl->data;
|
||||
bool consumed = false;
|
||||
while (*cursor) {
|
||||
if (*cursor == ';') {
|
||||
char *text = trim_span_copy(start, cursor + 1);
|
||||
if (text[0] && strchr(text, '(')) {
|
||||
ZCImportFunction function = {0};
|
||||
if (c_import_parse_function_line(text, &function)) c_import_function_vec_push(out, function);
|
||||
}
|
||||
free(text);
|
||||
start = cursor + 1;
|
||||
consumed = true;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
if (!consumed) return;
|
||||
char *tail = trim_span_copy(start, decl->data + strlen(decl->data));
|
||||
zbuf_free(decl);
|
||||
zbuf_init(decl);
|
||||
if (tail[0]) zbuf_append(decl, tail);
|
||||
free(tail);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool parent_active;
|
||||
bool active;
|
||||
bool branch_taken;
|
||||
} CImportPreprocFrame;
|
||||
|
||||
static bool c_import_parse_directive_word(const char *line, char *word, size_t word_len, const char **rest) {
|
||||
if (!line || line[0] != '#' || !word || word_len == 0) return false;
|
||||
const char *cursor = line + 1;
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
const char *start = cursor;
|
||||
while (c_ident_char(*cursor)) cursor++;
|
||||
size_t len = (size_t)(cursor - start);
|
||||
if (len == 0) return false;
|
||||
size_t copy_len = len < word_len - 1 ? len : word_len - 1;
|
||||
memcpy(word, start, copy_len);
|
||||
word[copy_len] = 0;
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
if (rest) *rest = cursor;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool c_import_preproc_active(const CImportPreprocFrame *frames, size_t depth) {
|
||||
return depth == 0 || (frames[depth - 1].parent_active && frames[depth - 1].active);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char **items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} CImportNameVec;
|
||||
|
||||
typedef struct {
|
||||
const ZTargetInfo *target;
|
||||
CImportNameVec defines;
|
||||
CImportNameVec truthy_defines;
|
||||
CImportNameVec undefines;
|
||||
} CImportPreprocEnv;
|
||||
|
||||
static void c_import_name_vec_free(CImportNameVec *vec) {
|
||||
if (!vec) return;
|
||||
for (size_t i = 0; i < vec->len; i++) free(vec->items[i]);
|
||||
free(vec->items);
|
||||
*vec = (CImportNameVec){0};
|
||||
}
|
||||
|
||||
static bool c_import_name_vec_contains(const CImportNameVec *vec, const char *name) {
|
||||
if (!vec || !name) return false;
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
if (vec->items[i] && strcmp(vec->items[i], name) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void c_import_name_vec_remove(CImportNameVec *vec, const char *name) {
|
||||
if (!vec || !name) return;
|
||||
for (size_t i = 0; i < vec->len; i++) {
|
||||
if (!vec->items[i] || strcmp(vec->items[i], name) != 0) continue;
|
||||
free(vec->items[i]);
|
||||
if (i + 1 < vec->len) memmove(&vec->items[i], &vec->items[i + 1], (vec->len - i - 1) * sizeof(char *));
|
||||
vec->len--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void c_import_name_vec_add(CImportNameVec *vec, const char *name) {
|
||||
if (!vec || !name || !name[0] || c_import_name_vec_contains(vec, name)) return;
|
||||
if (vec->len == vec->cap) {
|
||||
vec->cap = z_grow_capacity(vec->cap, vec->len + 1, 8);
|
||||
vec->items = z_checked_reallocarray(vec->items, vec->cap, sizeof(char *));
|
||||
}
|
||||
vec->items[vec->len++] = z_strdup(name);
|
||||
}
|
||||
|
||||
static void c_import_preproc_env_free(CImportPreprocEnv *env) {
|
||||
if (!env) return;
|
||||
c_import_name_vec_free(&env->defines);
|
||||
c_import_name_vec_free(&env->truthy_defines);
|
||||
c_import_name_vec_free(&env->undefines);
|
||||
}
|
||||
|
||||
static char *c_import_parse_macro_name_copy(const char *text) {
|
||||
const char *cursor = text ? text : "";
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
const char *start = cursor;
|
||||
while (c_ident_char(*cursor)) cursor++;
|
||||
return start < cursor ? z_strndup(start, (size_t)(cursor - start)) : z_strdup("");
|
||||
}
|
||||
|
||||
static char *c_import_parse_macro_definition_copy(const char *text, char **out_value) {
|
||||
const char *cursor = text ? text : "";
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
const char *start = cursor;
|
||||
while (c_ident_char(*cursor)) cursor++;
|
||||
char *name = start < cursor ? z_strndup(start, (size_t)(cursor - start)) : z_strdup("");
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
if (out_value) *out_value = trim_span_copy(cursor, cursor + strlen(cursor));
|
||||
return name;
|
||||
}
|
||||
|
||||
static bool c_import_string_eq(const char *left, const char *right) {
|
||||
return left && right && strcmp(left, right) == 0;
|
||||
}
|
||||
|
||||
static bool c_import_name_in_list(const char *name, const char *const *items, size_t len) {
|
||||
if (!name) return false;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (strcmp(name, items[i]) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool c_import_target_macro_defined(const ZTargetInfo *target, const char *name) {
|
||||
if (!name || !name[0]) return false;
|
||||
if (strcmp(name, "__cplusplus") == 0) return false;
|
||||
if (strcmp(name, "__STDC__") == 0 || strcmp(name, "__STDC_HOSTED__") == 0) return true;
|
||||
if (!target) return false;
|
||||
|
||||
const bool is_windows = c_import_string_eq(target->os, "windows");
|
||||
const bool is_linux = c_import_string_eq(target->os, "linux");
|
||||
const bool is_macos = c_import_string_eq(target->os, "macos");
|
||||
const bool is_x86_64 = c_import_string_eq(target->arch, "x86_64");
|
||||
const bool is_aarch64 = c_import_string_eq(target->arch, "aarch64");
|
||||
const bool is_msvc = c_import_string_eq(target->abi, "msvc");
|
||||
const bool is_gnu = c_import_string_eq(target->abi, "gnu");
|
||||
|
||||
static const char *const windows_names[] = {"_WIN32", "WIN32", "_WIN64", "WIN64", "__WIN32__", "__WIN64__"};
|
||||
static const char *const linux_names[] = {"__linux__", "__linux", "linux"};
|
||||
static const char *const macos_names[] = {"__APPLE__", "__MACH__", "__APPLE_CC__"};
|
||||
static const char *const x86_64_names[] = {"__x86_64__", "__x86_64", "__amd64__", "__amd64"};
|
||||
static const char *const msvc_x86_64_names[] = {"_M_X64", "_M_AMD64"};
|
||||
static const char *const aarch64_names[] = {"__aarch64__"};
|
||||
static const char *const macos_aarch64_names[] = {"__arm64__"};
|
||||
static const char *const msvc_aarch64_names[] = {"_M_ARM64"};
|
||||
|
||||
if (is_windows && c_import_name_in_list(name, windows_names, sizeof(windows_names) / sizeof(windows_names[0]))) return true;
|
||||
if (is_linux && c_import_name_in_list(name, linux_names, sizeof(linux_names) / sizeof(linux_names[0]))) return true;
|
||||
if (is_macos && c_import_name_in_list(name, macos_names, sizeof(macos_names) / sizeof(macos_names[0]))) return true;
|
||||
if (is_x86_64 && c_import_name_in_list(name, x86_64_names, sizeof(x86_64_names) / sizeof(x86_64_names[0]))) return true;
|
||||
if (is_msvc && is_x86_64 && c_import_name_in_list(name, msvc_x86_64_names, sizeof(msvc_x86_64_names) / sizeof(msvc_x86_64_names[0]))) return true;
|
||||
if (is_aarch64 && c_import_name_in_list(name, aarch64_names, sizeof(aarch64_names) / sizeof(aarch64_names[0]))) return true;
|
||||
if (is_macos && is_aarch64 && c_import_name_in_list(name, macos_aarch64_names, sizeof(macos_aarch64_names) / sizeof(macos_aarch64_names[0]))) return true;
|
||||
if (is_msvc && is_aarch64 && c_import_name_in_list(name, msvc_aarch64_names, sizeof(msvc_aarch64_names) / sizeof(msvc_aarch64_names[0]))) return true;
|
||||
if (!is_windows && (strcmp(name, "__LP64__") == 0 || strcmp(name, "_LP64") == 0)) return true;
|
||||
if (is_msvc && strcmp(name, "_MSC_VER") == 0) return true;
|
||||
if (is_gnu && (strcmp(name, "__GLIBC__") == 0 || strcmp(name, "__gnu_linux__") == 0)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool c_import_macro_defined(const CImportPreprocEnv *env, const char *name) {
|
||||
if (!env || !name || !name[0]) return false;
|
||||
if (c_import_name_vec_contains(&env->undefines, name)) return false;
|
||||
if (c_import_name_vec_contains(&env->defines, name)) return true;
|
||||
return c_import_target_macro_defined(env->target, name);
|
||||
}
|
||||
|
||||
static bool c_import_macro_expr_true(const CImportPreprocEnv *env, const char *name) {
|
||||
if (!env || !name || !name[0]) return false;
|
||||
if (c_import_name_vec_contains(&env->undefines, name)) return false;
|
||||
if (c_import_target_macro_defined(env->target, name)) return true;
|
||||
if (c_import_name_vec_contains(&env->defines, name)) return c_import_name_vec_contains(&env->truthy_defines, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void c_import_define_macro(CImportPreprocEnv *env, const char *name, bool truthy) {
|
||||
if (!env || !name || !name[0]) return;
|
||||
c_import_name_vec_remove(&env->undefines, name);
|
||||
c_import_name_vec_add(&env->defines, name);
|
||||
if (truthy) c_import_name_vec_add(&env->truthy_defines, name);
|
||||
else c_import_name_vec_remove(&env->truthy_defines, name);
|
||||
}
|
||||
|
||||
static void c_import_undef_macro(CImportPreprocEnv *env, const char *name) {
|
||||
if (!env || !name || !name[0]) return;
|
||||
c_import_name_vec_remove(&env->defines, name);
|
||||
c_import_name_vec_remove(&env->truthy_defines, name);
|
||||
c_import_name_vec_add(&env->undefines, name);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *cursor;
|
||||
const CImportPreprocEnv *env;
|
||||
bool valid;
|
||||
} CImportPPExprParser;
|
||||
|
||||
static void c_import_pp_skip_space(CImportPPExprParser *parser) {
|
||||
while (parser && isspace((unsigned char)*parser->cursor)) parser->cursor++;
|
||||
}
|
||||
|
||||
static bool c_import_pp_consume(CImportPPExprParser *parser, const char *token) {
|
||||
if (!parser || !token) return false;
|
||||
c_import_pp_skip_space(parser);
|
||||
size_t len = strlen(token);
|
||||
if (strncmp(parser->cursor, token, len) != 0) return false;
|
||||
parser->cursor += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool c_import_pp_parse_or(CImportPPExprParser *parser);
|
||||
|
||||
static bool c_import_pp_parse_macro_defined(CImportPPExprParser *parser) {
|
||||
c_import_pp_skip_space(parser);
|
||||
bool parenthesized = c_import_pp_consume(parser, "(");
|
||||
c_import_pp_skip_space(parser);
|
||||
const char *start = parser->cursor;
|
||||
while (c_ident_char(*parser->cursor)) parser->cursor++;
|
||||
if (start == parser->cursor) {
|
||||
parser->valid = false;
|
||||
return false;
|
||||
}
|
||||
char *name = z_strndup(start, (size_t)(parser->cursor - start));
|
||||
bool defined = c_import_macro_defined(parser->env, name);
|
||||
free(name);
|
||||
if (parenthesized && !c_import_pp_consume(parser, ")")) parser->valid = false;
|
||||
return defined;
|
||||
}
|
||||
|
||||
static bool c_import_pp_parse_primary(CImportPPExprParser *parser) {
|
||||
c_import_pp_skip_space(parser);
|
||||
if (c_import_pp_consume(parser, "(")) {
|
||||
bool value = c_import_pp_parse_or(parser);
|
||||
if (!c_import_pp_consume(parser, ")")) parser->valid = false;
|
||||
return value;
|
||||
}
|
||||
if (isdigit((unsigned char)*parser->cursor)) {
|
||||
char *end = NULL;
|
||||
unsigned long long value = strtoull(parser->cursor, &end, 0);
|
||||
if (!end || end == parser->cursor) {
|
||||
parser->valid = false;
|
||||
return false;
|
||||
}
|
||||
parser->cursor = end;
|
||||
return value != 0;
|
||||
}
|
||||
if (c_ident_char(*parser->cursor)) {
|
||||
const char *start = parser->cursor;
|
||||
while (c_ident_char(*parser->cursor)) parser->cursor++;
|
||||
char *name = z_strndup(start, (size_t)(parser->cursor - start));
|
||||
bool value = false;
|
||||
if (strcmp(name, "defined") == 0) {
|
||||
free(name);
|
||||
return c_import_pp_parse_macro_defined(parser);
|
||||
}
|
||||
if (strcmp(name, "true") == 0) value = true;
|
||||
else if (strcmp(name, "false") == 0) value = false;
|
||||
else value = c_import_macro_expr_true(parser->env, name);
|
||||
free(name);
|
||||
return value;
|
||||
}
|
||||
parser->valid = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool c_import_pp_parse_unary(CImportPPExprParser *parser) {
|
||||
if (c_import_pp_consume(parser, "!")) return !c_import_pp_parse_unary(parser);
|
||||
return c_import_pp_parse_primary(parser);
|
||||
}
|
||||
|
||||
static bool c_import_pp_parse_and(CImportPPExprParser *parser) {
|
||||
bool value = c_import_pp_parse_unary(parser);
|
||||
while (c_import_pp_consume(parser, "&&")) {
|
||||
bool right = c_import_pp_parse_unary(parser);
|
||||
value = value && right;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool c_import_pp_parse_or(CImportPPExprParser *parser) {
|
||||
bool value = c_import_pp_parse_and(parser);
|
||||
while (c_import_pp_consume(parser, "||")) {
|
||||
bool right = c_import_pp_parse_and(parser);
|
||||
value = value || right;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool c_import_pp_expr_active(const CImportPreprocEnv *env, const char *expr) {
|
||||
if (!expr) return false;
|
||||
CImportPPExprParser parser = {.cursor = expr, .env = env, .valid = true};
|
||||
bool value = c_import_pp_parse_or(&parser);
|
||||
c_import_pp_skip_space(&parser);
|
||||
return parser.valid && *parser.cursor == 0 && value;
|
||||
}
|
||||
|
||||
static void c_import_preproc_push(CImportPreprocFrame **frames, size_t *depth, size_t *cap, bool parent_active, bool active) {
|
||||
if (*depth == *cap) {
|
||||
*cap = z_grow_capacity(*cap, *depth + 1, 8);
|
||||
*frames = z_checked_reallocarray(*frames, *cap, sizeof(CImportPreprocFrame));
|
||||
}
|
||||
(*frames)[(*depth)++] = (CImportPreprocFrame){.parent_active = parent_active, .active = active, .branch_taken = active};
|
||||
}
|
||||
|
||||
static bool c_import_handle_preprocessor_line(const char *line, CImportPreprocFrame **frames, size_t *depth, size_t *cap, CImportPreprocEnv *env) {
|
||||
char directive[16];
|
||||
const char *rest = NULL;
|
||||
if (!c_import_parse_directive_word(line, directive, sizeof(directive), &rest)) return false;
|
||||
if (strcmp(directive, "if") == 0) {
|
||||
bool parent_active = c_import_preproc_active(*frames, *depth);
|
||||
c_import_preproc_push(frames, depth, cap, parent_active, parent_active && c_import_pp_expr_active(env, rest));
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "ifdef") == 0) {
|
||||
bool parent_active = c_import_preproc_active(*frames, *depth);
|
||||
char *name = c_import_parse_macro_name_copy(rest);
|
||||
c_import_preproc_push(frames, depth, cap, parent_active, parent_active && c_import_macro_defined(env, name));
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "ifndef") == 0) {
|
||||
bool parent_active = c_import_preproc_active(*frames, *depth);
|
||||
char *name = c_import_parse_macro_name_copy(rest);
|
||||
c_import_preproc_push(frames, depth, cap, parent_active, parent_active && !c_import_macro_defined(env, name));
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "elif") == 0 && *depth > 0) {
|
||||
CImportPreprocFrame *frame = &(*frames)[*depth - 1];
|
||||
bool active = frame->parent_active && !frame->branch_taken && c_import_pp_expr_active(env, rest);
|
||||
frame->active = active;
|
||||
frame->branch_taken = frame->branch_taken || active;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "else") == 0 && *depth > 0) {
|
||||
CImportPreprocFrame *frame = &(*frames)[*depth - 1];
|
||||
frame->active = frame->parent_active && !frame->branch_taken;
|
||||
frame->branch_taken = true;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "endif") == 0 && *depth > 0) {
|
||||
(*depth)--;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "define") == 0 && c_import_preproc_active(*frames, *depth)) {
|
||||
char *value = NULL;
|
||||
char *name = c_import_parse_macro_definition_copy(rest, &value);
|
||||
c_import_define_macro(env, name, value && value[0] ? c_import_pp_expr_active(env, value) : false);
|
||||
free(value);
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(directive, "undef") == 0 && c_import_preproc_active(*frames, *depth)) {
|
||||
char *name = c_import_parse_macro_name_copy(rest);
|
||||
c_import_undef_macro(env, name);
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool c_import_linkage_wrapper_line(const char *line) {
|
||||
if (!line) return false;
|
||||
if ((strstr(line, "extern \"C\"") || strstr(line, "extern \"C++\"")) && strchr(line, '{')) return true;
|
||||
const char *cursor = line;
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
if (*cursor != '}') return false;
|
||||
cursor++;
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
return *cursor == 0 || strncmp(cursor, "//", 2) == 0 || strncmp(cursor, "/*", 2) == 0;
|
||||
}
|
||||
|
||||
bool z_c_header_parse_functions_for_target(const char *header, const ZTargetInfo *target, ZCImportFunctionVec *out) {
|
||||
if (!out) return false;
|
||||
char *stripped = z_c_header_strip_comments(header);
|
||||
const char *cursor = stripped ? stripped : "";
|
||||
CImportPreprocFrame *frames = NULL;
|
||||
size_t preproc_depth = 0;
|
||||
size_t preproc_cap = 0;
|
||||
CImportPreprocEnv env = {.target = target};
|
||||
ZBuf declaration;
|
||||
zbuf_init(&declaration);
|
||||
while (*cursor) {
|
||||
const char *line_end = strchr(cursor, '\n');
|
||||
if (!line_end) line_end = cursor + strlen(cursor);
|
||||
char *line = trim_span_copy(cursor, line_end);
|
||||
bool wrapper_line = c_import_linkage_wrapper_line(line);
|
||||
if (line[0] == '#') {
|
||||
c_import_handle_preprocessor_line(line, &frames, &preproc_depth, &preproc_cap, &env);
|
||||
} else if (c_import_preproc_active(frames, preproc_depth) && !wrapper_line) {
|
||||
if (line[0]) c_import_append_declaration_fragment(&declaration, line);
|
||||
c_import_consume_complete_declarations(&declaration, out);
|
||||
} else if (!c_import_preproc_active(frames, preproc_depth)) {
|
||||
zbuf_free(&declaration);
|
||||
zbuf_init(&declaration);
|
||||
} else if (wrapper_line) {
|
||||
zbuf_free(&declaration);
|
||||
zbuf_init(&declaration);
|
||||
}
|
||||
free(line);
|
||||
cursor = *line_end ? line_end + 1 : line_end;
|
||||
}
|
||||
zbuf_free(&declaration);
|
||||
c_import_preproc_env_free(&env);
|
||||
free(frames);
|
||||
free(stripped);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_c_header_parse_functions(const char *header, ZCImportFunctionVec *out) {
|
||||
return z_c_header_parse_functions_for_target(header, NULL, out);
|
||||
}
|
||||
|
||||
static void c_import_function_clone(ZCImportFunction *out, const ZCImportFunction *source) {
|
||||
*out = (ZCImportFunction){
|
||||
.name = z_strdup(source->name),
|
||||
.import_header = source->import_header ? z_strdup(source->import_header) : NULL,
|
||||
.import_resolved_header = source->import_resolved_header ? z_strdup(source->import_resolved_header) : NULL,
|
||||
.return_c_type = z_strdup(source->return_c_type),
|
||||
.return_zero_type = z_strdup(source->return_zero_type),
|
||||
.old_style_params = source->old_style_params,
|
||||
};
|
||||
for (size_t i = 0; i < source->param_len; i++) {
|
||||
c_import_function_push_param(out, (ZCImportParam){
|
||||
.name = z_strdup(source->params[i].name),
|
||||
.c_type = z_strdup(source->params[i].c_type),
|
||||
.zero_type = z_strdup(source->params[i].zero_type),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool z_c_import_alias_exists(const Program *program, const char *alias) {
|
||||
if (!program || !alias) return false;
|
||||
for (size_t i = 0; i < program->c_imports.len; i++) {
|
||||
const CImport *import = &program->c_imports.items[i];
|
||||
if (import->alias && strcmp(import->alias, alias) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *c_import_read_path(const CImport *import) {
|
||||
return import && import->resolved_header && import->resolved_header[0] ? import->resolved_header : (import ? import->header : NULL);
|
||||
}
|
||||
|
||||
bool z_c_import_find_function_for_target(const Program *program, const ZTargetInfo *target, const char *alias, const char *symbol, ZCImportFunction *out, ZDiag *diag) {
|
||||
if (!program || !alias || !symbol || !out) return false;
|
||||
for (size_t i = 0; i < program->c_imports.len; i++) {
|
||||
const CImport *import = &program->c_imports.items[i];
|
||||
if (!import->alias || strcmp(import->alias, alias) != 0) continue;
|
||||
ZDiag read_diag = {0};
|
||||
const char *read_path = c_import_read_path(import);
|
||||
char *header = z_read_file(read_path, &read_diag);
|
||||
if (!header) {
|
||||
if (diag) {
|
||||
diag->code = 8001;
|
||||
diag->line = import->line;
|
||||
diag->column = import->column;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "extern c header could not be read");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "readable C header path");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", import->header ? import->header : "<missing>");
|
||||
snprintf(diag->help, sizeof(diag->help), "make the header path package-relative and target-specific");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ZCImportFunctionVec functions = {0};
|
||||
z_c_header_parse_functions_for_target(header, target, &functions);
|
||||
free(header);
|
||||
for (size_t fn = 0; fn < functions.len; fn++) {
|
||||
if (functions.items[fn].name && strcmp(functions.items[fn].name, symbol) == 0) {
|
||||
c_import_function_clone(out, &functions.items[fn]);
|
||||
free(out->import_header);
|
||||
free(out->import_resolved_header);
|
||||
out->import_header = import->header ? z_strdup(import->header) : NULL;
|
||||
out->import_resolved_header = import->resolved_header ? z_strdup(import->resolved_header) : NULL;
|
||||
z_c_import_function_vec_free(&functions);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
z_c_import_function_vec_free(&functions);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_c_import_find_function(const Program *program, const char *alias, const char *symbol, ZCImportFunction *out, ZDiag *diag) {
|
||||
return z_c_import_find_function_for_target(program, NULL, alias, symbol, out, diag);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef ZERO_C_C_IMPORT_H
|
||||
#define ZERO_C_C_IMPORT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *c_type;
|
||||
char *zero_type;
|
||||
} ZCImportParam;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *import_header;
|
||||
char *import_resolved_header;
|
||||
char *return_c_type;
|
||||
char *return_zero_type;
|
||||
bool old_style_params;
|
||||
ZCImportParam *params;
|
||||
size_t param_len;
|
||||
size_t param_cap;
|
||||
} ZCImportFunction;
|
||||
|
||||
typedef struct {
|
||||
ZCImportFunction *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} ZCImportFunctionVec;
|
||||
|
||||
void z_c_import_function_free(ZCImportFunction *function);
|
||||
void z_c_import_function_vec_free(ZCImportFunctionVec *vec);
|
||||
char *z_c_header_strip_comments(const char *header);
|
||||
bool z_c_type_to_zero(const char *c_type, char *out, size_t out_len);
|
||||
bool z_c_header_parse_functions(const char *header, ZCImportFunctionVec *out);
|
||||
bool z_c_header_parse_functions_for_target(const char *header, const ZTargetInfo *target, ZCImportFunctionVec *out);
|
||||
bool z_c_import_alias_exists(const Program *program, const char *alias);
|
||||
bool z_c_import_find_function(const Program *program, const char *alias, const char *symbol, ZCImportFunction *out, ZDiag *diag);
|
||||
bool z_c_import_find_function_for_target(const Program *program, const ZTargetInfo *target, const char *alias, const char *symbol, ZCImportFunction *out, ZDiag *diag);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "call_resolve.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void z_call_resolution_init(ZCallResolution *resolution) {
|
||||
if (!resolution) return;
|
||||
*resolution = (ZCallResolution){0};
|
||||
}
|
||||
|
||||
void z_call_resolution_free(ZCallResolution *resolution) {
|
||||
if (!resolution) return;
|
||||
for (size_t i = 0; i < resolution->binding_len; i++) {
|
||||
free(resolution->bindings[i].name);
|
||||
free(resolution->bindings[i].type);
|
||||
free(resolution->bindings[i].static_type);
|
||||
}
|
||||
free(resolution->bindings);
|
||||
for (size_t i = 0; i < resolution->arg_len; i++) {
|
||||
free(resolution->args[i].expected_type);
|
||||
free(resolution->args[i].actual_type);
|
||||
}
|
||||
free(resolution->args);
|
||||
for (size_t i = 0; i < resolution->error_len; i++) {
|
||||
free(resolution->errors[i].name);
|
||||
}
|
||||
free(resolution->errors);
|
||||
free(resolution->callee_name);
|
||||
free(resolution->return_type);
|
||||
free(resolution->effect_summary_key);
|
||||
*resolution = (ZCallResolution){0};
|
||||
}
|
||||
|
||||
void z_call_resolution_set_callee_name(ZCallResolution *resolution, const char *name) {
|
||||
if (!resolution) return;
|
||||
if (resolution->callee_name == name) return;
|
||||
char *copy = name ? z_strdup(name) : NULL;
|
||||
free(resolution->callee_name);
|
||||
resolution->callee_name = copy;
|
||||
}
|
||||
|
||||
void z_call_resolution_set_return_type(ZCallResolution *resolution, const char *return_type) {
|
||||
if (!resolution) return;
|
||||
if (resolution->return_type == return_type) return;
|
||||
char *copy = z_strdup(return_type ? return_type : "Unknown");
|
||||
free(resolution->return_type);
|
||||
resolution->return_type = copy;
|
||||
}
|
||||
|
||||
void z_call_resolution_set_effect_summary_key(ZCallResolution *resolution, const char *key) {
|
||||
if (!resolution) return;
|
||||
if (resolution->effect_summary_key == key) return;
|
||||
char *copy = key ? z_strdup(key) : NULL;
|
||||
free(resolution->effect_summary_key);
|
||||
resolution->effect_summary_key = copy;
|
||||
}
|
||||
|
||||
size_t z_call_resolution_expected_arg_count(const ZCallResolution *resolution) {
|
||||
if (!resolution || resolution->param_len < resolution->param_offset) return 0;
|
||||
return resolution->param_len - resolution->param_offset;
|
||||
}
|
||||
|
||||
void z_call_resolution_add_arg(ZCallResolution *resolution, size_t param_index, const Expr *arg_expr, const char *expected_type, const char *actual_type) {
|
||||
if (!resolution) return;
|
||||
for (size_t i = 0; i < resolution->arg_len; i++) {
|
||||
if (resolution->args[i].param_index == param_index) {
|
||||
resolution->args[i].arg_expr = arg_expr;
|
||||
char *expected_copy = z_strdup(expected_type ? expected_type : "Unknown");
|
||||
char *actual_copy = z_strdup(actual_type ? actual_type : "Unknown");
|
||||
free(resolution->args[i].expected_type);
|
||||
free(resolution->args[i].actual_type);
|
||||
resolution->args[i].expected_type = expected_copy;
|
||||
resolution->args[i].actual_type = actual_copy;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (resolution->arg_len == resolution->arg_cap) {
|
||||
size_t next_cap = resolution->arg_cap ? resolution->arg_cap * 2 : 4;
|
||||
resolution->args = z_checked_reallocarray(resolution->args, next_cap, sizeof(ZCallArgument));
|
||||
for (size_t i = resolution->arg_cap; i < next_cap; i++) {
|
||||
resolution->args[i] = (ZCallArgument){0};
|
||||
}
|
||||
resolution->arg_cap = next_cap;
|
||||
}
|
||||
ZCallArgument *arg = &resolution->args[resolution->arg_len++];
|
||||
arg->param_index = param_index;
|
||||
arg->arg_expr = arg_expr;
|
||||
arg->expected_type = z_strdup(expected_type ? expected_type : "Unknown");
|
||||
arg->actual_type = z_strdup(actual_type ? actual_type : "Unknown");
|
||||
}
|
||||
|
||||
const char *z_call_resolution_param_type(const ZCallResolution *resolution, size_t param_index) {
|
||||
if (!resolution) return NULL;
|
||||
for (size_t i = 0; i < resolution->arg_len; i++) {
|
||||
if (resolution->args[i].param_index == param_index) return resolution->args[i].expected_type;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void z_call_resolution_add_binding(ZCallResolution *resolution, const char *name, const char *type, bool is_static, const char *static_type) {
|
||||
if (!resolution || !name) return;
|
||||
for (size_t i = 0; i < resolution->binding_len; i++) {
|
||||
if (resolution->bindings[i].name && strcmp(resolution->bindings[i].name, name) == 0) {
|
||||
char *type_copy = z_strdup(type ? type : "Unknown");
|
||||
char *static_type_copy = static_type ? z_strdup(static_type) : NULL;
|
||||
free(resolution->bindings[i].type);
|
||||
free(resolution->bindings[i].static_type);
|
||||
resolution->bindings[i].type = type_copy;
|
||||
resolution->bindings[i].is_static = is_static;
|
||||
resolution->bindings[i].static_type = static_type_copy;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (resolution->binding_len == resolution->binding_cap) {
|
||||
size_t next_cap = resolution->binding_cap ? resolution->binding_cap * 2 : 4;
|
||||
resolution->bindings = z_checked_reallocarray(resolution->bindings, next_cap, sizeof(ZCallBinding));
|
||||
for (size_t i = resolution->binding_cap; i < next_cap; i++) {
|
||||
resolution->bindings[i] = (ZCallBinding){0};
|
||||
}
|
||||
resolution->binding_cap = next_cap;
|
||||
}
|
||||
ZCallBinding *binding = &resolution->bindings[resolution->binding_len++];
|
||||
binding->name = z_strdup(name);
|
||||
binding->type = z_strdup(type ? type : "Unknown");
|
||||
binding->is_static = is_static;
|
||||
binding->static_type = static_type ? z_strdup(static_type) : NULL;
|
||||
}
|
||||
|
||||
const char *z_call_resolution_binding_type(const ZCallResolution *resolution, const char *name) {
|
||||
if (!resolution || !name) return NULL;
|
||||
for (size_t i = 0; i < resolution->binding_len; i++) {
|
||||
if (resolution->bindings[i].name && strcmp(resolution->bindings[i].name, name) == 0) return resolution->bindings[i].type;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void z_call_resolution_add_error(ZCallResolution *resolution, const char *name) {
|
||||
if (!resolution || !name) return;
|
||||
if (resolution->error_len == resolution->error_cap) {
|
||||
size_t next_cap = resolution->error_cap ? resolution->error_cap * 2 : 4;
|
||||
resolution->errors = z_checked_reallocarray(resolution->errors, next_cap, sizeof(ZCallError));
|
||||
for (size_t i = resolution->error_cap; i < next_cap; i++) {
|
||||
resolution->errors[i] = (ZCallError){0};
|
||||
}
|
||||
resolution->error_cap = next_cap;
|
||||
}
|
||||
resolution->errors[resolution->error_len++].name = z_strdup(name);
|
||||
resolution->fallible = true;
|
||||
}
|
||||
|
||||
void z_call_resolution_error_set_text(const ZCallResolution *resolution, char *buf, size_t cap) {
|
||||
if (!buf || cap == 0) return;
|
||||
snprintf(buf, cap, "raises [");
|
||||
size_t used = strlen(buf);
|
||||
bool first = true;
|
||||
for (size_t i = 0; resolution && i < resolution->error_len; i++) {
|
||||
const char *name = resolution->errors[i].name;
|
||||
if (!name) continue;
|
||||
if (used >= cap - 1) break;
|
||||
snprintf(buf + used, cap - used, "%s%s", first ? "" : ", ", name);
|
||||
used = strlen(buf);
|
||||
first = false;
|
||||
}
|
||||
if (used < cap - 1) snprintf(buf + used, cap - used, "]");
|
||||
else buf[cap - 1] = '\0';
|
||||
}
|
||||
|
||||
const char *z_call_kind_name(ZCallKind kind) {
|
||||
switch (kind) {
|
||||
case Z_CALL_FUNCTION: return "function";
|
||||
case Z_CALL_STDLIB: return "stdlib";
|
||||
case Z_CALL_RECEIVER: return "receiver";
|
||||
case Z_CALL_SHAPE_NAMESPACE: return "shape_namespace";
|
||||
case Z_CALL_CONSTRAINED_INTERFACE: return "constrained_interface";
|
||||
case Z_CALL_CONCRETE_CONSTRAINED_SHAPE: return "concrete_constrained_shape";
|
||||
case Z_CALL_CHOICE_CONSTRUCTOR: return "choice_constructor";
|
||||
case Z_CALL_C_IMPORT: return "c_import";
|
||||
case Z_CALL_UNKNOWN:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef ZERO_C_CALL_RESOLVE_H
|
||||
#define ZERO_C_CALL_RESOLVE_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef struct ZStdHelperInfo ZStdHelperInfo;
|
||||
|
||||
typedef enum {
|
||||
Z_CALL_UNKNOWN,
|
||||
Z_CALL_FUNCTION,
|
||||
Z_CALL_STDLIB,
|
||||
Z_CALL_RECEIVER,
|
||||
Z_CALL_SHAPE_NAMESPACE,
|
||||
Z_CALL_CONSTRAINED_INTERFACE,
|
||||
Z_CALL_CONCRETE_CONSTRAINED_SHAPE,
|
||||
Z_CALL_CHOICE_CONSTRUCTOR,
|
||||
Z_CALL_C_IMPORT
|
||||
} ZCallKind;
|
||||
|
||||
typedef struct {
|
||||
size_t param_index;
|
||||
const Expr *arg_expr;
|
||||
char *expected_type;
|
||||
char *actual_type;
|
||||
} ZCallArgument;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *type;
|
||||
bool is_static;
|
||||
char *static_type;
|
||||
} ZCallBinding;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
} ZCallError;
|
||||
|
||||
typedef struct {
|
||||
ZCallKind kind;
|
||||
const Expr *call_expr;
|
||||
const Expr *callee_expr;
|
||||
const Expr *receiver_expr;
|
||||
const TypeArgVec *type_args;
|
||||
const Function *callee;
|
||||
const Shape *shape;
|
||||
const InterfaceDecl *interface;
|
||||
const ZStdHelperInfo *std_helper;
|
||||
const Choice *choice;
|
||||
const Param *choice_case;
|
||||
size_t param_offset;
|
||||
size_t param_len;
|
||||
char *callee_name;
|
||||
char *return_type;
|
||||
char *effect_summary_key;
|
||||
ZCallArgument *args;
|
||||
size_t arg_len;
|
||||
size_t arg_cap;
|
||||
ZCallBinding *bindings;
|
||||
size_t binding_len;
|
||||
size_t binding_cap;
|
||||
ZCallError *errors;
|
||||
size_t error_len;
|
||||
size_t error_cap;
|
||||
bool fallible;
|
||||
} ZCallResolution;
|
||||
|
||||
void z_call_resolution_init(ZCallResolution *resolution);
|
||||
void z_call_resolution_free(ZCallResolution *resolution);
|
||||
void z_call_resolution_set_callee_name(ZCallResolution *resolution, const char *name);
|
||||
void z_call_resolution_set_return_type(ZCallResolution *resolution, const char *return_type);
|
||||
void z_call_resolution_set_effect_summary_key(ZCallResolution *resolution, const char *key);
|
||||
size_t z_call_resolution_expected_arg_count(const ZCallResolution *resolution);
|
||||
void z_call_resolution_add_arg(ZCallResolution *resolution, size_t param_index, const Expr *arg_expr, const char *expected_type, const char *actual_type);
|
||||
const char *z_call_resolution_param_type(const ZCallResolution *resolution, size_t param_index);
|
||||
void z_call_resolution_add_binding(ZCallResolution *resolution, const char *name, const char *type, bool is_static, const char *static_type);
|
||||
const char *z_call_resolution_binding_type(const ZCallResolution *resolution, const char *name);
|
||||
void z_call_resolution_add_error(ZCallResolution *resolution, const char *name);
|
||||
void z_call_resolution_error_set_text(const ZCallResolution *resolution, char *buf, size_t cap);
|
||||
const char *z_call_kind_name(ZCallKind kind);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
#ifndef ZERO_C_CANONICAL_TEXT_H
|
||||
#define ZERO_C_CANONICAL_TEXT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef enum {
|
||||
Z_CANON_TOKEN_WORD,
|
||||
Z_CANON_TOKEN_STRING,
|
||||
Z_CANON_TOKEN_CHAR,
|
||||
Z_CANON_TOKEN_NUMBER,
|
||||
Z_CANON_TOKEN_SYMBOL,
|
||||
Z_CANON_TOKEN_COMMENT,
|
||||
Z_CANON_TOKEN_NEWLINE,
|
||||
Z_CANON_TOKEN_EOF
|
||||
} ZCanonicalTokenKind;
|
||||
|
||||
typedef struct {
|
||||
ZCanonicalTokenKind kind;
|
||||
char *text;
|
||||
int line;
|
||||
int column;
|
||||
size_t offset;
|
||||
size_t length;
|
||||
} ZCanonicalToken;
|
||||
|
||||
typedef struct {
|
||||
ZCanonicalToken *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} ZCanonicalTokenVec;
|
||||
|
||||
typedef enum {
|
||||
Z_CANON_NODE_DECL,
|
||||
Z_CANON_NODE_BLOCK,
|
||||
Z_CANON_NODE_STMT
|
||||
} ZCanonicalNodeKind;
|
||||
|
||||
typedef struct {
|
||||
ZCanonicalNodeKind kind;
|
||||
size_t first_token;
|
||||
size_t token_count;
|
||||
size_t depth;
|
||||
int line;
|
||||
int column;
|
||||
} ZCanonicalNode;
|
||||
|
||||
typedef struct {
|
||||
ZCanonicalNode *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} ZCanonicalTree;
|
||||
|
||||
typedef struct {
|
||||
size_t declaration_count;
|
||||
size_t function_count;
|
||||
size_t type_count;
|
||||
size_t enum_count;
|
||||
size_t choice_count;
|
||||
size_t interface_count;
|
||||
size_t test_count;
|
||||
size_t block_count;
|
||||
size_t statement_count;
|
||||
size_t comment_count;
|
||||
size_t max_block_depth;
|
||||
} ZCanonicalFacts;
|
||||
|
||||
ZCanonicalTokenVec z_canonical_text_tokenize(const char *source, ZDiag *diag);
|
||||
bool z_canonical_text_parse(const ZCanonicalTokenVec *tokens, ZCanonicalTree *tree, ZCanonicalFacts *facts, ZDiag *diag);
|
||||
bool z_canonical_text_format(const ZCanonicalTokenVec *tokens, ZBuf *out, ZDiag *diag);
|
||||
bool z_canonical_text_format_source(const char *source, ZBuf *out, ZDiag *diag);
|
||||
bool z_canonical_text_write_program(const Program *program, ZBuf *out, ZDiag *diag);
|
||||
Program z_parse_canonical_text_program(const ZCanonicalTokenVec *tokens, ZDiag *diag);
|
||||
bool z_parse_canonical_text_program_source(const char *source, Program *out, ZDiag *diag);
|
||||
void z_free_canonical_text_tokens(ZCanonicalTokenVec *tokens);
|
||||
void z_free_canonical_text_tree(ZCanonicalTree *tree);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,354 @@
|
||||
#include "canonical_text.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
const ZCanonicalTokenVec *tokens;
|
||||
ZBuf *out;
|
||||
bool *compact_angles;
|
||||
const ZCanonicalToken *previous;
|
||||
const ZCanonicalToken *before_previous;
|
||||
const ZCanonicalToken *line_first;
|
||||
size_t indent;
|
||||
size_t paren_depth, bracket_depth, angle_depth;
|
||||
bool line_start, line_has_assignment, line_has_list_declaration;
|
||||
unsigned char *brace_flags;
|
||||
size_t brace_depth;
|
||||
size_t brace_cap;
|
||||
} CanonFormat;
|
||||
|
||||
enum { FMT_BRACE_MULTILINE = 1, FMT_BRACE_LIST = 2, FMT_BRACE_MATCH = 4 };
|
||||
|
||||
static bool fmt_text_eq(const char *left, const char *right) {
|
||||
if (!left || !right) return left == right;
|
||||
while (*left && *right && *left == *right) {
|
||||
left++;
|
||||
right++;
|
||||
}
|
||||
return *left == 0 && *right == 0;
|
||||
}
|
||||
|
||||
static bool fmt_is_symbol(const ZCanonicalToken *token, const char *text) {
|
||||
return token && token->kind == Z_CANON_TOKEN_SYMBOL && fmt_text_eq(token->text, text);
|
||||
}
|
||||
|
||||
static bool fmt_is_word(const ZCanonicalToken *token, const char *text) {
|
||||
return token && token->kind == Z_CANON_TOKEN_WORD && fmt_text_eq(token->text, text);
|
||||
}
|
||||
|
||||
static void fmt_append_char(ZBuf *out, char ch) {
|
||||
size_t required = out->len + 2;
|
||||
if (required > out->cap) {
|
||||
out->cap = z_grow_capacity(out->cap, required, 64);
|
||||
out->data = z_checked_reallocarray(out->data, out->cap, sizeof(char));
|
||||
}
|
||||
out->data[out->len++] = ch;
|
||||
out->data[out->len] = 0;
|
||||
}
|
||||
|
||||
static void fmt_append(ZBuf *out, const char *text) {
|
||||
while (text && *text) fmt_append_char(out, *text++);
|
||||
}
|
||||
|
||||
static void fmt_newline(CanonFormat *fmt) {
|
||||
if (fmt->out->len > 0 && fmt->out->data[fmt->out->len - 1] != '\n') fmt_append_char(fmt->out, '\n');
|
||||
fmt->line_start = true;
|
||||
fmt->previous = NULL;
|
||||
fmt->before_previous = NULL;
|
||||
fmt->line_first = NULL;
|
||||
fmt->line_has_assignment = false;
|
||||
fmt->line_has_list_declaration = false;
|
||||
fmt->paren_depth = 0;
|
||||
fmt->bracket_depth = 0;
|
||||
fmt->angle_depth = 0;
|
||||
}
|
||||
|
||||
static void fmt_blank_line(CanonFormat *fmt) {
|
||||
fmt_newline(fmt);
|
||||
if (fmt->out->len < 2 || fmt->out->data[fmt->out->len - 2] != '\n') fmt_append_char(fmt->out, '\n');
|
||||
}
|
||||
|
||||
static void fmt_indent(CanonFormat *fmt) {
|
||||
if (!fmt->line_start) return;
|
||||
for (size_t i = 0; i < fmt->indent; i++) fmt_append(fmt->out, " ");
|
||||
fmt->line_start = false;
|
||||
}
|
||||
|
||||
static const ZCanonicalToken *fmt_next_significant(const ZCanonicalTokenVec *tokens, size_t index) {
|
||||
for (size_t i = index + 1; i < tokens->len; i++) {
|
||||
const ZCanonicalToken *token = &tokens->items[i];
|
||||
if (token->kind != Z_CANON_TOKEN_NEWLINE && token->kind != Z_CANON_TOKEN_COMMENT) return token;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool fmt_angle_tail_allows_compact(const ZCanonicalToken *token) {
|
||||
if (!token || token->kind == Z_CANON_TOKEN_EOF || token->kind == Z_CANON_TOKEN_NEWLINE || token->kind == Z_CANON_TOKEN_COMMENT) return true;
|
||||
if (fmt_is_word(token, "raises")) return true;
|
||||
return fmt_is_symbol(token, "(") || fmt_is_symbol(token, "{") || fmt_is_symbol(token, "[") ||
|
||||
fmt_is_symbol(token, ")") || fmt_is_symbol(token, "}") || fmt_is_symbol(token, "]") ||
|
||||
fmt_is_symbol(token, ",") || fmt_is_symbol(token, "=") || fmt_is_symbol(token, "->") ||
|
||||
fmt_is_symbol(token, ">");
|
||||
}
|
||||
|
||||
static bool fmt_find_matching_angle(const ZCanonicalTokenVec *tokens, size_t open_index, size_t *close_index) {
|
||||
size_t depth = 0;
|
||||
for (size_t i = open_index; i < tokens->len; i++) {
|
||||
const ZCanonicalToken *token = &tokens->items[i];
|
||||
if (token->kind == Z_CANON_TOKEN_EOF || token->kind == Z_CANON_TOKEN_NEWLINE || token->kind == Z_CANON_TOKEN_COMMENT) break;
|
||||
if (fmt_is_symbol(token, "<")) {
|
||||
depth++;
|
||||
continue;
|
||||
}
|
||||
if (!fmt_is_symbol(token, ">")) continue;
|
||||
if (depth == 0) return false;
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
*close_index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool *fmt_build_compact_angles(const ZCanonicalTokenVec *tokens) {
|
||||
if (!tokens || tokens->len == 0) return NULL;
|
||||
bool *compact = z_checked_reallocarray(NULL, tokens->len, sizeof(bool));
|
||||
for (size_t i = 0; i < tokens->len; i++) compact[i] = false;
|
||||
for (size_t i = 1; i < tokens->len; i++) {
|
||||
const ZCanonicalToken *token = &tokens->items[i];
|
||||
const ZCanonicalToken *previous = &tokens->items[i - 1];
|
||||
if (!fmt_is_symbol(token, "<") || previous->kind != Z_CANON_TOKEN_WORD) continue;
|
||||
size_t close = 0;
|
||||
if (!fmt_find_matching_angle(tokens, i, &close) || close == i + 1) continue;
|
||||
const ZCanonicalToken *tail = close + 1 < tokens->len ? &tokens->items[close + 1] : NULL;
|
||||
if (!fmt_angle_tail_allows_compact(tail)) continue;
|
||||
compact[i] = true;
|
||||
compact[close] = true;
|
||||
}
|
||||
return compact;
|
||||
}
|
||||
|
||||
static bool fmt_token_is_compact_angle(const CanonFormat *fmt, const ZCanonicalToken *token) {
|
||||
if (!fmt || !fmt->tokens || !fmt->compact_angles || !token || (!fmt_is_symbol(token, "<") && !fmt_is_symbol(token, ">"))) return false;
|
||||
size_t index = (size_t)(token - fmt->tokens->items);
|
||||
return index < fmt->tokens->len && fmt->compact_angles[index];
|
||||
}
|
||||
|
||||
static bool fmt_compact_angle_pair(const CanonFormat *fmt, const ZCanonicalToken *left, const ZCanonicalToken *right) {
|
||||
if (!left || !right) return false;
|
||||
if (fmt_token_is_compact_angle(fmt, right)) return true;
|
||||
if (fmt_token_is_compact_angle(fmt, left) && fmt_is_symbol(left, "<")) return true;
|
||||
return fmt_token_is_compact_angle(fmt, left) && fmt_is_symbol(left, ">") && (fmt_is_symbol(right, ">") || fmt_is_symbol(right, "("));
|
||||
}
|
||||
|
||||
static void fmt_push_brace(CanonFormat *fmt, unsigned char flags) {
|
||||
if (fmt->brace_depth == fmt->brace_cap) {
|
||||
size_t next_cap = z_grow_capacity(fmt->brace_cap, fmt->brace_depth + 1, 64);
|
||||
fmt->brace_flags = z_checked_reallocarray(fmt->brace_flags, next_cap, sizeof(unsigned char));
|
||||
fmt->brace_cap = next_cap;
|
||||
}
|
||||
fmt->brace_flags[fmt->brace_depth++] = flags;
|
||||
}
|
||||
|
||||
static bool fmt_current_brace(const CanonFormat *fmt, unsigned char flag) { return fmt->brace_depth > 0 && (fmt->brace_flags[fmt->brace_depth - 1] & flag) != 0; }
|
||||
|
||||
static bool fmt_starts_declaration(const ZCanonicalToken *token) {
|
||||
return fmt_is_word(token, "use") || fmt_is_word(token, "extern") || fmt_is_word(token, "const") ||
|
||||
fmt_is_word(token, "alias") || fmt_is_word(token, "type") || fmt_is_word(token, "packed") ||
|
||||
fmt_is_word(token, "enum") || fmt_is_word(token, "choice") || fmt_is_word(token, "interface") ||
|
||||
fmt_is_word(token, "fn") || fmt_is_word(token, "pub") || fmt_is_word(token, "export") ||
|
||||
fmt_is_word(token, "test");
|
||||
}
|
||||
|
||||
static bool fmt_line_opens_multiline_brace(const CanonFormat *fmt) {
|
||||
if (fmt->line_has_assignment) return false;
|
||||
const ZCanonicalToken *first = fmt->line_first;
|
||||
return fmt_is_word(first, "type") || fmt_is_word(first, "packed") || fmt_is_word(first, "enum") ||
|
||||
fmt_is_word(first, "choice") || fmt_is_word(first, "interface") || fmt_is_word(first, "extern") || fmt_is_word(first, "fn") ||
|
||||
fmt_is_word(first, "pub") || fmt_is_word(first, "export") || fmt_is_word(first, "test") ||
|
||||
fmt_is_word(first, "if") || fmt_is_word(first, "else") || fmt_is_word(first, "while") ||
|
||||
fmt_is_word(first, "for") || fmt_is_word(first, "match") ||
|
||||
fmt_is_symbol(first, ".");
|
||||
}
|
||||
|
||||
static bool fmt_operator(const ZCanonicalToken *token) {
|
||||
return fmt_is_symbol(token, "=") || fmt_is_symbol(token, "->") || fmt_is_symbol(token, "=>") ||
|
||||
fmt_is_symbol(token, "+") || fmt_is_symbol(token, "-") || fmt_is_symbol(token, "*") ||
|
||||
fmt_is_symbol(token, "/") || fmt_is_symbol(token, "%") || fmt_is_symbol(token, "==") ||
|
||||
fmt_is_symbol(token, "!=") || fmt_is_symbol(token, "<=") || fmt_is_symbol(token, ">=") ||
|
||||
fmt_is_symbol(token, "&&") || fmt_is_symbol(token, "||") || fmt_is_symbol(token, "+%") ||
|
||||
fmt_is_symbol(token, "+|");
|
||||
}
|
||||
|
||||
static bool fmt_prefix_context(const ZCanonicalToken *token) {
|
||||
if (!token) return true;
|
||||
if (fmt_operator(token) || fmt_is_symbol(token, "(") || fmt_is_symbol(token, "[") || fmt_is_symbol(token, "{") ||
|
||||
fmt_is_symbol(token, "<") || fmt_is_symbol(token, ">") || fmt_is_symbol(token, ",") || fmt_is_symbol(token, ":") || fmt_is_symbol(token, ";")) return true;
|
||||
return fmt_is_word(token, "if") || fmt_is_word(token, "while") || fmt_is_word(token, "return") || fmt_is_word(token, "check") || fmt_is_word(token, "expect") || fmt_is_word(token, "rescue");
|
||||
}
|
||||
|
||||
static bool fmt_prefix_operator_pair(const CanonFormat *fmt, const ZCanonicalToken *left, const ZCanonicalToken *right) {
|
||||
if (!left || !right) return false;
|
||||
return fmt_is_symbol(left, "&") || fmt_is_symbol(left, "!") || ((fmt_is_symbol(left, "-") || fmt_is_symbol(left, "+") || fmt_is_symbol(left, "*")) && fmt_prefix_context(fmt->before_previous));
|
||||
}
|
||||
|
||||
static bool fmt_operand_can_take_postfix(const ZCanonicalToken *token) {
|
||||
return token && (token->kind == Z_CANON_TOKEN_WORD || token->kind == Z_CANON_TOKEN_STRING ||
|
||||
token->kind == Z_CANON_TOKEN_CHAR || token->kind == Z_CANON_TOKEN_NUMBER ||
|
||||
fmt_is_symbol(token, ")") || fmt_is_symbol(token, "]"));
|
||||
}
|
||||
|
||||
static bool fmt_space_before(const CanonFormat *fmt, const ZCanonicalToken *token) {
|
||||
const ZCanonicalToken *prev = fmt->previous;
|
||||
if (!prev || fmt->line_start) return false;
|
||||
if (fmt_is_symbol(token, ",") || fmt_is_symbol(token, ":") || fmt_is_symbol(token, ";") || fmt_is_symbol(token, ")") || fmt_is_symbol(token, "]")) return false;
|
||||
if (fmt_is_symbol(token, "}")) return !fmt_is_symbol(prev, "{");
|
||||
if (fmt_is_symbol(token, ".") || fmt_is_symbol(prev, ".")) return false;
|
||||
if (fmt_is_symbol(token, "..") || fmt_is_symbol(prev, "..")) return false;
|
||||
if (fmt_prefix_operator_pair(fmt, prev, token)) return false;
|
||||
if (fmt_is_symbol(prev, "]") && token->kind == Z_CANON_TOKEN_WORD && !fmt_is_word(token, "as") && !fmt_is_word(token, "err") && !fmt_is_word(token, "in")) return false;
|
||||
if (fmt_compact_angle_pair(fmt, prev, token)) return false;
|
||||
if (fmt_is_symbol(token, "(")) {
|
||||
if (fmt_is_symbol(prev, "]")) return false;
|
||||
if (prev->kind == Z_CANON_TOKEN_WORD && !fmt_is_word(prev, "if") && !fmt_is_word(prev, "while") &&
|
||||
!fmt_is_word(prev, "for") && !fmt_is_word(prev, "match") && !fmt_is_word(prev, "return") &&
|
||||
!fmt_is_word(prev, "check") && !fmt_is_word(prev, "expect") && !fmt_is_word(prev, "rescue")) return false;
|
||||
return !(fmt_is_symbol(prev, "(") || fmt_is_symbol(prev, "["));
|
||||
}
|
||||
if (fmt_is_symbol(token, "[")) {
|
||||
if (fmt_is_word(prev, "raises")) return true;
|
||||
if (fmt_operand_can_take_postfix(prev)) return false;
|
||||
return !(fmt_is_symbol(prev, "(") || fmt_is_symbol(prev, "[") || fmt_is_symbol(prev, "<"));
|
||||
}
|
||||
if (fmt_is_symbol(prev, "(") || fmt_is_symbol(prev, "[")) return false;
|
||||
if (fmt_is_symbol(token, "{")) return true;
|
||||
if (fmt_is_symbol(prev, "{")) return true;
|
||||
if (fmt_operator(prev) || fmt_operator(token) || fmt_is_symbol(token, "<") || fmt_is_symbol(token, ">") || fmt_is_symbol(prev, ":") || fmt_is_symbol(prev, ",") || fmt_is_symbol(prev, ";")) return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fmt_emit_token(CanonFormat *fmt, const ZCanonicalToken *token) {
|
||||
if (fmt_space_before(fmt, token)) fmt_append_char(fmt->out, ' ');
|
||||
fmt_indent(fmt);
|
||||
fmt_append(fmt->out, token->text);
|
||||
if (!fmt->line_first && (token->kind != Z_CANON_TOKEN_SYMBOL || fmt_is_symbol(token, "."))) fmt->line_first = token;
|
||||
if (fmt_is_symbol(token, "=")) fmt->line_has_assignment = true;
|
||||
if (fmt_is_word(token, "type") || fmt_is_word(token, "packed") || fmt_is_word(token, "extern") ||
|
||||
fmt_is_word(token, "enum") || fmt_is_word(token, "choice")) fmt->line_has_list_declaration = true;
|
||||
fmt->before_previous = fmt->previous;
|
||||
fmt->previous = token;
|
||||
}
|
||||
|
||||
static void fmt_note_delimiter(CanonFormat *fmt, const ZCanonicalToken *token) {
|
||||
if (fmt_is_symbol(token, "(")) fmt->paren_depth++;
|
||||
else if (fmt_is_symbol(token, ")") && fmt->paren_depth > 0) fmt->paren_depth--;
|
||||
else if (fmt_is_symbol(token, "[")) fmt->bracket_depth++;
|
||||
else if (fmt_is_symbol(token, "]") && fmt->bracket_depth > 0) fmt->bracket_depth--;
|
||||
else if (fmt_token_is_compact_angle(fmt, token) && fmt_is_symbol(token, "<")) fmt->angle_depth++;
|
||||
else if (fmt_token_is_compact_angle(fmt, token) && fmt_is_symbol(token, ">") && fmt->angle_depth > 0) fmt->angle_depth--;
|
||||
}
|
||||
|
||||
static bool fmt_at_list_item_separator(const CanonFormat *fmt) {
|
||||
return fmt_current_brace(fmt, FMT_BRACE_LIST) && fmt->paren_depth == 0 && fmt->bracket_depth == 0 && fmt->angle_depth == 0;
|
||||
}
|
||||
|
||||
bool z_canonical_text_format(const ZCanonicalTokenVec *tokens, ZBuf *out, ZDiag *diag) {
|
||||
if (!tokens || !out) return false;
|
||||
CanonFormat fmt = {.tokens = tokens, .out = out, .compact_angles = fmt_build_compact_angles(tokens), .line_start = true};
|
||||
for (size_t i = 0; i < tokens->len; i++) {
|
||||
const ZCanonicalToken *token = &tokens->items[i];
|
||||
if (token->kind == Z_CANON_TOKEN_EOF) break;
|
||||
if (token->kind == Z_CANON_TOKEN_NEWLINE) {
|
||||
const ZCanonicalToken *next = fmt_next_significant(tokens, i);
|
||||
size_t j = i + 1;
|
||||
while (j < tokens->len && tokens->items[j].kind == Z_CANON_TOKEN_NEWLINE) j++;
|
||||
const ZCanonicalToken *next_non_newline = j < tokens->len ? &tokens->items[j] : NULL;
|
||||
if (fmt_is_symbol(fmt.previous, "}") && fmt_is_word(next_non_newline, "else")) continue;
|
||||
if (next && fmt.indent == 0 && fmt_starts_declaration(next) && fmt.out->len > 0) fmt_blank_line(&fmt);
|
||||
else fmt_newline(&fmt);
|
||||
continue;
|
||||
}
|
||||
if (token->kind == Z_CANON_TOKEN_COMMENT) {
|
||||
if (!fmt.line_start) fmt_append_char(out, ' ');
|
||||
fmt_indent(&fmt);
|
||||
fmt_append(out, token->text);
|
||||
fmt_newline(&fmt);
|
||||
continue;
|
||||
}
|
||||
if (fmt_is_symbol(token, "{")) {
|
||||
const ZCanonicalToken *next = fmt_next_significant(tokens, i);
|
||||
bool multiline = next && !fmt_is_symbol(next, "}") && (fmt_line_opens_multiline_brace(&fmt) || fmt_current_brace(&fmt, FMT_BRACE_MATCH));
|
||||
unsigned char flags = multiline ? FMT_BRACE_MULTILINE : 0;
|
||||
if (multiline && fmt.line_has_list_declaration) flags |= FMT_BRACE_LIST;
|
||||
if (multiline && fmt_is_word(fmt.line_first, "match")) flags |= FMT_BRACE_MATCH;
|
||||
fmt_push_brace(&fmt, flags);
|
||||
fmt_emit_token(&fmt, token);
|
||||
if (multiline) {
|
||||
fmt.indent++;
|
||||
fmt_newline(&fmt);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (fmt_is_symbol(token, "}")) {
|
||||
bool multiline = fmt_current_brace(&fmt, FMT_BRACE_MULTILINE);
|
||||
if (multiline) {
|
||||
if (!fmt.line_start) fmt_newline(&fmt);
|
||||
if (fmt.indent > 0) fmt.indent--;
|
||||
}
|
||||
if (fmt.brace_depth > 0) fmt.brace_depth--;
|
||||
fmt_emit_token(&fmt, token);
|
||||
const ZCanonicalToken *next = fmt_next_significant(tokens, i);
|
||||
if (multiline && next && !fmt_is_word(next, "else") && !fmt_is_symbol(next, ",") && !fmt_is_symbol(next, ")") && !fmt_is_symbol(next, "]")) {
|
||||
if (fmt.indent == 0 && fmt_starts_declaration(next)) fmt_blank_line(&fmt);
|
||||
else fmt_newline(&fmt);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (fmt_is_symbol(token, ",") && fmt_at_list_item_separator(&fmt)) {
|
||||
fmt_emit_token(&fmt, token);
|
||||
const ZCanonicalToken *next = fmt_next_significant(tokens, i);
|
||||
if (next && !fmt_is_symbol(next, "}")) fmt_newline(&fmt);
|
||||
continue;
|
||||
}
|
||||
fmt_emit_token(&fmt, token);
|
||||
fmt_note_delimiter(&fmt, token);
|
||||
}
|
||||
fmt_newline(&fmt);
|
||||
free(fmt.compact_angles);
|
||||
free(fmt.brace_flags);
|
||||
(void)diag;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_canonical_text_format_source(const char *source, ZBuf *out, ZDiag *diag) {
|
||||
ZDiag local_diag = {0};
|
||||
ZDiag *active_diag = diag ? diag : &local_diag;
|
||||
ZCanonicalTokenVec tokens = z_canonical_text_tokenize(source, active_diag);
|
||||
if (active_diag->code != 0) {
|
||||
z_free_canonical_text_tokens(&tokens);
|
||||
return false;
|
||||
}
|
||||
ZCanonicalTree tree = {0};
|
||||
ZCanonicalFacts facts = {0};
|
||||
if (!z_canonical_text_parse(&tokens, &tree, &facts, active_diag)) {
|
||||
z_free_canonical_text_tree(&tree);
|
||||
z_free_canonical_text_tokens(&tokens);
|
||||
return false;
|
||||
}
|
||||
bool ok = z_canonical_text_format(&tokens, out, active_diag);
|
||||
z_free_canonical_text_tree(&tree);
|
||||
z_free_canonical_text_tokens(&tokens);
|
||||
if (!ok) return false;
|
||||
ZCanonicalTokenVec formatted_tokens = z_canonical_text_tokenize(out->data ? out->data : "", active_diag);
|
||||
if (active_diag->code != 0) {
|
||||
z_free_canonical_text_tokens(&formatted_tokens);
|
||||
return false;
|
||||
}
|
||||
ZCanonicalTree formatted_tree = {0};
|
||||
ZCanonicalFacts formatted_facts = {0};
|
||||
ok = z_canonical_text_parse(&formatted_tokens, &formatted_tree, &formatted_facts, active_diag);
|
||||
z_free_canonical_text_tree(&formatted_tree);
|
||||
z_free_canonical_text_tokens(&formatted_tokens);
|
||||
return ok;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,604 @@
|
||||
#include "canonical_text.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
ZBuf *buf;
|
||||
} CanonWriter;
|
||||
|
||||
static bool cw_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
static bool cw_starts_with(const char *text, const char *prefix) {
|
||||
return text && prefix && strncmp(text, prefix, strlen(prefix)) == 0;
|
||||
}
|
||||
|
||||
static void cw_indent(CanonWriter *writer, unsigned indent) {
|
||||
for (unsigned i = 0; i < indent; i++) zbuf_append(writer->buf, " ");
|
||||
}
|
||||
|
||||
static void cw_append_name(CanonWriter *writer, const char *name) {
|
||||
zbuf_append(writer->buf, name && name[0] ? name : "__unnamed");
|
||||
}
|
||||
|
||||
static void cw_append_escaped_string(CanonWriter *writer, const char *text) {
|
||||
zbuf_append_char(writer->buf, '"');
|
||||
for (const char *p = text ? text : ""; *p; p++) {
|
||||
unsigned char ch = (unsigned char)*p;
|
||||
switch (ch) {
|
||||
case '\\': zbuf_append(writer->buf, "\\\\"); break;
|
||||
case '"': zbuf_append(writer->buf, "\\\""); break;
|
||||
case '\n': zbuf_append(writer->buf, "\\n"); break;
|
||||
case '\r': zbuf_append(writer->buf, "\\r"); break;
|
||||
case '\t': zbuf_append(writer->buf, "\\t"); break;
|
||||
default:
|
||||
if (ch < 0x20) zbuf_appendf(writer->buf, "\\x%02x", ch);
|
||||
else zbuf_append_char(writer->buf, (char)ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
zbuf_append_char(writer->buf, '"');
|
||||
}
|
||||
|
||||
static void cw_append_char_literal(CanonWriter *writer, const char *text) {
|
||||
unsigned value = (unsigned)strtoul(text ? text : "0", NULL, 10);
|
||||
zbuf_append_char(writer->buf, '\'');
|
||||
if (value == '\n') zbuf_append(writer->buf, "\\n");
|
||||
else if (value == '\r') zbuf_append(writer->buf, "\\r");
|
||||
else if (value == '\t') zbuf_append(writer->buf, "\\t");
|
||||
else if (value == '\'') zbuf_append(writer->buf, "\\'");
|
||||
else if (value == '\\') zbuf_append(writer->buf, "\\\\");
|
||||
else if (value >= 32 && value < 127) zbuf_append_char(writer->buf, (char)value);
|
||||
else zbuf_appendf(writer->buf, "\\x%02x", value & 0xff);
|
||||
zbuf_append_char(writer->buf, '\'');
|
||||
}
|
||||
|
||||
static void cw_append_type_args(CanonWriter *writer, const TypeArgVec *args) {
|
||||
if (!args || args->len == 0) return;
|
||||
zbuf_append_char(writer->buf, '<');
|
||||
for (size_t i = 0; i < args->len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
zbuf_append(writer->buf, args->items[i].type ? args->items[i].type : "");
|
||||
}
|
||||
zbuf_append_char(writer->buf, '>');
|
||||
}
|
||||
|
||||
static int cw_binary_precedence(const char *op) {
|
||||
if (cw_text_eq(op, "||")) return 1;
|
||||
if (cw_text_eq(op, "&&")) return 2;
|
||||
if (cw_text_eq(op, "==") || cw_text_eq(op, "!=")) return 3;
|
||||
if (cw_text_eq(op, "<") || cw_text_eq(op, ">") || cw_text_eq(op, "<=") || cw_text_eq(op, ">=")) return 4;
|
||||
if (cw_text_eq(op, "+") || cw_text_eq(op, "-") || cw_text_eq(op, "+%") || cw_text_eq(op, "+|")) return 5;
|
||||
if (cw_text_eq(op, "*") || cw_text_eq(op, "/") || cw_text_eq(op, "%")) return 6;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cw_expr_precedence(const Expr *expr) {
|
||||
if (!expr) return 10;
|
||||
if (expr->kind == EXPR_BINARY) return cw_binary_precedence(expr->text);
|
||||
if (expr->kind == EXPR_CAST) return 7;
|
||||
if (expr->kind == EXPR_BORROW || expr->kind == EXPR_CHECK || expr->kind == EXPR_RESCUE || expr->kind == EXPR_META) return 8;
|
||||
return 10;
|
||||
}
|
||||
|
||||
static bool cw_number_is_zero(const Expr *expr) {
|
||||
return expr && expr->kind == EXPR_NUMBER && expr->text && strcmp(expr->text, "0") == 0;
|
||||
}
|
||||
|
||||
static bool cw_bool_is_false(const Expr *expr) {
|
||||
return expr && expr->kind == EXPR_BOOL && !expr->bool_value;
|
||||
}
|
||||
|
||||
static bool cw_call_is_deref(const Expr *expr) {
|
||||
return expr && expr->prefix_deref && expr->kind == EXPR_CALL && expr->left && expr->left->kind == EXPR_IDENT &&
|
||||
cw_text_eq(expr->left->text, "deref") && expr->args.len == 1;
|
||||
}
|
||||
|
||||
static void cw_append_expr_prec(CanonWriter *writer, const Expr *expr, int parent_prec, bool right_assoc);
|
||||
|
||||
static void cw_append_expr_grouped(CanonWriter *writer, const Expr *expr) {
|
||||
bool grouped = cw_expr_precedence(expr) < 10;
|
||||
if (grouped) zbuf_append_char(writer->buf, '(');
|
||||
cw_append_expr_prec(writer, expr, 0, false);
|
||||
if (grouped) zbuf_append_char(writer->buf, ')');
|
||||
}
|
||||
|
||||
static void cw_append_call_args(CanonWriter *writer, const ExprVec *args) {
|
||||
zbuf_append_char(writer->buf, '(');
|
||||
for (size_t i = 0; args && i < args->len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
cw_append_expr_prec(writer, args->items[i], 0, false);
|
||||
}
|
||||
zbuf_append_char(writer->buf, ')');
|
||||
}
|
||||
|
||||
static void cw_append_shape_literal(CanonWriter *writer, const Expr *expr) {
|
||||
cw_append_name(writer, expr && expr->text ? expr->text : "Unknown");
|
||||
if (!expr || expr->fields.len == 0) {
|
||||
zbuf_append(writer->buf, " {}");
|
||||
return;
|
||||
}
|
||||
zbuf_append(writer->buf, " { ");
|
||||
for (size_t i = 0; i < expr->fields.len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
cw_append_name(writer, expr->fields.items[i].name);
|
||||
zbuf_append(writer->buf, ": ");
|
||||
cw_append_expr_prec(writer, expr->fields.items[i].value, 0, false);
|
||||
}
|
||||
zbuf_append(writer->buf, " }");
|
||||
}
|
||||
|
||||
static void cw_append_array_literal(CanonWriter *writer, const Expr *expr) {
|
||||
zbuf_append_char(writer->buf, '[');
|
||||
if (expr && expr->array_repeat) {
|
||||
cw_append_expr_prec(writer, expr->args.len > 0 ? expr->args.items[0] : NULL, 0, false);
|
||||
zbuf_append(writer->buf, "; ");
|
||||
cw_append_expr_prec(writer, expr->args.len > 1 ? expr->args.items[1] : NULL, 0, false);
|
||||
} else {
|
||||
for (size_t i = 0; expr && i < expr->args.len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
cw_append_expr_prec(writer, expr->args.items[i], 0, false);
|
||||
}
|
||||
}
|
||||
zbuf_append_char(writer->buf, ']');
|
||||
}
|
||||
|
||||
static void cw_append_expr_prec(CanonWriter *writer, const Expr *expr, int parent_prec, bool right_assoc) {
|
||||
if (!expr) {
|
||||
zbuf_append(writer->buf, "__missing_expr__");
|
||||
return;
|
||||
}
|
||||
|
||||
if (expr->kind == EXPR_BINARY && (cw_text_eq(expr->text, "-") || cw_text_eq(expr->text, "+")) && cw_number_is_zero(expr->left)) {
|
||||
zbuf_append(writer->buf, expr->text);
|
||||
cw_append_expr_prec(writer, expr->right, 8, false);
|
||||
return;
|
||||
}
|
||||
if (expr->kind == EXPR_BINARY && cw_text_eq(expr->text, "==") && cw_bool_is_false(expr->right)) {
|
||||
zbuf_append_char(writer->buf, '!');
|
||||
cw_append_expr_prec(writer, expr->left, 8, false);
|
||||
return;
|
||||
}
|
||||
bool prefix_deref = cw_call_is_deref(expr);
|
||||
int prec = prefix_deref ? 8 : cw_expr_precedence(expr);
|
||||
bool grouped = prec > 0 && (prec < parent_prec || (right_assoc && prec == parent_prec));
|
||||
if (grouped) zbuf_append_char(writer->buf, '(');
|
||||
if (prefix_deref) {
|
||||
zbuf_append_char(writer->buf, '*');
|
||||
cw_append_expr_prec(writer, expr->args.items[0], prec, false);
|
||||
} else switch (expr->kind) {
|
||||
case EXPR_IDENT:
|
||||
cw_append_name(writer, expr->text);
|
||||
cw_append_type_args(writer, &expr->type_args);
|
||||
break;
|
||||
case EXPR_STRING:
|
||||
cw_append_escaped_string(writer, expr->text);
|
||||
break;
|
||||
case EXPR_CHAR:
|
||||
cw_append_char_literal(writer, expr->text);
|
||||
break;
|
||||
case EXPR_NUMBER:
|
||||
zbuf_append(writer->buf, expr->text ? expr->text : "0");
|
||||
break;
|
||||
case EXPR_BOOL:
|
||||
zbuf_append(writer->buf, expr->bool_value ? "true" : "false");
|
||||
break;
|
||||
case EXPR_NULL:
|
||||
zbuf_append(writer->buf, "null");
|
||||
break;
|
||||
case EXPR_MEMBER:
|
||||
cw_append_expr_prec(writer, expr->left, 10, false);
|
||||
zbuf_append_char(writer->buf, '.');
|
||||
cw_append_name(writer, expr->text);
|
||||
cw_append_type_args(writer, &expr->type_args);
|
||||
break;
|
||||
case EXPR_INDEX:
|
||||
cw_append_expr_prec(writer, expr->left, 10, false);
|
||||
zbuf_append_char(writer->buf, '[');
|
||||
cw_append_expr_prec(writer, expr->right, 0, false);
|
||||
zbuf_append_char(writer->buf, ']');
|
||||
break;
|
||||
case EXPR_SLICE:
|
||||
cw_append_expr_prec(writer, expr->left, 10, false);
|
||||
zbuf_append_char(writer->buf, '[');
|
||||
if (expr->args.len > 0 && expr->args.items[0]) cw_append_expr_prec(writer, expr->args.items[0], 0, false);
|
||||
zbuf_append(writer->buf, "..");
|
||||
if (expr->args.len > 1 && expr->args.items[1]) cw_append_expr_prec(writer, expr->args.items[1], 0, false);
|
||||
zbuf_append_char(writer->buf, ']');
|
||||
break;
|
||||
case EXPR_CALL:
|
||||
cw_append_expr_prec(writer, expr->left, 10, false);
|
||||
cw_append_type_args(writer, &expr->type_args);
|
||||
cw_append_call_args(writer, &expr->args);
|
||||
break;
|
||||
case EXPR_BINARY:
|
||||
cw_append_expr_prec(writer, expr->left, prec, false);
|
||||
zbuf_append_char(writer->buf, ' ');
|
||||
zbuf_append(writer->buf, expr->text ? expr->text : "__op__");
|
||||
zbuf_append_char(writer->buf, ' ');
|
||||
cw_append_expr_prec(writer, expr->right, prec, true);
|
||||
break;
|
||||
case EXPR_CAST:
|
||||
cw_append_expr_prec(writer, expr->left, prec + 1, false);
|
||||
zbuf_append(writer->buf, " as ");
|
||||
zbuf_append(writer->buf, expr->text ? expr->text : "");
|
||||
break;
|
||||
case EXPR_BORROW:
|
||||
zbuf_append(writer->buf, expr->mutable_borrow ? "&mut " : "&");
|
||||
cw_append_expr_prec(writer, expr->left, prec, false);
|
||||
break;
|
||||
case EXPR_CHECK:
|
||||
zbuf_append(writer->buf, "check ");
|
||||
cw_append_expr_prec(writer, expr->left, prec, false);
|
||||
break;
|
||||
case EXPR_RESCUE:
|
||||
zbuf_append(writer->buf, "rescue ");
|
||||
cw_append_expr_grouped(writer, expr->left);
|
||||
zbuf_append(writer->buf, " err ");
|
||||
cw_append_expr_grouped(writer, expr->right);
|
||||
break;
|
||||
case EXPR_META:
|
||||
zbuf_append(writer->buf, "meta ");
|
||||
cw_append_expr_prec(writer, expr->left, prec, false);
|
||||
break;
|
||||
case EXPR_SHAPE_LITERAL:
|
||||
cw_append_shape_literal(writer, expr);
|
||||
break;
|
||||
case EXPR_ARRAY_LITERAL:
|
||||
cw_append_array_literal(writer, expr);
|
||||
break;
|
||||
}
|
||||
if (grouped) zbuf_append_char(writer->buf, ')');
|
||||
}
|
||||
|
||||
static void cw_append_param(CanonWriter *writer, const Param *param, bool include_default) {
|
||||
if (param && param->is_static) zbuf_append(writer->buf, "static ");
|
||||
cw_append_name(writer, param ? param->name : NULL);
|
||||
if (param && param->type && param->type[0]) {
|
||||
zbuf_append(writer->buf, ": ");
|
||||
zbuf_append(writer->buf, param->type);
|
||||
}
|
||||
if (include_default && param && param->default_value) {
|
||||
zbuf_append(writer->buf, " = ");
|
||||
cw_append_expr_prec(writer, param->default_value, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void cw_append_type_param_list(CanonWriter *writer, const ParamVec *params) {
|
||||
if (!params || params->len == 0) return;
|
||||
zbuf_append_char(writer->buf, '<');
|
||||
for (size_t i = 0; i < params->len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
cw_append_param(writer, ¶ms->items[i], false);
|
||||
}
|
||||
zbuf_append_char(writer->buf, '>');
|
||||
}
|
||||
|
||||
static void cw_append_stmt_vec(CanonWriter *writer, const StmtVec *body, unsigned indent);
|
||||
|
||||
static void cw_append_block(CanonWriter *writer, const StmtVec *body, unsigned indent) {
|
||||
zbuf_append(writer->buf, " {\n");
|
||||
cw_append_stmt_vec(writer, body, indent + 1);
|
||||
cw_indent(writer, indent);
|
||||
zbuf_append(writer->buf, "}\n");
|
||||
}
|
||||
|
||||
static void cw_append_match_arm(CanonWriter *writer, const MatchArm *arm, unsigned indent) {
|
||||
cw_indent(writer, indent);
|
||||
if (arm && arm->payload_name) {
|
||||
zbuf_append_char(writer->buf, '.');
|
||||
cw_append_name(writer, arm->case_name);
|
||||
zbuf_append_char(writer->buf, '(');
|
||||
cw_append_name(writer, arm->payload_name);
|
||||
zbuf_append_char(writer->buf, ')');
|
||||
} else {
|
||||
cw_append_name(writer, arm ? arm->case_name : NULL);
|
||||
}
|
||||
if (arm && arm->range_end) {
|
||||
zbuf_append(writer->buf, "..");
|
||||
zbuf_append(writer->buf, arm->range_end);
|
||||
}
|
||||
if (arm && arm->guard) {
|
||||
zbuf_append(writer->buf, " if ");
|
||||
cw_append_expr_prec(writer, arm->guard, 0, false);
|
||||
}
|
||||
cw_append_block(writer, arm ? &arm->body : NULL, indent);
|
||||
}
|
||||
|
||||
static void cw_append_stmt(CanonWriter *writer, const Stmt *stmt, unsigned indent) {
|
||||
cw_indent(writer, indent);
|
||||
if (!stmt) {
|
||||
zbuf_append(writer->buf, "__missing_stmt__\n");
|
||||
return;
|
||||
}
|
||||
switch (stmt->kind) {
|
||||
case STMT_LET:
|
||||
zbuf_append(writer->buf, stmt->mutable_binding ? "var " : "let ");
|
||||
cw_append_name(writer, stmt->name);
|
||||
zbuf_append(writer->buf, ": ");
|
||||
zbuf_append(writer->buf, stmt->type ? stmt->type : (stmt->resolved_type ? stmt->resolved_type : "Unknown"));
|
||||
zbuf_append(writer->buf, " = ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
case STMT_ASSIGN:
|
||||
cw_append_expr_prec(writer, stmt->target, 0, false);
|
||||
zbuf_append(writer->buf, " = ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
case STMT_DEFER:
|
||||
zbuf_append(writer->buf, "defer ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
case STMT_CHECK:
|
||||
zbuf_append(writer->buf, "check ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
case STMT_RETURN:
|
||||
zbuf_append(writer->buf, "return");
|
||||
if (stmt->expr) {
|
||||
zbuf_append_char(writer->buf, ' ');
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
}
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
case STMT_EXPR:
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
case STMT_IF:
|
||||
zbuf_append(writer->buf, "if ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
cw_append_block(writer, &stmt->then_body, indent);
|
||||
if (stmt->else_body.len > 0) {
|
||||
if (stmt->else_body.len == 1 && stmt->else_body.items[0] && stmt->else_body.items[0]->kind == STMT_IF) {
|
||||
cw_indent(writer, indent);
|
||||
zbuf_append(writer->buf, "else ");
|
||||
cw_append_stmt(writer, stmt->else_body.items[0], 0);
|
||||
} else {
|
||||
cw_indent(writer, indent);
|
||||
zbuf_append(writer->buf, "else");
|
||||
cw_append_block(writer, &stmt->else_body, indent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STMT_WHILE:
|
||||
zbuf_append(writer->buf, "while ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
cw_append_block(writer, &stmt->then_body, indent);
|
||||
break;
|
||||
case STMT_FOR:
|
||||
zbuf_append(writer->buf, "for ");
|
||||
cw_append_name(writer, stmt->name);
|
||||
zbuf_append(writer->buf, " in ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append(writer->buf, "..");
|
||||
cw_append_expr_prec(writer, stmt->range_end, 0, false);
|
||||
cw_append_block(writer, &stmt->then_body, indent);
|
||||
break;
|
||||
case STMT_BREAK:
|
||||
zbuf_append(writer->buf, "break\n");
|
||||
break;
|
||||
case STMT_CONTINUE:
|
||||
zbuf_append(writer->buf, "continue\n");
|
||||
break;
|
||||
case STMT_MATCH:
|
||||
zbuf_append(writer->buf, "match ");
|
||||
cw_append_expr_prec(writer, stmt->expr, 0, false);
|
||||
zbuf_append(writer->buf, " {\n");
|
||||
for (size_t i = 0; i < stmt->match_arms.len; i++) cw_append_match_arm(writer, &stmt->match_arms.items[i], indent + 1);
|
||||
cw_indent(writer, indent);
|
||||
zbuf_append(writer->buf, "}\n");
|
||||
break;
|
||||
case STMT_RAISE:
|
||||
zbuf_append(writer->buf, "raise ");
|
||||
cw_append_name(writer, stmt->name);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cw_append_stmt_vec(CanonWriter *writer, const StmtVec *body, unsigned indent) {
|
||||
for (size_t i = 0; body && i < body->len; i++) cw_append_stmt(writer, body->items[i], indent);
|
||||
}
|
||||
|
||||
static void cw_append_function_signature(CanonWriter *writer, const Function *fun) {
|
||||
if (fun->is_public) zbuf_append(writer->buf, "pub ");
|
||||
if (fun->export_c) zbuf_append(writer->buf, "export c ");
|
||||
zbuf_append(writer->buf, "fn ");
|
||||
cw_append_name(writer, fun->name);
|
||||
cw_append_type_param_list(writer, &fun->type_params);
|
||||
zbuf_append_char(writer->buf, '(');
|
||||
for (size_t i = 0; i < fun->params.len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
cw_append_param(writer, &fun->params.items[i], false);
|
||||
}
|
||||
zbuf_append(writer->buf, ") -> ");
|
||||
zbuf_append(writer->buf, fun->return_type && fun->return_type[0] ? fun->return_type : "Void");
|
||||
if (fun->raises) {
|
||||
zbuf_append(writer->buf, " raises");
|
||||
if (fun->errors.len > 0) {
|
||||
zbuf_append(writer->buf, " [");
|
||||
for (size_t i = 0; i < fun->errors.len; i++) {
|
||||
if (i > 0) zbuf_append(writer->buf, ", ");
|
||||
cw_append_name(writer, fun->errors.items[i].name);
|
||||
}
|
||||
zbuf_append_char(writer->buf, ']');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cw_append_function(CanonWriter *writer, const Function *fun, unsigned indent, bool interface_method) {
|
||||
cw_indent(writer, indent);
|
||||
if (fun->is_test) {
|
||||
zbuf_append(writer->buf, "test ");
|
||||
cw_append_escaped_string(writer, fun->test_name ? fun->test_name : "");
|
||||
cw_append_block(writer, &fun->body, indent);
|
||||
return;
|
||||
}
|
||||
cw_append_function_signature(writer, fun);
|
||||
if (interface_method) zbuf_append_char(writer->buf, '\n');
|
||||
else cw_append_block(writer, &fun->body, indent);
|
||||
}
|
||||
|
||||
static void cw_append_blank_between(CanonWriter *writer, bool *wrote_any) {
|
||||
if (*wrote_any) zbuf_append_char(writer->buf, '\n');
|
||||
*wrote_any = true;
|
||||
}
|
||||
|
||||
static bool cw_program_has_non_std_source_items(const Program *program) {
|
||||
if (!program) return false;
|
||||
if (program->c_imports.len || program->use_imports.len || program->consts.len || program->aliases.len ||
|
||||
program->shapes.len || program->interfaces.len || program->enums.len || program->choices.len) return true;
|
||||
for (size_t i = 0; i < program->functions.len; i++) {
|
||||
if (!cw_starts_with(program->functions.items[i].name, "__zero_std_")) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void cw_append_use(CanonWriter *writer, const UseImport *item) {
|
||||
zbuf_append(writer->buf, "use ");
|
||||
zbuf_append(writer->buf, item->module ? item->module : "");
|
||||
if (item->alias && item->alias[0]) {
|
||||
zbuf_append(writer->buf, " as ");
|
||||
cw_append_name(writer, item->alias);
|
||||
}
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
}
|
||||
|
||||
static void cw_append_c_import(CanonWriter *writer, const CImport *item) {
|
||||
zbuf_append(writer->buf, "extern c ");
|
||||
cw_append_escaped_string(writer, item->header);
|
||||
zbuf_append(writer->buf, " as ");
|
||||
cw_append_name(writer, item->alias);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
}
|
||||
|
||||
static void cw_append_const(CanonWriter *writer, const ConstDecl *item) {
|
||||
if (item->is_public) zbuf_append(writer->buf, "pub ");
|
||||
zbuf_append(writer->buf, "const ");
|
||||
cw_append_name(writer, item->name);
|
||||
if (item->type && item->type[0] && !cw_text_eq(item->type, "Unknown")) {
|
||||
zbuf_append(writer->buf, ": ");
|
||||
zbuf_append(writer->buf, item->type);
|
||||
}
|
||||
zbuf_append(writer->buf, " = ");
|
||||
cw_append_expr_prec(writer, item->expr, 0, false);
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
}
|
||||
|
||||
static void cw_append_alias(CanonWriter *writer, const TypeAlias *item) {
|
||||
if (item->is_public) zbuf_append(writer->buf, "pub ");
|
||||
zbuf_append(writer->buf, "alias ");
|
||||
cw_append_name(writer, item->name);
|
||||
zbuf_append(writer->buf, " = ");
|
||||
zbuf_append(writer->buf, item->target ? item->target : "Unknown");
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
}
|
||||
|
||||
static void cw_append_shape(CanonWriter *writer, const Shape *shape) {
|
||||
if (shape->is_public) zbuf_append(writer->buf, "pub ");
|
||||
if (cw_text_eq(shape->layout, "extern")) zbuf_append(writer->buf, "extern type ");
|
||||
else if (cw_text_eq(shape->layout, "packed")) zbuf_append(writer->buf, "packed type ");
|
||||
else zbuf_append(writer->buf, "type ");
|
||||
cw_append_name(writer, shape->name);
|
||||
cw_append_type_param_list(writer, &shape->type_params);
|
||||
if (cw_text_eq(shape->layout, "extern") && shape->fields.len == 0 && shape->methods.len == 0) {
|
||||
zbuf_append_char(writer->buf, '\n');
|
||||
return;
|
||||
}
|
||||
zbuf_append(writer->buf, " {\n");
|
||||
for (size_t i = 0; i < shape->fields.len; i++) {
|
||||
cw_indent(writer, 1);
|
||||
cw_append_param(writer, &shape->fields.items[i], true);
|
||||
zbuf_append(writer->buf, ",\n");
|
||||
}
|
||||
for (size_t i = 0; i < shape->methods.len; i++) cw_append_function(writer, &shape->methods.items[i], 1, false);
|
||||
zbuf_append(writer->buf, "}\n");
|
||||
}
|
||||
|
||||
static void cw_append_interface(CanonWriter *writer, const InterfaceDecl *item) {
|
||||
if (item->is_public) zbuf_append(writer->buf, "pub ");
|
||||
zbuf_append(writer->buf, "interface ");
|
||||
cw_append_name(writer, item->name);
|
||||
cw_append_type_param_list(writer, &item->type_params);
|
||||
zbuf_append(writer->buf, " {\n");
|
||||
for (size_t i = 0; i < item->methods.len; i++) cw_append_function(writer, &item->methods.items[i], 1, true);
|
||||
zbuf_append(writer->buf, "}\n");
|
||||
}
|
||||
|
||||
static void cw_append_enum(CanonWriter *writer, const EnumDecl *item) {
|
||||
if (item->is_public) zbuf_append(writer->buf, "pub ");
|
||||
zbuf_append(writer->buf, "enum ");
|
||||
cw_append_name(writer, item->name);
|
||||
if (item->type && item->type[0]) {
|
||||
zbuf_append(writer->buf, ": ");
|
||||
zbuf_append(writer->buf, item->type);
|
||||
}
|
||||
zbuf_append(writer->buf, " {\n");
|
||||
for (size_t i = 0; i < item->cases.len; i++) {
|
||||
cw_indent(writer, 1);
|
||||
cw_append_name(writer, item->cases.items[i].name);
|
||||
zbuf_append(writer->buf, ",\n");
|
||||
}
|
||||
zbuf_append(writer->buf, "}\n");
|
||||
}
|
||||
|
||||
static void cw_append_choice(CanonWriter *writer, const Choice *item) {
|
||||
if (item->is_public) zbuf_append(writer->buf, "pub ");
|
||||
zbuf_append(writer->buf, "choice ");
|
||||
cw_append_name(writer, item->name);
|
||||
zbuf_append(writer->buf, " {\n");
|
||||
for (size_t i = 0; i < item->cases.len; i++) {
|
||||
cw_indent(writer, 1);
|
||||
cw_append_param(writer, &item->cases.items[i], false);
|
||||
zbuf_append(writer->buf, ",\n");
|
||||
}
|
||||
zbuf_append(writer->buf, "}\n");
|
||||
}
|
||||
|
||||
static bool cw_format_raw_source(ZBuf *raw, ZBuf *out, ZDiag *diag) {
|
||||
ZBuf formatted;
|
||||
zbuf_init(&formatted);
|
||||
bool ok = z_canonical_text_format_source(raw->data ? raw->data : "", &formatted, diag);
|
||||
if (!ok) {
|
||||
zbuf_free(&formatted);
|
||||
return false;
|
||||
}
|
||||
*out = formatted;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_canonical_text_write_program(const Program *program, ZBuf *out, ZDiag *diag) {
|
||||
if (!program || !out) return false;
|
||||
ZBuf raw;
|
||||
zbuf_init(&raw);
|
||||
CanonWriter writer = {.buf = &raw};
|
||||
bool wrote_any = false;
|
||||
|
||||
for (size_t i = 0; i < program->c_imports.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_c_import(&writer, &program->c_imports.items[i]); }
|
||||
for (size_t i = 0; i < program->use_imports.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_use(&writer, &program->use_imports.items[i]); }
|
||||
for (size_t i = 0; i < program->consts.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_const(&writer, &program->consts.items[i]); }
|
||||
for (size_t i = 0; i < program->aliases.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_alias(&writer, &program->aliases.items[i]); }
|
||||
for (size_t i = 0; i < program->shapes.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_shape(&writer, &program->shapes.items[i]); }
|
||||
for (size_t i = 0; i < program->interfaces.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_interface(&writer, &program->interfaces.items[i]); }
|
||||
for (size_t i = 0; i < program->enums.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_enum(&writer, &program->enums.items[i]); }
|
||||
for (size_t i = 0; i < program->choices.len; i++) { cw_append_blank_between(&writer, &wrote_any); cw_append_choice(&writer, &program->choices.items[i]); }
|
||||
bool skip_std_helpers = cw_program_has_non_std_source_items(program);
|
||||
for (size_t i = 0; i < program->functions.len; i++) {
|
||||
if (skip_std_helpers && cw_starts_with(program->functions.items[i].name, "__zero_std_")) continue;
|
||||
cw_append_blank_between(&writer, &wrote_any);
|
||||
cw_append_function(&writer, &program->functions.items[i], 0, false);
|
||||
}
|
||||
|
||||
bool ok = cw_format_raw_source(&raw, out, diag);
|
||||
zbuf_free(&raw);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "capability_summary.h"
|
||||
#include "std_sig.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef enum {
|
||||
CAPABILITY_NAME_ARGS,
|
||||
CAPABILITY_NAME_ENV,
|
||||
CAPABILITY_NAME_FS,
|
||||
CAPABILITY_NAME_MEMORY,
|
||||
CAPABILITY_NAME_ALLOC,
|
||||
CAPABILITY_NAME_PATH,
|
||||
CAPABILITY_NAME_CODEC,
|
||||
CAPABILITY_NAME_PARSE,
|
||||
CAPABILITY_NAME_TIME,
|
||||
CAPABILITY_NAME_RAND,
|
||||
CAPABILITY_NAME_NET,
|
||||
CAPABILITY_NAME_PROC,
|
||||
CAPABILITY_NAME_WEB,
|
||||
CAPABILITY_NAME_WORLD
|
||||
} CapabilityName;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
CapabilityName capability;
|
||||
} CapabilityNameEntry;
|
||||
|
||||
static bool capability_name_equals(const char *left, const char *right) {
|
||||
return left && right && strcmp(left, right) == 0;
|
||||
}
|
||||
|
||||
static bool capability_name_starts_with(const char *name, const char *prefix) {
|
||||
return name && prefix && strncmp(name, prefix, strlen(prefix)) == 0;
|
||||
}
|
||||
|
||||
static void capability_summary_set_id(CapabilitySummary *caps, CapabilityName capability) {
|
||||
if (!caps) return;
|
||||
switch (capability) {
|
||||
case CAPABILITY_NAME_ARGS: caps->args = true; break;
|
||||
case CAPABILITY_NAME_ENV: caps->env = true; break;
|
||||
case CAPABILITY_NAME_FS: caps->fs = true; break;
|
||||
case CAPABILITY_NAME_MEMORY: caps->memory = true; break;
|
||||
case CAPABILITY_NAME_ALLOC:
|
||||
caps->alloc = true;
|
||||
caps->memory = true;
|
||||
break;
|
||||
case CAPABILITY_NAME_PATH: caps->path = true; break;
|
||||
case CAPABILITY_NAME_CODEC: caps->codec = true; break;
|
||||
case CAPABILITY_NAME_PARSE: caps->parse = true; break;
|
||||
case CAPABILITY_NAME_TIME: caps->time = true; break;
|
||||
case CAPABILITY_NAME_RAND: caps->rand = true; break;
|
||||
case CAPABILITY_NAME_NET: caps->net = true; break;
|
||||
case CAPABILITY_NAME_PROC: caps->proc = true; break;
|
||||
case CAPABILITY_NAME_WEB: caps->web = true; break;
|
||||
case CAPABILITY_NAME_WORLD: caps->world = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool capability_summary_apply_exact(const CapabilityNameEntry *entries, size_t len, const char *name, CapabilitySummary *caps) {
|
||||
if (!name || !caps) return false;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!capability_name_equals(name, entries[i].name)) continue;
|
||||
capability_summary_set_id(caps, entries[i].capability);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool capability_summary_apply_prefix(const CapabilityNameEntry *entries, size_t len, const char *name, CapabilitySummary *caps) {
|
||||
if (!name || !caps) return false;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!capability_name_starts_with(name, entries[i].name)) continue;
|
||||
capability_summary_set_id(caps, entries[i].capability);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void z_capability_summary_set(CapabilitySummary *caps, const char *capability) {
|
||||
static const CapabilityNameEntry names[] = {
|
||||
{"args", CAPABILITY_NAME_ARGS},
|
||||
{"env", CAPABILITY_NAME_ENV},
|
||||
{"fs", CAPABILITY_NAME_FS},
|
||||
{"memory", CAPABILITY_NAME_MEMORY},
|
||||
{"alloc", CAPABILITY_NAME_ALLOC},
|
||||
{"path", CAPABILITY_NAME_PATH},
|
||||
{"codec", CAPABILITY_NAME_CODEC},
|
||||
{"parse", CAPABILITY_NAME_PARSE},
|
||||
{"time", CAPABILITY_NAME_TIME},
|
||||
{"rand", CAPABILITY_NAME_RAND},
|
||||
{"net", CAPABILITY_NAME_NET},
|
||||
{"proc", CAPABILITY_NAME_PROC},
|
||||
{"web", CAPABILITY_NAME_WEB},
|
||||
{"world", CAPABILITY_NAME_WORLD},
|
||||
};
|
||||
capability_summary_apply_exact(names, sizeof(names) / sizeof(names[0]), capability, caps);
|
||||
}
|
||||
|
||||
static void capability_summary_collect_std_mem_name(const char *name, CapabilitySummary *caps) {
|
||||
static const CapabilityNameEntry allocator_names[] = {
|
||||
{"std.mem.nullAlloc", CAPABILITY_NAME_ALLOC},
|
||||
{"std.mem.fixedBufAlloc", CAPABILITY_NAME_ALLOC},
|
||||
{"std.mem.arena", CAPABILITY_NAME_ALLOC},
|
||||
{"std.mem.allocBytes", CAPABILITY_NAME_ALLOC},
|
||||
{"std.mem.byteBuf", CAPABILITY_NAME_ALLOC},
|
||||
{"std.mem.reset", CAPABILITY_NAME_ALLOC},
|
||||
{"std.mem.capacity", CAPABILITY_NAME_ALLOC},
|
||||
};
|
||||
if (!name || !caps || !capability_name_starts_with(name, "std.mem.")) return;
|
||||
caps->memory = true;
|
||||
capability_summary_apply_exact(allocator_names, sizeof(allocator_names) / sizeof(allocator_names[0]), name, caps);
|
||||
}
|
||||
|
||||
void z_capability_summary_collect_std_name(const char *name, CapabilitySummary *caps) {
|
||||
static const CapabilityNameEntry prefixes[] = {
|
||||
{"std.args.", CAPABILITY_NAME_ARGS},
|
||||
{"std.env.", CAPABILITY_NAME_ENV},
|
||||
{"std.fs.", CAPABILITY_NAME_FS},
|
||||
{"std.path.", CAPABILITY_NAME_PATH},
|
||||
{"std.codec.", CAPABILITY_NAME_CODEC},
|
||||
{"std.parse.", CAPABILITY_NAME_PARSE},
|
||||
{"std.json.", CAPABILITY_NAME_PARSE},
|
||||
{"std.time.", CAPABILITY_NAME_TIME},
|
||||
{"std.rand.", CAPABILITY_NAME_RAND},
|
||||
{"std.proc.", CAPABILITY_NAME_PROC},
|
||||
{"std.crypto.secureRandom", CAPABILITY_NAME_RAND},
|
||||
{"std.crypto.", CAPABILITY_NAME_CODEC},
|
||||
{"std.net.", CAPABILITY_NAME_NET},
|
||||
{"std.http.", CAPABILITY_NAME_NET},
|
||||
};
|
||||
if (!name || !caps) return;
|
||||
const ZStdHelperInfo *helper = z_std_helper_find(name);
|
||||
if (helper) {
|
||||
z_capability_summary_set(caps, helper->capability);
|
||||
return;
|
||||
}
|
||||
if (capability_summary_apply_prefix(prefixes, sizeof(prefixes) / sizeof(prefixes[0]), name, caps)) {
|
||||
return;
|
||||
}
|
||||
if (capability_name_starts_with(name, "std.mem.")) {
|
||||
capability_summary_collect_std_mem_name(name, caps);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "capability_summary.h"
|
||||
|
||||
void z_capability_summary_merge(CapabilitySummary *caps, const CapabilitySummary *other) {
|
||||
if (!caps || !other) return;
|
||||
caps->args = caps->args || other->args;
|
||||
caps->env = caps->env || other->env;
|
||||
caps->fs = caps->fs || other->fs;
|
||||
caps->memory = caps->memory || other->memory;
|
||||
caps->alloc = caps->alloc || other->alloc;
|
||||
caps->path = caps->path || other->path;
|
||||
caps->codec = caps->codec || other->codec;
|
||||
caps->parse = caps->parse || other->parse;
|
||||
caps->time = caps->time || other->time;
|
||||
caps->rand = caps->rand || other->rand;
|
||||
caps->net = caps->net || other->net;
|
||||
caps->proc = caps->proc || other->proc;
|
||||
caps->web = caps->web || other->web;
|
||||
caps->world = caps->world || other->world;
|
||||
}
|
||||
|
||||
static void ir_value_kind_capabilities(IrValueKind kind, CapabilitySummary *caps) {
|
||||
switch (kind) {
|
||||
case IR_VALUE_STRING_LITERAL:
|
||||
case IR_VALUE_ARRAY_BYTE_VIEW:
|
||||
case IR_VALUE_BYTE_SLICE:
|
||||
case IR_VALUE_BYTE_VIEW_LEN:
|
||||
case IR_VALUE_BYTE_VIEW_REMAINING:
|
||||
case IR_VALUE_BYTE_VIEW_INDEX_LOAD:
|
||||
case IR_VALUE_BYTE_VIEW_EQ:
|
||||
case IR_VALUE_STR_CONTAINS:
|
||||
case IR_VALUE_STR_RUNTIME:
|
||||
case IR_VALUE_FMT_BOOL:
|
||||
case IR_VALUE_FMT_HEX_U32:
|
||||
case IR_VALUE_FMT_I32:
|
||||
case IR_VALUE_FMT_U32:
|
||||
case IR_VALUE_FMT_USIZE:
|
||||
case IR_VALUE_BYTE_COPY:
|
||||
case IR_VALUE_BYTE_FILL:
|
||||
case IR_VALUE_ITEM_COPY:
|
||||
case IR_VALUE_ITEM_FILL:
|
||||
case IR_VALUE_ITEM_CONTAINS:
|
||||
case IR_VALUE_FIXED_BUF_ALLOC:
|
||||
case IR_VALUE_VEC_INIT:
|
||||
case IR_VALUE_VEC_PUSH:
|
||||
case IR_VALUE_VEC_LEN:
|
||||
case IR_VALUE_VEC_CAPACITY:
|
||||
case IR_VALUE_VEC_BYTES:
|
||||
case IR_VALUE_VEC_GET:
|
||||
case IR_VALUE_VEC_SET:
|
||||
case IR_VALUE_VEC_CLEAR:
|
||||
case IR_VALUE_VEC_POP:
|
||||
case IR_VALUE_VEC_TRUNCATE:
|
||||
case IR_VALUE_VEC_REMOVE_SWAP:
|
||||
case IR_VALUE_VEC_INDEX:
|
||||
case IR_VALUE_VEC_CONTAINS:
|
||||
case IR_VALUE_VEC_INSERT_UNIQUE:
|
||||
case IR_VALUE_VEC_REMOVE_VALUE:
|
||||
caps->memory = true;
|
||||
break;
|
||||
case IR_VALUE_ALLOC_BYTES:
|
||||
caps->memory = true;
|
||||
caps->alloc = true;
|
||||
break;
|
||||
case IR_VALUE_ASCII_RUNTIME:
|
||||
case IR_VALUE_TEXT_RUNTIME:
|
||||
case IR_VALUE_PARSE_RUNTIME:
|
||||
case IR_VALUE_PARSE_I32:
|
||||
case IR_VALUE_PARSE_U32:
|
||||
caps->parse = true;
|
||||
break;
|
||||
case IR_VALUE_ARGS_LEN:
|
||||
case IR_VALUE_ARGS_GET:
|
||||
case IR_VALUE_ARGS_EQ:
|
||||
case IR_VALUE_ARGS_GET_OR:
|
||||
case IR_VALUE_ARGS_FIND:
|
||||
case IR_VALUE_ARGS_CONTAINS:
|
||||
case IR_VALUE_ARGS_VALUE_AFTER:
|
||||
case IR_VALUE_ARGS_VALUE_AFTER_OR:
|
||||
caps->args = true;
|
||||
break;
|
||||
case IR_VALUE_ARGS_PARSE_U32:
|
||||
case IR_VALUE_ARGS_VALUE_AFTER_PARSE_U32:
|
||||
caps->args = true;
|
||||
caps->parse = true;
|
||||
break;
|
||||
case IR_VALUE_ENV_GET:
|
||||
caps->env = true;
|
||||
break;
|
||||
case IR_VALUE_PROC_SPAWN_INHERIT: caps->proc = true; break;
|
||||
case IR_VALUE_PROC_CAPTURE: caps->proc = true; caps->memory = true; break;
|
||||
case IR_VALUE_PROC_CAPTURE_FILES: caps->proc = true; caps->fs = true; caps->path = true; break;
|
||||
case IR_VALUE_PROC_CHILD_SPAWN: caps->proc = true; break;
|
||||
case IR_VALUE_PROC_CHILD_OP: caps->proc = true; break;
|
||||
case IR_VALUE_PROC_CHILD_IO: caps->proc = true; caps->memory = true; break;
|
||||
case IR_VALUE_PROC_PTY_RESIZE: caps->proc = true; break;
|
||||
case IR_VALUE_TIME_RUNTIME:
|
||||
case IR_VALUE_TIME_WALL_SECONDS:
|
||||
case IR_VALUE_TIME_MONOTONIC:
|
||||
case IR_VALUE_TIME_AS_MS:
|
||||
caps->time = true;
|
||||
break;
|
||||
case IR_VALUE_RAND_NEXT_U32:
|
||||
case IR_VALUE_RAND_NEXT_BELOW:
|
||||
case IR_VALUE_RAND_RANGE_U32:
|
||||
case IR_VALUE_RAND_ENTROPY_U32:
|
||||
caps->rand = true;
|
||||
break;
|
||||
case IR_VALUE_FS_HOST:
|
||||
case IR_VALUE_FS_OPEN:
|
||||
case IR_VALUE_FS_CREATE:
|
||||
case IR_VALUE_FS_READ_PATH:
|
||||
case IR_VALUE_FS_WRITE_PATH:
|
||||
case IR_VALUE_FS_READ_BYTES_PATH:
|
||||
case IR_VALUE_FS_READ_BYTES_AT_PATH:
|
||||
case IR_VALUE_FS_WRITE_BYTES_PATH:
|
||||
case IR_VALUE_FS_APPEND_BYTES_PATH:
|
||||
case IR_VALUE_FS_READ_ALL:
|
||||
case IR_VALUE_FS_READ_FILE:
|
||||
case IR_VALUE_FS_WRITE_ALL_FILE:
|
||||
case IR_VALUE_FS_CLOSE_FILE:
|
||||
case IR_VALUE_FS_EXISTS:
|
||||
case IR_VALUE_FS_REMOVE:
|
||||
case IR_VALUE_FS_RENAME:
|
||||
case IR_VALUE_FS_FILE_LEN:
|
||||
case IR_VALUE_FS_MAKE_DIR:
|
||||
case IR_VALUE_FS_REMOVE_DIR:
|
||||
case IR_VALUE_FS_IS_DIR:
|
||||
case IR_VALUE_FS_DIR_ENTRY_COUNT:
|
||||
case IR_VALUE_FS_DIR_ENTRY_NAME:
|
||||
case IR_VALUE_FS_TEMP_NAME:
|
||||
case IR_VALUE_FS_ATOMIC_WRITE:
|
||||
caps->fs = true;
|
||||
caps->path = true;
|
||||
break;
|
||||
case IR_VALUE_JSON_PARSE_BYTES:
|
||||
case IR_VALUE_JSON_VALIDATE_BYTES:
|
||||
case IR_VALUE_JSON_STREAM_TOKENS_BYTES:
|
||||
case IR_VALUE_JSON_DIAGNOSTIC_BYTES:
|
||||
case IR_VALUE_JSON_FIELD:
|
||||
case IR_VALUE_JSON_LOOKUP_SCALAR:
|
||||
case IR_VALUE_JSON_STRING_DECODE:
|
||||
case IR_VALUE_JSON_STRING_FIELD:
|
||||
case IR_VALUE_JSON_WRITE_STRING:
|
||||
case IR_VALUE_JSON_WRITE_RUNTIME:
|
||||
case IR_VALUE_JSON_ERROR_LABEL:
|
||||
case IR_VALUE_CRC32_BYTES:
|
||||
caps->codec = true;
|
||||
caps->memory = true;
|
||||
break;
|
||||
case IR_VALUE_HTTP_FETCH:
|
||||
case IR_VALUE_HTTP_RESULT_OK:
|
||||
case IR_VALUE_HTTP_RESULT_STATUS:
|
||||
case IR_VALUE_HTTP_RESULT_BODY_LEN:
|
||||
case IR_VALUE_HTTP_RESULT_ERROR:
|
||||
case IR_VALUE_HTTP_RESPONSE_LEN:
|
||||
case IR_VALUE_HTTP_RESPONSE_HEADERS_LEN:
|
||||
case IR_VALUE_HTTP_RESPONSE_BODY_OFFSET:
|
||||
case IR_VALUE_HTTP_HEADER_VALUE:
|
||||
case IR_VALUE_HTTP_HEADER_FOUND:
|
||||
case IR_VALUE_HTTP_HEADER_OFFSET:
|
||||
case IR_VALUE_HTTP_HEADER_LEN:
|
||||
case IR_VALUE_HTTP_WRITE_JSON_RESPONSE:
|
||||
case IR_VALUE_HTTP_REQUEST_METHOD_NAME: case IR_VALUE_HTTP_REQUEST_PATH:
|
||||
case IR_VALUE_HTTP_REQUEST_MATCHES: case IR_VALUE_HTTP_REQUEST_BODY_WITHIN:
|
||||
case IR_VALUE_HTTP_STATUS_CLASS:
|
||||
caps->net = true;
|
||||
caps->web = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_value_capabilities(const IrValue *value, CapabilitySummary *caps) {
|
||||
if (!value || !caps) return;
|
||||
ir_value_kind_capabilities(value->kind, caps);
|
||||
ir_value_capabilities(value->index, caps);
|
||||
ir_value_capabilities(value->left, caps);
|
||||
ir_value_capabilities(value->right, caps);
|
||||
for (size_t i = 0; i < value->arg_len; i++) ir_value_capabilities(value->args[i], caps);
|
||||
}
|
||||
|
||||
static void ir_instr_capabilities(const IrInstr *instrs, size_t len, CapabilitySummary *caps) {
|
||||
if (!caps) return;
|
||||
for (size_t i = 0; instrs && i < len; i++) {
|
||||
const IrInstr *instr = &instrs[i];
|
||||
if (instr->kind == IR_INSTR_WORLD_WRITE) caps->world = true;
|
||||
ir_value_capabilities(instr->value, caps);
|
||||
ir_value_capabilities(instr->index, caps);
|
||||
ir_instr_capabilities(instr->then_instrs, instr->then_len, caps);
|
||||
ir_instr_capabilities(instr->else_instrs, instr->else_len, caps);
|
||||
}
|
||||
}
|
||||
|
||||
CapabilitySummary z_ir_program_capabilities(const IrProgram *ir) {
|
||||
CapabilitySummary caps = {0};
|
||||
for (size_t i = 0; ir && i < ir->function_len; i++) {
|
||||
const IrFunction *fun = &ir->functions[i];
|
||||
if (fun->world_param_name) caps.world = true;
|
||||
for (size_t local_index = 0; local_index < fun->local_len; local_index++) {
|
||||
const IrLocal *local = &fun->locals[local_index];
|
||||
if (local->is_array || local->is_record) caps.memory = true;
|
||||
}
|
||||
ir_instr_capabilities(fun->instrs, fun->instr_len, &caps);
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef ZERO_CAPABILITY_SUMMARY_H
|
||||
#define ZERO_CAPABILITY_SUMMARY_H
|
||||
#include "zero.h"
|
||||
|
||||
typedef struct {
|
||||
bool args;
|
||||
bool env;
|
||||
bool fs;
|
||||
bool memory;
|
||||
bool alloc;
|
||||
bool path;
|
||||
bool codec;
|
||||
bool parse;
|
||||
bool time;
|
||||
bool rand;
|
||||
bool net;
|
||||
bool proc;
|
||||
bool web;
|
||||
bool world;
|
||||
} CapabilitySummary;
|
||||
|
||||
void z_capability_summary_merge(CapabilitySummary *caps, const CapabilitySummary *other);
|
||||
void z_capability_summary_set(CapabilitySummary *caps, const char *capability);
|
||||
void z_capability_summary_collect_std_name(const char *name, CapabilitySummary *caps);
|
||||
CapabilitySummary z_ir_program_capabilities(const IrProgram *ir);
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
#include "cli_help.h"
|
||||
|
||||
#include "program_graph_command.h"
|
||||
#include "program_graph_patch.h"
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool cli_help_arg_is(const char *arg, const char *expected) {
|
||||
return strcmp(arg ? arg : "", expected) == 0;
|
||||
}
|
||||
|
||||
static bool cli_help_is_program_graph_root_command(const char *command) {
|
||||
static const char *const commands[] = {"init", "dump", "import", "export", "query", "inspect", "validate", "view", "diff", "source-map", "reconcile", "status", "verify-projection", "merge", "roundtrip", "patch"};
|
||||
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
|
||||
if (cli_help_arg_is(command, commands[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void z_cli_print_help(void) {
|
||||
printf("zero %s native bootstrap\n\n", ZERO_VERSION);
|
||||
fputs("Usage:\n zero --version [--json]\n zero skills [list|get] [--json]\n zero init [--json] [--manifest toml|json] [--format text|binary] [--template cli|lib|package] [project-path]\n zero check [--json] [--target <target>] [--emit exe|obj|llvm-ir] [graph-input]\n zero patch [--json] [--check-only|--dry-run] [--format text|binary] [--out <program-graph-artifact>] [graph-input] (<patch-file>|--op <operation>|--replace-fn <name> --body-file <file|->|--replace-in-fn <name> --old <text> --new <text>|--rewrite <pattern> --to <template> [--fn <name>] [--apply])\n zero test [graph-input]\n zero fmt <file.0|project|zero.toml|zero.json>\n zero build [--json] [--emit exe|obj|llvm-ir] [--backend direct|llvm|<direct-emitter>] [--target <target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <file>] [graph-input]\n zero run [--backend direct|llvm|<direct-emitter>] [--target <target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <file>] [graph-input] [-- args...]\n zero tokens --json <file.0|project|zero.toml|zero.json>\n zero parse --json <file.0|project|zero.toml|zero.json>\n zero query [--json] [--fn <name>] [--find <text>] [--refs <name>] [--calls <name>] [--node <id>] [--depth <n>] [--full] [--handles] [--no-help] [graph-input|name]\n zero view [--json] [--fn <name> [--around <text>|--handles]] [--outline <module-or-file>] [--out <file.0>] [graph-input]\n zero diff [--fn <name>] [graph-input]\n zero status|verify-projection [--json] [project|zero.toml|zero.json|file.0|zero.graph]\n zero import [--json] [--format text|binary] [--out <program-graph-artifact>] [project|zero.toml|zero.json|file.0]\n zero export [--json] [project|zero.toml|zero.json|file.0]\n zero dump|validate|roundtrip [--json] [--format text|binary] [--out <program-graph-artifact>] [graph-input]\n zero source-map [--json] [graph-input]\n zero reconcile [--json] <base-graph-input> --source <edited-file.0|project|zero.toml|zero.json>\n zero merge --base <base-zero.graph> --left <left-zero.graph> --right <right-zero.graph> [--json] [--format text|binary] <project|zero.toml|zero.json|file.0>\n zero doc [--json] [graph-input]\n zero size [--json] [--out <artifact>] [graph-input]\n zero mem [--json] [--target <target>] [graph-input]\n zero dev [--json] [--trace] [graph-input]\n zero time --json [graph-input]\n zero abi check|dump [--json] [--target <target>] [graph-input]\n zero explain [--json] <code>\n zero fix --plan --json [graph-input]\n zero doctor [--json]\n zero clean [--all]\n zero targets\n\nExamples:\n zero init\n zero init --template cli hello\n zero run examples/add.graph\n zero build --emit exe examples/hello.graph --out .zero/out/hello\n zero check --json examples/hello.graph\n zero build --target linux-musl-x64 examples/memory-package\n", stdout);
|
||||
}
|
||||
|
||||
void z_cli_print_graph_patch_help_text(void) {
|
||||
printf("program graph patch operations\n");
|
||||
printf("\nTo replace text inside one function without retyping the body, use --replace-in-fn (Edit semantics):\n");
|
||||
printf(" $ zero patch . --replace-in-fn greet --old 'return 1' --new 'return 2'\n");
|
||||
printf("--old must match the body text zero view --fn <name> prints exactly once; a missing or non-unique\n");
|
||||
printf("match fails with the occurrence count. Inline --old/--new accept \\n escapes; --old-file/--new-file\n");
|
||||
printf("read the text from a file or - (stdin) for multi-line replacements.\n");
|
||||
printf("\nTo replace one whole function body, use --replace-fn with --body-file.\n");
|
||||
printf("--body-file - reads the body rows from stdin, so a heredoc does the whole edit in one call:\n");
|
||||
printf(" $ zero patch . --replace-fn greet --body-file - <<'EOF'\n");
|
||||
printf(" check world.out.write(\"hello agent\\n\")\n");
|
||||
printf(" EOF\n");
|
||||
printf("The body holds only the new body rows in canonical projection syntax, exactly what\n");
|
||||
printf("zero view --fn <name> prints between the signature braces. No header, no end marker.\n");
|
||||
printf("Alternative: write the rows to a file and pass its path:\n");
|
||||
printf(" $ zero patch . --replace-fn greet --body-file /tmp/greet.body\n");
|
||||
printf("\nDeclaration-level ops: setConst replaces a top-level const's initializer expression by name;\n");
|
||||
printf("addParamTo appends a parameter to an existing function and, given default=\"<expr>\", updates\n");
|
||||
printf("every call site in the package to pass it explicitly (without default it fails with the\n");
|
||||
printf("call-site count); setReturnType changes a function's declared return type.\n");
|
||||
printf("\nOperation grammar accepted by zero patch --op, --patch-text, and zero-program-graph-patch v1 files:\n");
|
||||
const char *const *ops = z_program_graph_patch_authoring_operation_examples();
|
||||
for (size_t i = 0; ops[i]; i++) printf(" %s\n", ops[i]);
|
||||
printf("\nA minimal complete patch file looks exactly like this:\n");
|
||||
fputs(z_program_graph_patch_minimal_file_example(), stdout);
|
||||
printf("\nFor larger graph edits, put these lines in a patch file under /tmp or pass --patch-text; --patch-text - reads a complete patch from stdin.\n");
|
||||
printf("\nadvanced: node-level ops (see zero view --fn <name> --handles)\n");
|
||||
const char *const *node_ops = z_program_graph_patch_node_operation_examples();
|
||||
for (size_t i = 0; node_ops[i]; i++) printf(" %s\n", node_ops[i]);
|
||||
printf("\nstructural rewrite by example (cross-cutting edits expressed once):\n");
|
||||
printf(" zero patch . --rewrite 'bnCmp($A, $B) == 0' --to 'bnEq($A, $B)'\n");
|
||||
printf("Pattern and template are canonical projection expressions; $A, $B bind arbitrary subtrees and\n");
|
||||
printf("the same metavariable twice must match equal subtrees. Default is a dry run listing each site\n");
|
||||
printf("as file fn:handle with rendered before/after; --apply rewrites every site in one batch with one\n");
|
||||
printf("revalidation pass, and --fn <name> scopes matching to one function. Expression-level patterns only;\n");
|
||||
printf("unsupported subtree kinds are skipped and counted.\n");
|
||||
printf("\nNode handles come from zero view --fn <name> --handles or zero query --fn <name> --handles;\n");
|
||||
printf("short forms (#55ae, #8b..a5) and full ids both work. replaceExpr swaps one expression subtree:\n");
|
||||
printf("aimed at a statement handle it replaces that statement's expression (a let initializer, condition,\n");
|
||||
printf("or return value), so changing a value, operator, or condition is a one-line --op. Multiple --op\n");
|
||||
printf("flags batch into one patch with a single revalidation pass.\n");
|
||||
}
|
||||
|
||||
void z_cli_print_command_help(const char *command) {
|
||||
if (cli_help_arg_is(command, "skills")) {
|
||||
printf("Usage: zero skills [list|get] [--json]\n\n");
|
||||
printf("List and retrieve version-matched skill content for agents.\n\n");
|
||||
printf("Subcommands:\n");
|
||||
printf(" list list available skills (default)\n");
|
||||
printf(" get <name> [--full] print bundled skill content\n");
|
||||
printf(" get <name> --topic <section-prefix> print only the matching sections, e.g. zero skills get stdlib --topic std.time\n");
|
||||
printf(" get --all print every visible skill\n");
|
||||
} else if (cli_help_arg_is(command, "doctor")) {
|
||||
printf("Usage: zero doctor [--json]\n\n");
|
||||
printf("Check host, compiler, target toolchain, and docs/example readiness.\n");
|
||||
} else if (cli_help_arg_is(command, "clean")) {
|
||||
printf("Usage: zero clean [--all]\n\n");
|
||||
printf("Remove generated output while preserving compiler caches by default.\n\n");
|
||||
printf("Flags:\n");
|
||||
printf(" --all remove broader .zero generated state while preserving .zero/bin\n");
|
||||
} else if (cli_help_arg_is(command, "check")) {
|
||||
printf("Usage: zero check [--json] [--target <target>] [--emit exe|obj|llvm-ir] [--backend direct|llvm|<direct-emitter>] [graph-input]\n\n");
|
||||
printf("Validate and typecheck graph-backed Zero input without emitting artifacts.\n");
|
||||
} else if (cli_help_arg_is(command, "patch")) {
|
||||
printf("Usage: zero patch [--json] [--check-only|--dry-run] [--format text|binary] [--out <program-graph-artifact>] [graph-input] (<patch-file>|--op <operation>|--replace-fn <name> --body-file <file|->|--replace-in-fn <name> --old <text> --new <text>|--rewrite <pattern> --to <template> [--fn <name>] [--apply])\n\n");
|
||||
z_cli_print_graph_patch_help_text();
|
||||
} else if (cli_help_arg_is(command, "build")) {
|
||||
printf("Usage: zero build [--json] [--emit exe|obj|llvm-ir] [--backend direct|llvm|<direct-emitter>] [--target <target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <file>] [graph-input]\n\n");
|
||||
printf("Build direct native executable or object artifacts.\n\n");
|
||||
printf("Example: zero build --release tiny --emit exe examples/hello.graph --out .zero/out/hello\n");
|
||||
} else if (cli_help_arg_is(command, "run")) {
|
||||
printf("Usage: zero run [--backend direct|llvm|<direct-emitter>] [--target <target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <file>] [graph-input] [-- args...]\n\n");
|
||||
printf("Build a host executable with the selected backend and run it. Direct is the default; LLVM is explicit and requires clang. Program stdout and stderr are passed through unchanged.\n\n");
|
||||
printf("Example: zero run examples/add.graph\n");
|
||||
} else if (cli_help_arg_is(command, "test")) {
|
||||
printf("Usage: zero test [--json] [--filter <name>] [--target <target>] [--cc <path>] [--out <file>] [graph-input]\n\n");
|
||||
printf("Build and run inline `test` blocks.\n");
|
||||
} else if (cli_help_arg_is(command, "fmt")) {
|
||||
printf("Usage: zero fmt [--check] <file.0|project|zero.toml|zero.json>\n\n");
|
||||
printf("Print deterministic bootstrap formatting for Zero source.\n");
|
||||
} else if (cli_help_arg_is(command, "targets")) {
|
||||
printf("Usage: zero targets\n\n");
|
||||
printf("Print supported target facts as JSON.\n");
|
||||
} else if (cli_help_arg_is(command, "tokens")) {
|
||||
printf("Usage: zero tokens --json <file.0|project|zero.toml|zero.json>\n\n");
|
||||
printf("Emit source token JSON for oracle comparisons.\n");
|
||||
} else if (cli_help_arg_is(command, "parse")) {
|
||||
printf("Usage: zero parse --json <file.0|project|zero.toml|zero.json>\n\n");
|
||||
printf("Emit normalized source parse JSON for oracle comparisons.\n");
|
||||
} else if (cli_help_arg_is(command, "abi")) {
|
||||
printf("Usage: zero abi check|dump [--json] [--target <target>] [graph-input]\n\n");
|
||||
printf("Check ABI-safe declarations or dump target-aware graph layout facts.\n");
|
||||
} else if (cli_help_arg_is(command, "query")) {
|
||||
printf("Usage: zero query [--json] [--fn <name>] [--find <text>] [--refs <name>] [--calls <name>] [--node <id>] [--depth <n>] [--full] [--handles] [--no-help] [graph-input|name]\n\n");
|
||||
printf("Report compact module, function, body, and patch facts for agents.\n\n");
|
||||
printf("A bare name argument that is not an existing path runs --find with that name against the current package.\n");
|
||||
printf("--node <id> returns a node-scoped report with span, parents, and children; add --depth <n> (default 1) for a deeper child subtree, or --full for the whole-module report.\n");
|
||||
printf("--handles adds stmt and param patch handles to function reports; use it when you are about to patch. --no-help suppresses the long patch-operation footer when handles are already enough.\n");
|
||||
printf("Use zero view --fn <name> when you want one function's source instead of graph facts.\n\n");
|
||||
printf("Examples:\n");
|
||||
printf(" zero query userTotals find nodes named userTotals in the current package\n");
|
||||
printf(" zero query --json --find handleLine . locate node ids for a name, with source spans\n");
|
||||
printf(" zero query --fn handleLine --handles . stmt and param patch handles for one function\n");
|
||||
printf(" zero query --json --node '#decl_12ab34cd' --depth 2 . one node's fields, edges, and grandchildren\n");
|
||||
} else if (cli_help_arg_is(command, "reconcile")) {
|
||||
printf("Usage: zero reconcile [--json] <base-graph-input> --source <edited-file.0|project|zero.toml|zero.json>\n\n");
|
||||
printf("Compare a prior graph with edited source and report durable node identity decisions before importing.\n\n");
|
||||
printf("Examples:\n");
|
||||
printf(" zero reconcile zero.graph --source src/main.0 report which node ids survive the edit\n");
|
||||
printf(" zero reconcile --json . --source src/main.0 machine-readable identity decisions for the package store\n");
|
||||
printf(" zero reconcile --json baseline.graph --source . compare a saved artifact against the edited package\n");
|
||||
} else if (cli_help_arg_is(command, "view")) {
|
||||
printf("Usage: zero view [--json] [--fn <name> [--around <text>|--handles]] [--outline <module-or-file>] [--out <file.0>] [graph-input]\n\n");
|
||||
printf("Render ProgramGraph input as a generated Zero view.\n\n");
|
||||
printf("--fn <name> prints just that function's canonical source; a missing name fails with close matches.\n");
|
||||
printf("--fn <name> --around <text> prints only the enclosing block that contains the text, eliding the rest of the function.\n");
|
||||
printf("--fn <name> --handles prints the same source with a trailing // #handle comment per statement; compound headers show `#stmt #body-block`, else/arm lines show the clause block handle. The handles are the node ids zero patch --op accepts (short forms included).\n");
|
||||
printf("--outline <module-or-file> prints function signatures with one-line docs and no bodies; pass . for every module.\n\n");
|
||||
printf("Examples:\n");
|
||||
printf(" zero view --fn handleLine . print one function's source from the current package\n");
|
||||
printf(" zero view --fn handleLine --handles . the same source with patch handles in the margin\n");
|
||||
printf(" zero view --fn handleLine --around limit . only the enclosing block mentioning limit\n");
|
||||
printf(" zero view --outline src/main.0 signatures plus one-line docs, no bodies\n");
|
||||
printf(" zero view --out /tmp/main.0 . write the whole canonical view to a .0 file\n");
|
||||
} else if (cli_help_arg_is(command, "diff")) {
|
||||
printf("Usage: zero diff [--fn <name>] [graph-input]\n\n");
|
||||
printf("Print canonical review text for ProgramGraph input, for humans and Git textconv diff drivers.\n\n");
|
||||
printf("--fn <name> prints just that function's canonical source; a missing name fails with close matches.\n\n");
|
||||
printf("Examples:\n");
|
||||
printf(" zero diff . print the current package's canonical review text\n");
|
||||
printf(" zero diff --fn handleLine . print one function's review text\n");
|
||||
printf(" zero diff zero.graph render the repository graph store for git textconv\n");
|
||||
} else if (cli_help_arg_is(command, "graph") || cli_help_is_program_graph_root_command(command)) {
|
||||
z_program_graph_print_command_help();
|
||||
} else if (cli_help_arg_is(command, "doc")) {
|
||||
printf("Usage: zero doc [--json] [--target <target>] [graph-input]\n\n");
|
||||
printf("Emit package API documentation facts without emitting artifacts.\n");
|
||||
} else if (cli_help_arg_is(command, "size")) {
|
||||
printf("Usage: zero size [--json] [--target <target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <artifact>] [graph-input]\n\n");
|
||||
printf("Report direct IR size, optional artifact bytes, capabilities, and stdlib helper metadata.\n");
|
||||
} else if (cli_help_arg_is(command, "mem")) {
|
||||
printf("Usage: zero mem [--json] [--target <target>] [graph-input]\n\n");
|
||||
printf("Report direct stack, static, heap, buffer, and runtime memory facts.\n");
|
||||
} else if (cli_help_arg_is(command, "dev")) {
|
||||
printf("Usage: zero dev [--json] [--trace] [--target <target>] [graph-input]\n\n");
|
||||
printf("Emit a direct incremental watch plan, interface fingerprints, and affected-test summary.\n");
|
||||
} else if (cli_help_arg_is(command, "time")) {
|
||||
printf("Usage: zero time --json [--target <target>] [graph-input]\n\n");
|
||||
printf("Emit compiler phase, cache, and invalidation timing facts.\n");
|
||||
} else if (cli_help_arg_is(command, "explain")) {
|
||||
printf("Usage: zero explain [--json] <diagnostic-code>\n\n");
|
||||
printf("Explain a diagnostic and its repair metadata.\n");
|
||||
} else if (cli_help_arg_is(command, "fix")) {
|
||||
printf("Usage: zero fix (--plan|--patch|--apply) --json [--target <target>] [graph-input]\n\n");
|
||||
printf("Print repair plans, reviewable patches, or apply behavior-preserving edits for graph-backed inputs.\n");
|
||||
} else if (cli_help_arg_is(command, "version") || cli_help_arg_is(command, "--version")) {
|
||||
printf("Usage: zero --version [--json]\n\n");
|
||||
printf("Print version, commit, host target, compiler backend, and target toolchain availability.\n");
|
||||
} else {
|
||||
z_cli_print_help();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef ZERO_C_CLI_HELP_H
|
||||
#define ZERO_C_CLI_HELP_H
|
||||
|
||||
void z_cli_print_help(void);
|
||||
void z_cli_print_command_help(const char *command);
|
||||
void z_cli_print_graph_patch_help_text(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "coff_emit_state.h"
|
||||
#include "x64_emit.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *const runtime_helper_symbols[COFF_RUNTIME_HELPER_COUNT] = {
|
||||
"zero_world_write",
|
||||
"zero_json_parse_bytes",
|
||||
"zero_json_diagnostic",
|
||||
"zero_json_field",
|
||||
"zero_json_lookup_scalar",
|
||||
"zero_json_string_decode",
|
||||
"zero_json_string_field",
|
||||
"zero_json_write_string",
|
||||
"zero_json_write_field_raw",
|
||||
"zero_json_write_field_string",
|
||||
"zero_json_write_field_u32",
|
||||
"zero_json_write_field_bool",
|
||||
"zero_json_write_object1_string",
|
||||
"zero_json_write_object1_u32",
|
||||
"zero_json_write_object1_bool",
|
||||
"zero_json_write_object2_fields",
|
||||
"zero_json_write_object2_string_field",
|
||||
"zero_json_write_object2_u32_field",
|
||||
"zero_json_write_object2_bool_field",
|
||||
"zero_json_write_array2_strings",
|
||||
"zero_json_write_array2_u32",
|
||||
"zero_json_write_array2_bools",
|
||||
"zero_str_buffer_op",
|
||||
"zero_str_concat",
|
||||
"zero_str_repeat",
|
||||
"zero_str_trim_op",
|
||||
"zero_str_pair_op",
|
||||
"zero_str_count_byte",
|
||||
"zero_str_word_count_ascii",
|
||||
"zero_crypto_digest",
|
||||
"zero_crypto_hmac_sha256",
|
||||
"zero_crypto_hmac_sha256_hex",
|
||||
"zero_ascii_op",
|
||||
"zero_text_op",
|
||||
"zero_parse_op",
|
||||
"zero_parse_usize",
|
||||
"zero_parse_i32",
|
||||
"zero_parse_u32",
|
||||
"zero_fmt_bool",
|
||||
"zero_fmt_hex_lower_u32",
|
||||
"zero_fmt_i32",
|
||||
"zero_fmt_u32",
|
||||
"zero_fmt_usize",
|
||||
"zero_time_op",
|
||||
"zero_term_op",
|
||||
"zero_term_read_input",
|
||||
"zero_math_op",
|
||||
"zero_math_usize_op",
|
||||
"zero_search_op",
|
||||
"zero_sort_op",
|
||||
"zero_sort_is_sorted_op",
|
||||
"zero_http_request_method_name",
|
||||
"zero_http_request_path",
|
||||
"zero_http_request_body_within",
|
||||
"zero_proc_spawn_inherit",
|
||||
"zero_proc_spawn_inherit_args",
|
||||
"zero_proc_capture",
|
||||
"zero_proc_capture_args",
|
||||
"zero_proc_capture_files",
|
||||
"zero_proc_capture_files_args",
|
||||
"zero_proc_spawn_child",
|
||||
"zero_proc_spawn_child_in",
|
||||
"zero_proc_spawn_child_in_env",
|
||||
"zero_proc_spawn_child_args",
|
||||
"zero_proc_child_op",
|
||||
"zero_proc_child_io",
|
||||
"zero_pty_spawn",
|
||||
"zero_pty_spawn_in",
|
||||
"zero_pty_spawn_in_env",
|
||||
"zero_pty_spawn_args",
|
||||
"zero_pty_resize",
|
||||
};
|
||||
|
||||
static bool coff_emit_state_diag(ZDiag *diag, const char *message, int line, int column, const char *actual) {
|
||||
if (diag) {
|
||||
diag->code = 4004;
|
||||
diag->line = line > 0 ? line : 1;
|
||||
diag->column = column > 0 ? column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message);
|
||||
snprintf(diag->expected, sizeof(diag->expected), "direct COFF x64 object MVP subset");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "unsupported feature");
|
||||
snprintf(diag->help, sizeof(diag->help), "reduce the program to primitive direct-backend constructs or choose a supported direct target");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool coff_runtime_helper_valid(CoffRuntimeHelper helper) {
|
||||
return helper >= 0 && helper < COFF_RUNTIME_HELPER_COUNT;
|
||||
}
|
||||
|
||||
const char *z_coff_runtime_helper_symbol(CoffRuntimeHelper helper) {
|
||||
if (!coff_runtime_helper_valid(helper)) return "";
|
||||
return runtime_helper_symbols[helper];
|
||||
}
|
||||
|
||||
void z_coff_emit_context_free(CoffEmitContext *ctx) {
|
||||
if (!ctx) return;
|
||||
free(ctx->call_patches);
|
||||
free(ctx->rodata_patches);
|
||||
for (unsigned i = 0; i < COFF_RUNTIME_HELPER_COUNT; i++) {
|
||||
free(ctx->runtime_patches[i].items);
|
||||
}
|
||||
z_direct_trap_branches_free(ctx->trap_branches, Z_DIRECT_TRAP_KIND_COUNT);
|
||||
}
|
||||
|
||||
bool z_coff_record_call_patch(CoffEmitContext *ctx, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag) {
|
||||
if (!ctx || (((!value) || !value->external_call) && callee_index >= ctx->function_count)) {
|
||||
return coff_emit_state_diag(diag, "direct COFF call target index is out of range", value ? value->line : 1, value ? value->column : 1, "invalid callee");
|
||||
}
|
||||
if (ctx->call_patch_len == ctx->call_patch_cap) {
|
||||
ctx->call_patch_cap = z_grow_capacity(ctx->call_patch_cap, ctx->call_patch_len + 1, 8);
|
||||
ctx->call_patches = z_checked_reallocarray(ctx->call_patches, ctx->call_patch_cap, sizeof(CoffCallPatch));
|
||||
}
|
||||
ctx->call_patches[ctx->call_patch_len++] = (CoffCallPatch){
|
||||
.patch_offset = patch_offset,
|
||||
.callee_index = callee_index,
|
||||
.external_index = value ? value->external_index : 0,
|
||||
.external_call = value && value->external_call
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_coff_record_rodata_patch(CoffEmitContext *ctx, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag) {
|
||||
if (!ctx) return coff_emit_state_diag(diag, "direct COFF readonly data relocation requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (ctx->rodata_patch_len == ctx->rodata_patch_cap) {
|
||||
ctx->rodata_patch_cap = z_grow_capacity(ctx->rodata_patch_cap, ctx->rodata_patch_len + 1, 8);
|
||||
ctx->rodata_patches = z_checked_reallocarray(ctx->rodata_patches, ctx->rodata_patch_cap, sizeof(ZCoffImageDataPatch));
|
||||
}
|
||||
ctx->rodata_patches[ctx->rodata_patch_len++] = (ZCoffImageDataPatch){.patch_offset = patch_offset, .data_offset = data_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool coff_record_runtime_patch_at(CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t patch_offset, int line, int column, ZDiag *diag) {
|
||||
if (!ctx || !coff_runtime_helper_valid(helper)) {
|
||||
return coff_emit_state_diag(diag, "direct COFF runtime relocation requires an emit context", line, column, "missing context");
|
||||
}
|
||||
CoffPatchList *list = &ctx->runtime_patches[helper];
|
||||
if (list->len == list->cap) {
|
||||
list->cap = z_grow_capacity(list->cap, list->len + 1, 4);
|
||||
list->items = z_checked_reallocarray(list->items, list->cap, sizeof(CoffPatch));
|
||||
}
|
||||
list->items[list->len++] = (CoffPatch){.patch_offset = patch_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_coff_record_value_runtime_patch(CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t patch_offset, const IrValue *value, ZDiag *diag) {
|
||||
return coff_record_runtime_patch_at(ctx, helper, patch_offset, value ? value->line : 1, value ? value->column : 1, diag);
|
||||
}
|
||||
|
||||
bool z_coff_record_instr_runtime_patch(CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t patch_offset, const IrInstr *instr, ZDiag *diag) {
|
||||
return coff_record_runtime_patch_at(ctx, helper, patch_offset, instr ? instr->line : 1, instr ? instr->column : 1, diag);
|
||||
}
|
||||
|
||||
size_t z_coff_runtime_patch_count(const CoffEmitContext *ctx, CoffRuntimeHelper helper) {
|
||||
if (!ctx || !coff_runtime_helper_valid(helper)) return 0;
|
||||
return ctx->runtime_patches[helper].len;
|
||||
}
|
||||
|
||||
size_t z_coff_text_relocation_count(const CoffEmitContext *ctx) {
|
||||
if (!ctx) return 0;
|
||||
size_t count = ctx->call_patch_len + ctx->rodata_patch_len;
|
||||
for (unsigned i = 0; i < COFF_RUNTIME_HELPER_COUNT; i++) {
|
||||
count += ctx->runtime_patches[i].len;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void z_coff_patch_call_patches(ZBuf *text, const CoffEmitContext *ctx) {
|
||||
for (size_t i = 0; ctx && i < ctx->call_patch_len; i++) {
|
||||
const CoffCallPatch *patch = &ctx->call_patches[i];
|
||||
if (patch->external_call) continue;
|
||||
z_x64_patch_rel32(text, patch->patch_offset, ctx->function_offsets[patch->callee_index]);
|
||||
}
|
||||
}
|
||||
|
||||
void z_coff_patch_runtime_patches(ZBuf *text, const CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t target_offset) {
|
||||
if (!ctx || !coff_runtime_helper_valid(helper)) return;
|
||||
const CoffPatchList *patches = &ctx->runtime_patches[helper];
|
||||
for (size_t i = 0; i < patches->len; i++) {
|
||||
z_x64_patch_rel32(text, patches->items[i].patch_offset, target_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void z_coff_append_call_relocations(ZBuf *relocs, const CoffEmitContext *ctx, uint32_t function_symbol_base, uint32_t external_symbol_base) {
|
||||
for (size_t i = 0; ctx && i < ctx->call_patch_len; i++) {
|
||||
const CoffCallPatch *patch = &ctx->call_patches[i];
|
||||
uint32_t symbol_index = patch->external_call ? external_symbol_base + patch->external_index : function_symbol_base + patch->callee_index;
|
||||
z_coff_append_reloc_amd64(relocs, (uint32_t)patch->patch_offset, symbol_index, Z_COFF_RELOC_AMD64_REL32);
|
||||
}
|
||||
}
|
||||
|
||||
void z_coff_append_rodata_relocations(ZBuf *relocs, const CoffEmitContext *ctx, uint32_t rodata_symbol) {
|
||||
for (size_t i = 0; ctx && i < ctx->rodata_patch_len; i++) {
|
||||
z_coff_append_reloc_amd64(relocs, (uint32_t)ctx->rodata_patches[i].patch_offset, rodata_symbol, Z_COFF_RELOC_AMD64_ADDR64);
|
||||
}
|
||||
}
|
||||
|
||||
void z_coff_append_runtime_relocations(ZBuf *relocs, const CoffEmitContext *ctx, CoffRuntimeHelper helper, uint32_t runtime_symbol) {
|
||||
if (!ctx || !coff_runtime_helper_valid(helper)) return;
|
||||
const CoffPatchList *patches = &ctx->runtime_patches[helper];
|
||||
for (size_t i = 0; i < patches->len; i++) {
|
||||
z_coff_append_reloc_amd64(relocs, (uint32_t)patches->items[i].patch_offset, runtime_symbol, Z_COFF_RELOC_AMD64_REL32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
#ifndef ZERO_C_COFF_EMIT_STATE_H
|
||||
#define ZERO_C_COFF_EMIT_STATE_H
|
||||
|
||||
#include "zero.h"
|
||||
#include "coff_format.h"
|
||||
|
||||
typedef enum {
|
||||
COFF_RUNTIME_WORLD_WRITE,
|
||||
COFF_RUNTIME_JSON_PARSE_BYTES,
|
||||
COFF_RUNTIME_JSON_DIAGNOSTIC,
|
||||
COFF_RUNTIME_JSON_FIELD,
|
||||
COFF_RUNTIME_JSON_LOOKUP_SCALAR,
|
||||
COFF_RUNTIME_JSON_STRING_DECODE,
|
||||
COFF_RUNTIME_JSON_STRING_FIELD,
|
||||
COFF_RUNTIME_JSON_WRITE_STRING,
|
||||
COFF_RUNTIME_JSON_WRITE_FIELD_RAW,
|
||||
COFF_RUNTIME_JSON_WRITE_FIELD_STRING,
|
||||
COFF_RUNTIME_JSON_WRITE_FIELD_U32,
|
||||
COFF_RUNTIME_JSON_WRITE_FIELD_BOOL,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT1_STRING,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT1_U32,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT1_BOOL,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT2_FIELDS,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT2_STRING_FIELD,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT2_U32_FIELD,
|
||||
COFF_RUNTIME_JSON_WRITE_OBJECT2_BOOL_FIELD,
|
||||
COFF_RUNTIME_JSON_WRITE_ARRAY2_STRINGS,
|
||||
COFF_RUNTIME_JSON_WRITE_ARRAY2_U32,
|
||||
COFF_RUNTIME_JSON_WRITE_ARRAY2_BOOLS,
|
||||
COFF_RUNTIME_STR_BUFFER_OP,
|
||||
COFF_RUNTIME_STR_CONCAT,
|
||||
COFF_RUNTIME_STR_REPEAT,
|
||||
COFF_RUNTIME_STR_TRIM_OP,
|
||||
COFF_RUNTIME_STR_PAIR_OP,
|
||||
COFF_RUNTIME_STR_COUNT_BYTE,
|
||||
COFF_RUNTIME_STR_WORD_COUNT_ASCII,
|
||||
COFF_RUNTIME_CRYPTO_DIGEST,
|
||||
COFF_RUNTIME_CRYPTO_HMAC_SHA256,
|
||||
COFF_RUNTIME_CRYPTO_HMAC_SHA256_HEX,
|
||||
COFF_RUNTIME_ASCII_OP,
|
||||
COFF_RUNTIME_TEXT_OP,
|
||||
COFF_RUNTIME_PARSE_OP,
|
||||
COFF_RUNTIME_PARSE_USIZE,
|
||||
COFF_RUNTIME_PARSE_I32,
|
||||
COFF_RUNTIME_PARSE_U32,
|
||||
COFF_RUNTIME_FMT_BOOL,
|
||||
COFF_RUNTIME_FMT_HEX_U32,
|
||||
COFF_RUNTIME_FMT_I32,
|
||||
COFF_RUNTIME_FMT_U32,
|
||||
COFF_RUNTIME_FMT_USIZE,
|
||||
COFF_RUNTIME_TIME_OP,
|
||||
COFF_RUNTIME_TERM_OP,
|
||||
COFF_RUNTIME_TERM_READ_INPUT,
|
||||
COFF_RUNTIME_MATH_OP,
|
||||
COFF_RUNTIME_MATH_USIZE_OP,
|
||||
COFF_RUNTIME_SEARCH_OP,
|
||||
COFF_RUNTIME_SORT_OP,
|
||||
COFF_RUNTIME_SORT_IS_SORTED_OP,
|
||||
COFF_RUNTIME_HTTP_REQUEST_METHOD_NAME,
|
||||
COFF_RUNTIME_HTTP_REQUEST_PATH,
|
||||
COFF_RUNTIME_HTTP_REQUEST_BODY_WITHIN,
|
||||
COFF_RUNTIME_PROC_SPAWN_INHERIT,
|
||||
COFF_RUNTIME_PROC_SPAWN_INHERIT_ARGS,
|
||||
COFF_RUNTIME_PROC_CAPTURE,
|
||||
COFF_RUNTIME_PROC_CAPTURE_ARGS,
|
||||
COFF_RUNTIME_PROC_CAPTURE_FILES,
|
||||
COFF_RUNTIME_PROC_CAPTURE_FILES_ARGS,
|
||||
COFF_RUNTIME_PROC_SPAWN_CHILD,
|
||||
COFF_RUNTIME_PROC_SPAWN_CHILD_IN,
|
||||
COFF_RUNTIME_PROC_SPAWN_CHILD_IN_ENV,
|
||||
COFF_RUNTIME_PROC_SPAWN_CHILD_ARGS,
|
||||
COFF_RUNTIME_PROC_CHILD_OP,
|
||||
COFF_RUNTIME_PROC_CHILD_IO,
|
||||
COFF_RUNTIME_PTY_SPAWN,
|
||||
COFF_RUNTIME_PTY_SPAWN_IN,
|
||||
COFF_RUNTIME_PTY_SPAWN_IN_ENV,
|
||||
COFF_RUNTIME_PTY_SPAWN_ARGS,
|
||||
COFF_RUNTIME_PTY_RESIZE,
|
||||
COFF_RUNTIME_HELPER_COUNT
|
||||
} CoffRuntimeHelper;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
} CoffPatch;
|
||||
|
||||
typedef struct {
|
||||
CoffPatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} CoffPatchList;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned callee_index;
|
||||
unsigned external_index;
|
||||
bool external_call;
|
||||
} CoffCallPatch;
|
||||
|
||||
typedef struct {
|
||||
const IrProgram *program;
|
||||
size_t *function_offsets;
|
||||
size_t function_count;
|
||||
CoffCallPatch *call_patches;
|
||||
size_t call_patch_len;
|
||||
size_t call_patch_cap;
|
||||
ZCoffImageDataPatch *rodata_patches;
|
||||
size_t rodata_patch_len;
|
||||
size_t rodata_patch_cap;
|
||||
CoffPatchList runtime_patches[COFF_RUNTIME_HELPER_COUNT];
|
||||
unsigned rodata_base_offset;
|
||||
ZDirectTrapMessages trap_messages;
|
||||
ZDirectTrapBranchList trap_branches[Z_DIRECT_TRAP_KIND_COUNT];
|
||||
ZDirectLoopFrame *loop;
|
||||
} CoffEmitContext;
|
||||
|
||||
const char *z_coff_runtime_helper_symbol(CoffRuntimeHelper helper);
|
||||
void z_coff_emit_context_free(CoffEmitContext *ctx);
|
||||
bool z_coff_record_call_patch(CoffEmitContext *ctx, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag);
|
||||
bool z_coff_record_rodata_patch(CoffEmitContext *ctx, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag);
|
||||
bool z_coff_record_value_runtime_patch(CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t patch_offset, const IrValue *value, ZDiag *diag);
|
||||
bool z_coff_record_instr_runtime_patch(CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t patch_offset, const IrInstr *instr, ZDiag *diag);
|
||||
size_t z_coff_runtime_patch_count(const CoffEmitContext *ctx, CoffRuntimeHelper helper);
|
||||
size_t z_coff_text_relocation_count(const CoffEmitContext *ctx);
|
||||
void z_coff_patch_call_patches(ZBuf *text, const CoffEmitContext *ctx);
|
||||
void z_coff_patch_runtime_patches(ZBuf *text, const CoffEmitContext *ctx, CoffRuntimeHelper helper, size_t target_offset);
|
||||
void z_coff_append_call_relocations(ZBuf *relocs, const CoffEmitContext *ctx, uint32_t function_symbol_base, uint32_t external_symbol_base);
|
||||
void z_coff_append_rodata_relocations(ZBuf *relocs, const CoffEmitContext *ctx, uint32_t rodata_symbol);
|
||||
void z_coff_append_runtime_relocations(ZBuf *relocs, const CoffEmitContext *ctx, CoffRuntimeHelper helper, uint32_t runtime_symbol);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,350 @@
|
||||
#include "coff_format.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t import_directory_rva;
|
||||
uint32_t import_directory_size;
|
||||
uint32_t iat_rva;
|
||||
uint32_t iat_size;
|
||||
uint32_t iat_offsets[Z_COFF_IMPORT_COUNT];
|
||||
} CoffImportLayout;
|
||||
|
||||
size_t z_coff_align(size_t value, size_t alignment) {
|
||||
size_t remainder = alignment ? value % alignment : 0;
|
||||
return remainder == 0 ? value : value + (alignment - remainder);
|
||||
}
|
||||
|
||||
void z_coff_append_u8(ZBuf *buf, unsigned value) {
|
||||
zbuf_append_char(buf, (char)(value & 0xffu));
|
||||
}
|
||||
|
||||
void z_coff_append_u16(ZBuf *buf, uint16_t value) {
|
||||
z_coff_append_u8(buf, value);
|
||||
z_coff_append_u8(buf, value >> 8);
|
||||
}
|
||||
|
||||
void z_coff_append_u32(ZBuf *buf, uint32_t value) {
|
||||
z_coff_append_u8(buf, value);
|
||||
z_coff_append_u8(buf, value >> 8);
|
||||
z_coff_append_u8(buf, value >> 16);
|
||||
z_coff_append_u8(buf, value >> 24);
|
||||
}
|
||||
|
||||
void z_coff_append_u64(ZBuf *buf, uint64_t value) {
|
||||
z_coff_append_u32(buf, (uint32_t)(value & 0xffffffffu));
|
||||
z_coff_append_u32(buf, (uint32_t)(value >> 32));
|
||||
}
|
||||
|
||||
void z_coff_append_bytes(ZBuf *buf, const char *bytes, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) z_coff_append_u8(buf, (unsigned char)bytes[i]);
|
||||
}
|
||||
|
||||
void z_coff_append_zeros(ZBuf *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) z_coff_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_coff_pad_to(ZBuf *buf, size_t offset) {
|
||||
while (buf->len < offset) z_coff_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_coff_patch_u32(ZBuf *buf, size_t offset, uint32_t value) {
|
||||
buf->data[offset + 0] = (char)(value & 0xff);
|
||||
buf->data[offset + 1] = (char)((value >> 8) & 0xff);
|
||||
buf->data[offset + 2] = (char)((value >> 16) & 0xff);
|
||||
buf->data[offset + 3] = (char)((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
void z_coff_patch_u64(ZBuf *buf, size_t offset, uint64_t value) {
|
||||
for (unsigned i = 0; i < 8; i++) buf->data[offset + i] = (char)((value >> (i * 8)) & 0xffu);
|
||||
}
|
||||
|
||||
void z_coff_append_reloc(ZBuf *buf, uint32_t offset, uint32_t symbol_index, uint16_t type) {
|
||||
z_coff_append_u32(buf, offset);
|
||||
z_coff_append_u32(buf, symbol_index);
|
||||
z_coff_append_u16(buf, type);
|
||||
}
|
||||
|
||||
void z_coff_append_reloc_amd64(ZBuf *buf, uint32_t offset, uint32_t symbol_index, uint16_t type) {
|
||||
z_coff_append_reloc(buf, offset, symbol_index, type);
|
||||
}
|
||||
|
||||
static void coff_append_name(ZBuf *buf, const char *name) {
|
||||
size_t len = name ? strlen(name) : 0;
|
||||
if (len > 8) len = 8;
|
||||
for (size_t i = 0; i < len; i++) z_coff_append_u8(buf, (unsigned char)name[i]);
|
||||
for (size_t i = len; i < 8; i++) z_coff_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
static void coff_append_symbol_name(ZBuf *buf, const char *name, ZBuf *strings) {
|
||||
size_t len = name ? strlen(name) : 0;
|
||||
if (len <= 8) {
|
||||
coff_append_name(buf, name);
|
||||
return;
|
||||
}
|
||||
z_coff_append_u32(buf, 0);
|
||||
z_coff_append_u32(buf, (uint32_t)strings->len + 4);
|
||||
zbuf_append(strings, name);
|
||||
z_coff_append_u8(strings, 0);
|
||||
}
|
||||
|
||||
static void coff_append_symbol(ZBuf *out, const ZCoffSymbol *symbol, ZBuf *strings) {
|
||||
coff_append_symbol_name(out, symbol && symbol->name ? symbol->name : "zero_fn", strings);
|
||||
z_coff_append_u32(out, symbol ? symbol->value : 0);
|
||||
z_coff_append_u16(out, symbol ? symbol->section_number : 0);
|
||||
z_coff_append_u16(out, symbol ? symbol->type : 0);
|
||||
z_coff_append_u8(out, symbol ? symbol->storage_class : Z_COFF_SYMBOL_STATIC);
|
||||
z_coff_append_u8(out, 0);
|
||||
}
|
||||
|
||||
void z_coff_write_object(ZBuf *out, const ZCoffObjectImage *image) {
|
||||
const ZBuf empty = {0};
|
||||
const ZBuf *text = image && image->text ? image->text : ∅
|
||||
const ZBuf *rodata = image && image->rodata ? image->rodata : ∅
|
||||
const ZBuf *relocs = image && image->text_relocs ? image->text_relocs : ∅
|
||||
bool has_rodata = rodata->len > 0;
|
||||
uint16_t section_count = has_rodata ? 2 : 1;
|
||||
uint32_t section_symbol_count = has_rodata ? 2u : 1u;
|
||||
uint32_t header_size = 20 + 40 * section_count;
|
||||
uint32_t raw_text_offset = header_size;
|
||||
uint32_t raw_rodata_offset = has_rodata ? raw_text_offset + (uint32_t)text->len : 0;
|
||||
uint32_t reloc_offset = raw_text_offset + (uint32_t)text->len + (uint32_t)rodata->len;
|
||||
uint32_t symbol_offset = reloc_offset + (uint32_t)relocs->len;
|
||||
uint32_t symbol_count = section_symbol_count + (uint32_t)(image ? image->symbol_len : 0);
|
||||
|
||||
ZBuf strings;
|
||||
zbuf_init(&strings);
|
||||
zbuf_init(out);
|
||||
z_coff_append_u16(out, image ? (uint16_t)image->machine : 0);
|
||||
z_coff_append_u16(out, section_count);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, symbol_offset);
|
||||
z_coff_append_u32(out, symbol_count);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
|
||||
coff_append_name(out, ".text");
|
||||
z_coff_append_u32(out, (uint32_t)text->len);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, (uint32_t)text->len);
|
||||
z_coff_append_u32(out, raw_text_offset);
|
||||
z_coff_append_u32(out, relocs->len > 0 ? reloc_offset : 0);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u16(out, image ? image->text_reloc_count : 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u32(out, 0x60000020u);
|
||||
|
||||
if (has_rodata) {
|
||||
coff_append_name(out, ".rdata");
|
||||
z_coff_append_u32(out, (uint32_t)rodata->len);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, (uint32_t)rodata->len);
|
||||
z_coff_append_u32(out, raw_rodata_offset);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u32(out, 0x40000040u);
|
||||
}
|
||||
|
||||
if (text->data) z_coff_append_bytes(out, text->data, text->len);
|
||||
if (rodata->data) z_coff_append_bytes(out, rodata->data, rodata->len);
|
||||
if (relocs->data) z_coff_append_bytes(out, relocs->data, relocs->len);
|
||||
|
||||
ZCoffSymbol text_symbol = {.name = ".text", .section_number = 1, .storage_class = Z_COFF_SYMBOL_STATIC};
|
||||
coff_append_symbol(out, &text_symbol, &strings);
|
||||
if (has_rodata) {
|
||||
ZCoffSymbol rodata_symbol = {.name = ".rdata", .section_number = 2, .storage_class = Z_COFF_SYMBOL_STATIC};
|
||||
coff_append_symbol(out, &rodata_symbol, &strings);
|
||||
}
|
||||
for (size_t i = 0; image && i < image->symbol_len; i++) coff_append_symbol(out, &image->symbols[i], &strings);
|
||||
|
||||
z_coff_append_u32(out, (uint32_t)strings.len + 4);
|
||||
if (strings.data) z_coff_append_bytes(out, strings.data, strings.len);
|
||||
zbuf_free(&strings);
|
||||
}
|
||||
|
||||
static void coff_patch_rel32_to_va(ZBuf *text, size_t patch_offset, uint64_t text_base_va, uint64_t target_va) {
|
||||
int64_t rel = (int64_t)target_va - (int64_t)(text_base_va + patch_offset + 4);
|
||||
z_coff_patch_u32(text, patch_offset, (uint32_t)(int32_t)rel);
|
||||
}
|
||||
|
||||
static void coff_append_import_table(ZBuf *rdata, uint32_t rdata_rva, CoffImportLayout *layout) {
|
||||
static const char *names[Z_COFF_IMPORT_COUNT] = {"ExitProcess", "GetStdHandle", "WriteFile"};
|
||||
z_coff_pad_to(rdata, z_coff_align(rdata->len, 4));
|
||||
uint32_t descriptor_offset = (uint32_t)rdata->len;
|
||||
z_coff_append_zeros(rdata, 40);
|
||||
z_coff_pad_to(rdata, z_coff_align(rdata->len, 8));
|
||||
uint32_t int_offset = (uint32_t)rdata->len;
|
||||
z_coff_append_zeros(rdata, (Z_COFF_IMPORT_COUNT + 1) * 8);
|
||||
z_coff_pad_to(rdata, z_coff_align(rdata->len, 8));
|
||||
uint32_t iat_offset = (uint32_t)rdata->len;
|
||||
z_coff_append_zeros(rdata, (Z_COFF_IMPORT_COUNT + 1) * 8);
|
||||
uint32_t dll_name_offset = (uint32_t)rdata->len;
|
||||
z_coff_append_bytes(rdata, "KERNEL32.dll", strlen("KERNEL32.dll") + 1);
|
||||
|
||||
uint32_t hint_name_offsets[Z_COFF_IMPORT_COUNT];
|
||||
for (unsigned i = 0; i < Z_COFF_IMPORT_COUNT; i++) {
|
||||
z_coff_pad_to(rdata, z_coff_align(rdata->len, 2));
|
||||
hint_name_offsets[i] = (uint32_t)rdata->len;
|
||||
z_coff_append_u16(rdata, 0);
|
||||
z_coff_append_bytes(rdata, names[i], strlen(names[i]) + 1);
|
||||
}
|
||||
|
||||
z_coff_patch_u32(rdata, descriptor_offset + 0, rdata_rva + int_offset);
|
||||
z_coff_patch_u32(rdata, descriptor_offset + 12, rdata_rva + dll_name_offset);
|
||||
z_coff_patch_u32(rdata, descriptor_offset + 16, rdata_rva + iat_offset);
|
||||
for (unsigned i = 0; i < Z_COFF_IMPORT_COUNT; i++) {
|
||||
uint64_t thunk = rdata_rva + hint_name_offsets[i];
|
||||
z_coff_patch_u64(rdata, int_offset + i * 8, thunk);
|
||||
z_coff_patch_u64(rdata, iat_offset + i * 8, thunk);
|
||||
if (layout) layout->iat_offsets[i] = iat_offset + i * 8;
|
||||
}
|
||||
if (layout) {
|
||||
layout->import_directory_rva = rdata_rva + descriptor_offset;
|
||||
layout->import_directory_size = 40;
|
||||
layout->iat_rva = rdata_rva + iat_offset;
|
||||
layout->iat_size = (Z_COFF_IMPORT_COUNT + 1) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void coff_append_pe_data_directory(ZBuf *out, uint32_t rva, uint32_t size) {
|
||||
z_coff_append_u32(out, rva);
|
||||
z_coff_append_u32(out, size);
|
||||
}
|
||||
|
||||
static void coff_append_pe_data_directories(ZBuf *out, const CoffImportLayout *imports) {
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, imports ? imports->import_directory_rva : 0, imports ? imports->import_directory_size : 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, imports ? imports->iat_rva : 0, imports ? imports->iat_size : 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
coff_append_pe_data_directory(out, 0, 0);
|
||||
}
|
||||
|
||||
static void coff_append_pe64_optional_header(ZBuf *out, uint32_t text_raw_size, uint32_t rdata_raw_size, uint32_t text_rva, uint64_t image_base, uint32_t section_alignment, uint32_t file_alignment, uint32_t size_of_image, uint32_t headers_size, const CoffImportLayout *imports) {
|
||||
z_coff_append_u16(out, 0x20b);
|
||||
z_coff_append_u8(out, 0);
|
||||
z_coff_append_u8(out, 1);
|
||||
z_coff_append_u32(out, text_raw_size);
|
||||
z_coff_append_u32(out, rdata_raw_size);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, text_rva);
|
||||
z_coff_append_u32(out, text_rva);
|
||||
z_coff_append_u64(out, image_base);
|
||||
z_coff_append_u32(out, section_alignment);
|
||||
z_coff_append_u32(out, file_alignment);
|
||||
z_coff_append_u16(out, 6);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u16(out, 6);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, size_of_image);
|
||||
z_coff_append_u32(out, headers_size);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u16(out, 3);
|
||||
z_coff_append_u16(out, 0x0100);
|
||||
z_coff_append_u64(out, 0x100000);
|
||||
z_coff_append_u64(out, 0x1000);
|
||||
z_coff_append_u64(out, 0x100000);
|
||||
z_coff_append_u64(out, 0x1000);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, 16);
|
||||
coff_append_pe_data_directories(out, imports);
|
||||
}
|
||||
|
||||
static void coff_append_pe_section_header(ZBuf *out, const char *name, uint32_t virtual_size, uint32_t virtual_address, uint32_t raw_size, uint32_t raw_offset, uint32_t characteristics) {
|
||||
coff_append_name(out, name);
|
||||
z_coff_append_u32(out, virtual_size);
|
||||
z_coff_append_u32(out, virtual_address);
|
||||
z_coff_append_u32(out, raw_size);
|
||||
z_coff_append_u32(out, raw_offset);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u16(out, 0);
|
||||
z_coff_append_u32(out, characteristics);
|
||||
}
|
||||
|
||||
void z_coff_write_pe64_executable(ZBuf *out, const ZCoffExecutableImage *image) {
|
||||
ZBuf empty_text;
|
||||
ZBuf empty_rdata;
|
||||
zbuf_init(&empty_text);
|
||||
zbuf_init(&empty_rdata);
|
||||
ZBuf *text = image && image->text ? image->text : &empty_text;
|
||||
ZBuf *rdata = image && image->rdata ? image->rdata : &empty_rdata;
|
||||
uint64_t image_base = image && image->image_base ? image->image_base : 0x140000000ull;
|
||||
uint32_t section_alignment = image && image->section_alignment ? image->section_alignment : 0x1000;
|
||||
uint32_t file_alignment = image && image->file_alignment ? image->file_alignment : 0x200;
|
||||
uint32_t dos_header_size = 0x80;
|
||||
uint16_t section_count = 2;
|
||||
uint32_t optional_header_size = 240;
|
||||
uint32_t pe_header_offset = dos_header_size;
|
||||
uint32_t headers_unaligned = pe_header_offset + 4 + 20 + optional_header_size + section_count * 40;
|
||||
uint32_t headers_size = (uint32_t)z_coff_align(headers_unaligned, file_alignment);
|
||||
uint32_t text_rva = section_alignment;
|
||||
uint32_t text_raw_offset = headers_size;
|
||||
uint32_t text_raw_size = (uint32_t)z_coff_align(text->len, file_alignment);
|
||||
uint32_t rdata_rva = (uint32_t)z_coff_align(text_rva + text->len, section_alignment);
|
||||
CoffImportLayout imports = {0};
|
||||
coff_append_import_table(rdata, rdata_rva, &imports);
|
||||
uint32_t rdata_raw_offset = text_raw_offset + text_raw_size;
|
||||
uint32_t rdata_raw_size = (uint32_t)z_coff_align(rdata->len, file_alignment);
|
||||
uint32_t size_of_image = (uint32_t)z_coff_align(rdata_rva + rdata->len, section_alignment);
|
||||
|
||||
for (size_t i = 0; image && i < image->rodata_patch_len; i++) {
|
||||
const ZCoffImageDataPatch *patch = &image->rodata_patches[i];
|
||||
uint64_t addr = image_base + rdata_rva + (patch->data_offset - image->rodata_base_offset);
|
||||
z_coff_patch_u64(text, patch->patch_offset, addr);
|
||||
}
|
||||
for (size_t i = 0; image && i < image->import_patch_len; i++) {
|
||||
const ZCoffImportPatch *patch = &image->import_patches[i];
|
||||
if (patch->import_index >= Z_COFF_IMPORT_COUNT) continue;
|
||||
uint64_t target = image_base + rdata_rva + imports.iat_offsets[patch->import_index];
|
||||
if (image && image->machine == Z_COFF_MACHINE_ARM64) z_coff_patch_u64(text, patch->patch_offset, target);
|
||||
else coff_patch_rel32_to_va(text, patch->patch_offset, image_base + text_rva, target);
|
||||
}
|
||||
|
||||
zbuf_init(out);
|
||||
z_coff_append_u8(out, 'M');
|
||||
z_coff_append_u8(out, 'Z');
|
||||
z_coff_append_zeros(out, 0x3a);
|
||||
z_coff_append_u32(out, pe_header_offset);
|
||||
z_coff_pad_to(out, dos_header_size);
|
||||
|
||||
z_coff_append_u8(out, 'P');
|
||||
z_coff_append_u8(out, 'E');
|
||||
z_coff_append_u8(out, 0);
|
||||
z_coff_append_u8(out, 0);
|
||||
z_coff_append_u16(out, image ? (uint16_t)image->machine : 0);
|
||||
z_coff_append_u16(out, section_count);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u32(out, 0);
|
||||
z_coff_append_u16(out, optional_header_size);
|
||||
z_coff_append_u16(out, 0x0022);
|
||||
|
||||
coff_append_pe64_optional_header(out, text_raw_size, rdata_raw_size, text_rva, image_base, section_alignment, file_alignment, size_of_image, headers_size, &imports);
|
||||
coff_append_pe_section_header(out, ".text", (uint32_t)text->len, text_rva, text_raw_size, text_raw_offset, 0x60000020u);
|
||||
coff_append_pe_section_header(out, ".rdata", (uint32_t)rdata->len, rdata_rva, rdata_raw_size, rdata_raw_offset, 0x40000040u);
|
||||
|
||||
z_coff_pad_to(out, headers_size);
|
||||
if (text->data) z_coff_append_bytes(out, text->data, text->len);
|
||||
z_coff_pad_to(out, text_raw_offset + text_raw_size);
|
||||
if (rdata->data) z_coff_append_bytes(out, rdata->data, rdata->len);
|
||||
z_coff_pad_to(out, rdata_raw_offset + rdata_raw_size);
|
||||
zbuf_free(&empty_rdata);
|
||||
zbuf_free(&empty_text);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#ifndef ZERO_C_COFF_FORMAT_H
|
||||
#define ZERO_C_COFF_FORMAT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
Z_COFF_MACHINE_ARM64 = 0xaa64,
|
||||
Z_COFF_MACHINE_AMD64 = 0x8664
|
||||
} ZCoffMachine;
|
||||
|
||||
enum {
|
||||
Z_COFF_RELOC_AMD64_ADDR64 = 0x0001,
|
||||
Z_COFF_RELOC_AMD64_REL32 = 0x0004,
|
||||
Z_COFF_RELOC_ARM64_BRANCH26 = 0x0003,
|
||||
Z_COFF_RELOC_ARM64_ADDR64 = 0x000e
|
||||
};
|
||||
|
||||
enum {
|
||||
Z_COFF_SYMBOL_EXTERNAL = 2,
|
||||
Z_COFF_SYMBOL_STATIC = 3
|
||||
};
|
||||
|
||||
enum {
|
||||
Z_COFF_IMPORT_EXIT_PROCESS = 0,
|
||||
Z_COFF_IMPORT_GET_STD_HANDLE = 1,
|
||||
Z_COFF_IMPORT_WRITE_FILE = 2,
|
||||
Z_COFF_IMPORT_COUNT = 3
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
uint32_t value;
|
||||
uint16_t section_number;
|
||||
uint16_t type;
|
||||
unsigned char storage_class;
|
||||
} ZCoffSymbol;
|
||||
|
||||
typedef struct {
|
||||
ZCoffMachine machine;
|
||||
const ZBuf *text;
|
||||
const ZBuf *rodata;
|
||||
const ZBuf *text_relocs;
|
||||
uint16_t text_reloc_count;
|
||||
const ZCoffSymbol *symbols;
|
||||
size_t symbol_len;
|
||||
} ZCoffObjectImage;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned data_offset;
|
||||
} ZCoffImageDataPatch;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned import_index;
|
||||
} ZCoffImportPatch;
|
||||
|
||||
typedef struct {
|
||||
ZCoffMachine machine;
|
||||
uint64_t image_base;
|
||||
uint32_t section_alignment;
|
||||
uint32_t file_alignment;
|
||||
ZBuf *text;
|
||||
ZBuf *rdata;
|
||||
unsigned rodata_base_offset;
|
||||
const ZCoffImageDataPatch *rodata_patches;
|
||||
size_t rodata_patch_len;
|
||||
const ZCoffImportPatch *import_patches;
|
||||
size_t import_patch_len;
|
||||
} ZCoffExecutableImage;
|
||||
|
||||
size_t z_coff_align(size_t value, size_t alignment);
|
||||
void z_coff_append_u8(ZBuf *buf, unsigned value);
|
||||
void z_coff_append_u16(ZBuf *buf, uint16_t value);
|
||||
void z_coff_append_u32(ZBuf *buf, uint32_t value);
|
||||
void z_coff_append_u64(ZBuf *buf, uint64_t value);
|
||||
void z_coff_append_bytes(ZBuf *buf, const char *bytes, size_t len);
|
||||
void z_coff_append_zeros(ZBuf *buf, size_t len);
|
||||
void z_coff_pad_to(ZBuf *buf, size_t offset);
|
||||
void z_coff_patch_u32(ZBuf *buf, size_t offset, uint32_t value);
|
||||
void z_coff_patch_u64(ZBuf *buf, size_t offset, uint64_t value);
|
||||
void z_coff_append_reloc(ZBuf *buf, uint32_t offset, uint32_t symbol_index, uint16_t type);
|
||||
void z_coff_append_reloc_amd64(ZBuf *buf, uint32_t offset, uint32_t symbol_index, uint16_t type);
|
||||
void z_coff_write_object(ZBuf *out, const ZCoffObjectImage *image);
|
||||
void z_coff_write_pe64_executable(ZBuf *out, const ZCoffExecutableImage *image);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,122 @@
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
bool z_direct_trap_branches_record(ZDirectTrapBranchList *list, size_t patch_offset) {
|
||||
if (!list) return false;
|
||||
if (list->len == list->cap) {
|
||||
size_t cap = list->cap ? list->cap * 2 : 8;
|
||||
size_t *items = realloc(list->items, cap * sizeof(size_t));
|
||||
if (!items) return false;
|
||||
list->items = items;
|
||||
list->cap = cap;
|
||||
}
|
||||
list->items[list->len++] = patch_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
void z_direct_trap_branches_free(ZDirectTrapBranchList *lists, size_t count) {
|
||||
for (size_t i = 0; lists && i < count; i++) {
|
||||
free(lists[i].items);
|
||||
lists[i] = (ZDirectTrapBranchList){0};
|
||||
}
|
||||
}
|
||||
|
||||
const char *z_direct_trap_message(ZDirectTrapKind kind) {
|
||||
switch (kind) {
|
||||
case Z_DIRECT_TRAP_INDEX_BOUNDS: return "trap: index out of bounds\n";
|
||||
case Z_DIRECT_TRAP_VALUE_BOUNDS: return "trap: value out of bounds\n";
|
||||
case Z_DIRECT_TRAP_WRITE_FAILED: return "trap: write failed\n";
|
||||
case Z_DIRECT_TRAP_KIND_COUNT: break;
|
||||
}
|
||||
return "trap\n";
|
||||
}
|
||||
|
||||
bool z_direct_loop_frame_add_break(ZDirectLoopFrame *frame, size_t patch_offset) {
|
||||
if (!frame) return false;
|
||||
if (frame->break_len == frame->break_cap) {
|
||||
size_t cap = frame->break_cap ? frame->break_cap * 2 : 4;
|
||||
size_t *items = realloc(frame->break_patches, cap * sizeof(size_t));
|
||||
if (!items) return false;
|
||||
frame->break_patches = items;
|
||||
frame->break_cap = cap;
|
||||
}
|
||||
frame->break_patches[frame->break_len++] = patch_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_direct_detect_fill_run(const IrFunction *fun, const IrInstr *instrs, size_t len, size_t start, size_t min_run, ZDirectFillRun *out) {
|
||||
if (!fun || !instrs || start >= len) return false;
|
||||
const IrInstr *first = &instrs[start];
|
||||
if (first->kind != IR_INSTR_INDEX_STORE) return false;
|
||||
if (first->array_index >= fun->local_len) return false;
|
||||
const IrLocal *local = &fun->locals[first->array_index];
|
||||
// Only plain fixed-array locals with a scalar element are foldable. Byte
|
||||
// views are pointer-indirected and excluded.
|
||||
if (!local->is_array || local->type == IR_TYPE_BYTE_VIEW) return false;
|
||||
if (!first->index || first->index->kind != IR_VALUE_INT || first->index->int_value != 0) return false;
|
||||
if (!first->value || first->value->kind != IR_VALUE_INT) return false;
|
||||
IrTypeKind element_type = local->element_type;
|
||||
if (element_type == IR_TYPE_UNSUPPORTED) return false;
|
||||
if (first->value->type != element_type) return false;
|
||||
unsigned long long fill_value = first->value->int_value;
|
||||
size_t run = 1;
|
||||
while (start + run < len) {
|
||||
const IrInstr *next = &instrs[start + run];
|
||||
if (next->kind != IR_INSTR_INDEX_STORE) break;
|
||||
if (next->array_index != first->array_index) break;
|
||||
if (!next->index || next->index->kind != IR_VALUE_INT || next->index->int_value != run) break;
|
||||
if (!next->value || next->value->kind != IR_VALUE_INT) break;
|
||||
if (next->value->type != element_type || next->value->int_value != fill_value) break;
|
||||
run++;
|
||||
}
|
||||
if (run < min_run) return false;
|
||||
if (out) {
|
||||
out->array_index = first->array_index;
|
||||
out->count = run;
|
||||
out->fill_value = fill_value;
|
||||
out->element_type = element_type;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_direct_fill_run_from_instr(const IrFunction *fun, const IrInstr *instr, ZDirectFillRun *out) {
|
||||
if (!fun || !instr || instr->kind != IR_INSTR_ARRAY_FILL || instr->array_index >= fun->local_len) return false;
|
||||
const IrLocal *local = &fun->locals[instr->array_index];
|
||||
if (!local->is_array || local->array_len == 0 || local->type == IR_TYPE_BYTE_VIEW || local->element_type == IR_TYPE_UNSUPPORTED) return false;
|
||||
if (!instr->value || instr->value->type != local->element_type) return false;
|
||||
if (instr->value->kind != IR_VALUE_INT && instr->value->kind != IR_VALUE_BOOL) return false;
|
||||
if (out) {
|
||||
out->array_index = instr->array_index;
|
||||
out->count = local->array_len;
|
||||
out->fill_value = instr->value->int_value;
|
||||
out->element_type = local->element_type;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_emit_direct_object_from_ir(ZDirectBackend backend, const IrProgram *program, ZBuf *out, ZDiag *diag) {
|
||||
switch (backend) {
|
||||
case Z_DIRECT_BACKEND_ELF64: return z_emit_elf64_object_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_ELF_AARCH64: return z_emit_elf_aarch64_object_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_MACHO64: return z_emit_macho64_object_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_MACHO_X64: return z_emit_macho_x64_object_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_COFF_X64: return z_emit_coff_x64_object_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_COFF_AARCH64: return z_emit_coff_aarch64_object_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_NONE: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_emit_direct_executable_from_ir(ZDirectBackend backend, const IrProgram *program, ZBuf *out, ZDiag *diag) {
|
||||
switch (backend) {
|
||||
case Z_DIRECT_BACKEND_ELF64: return z_emit_elf64_exe_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_ELF_AARCH64: return z_emit_elf_aarch64_exe_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_MACHO64: return z_emit_macho64_exe_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_MACHO_X64: return z_emit_macho_x64_exe_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_COFF_X64: return z_emit_coff_x64_exe_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_COFF_AARCH64: return z_emit_coff_aarch64_exe_from_ir(program, out, diag);
|
||||
case Z_DIRECT_BACKEND_NONE: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef ZERO_C_DIRECT_EMIT_H
|
||||
#define ZERO_C_DIRECT_EMIT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static inline bool z_direct_exe_reject_c_import_calls(const IrProgram *program, ZDiag *diag, const char *backend_name) {
|
||||
if (!program || program->direct_c_import_call_count == 0) return true;
|
||||
const char *symbol = (program->external_function_len > 0 && program->external_functions[0].symbol) ? program->external_functions[0].symbol : "extern C call";
|
||||
if (diag) {
|
||||
diag->code = 4004;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "direct %s executable backend requires object emission and an explicit link step for extern C calls", backend_name ? backend_name : "native");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "direct executable without extern C calls");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", symbol);
|
||||
snprintf(diag->help, sizeof(diag->help), "emit an object and link it with the required C libraries");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "zero.h"
|
||||
#include "aarch64_direct.h"
|
||||
|
||||
size_t z_direct_target_stack_bytes(const ZTargetInfo *target, const IrProgram *program) {
|
||||
if (!program) return 0;
|
||||
if (z_direct_object_backend(target) == Z_DIRECT_BACKEND_MACHO64 ||
|
||||
z_direct_exe_backend(target) == Z_DIRECT_BACKEND_MACHO64) {
|
||||
return z_macho64_stack_bytes_from_ir(program);
|
||||
}
|
||||
if (z_direct_object_backend(target) == Z_DIRECT_BACKEND_ELF_AARCH64 ||
|
||||
z_direct_exe_backend(target) == Z_DIRECT_BACKEND_ELF_AARCH64 ||
|
||||
z_direct_object_backend(target) == Z_DIRECT_BACKEND_COFF_AARCH64 ||
|
||||
z_direct_exe_backend(target) == Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_aarch64_direct_stack_bytes_from_ir(program);
|
||||
}
|
||||
return program->direct_stack_bytes;
|
||||
}
|
||||
|
||||
size_t z_direct_target_max_frame_bytes(const ZTargetInfo *target, const IrProgram *program) {
|
||||
if (!program) return 0;
|
||||
if (z_direct_object_backend(target) == Z_DIRECT_BACKEND_MACHO64 ||
|
||||
z_direct_exe_backend(target) == Z_DIRECT_BACKEND_MACHO64) {
|
||||
return z_macho64_max_frame_bytes_from_ir(program);
|
||||
}
|
||||
if (z_direct_object_backend(target) == Z_DIRECT_BACKEND_ELF_AARCH64 ||
|
||||
z_direct_exe_backend(target) == Z_DIRECT_BACKEND_ELF_AARCH64 ||
|
||||
z_direct_object_backend(target) == Z_DIRECT_BACKEND_COFF_AARCH64 ||
|
||||
z_direct_exe_backend(target) == Z_DIRECT_BACKEND_COFF_AARCH64) {
|
||||
return z_aarch64_direct_max_frame_bytes_from_ir(program);
|
||||
}
|
||||
return program->direct_max_frame_bytes;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "zero.h"
|
||||
|
||||
static bool ir_value_needs_zero_runtime_object(const IrValue *value) {
|
||||
if (!value) return false;
|
||||
if (value->kind == IR_VALUE_JSON_PARSE_BYTES ||
|
||||
value->kind == IR_VALUE_JSON_VALIDATE_BYTES ||
|
||||
value->kind == IR_VALUE_JSON_STREAM_TOKENS_BYTES ||
|
||||
value->kind == IR_VALUE_JSON_DIAGNOSTIC_BYTES ||
|
||||
value->kind == IR_VALUE_JSON_FIELD ||
|
||||
value->kind == IR_VALUE_JSON_LOOKUP_SCALAR ||
|
||||
value->kind == IR_VALUE_JSON_STRING_DECODE ||
|
||||
value->kind == IR_VALUE_JSON_STRING_FIELD ||
|
||||
value->kind == IR_VALUE_JSON_WRITE_STRING ||
|
||||
value->kind == IR_VALUE_JSON_WRITE_RUNTIME ||
|
||||
value->kind == IR_VALUE_ASCII_RUNTIME ||
|
||||
value->kind == IR_VALUE_TEXT_RUNTIME ||
|
||||
value->kind == IR_VALUE_TIME_RUNTIME ||
|
||||
value->kind == IR_VALUE_TERM_RUNTIME ||
|
||||
value->kind == IR_VALUE_TIME_WALL_SECONDS ||
|
||||
value->kind == IR_VALUE_TIME_MONOTONIC ||
|
||||
value->kind == IR_VALUE_RAND_ENTROPY_U32 ||
|
||||
value->kind == IR_VALUE_MATH_RUNTIME ||
|
||||
value->kind == IR_VALUE_SEARCH_RUNTIME ||
|
||||
value->kind == IR_VALUE_SORT_RUNTIME ||
|
||||
value->kind == IR_VALUE_STR_CONTAINS ||
|
||||
value->kind == IR_VALUE_STR_RUNTIME ||
|
||||
value->kind == IR_VALUE_ENV_GET ||
|
||||
value->kind == IR_VALUE_ARGS_LEN ||
|
||||
value->kind == IR_VALUE_ARGS_GET ||
|
||||
value->kind == IR_VALUE_ARGS_EQ ||
|
||||
value->kind == IR_VALUE_ARGS_GET_OR ||
|
||||
value->kind == IR_VALUE_ARGS_FIND ||
|
||||
value->kind == IR_VALUE_ARGS_CONTAINS ||
|
||||
value->kind == IR_VALUE_ARGS_VALUE_AFTER ||
|
||||
value->kind == IR_VALUE_ARGS_VALUE_AFTER_OR ||
|
||||
value->kind == IR_VALUE_ARGS_VALUE_AFTER_PARSE_U32 ||
|
||||
value->kind == IR_VALUE_PARSE_RUNTIME ||
|
||||
value->kind == IR_VALUE_PARSE_I32 ||
|
||||
value->kind == IR_VALUE_PARSE_U32 ||
|
||||
value->kind == IR_VALUE_ARGS_PARSE_U32 ||
|
||||
value->kind == IR_VALUE_FMT_BOOL ||
|
||||
value->kind == IR_VALUE_FMT_HEX_U32 ||
|
||||
value->kind == IR_VALUE_FMT_I32 ||
|
||||
value->kind == IR_VALUE_FMT_U32 ||
|
||||
value->kind == IR_VALUE_FMT_USIZE ||
|
||||
value->kind == IR_VALUE_FS_READ_BYTES_PATH ||
|
||||
value->kind == IR_VALUE_FS_READ_BYTES_AT_PATH ||
|
||||
value->kind == IR_VALUE_FS_WRITE_BYTES_PATH ||
|
||||
value->kind == IR_VALUE_FS_APPEND_BYTES_PATH ||
|
||||
value->kind == IR_VALUE_FS_EXISTS ||
|
||||
value->kind == IR_VALUE_FS_REMOVE ||
|
||||
value->kind == IR_VALUE_FS_RENAME ||
|
||||
value->kind == IR_VALUE_FS_MAKE_DIR ||
|
||||
value->kind == IR_VALUE_FS_REMOVE_DIR ||
|
||||
value->kind == IR_VALUE_FS_IS_DIR ||
|
||||
value->kind == IR_VALUE_FS_DIR_ENTRY_COUNT ||
|
||||
value->kind == IR_VALUE_FS_DIR_ENTRY_NAME ||
|
||||
value->kind == IR_VALUE_FS_ATOMIC_WRITE ||
|
||||
value->kind == IR_VALUE_PROC_SPAWN_INHERIT ||
|
||||
value->kind == IR_VALUE_PROC_CAPTURE ||
|
||||
value->kind == IR_VALUE_PROC_CAPTURE_FILES ||
|
||||
value->kind == IR_VALUE_PROC_CHILD_SPAWN ||
|
||||
value->kind == IR_VALUE_PROC_CHILD_OP ||
|
||||
value->kind == IR_VALUE_PROC_CHILD_IO ||
|
||||
value->kind == IR_VALUE_PROC_PTY_RESIZE ||
|
||||
value->kind == IR_VALUE_HTTP_FETCH ||
|
||||
value->kind == IR_VALUE_HTTP_RESULT_OK ||
|
||||
value->kind == IR_VALUE_HTTP_RESULT_STATUS ||
|
||||
value->kind == IR_VALUE_HTTP_RESULT_BODY_LEN ||
|
||||
value->kind == IR_VALUE_HTTP_RESULT_ERROR ||
|
||||
value->kind == IR_VALUE_HTTP_RESPONSE_LEN ||
|
||||
value->kind == IR_VALUE_HTTP_RESPONSE_HEADERS_LEN ||
|
||||
value->kind == IR_VALUE_HTTP_RESPONSE_BODY_OFFSET ||
|
||||
value->kind == IR_VALUE_HTTP_HEADER_VALUE ||
|
||||
value->kind == IR_VALUE_HTTP_HEADER_FOUND ||
|
||||
value->kind == IR_VALUE_HTTP_HEADER_OFFSET ||
|
||||
value->kind == IR_VALUE_HTTP_HEADER_LEN ||
|
||||
value->kind == IR_VALUE_HTTP_WRITE_JSON_RESPONSE ||
|
||||
value->kind == IR_VALUE_HTTP_REQUEST_METHOD_NAME ||
|
||||
value->kind == IR_VALUE_HTTP_REQUEST_MATCHES ||
|
||||
value->kind == IR_VALUE_HTTP_REQUEST_BODY_WITHIN ||
|
||||
value->kind == IR_VALUE_HTTP_REQUEST_PATH) return true;
|
||||
if (ir_value_needs_zero_runtime_object(value->index) ||
|
||||
ir_value_needs_zero_runtime_object(value->left) ||
|
||||
ir_value_needs_zero_runtime_object(value->right)) {
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i < value->arg_len; i++) {
|
||||
if (ir_value_needs_zero_runtime_object(value->args[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ir_instrs_need_zero_runtime_object(const IrInstr *instrs, size_t len) {
|
||||
for (size_t i = 0; instrs && i < len; i++) {
|
||||
const IrInstr *instr = &instrs[i];
|
||||
if (ir_value_needs_zero_runtime_object(instr->value) ||
|
||||
ir_value_needs_zero_runtime_object(instr->index) ||
|
||||
ir_instrs_need_zero_runtime_object(instr->then_instrs, instr->then_len) ||
|
||||
ir_instrs_need_zero_runtime_object(instr->else_instrs, instr->else_len)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_ir_needs_zero_runtime_object(const IrProgram *ir) {
|
||||
if (!ir) return false;
|
||||
for (size_t i = 0; i < ir->function_len; i++) {
|
||||
if (ir_instrs_need_zero_runtime_object(ir->functions[i].instrs, ir->functions[i].instr_len)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "elf_emit_state.h"
|
||||
#include "elf_format.h"
|
||||
#include "x64_emit.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *const runtime_helper_symbols[ELF_RUNTIME_HELPER_COUNT] = {
|
||||
"zero_json_parse_bytes",
|
||||
"zero_json_diagnostic",
|
||||
"zero_json_field",
|
||||
"zero_json_lookup_scalar",
|
||||
"zero_json_string_decode",
|
||||
"zero_json_string_field",
|
||||
"zero_json_write_string",
|
||||
"zero_json_write_field_raw",
|
||||
"zero_json_write_field_string",
|
||||
"zero_json_write_field_u32",
|
||||
"zero_json_write_field_bool",
|
||||
"zero_json_write_object1_string",
|
||||
"zero_json_write_object1_u32",
|
||||
"zero_json_write_object1_bool",
|
||||
"zero_json_write_object2_fields",
|
||||
"zero_json_write_object2_string_field",
|
||||
"zero_json_write_object2_u32_field",
|
||||
"zero_json_write_object2_bool_field",
|
||||
"zero_json_write_array2_strings",
|
||||
"zero_json_write_array2_u32",
|
||||
"zero_json_write_array2_bools",
|
||||
"zero_http_fetch_result",
|
||||
"zero_http_result_ok",
|
||||
"zero_http_result_status",
|
||||
"zero_http_result_body_len",
|
||||
"zero_http_result_error",
|
||||
"zero_http_response_len",
|
||||
"zero_http_response_headers_len",
|
||||
"zero_http_response_body_offset",
|
||||
"zero_http_header_value",
|
||||
"zero_http_header_found",
|
||||
"zero_http_header_offset",
|
||||
"zero_http_header_len",
|
||||
"zero_http_write_json_response",
|
||||
"zero_http_request_method_name",
|
||||
"zero_http_request_path",
|
||||
"zero_http_request_matches",
|
||||
"zero_http_request_body_within",
|
||||
"zero_ascii_op",
|
||||
"zero_text_op",
|
||||
"zero_parse_op",
|
||||
"zero_parse_usize",
|
||||
"zero_time_op",
|
||||
"zero_term_op",
|
||||
"zero_term_read_input",
|
||||
"zero_math_op",
|
||||
"zero_math_usize_op",
|
||||
"zero_search_op",
|
||||
"zero_sort_op",
|
||||
"zero_sort_is_sorted_op",
|
||||
"zero_str_contains",
|
||||
"zero_str_buffer_op",
|
||||
"zero_str_concat",
|
||||
"zero_str_repeat",
|
||||
"zero_str_trim_op",
|
||||
"zero_str_pair_op",
|
||||
"zero_str_count_byte",
|
||||
"zero_str_word_count_ascii",
|
||||
"zero_crypto_digest",
|
||||
"zero_crypto_hmac_sha256",
|
||||
"zero_crypto_hmac_sha256_hex",
|
||||
"zero_fs_dir_entry_name",
|
||||
"zero_args_find",
|
||||
"zero_parse_i32",
|
||||
"zero_parse_u32",
|
||||
"zero_fmt_bool",
|
||||
"zero_fmt_hex_lower_u32",
|
||||
"zero_fmt_i32",
|
||||
"zero_fmt_u32",
|
||||
"zero_fmt_usize",
|
||||
"zero_proc_spawn_inherit",
|
||||
"zero_proc_spawn_inherit_args",
|
||||
"zero_proc_capture",
|
||||
"zero_proc_capture_args",
|
||||
"zero_proc_capture_files",
|
||||
"zero_proc_capture_files_args",
|
||||
"zero_proc_spawn_child",
|
||||
"zero_proc_spawn_child_in",
|
||||
"zero_proc_spawn_child_in_env",
|
||||
"zero_proc_spawn_child_args",
|
||||
"zero_proc_child_op",
|
||||
"zero_proc_child_io",
|
||||
"zero_pty_spawn",
|
||||
"zero_pty_spawn_in",
|
||||
"zero_pty_spawn_in_env",
|
||||
"zero_pty_spawn_args",
|
||||
"zero_pty_resize",
|
||||
};
|
||||
|
||||
static bool elf_emit_state_diag(ZDiag *diag, const char *message, int line, int column, const char *actual) {
|
||||
if (diag) {
|
||||
diag->code = 4004;
|
||||
diag->line = line > 0 ? line : 1;
|
||||
diag->column = column > 0 ? column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message);
|
||||
snprintf(diag->expected, sizeof(diag->expected), "direct ELF64 object MVP subset");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "unsupported construct");
|
||||
snprintf(diag->help, sizeof(diag->help), "choose a supported direct target or restrict this program to exported primitive integer arithmetic functions");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool elf_runtime_helper_valid(ElfRuntimeHelper helper) {
|
||||
return helper >= 0 && helper < ELF_RUNTIME_HELPER_COUNT;
|
||||
}
|
||||
|
||||
const char *z_elf_runtime_helper_symbol(ElfRuntimeHelper helper) {
|
||||
if (!elf_runtime_helper_valid(helper)) return "";
|
||||
return runtime_helper_symbols[helper];
|
||||
}
|
||||
|
||||
void z_elf_emit_context_free(ElfEmitContext *ctx) {
|
||||
if (!ctx) return;
|
||||
free(ctx->call_patches);
|
||||
free(ctx->rodata_patches);
|
||||
for (unsigned i = 0; i < ELF_RUNTIME_HELPER_COUNT; i++) {
|
||||
free(ctx->runtime_patches[i].items);
|
||||
}
|
||||
z_direct_trap_branches_free(ctx->trap_branches, Z_DIRECT_TRAP_KIND_COUNT);
|
||||
}
|
||||
|
||||
bool z_elf_record_call_patch(ElfEmitContext *ctx, size_t patch_offset, unsigned callee_index, ZDiag *diag, const IrValue *value) {
|
||||
if (!ctx || (((!value) || !value->external_call) && callee_index >= ctx->function_count)) {
|
||||
return elf_emit_state_diag(diag, "direct ELF64 call target index is out of range", value ? value->line : 1, value ? value->column : 1, "invalid callee");
|
||||
}
|
||||
if (ctx->call_patch_len + 1 > ctx->call_patch_cap) {
|
||||
ctx->call_patch_cap = z_grow_capacity(ctx->call_patch_cap, ctx->call_patch_len + 1, 8);
|
||||
ctx->call_patches = z_checked_reallocarray(ctx->call_patches, ctx->call_patch_cap, sizeof(ElfCallPatch));
|
||||
}
|
||||
ctx->call_patches[ctx->call_patch_len++] = (ElfCallPatch){
|
||||
.patch_offset = patch_offset,
|
||||
.callee_index = callee_index,
|
||||
.external_index = value ? value->external_index : 0,
|
||||
.external_call = value && value->external_call
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_elf_record_rodata_patch(ElfEmitContext *ctx, size_t patch_offset, unsigned data_offset, ZDiag *diag, const IrValue *value) {
|
||||
if (!ctx) return elf_emit_state_diag(diag, "direct ELF64 readonly data patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (ctx->rodata_patch_len + 1 > ctx->rodata_patch_cap) {
|
||||
ctx->rodata_patch_cap = z_grow_capacity(ctx->rodata_patch_cap, ctx->rodata_patch_len + 1, 8);
|
||||
ctx->rodata_patches = z_checked_reallocarray(ctx->rodata_patches, ctx->rodata_patch_cap, sizeof(ElfRodataPatch));
|
||||
}
|
||||
ctx->rodata_patches[ctx->rodata_patch_len++] = (ElfRodataPatch){.patch_offset = patch_offset, .data_offset = data_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_elf_record_value_runtime_patch(ElfEmitContext *ctx, ElfRuntimeHelper helper, size_t patch_offset, ZDiag *diag, const IrValue *value) {
|
||||
if (!ctx || !elf_runtime_helper_valid(helper)) {
|
||||
return elf_emit_state_diag(diag, "direct ELF64 runtime patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
}
|
||||
ElfPatchList *list = &ctx->runtime_patches[helper];
|
||||
if (list->len + 1 > list->cap) {
|
||||
list->cap = z_grow_capacity(list->cap, list->len + 1, 4);
|
||||
list->items = z_checked_reallocarray(list->items, list->cap, sizeof(ElfPatch));
|
||||
}
|
||||
list->items[list->len++] = (ElfPatch){.patch_offset = patch_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t z_elf_runtime_patch_count(const ElfEmitContext *ctx, ElfRuntimeHelper helper) {
|
||||
if (!ctx || !elf_runtime_helper_valid(helper)) return 0;
|
||||
return ctx->runtime_patches[helper].len;
|
||||
}
|
||||
|
||||
bool z_elf_has_runtime_patches(const ElfEmitContext *ctx) {
|
||||
if (!ctx) return false;
|
||||
for (unsigned i = 0; i < ELF_RUNTIME_HELPER_COUNT; i++) {
|
||||
if (ctx->runtime_patches[i].len > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void z_elf_patch_call_patches(ZBuf *code, const ElfEmitContext *ctx) {
|
||||
for (size_t i = 0; ctx && i < ctx->call_patch_len; i++) {
|
||||
const ElfCallPatch *patch = &ctx->call_patches[i];
|
||||
if (patch->external_call) continue;
|
||||
z_x64_patch_rel32(code, patch->patch_offset, ctx->function_offsets[patch->callee_index]);
|
||||
}
|
||||
}
|
||||
|
||||
void z_elf_patch_rodata_patches(ZBuf *code, const ElfEmitContext *ctx) {
|
||||
for (size_t i = 0; ctx && i < ctx->rodata_patch_len; i++) {
|
||||
const ElfRodataPatch *patch = &ctx->rodata_patches[i];
|
||||
uint64_t addr = ctx->rodata_addr + (patch->data_offset - ctx->rodata_base_offset);
|
||||
for (unsigned b = 0; b < 8; b++) {
|
||||
code->data[patch->patch_offset + b] = (char)((addr >> (8 * b)) & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void z_elf_append_rodata_relocations(ZBuf *rela_text, const ElfEmitContext *ctx, uint32_t rodata_symbol) {
|
||||
for (size_t i = 0; ctx && i < ctx->rodata_patch_len; i++) {
|
||||
z_elf_append_rela(rela_text, ctx->rodata_patches[i].patch_offset, rodata_symbol, 1, ctx->rodata_patches[i].data_offset - ctx->rodata_base_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void z_elf_append_external_call_relocations(ZBuf *rela_text, const ElfEmitContext *ctx, uint32_t external_symbol_base) {
|
||||
for (size_t i = 0; ctx && i < ctx->call_patch_len; i++) {
|
||||
const ElfCallPatch *patch = &ctx->call_patches[i];
|
||||
if (!patch->external_call) continue;
|
||||
z_elf_append_rela(rela_text, patch->patch_offset, external_symbol_base + patch->external_index, 4, -4);
|
||||
}
|
||||
}
|
||||
|
||||
void z_elf_append_runtime_relocations(ZBuf *rela_text, const ElfEmitContext *ctx, ElfRuntimeHelper helper, uint32_t runtime_symbol) {
|
||||
if (!ctx || !elf_runtime_helper_valid(helper)) return;
|
||||
const ElfPatchList *patches = &ctx->runtime_patches[helper];
|
||||
for (size_t i = 0; i < patches->len; i++) {
|
||||
z_elf_append_rela(rela_text, patches->items[i].patch_offset, runtime_symbol, 4, -4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#ifndef ZERO_C_ELF_EMIT_STATE_H
|
||||
#define ZERO_C_ELF_EMIT_STATE_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
ELF_RUNTIME_JSON_PARSE_BYTES,
|
||||
ELF_RUNTIME_JSON_DIAGNOSTIC,
|
||||
ELF_RUNTIME_JSON_FIELD,
|
||||
ELF_RUNTIME_JSON_LOOKUP_SCALAR,
|
||||
ELF_RUNTIME_JSON_STRING_DECODE,
|
||||
ELF_RUNTIME_JSON_STRING_FIELD,
|
||||
ELF_RUNTIME_JSON_WRITE_STRING,
|
||||
ELF_RUNTIME_JSON_WRITE_FIELD_RAW,
|
||||
ELF_RUNTIME_JSON_WRITE_FIELD_STRING,
|
||||
ELF_RUNTIME_JSON_WRITE_FIELD_U32,
|
||||
ELF_RUNTIME_JSON_WRITE_FIELD_BOOL,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT1_STRING,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT1_U32,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT1_BOOL,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT2_FIELDS,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT2_STRING_FIELD,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT2_U32_FIELD,
|
||||
ELF_RUNTIME_JSON_WRITE_OBJECT2_BOOL_FIELD,
|
||||
ELF_RUNTIME_JSON_WRITE_ARRAY2_STRINGS,
|
||||
ELF_RUNTIME_JSON_WRITE_ARRAY2_U32,
|
||||
ELF_RUNTIME_JSON_WRITE_ARRAY2_BOOLS,
|
||||
ELF_RUNTIME_HTTP_FETCH,
|
||||
ELF_RUNTIME_HTTP_RESULT_OK,
|
||||
ELF_RUNTIME_HTTP_RESULT_STATUS,
|
||||
ELF_RUNTIME_HTTP_RESULT_BODY_LEN,
|
||||
ELF_RUNTIME_HTTP_RESULT_ERROR,
|
||||
ELF_RUNTIME_HTTP_RESPONSE_LEN,
|
||||
ELF_RUNTIME_HTTP_RESPONSE_HEADERS_LEN,
|
||||
ELF_RUNTIME_HTTP_RESPONSE_BODY_OFFSET,
|
||||
ELF_RUNTIME_HTTP_HEADER_VALUE,
|
||||
ELF_RUNTIME_HTTP_HEADER_FOUND,
|
||||
ELF_RUNTIME_HTTP_HEADER_OFFSET,
|
||||
ELF_RUNTIME_HTTP_HEADER_LEN,
|
||||
ELF_RUNTIME_HTTP_WRITE_JSON_RESPONSE,
|
||||
ELF_RUNTIME_HTTP_REQUEST_METHOD_NAME,
|
||||
ELF_RUNTIME_HTTP_REQUEST_PATH,
|
||||
ELF_RUNTIME_HTTP_REQUEST_MATCHES,
|
||||
ELF_RUNTIME_HTTP_REQUEST_BODY_WITHIN,
|
||||
ELF_RUNTIME_ASCII_OP,
|
||||
ELF_RUNTIME_TEXT_OP,
|
||||
ELF_RUNTIME_PARSE_OP,
|
||||
ELF_RUNTIME_PARSE_USIZE,
|
||||
ELF_RUNTIME_TIME_OP,
|
||||
ELF_RUNTIME_TERM_OP,
|
||||
ELF_RUNTIME_TERM_READ_INPUT,
|
||||
ELF_RUNTIME_MATH_OP,
|
||||
ELF_RUNTIME_MATH_USIZE_OP,
|
||||
ELF_RUNTIME_SEARCH_OP,
|
||||
ELF_RUNTIME_SORT_OP,
|
||||
ELF_RUNTIME_SORT_IS_SORTED_OP,
|
||||
ELF_RUNTIME_STR_CONTAINS,
|
||||
ELF_RUNTIME_STR_BUFFER_OP,
|
||||
ELF_RUNTIME_STR_CONCAT,
|
||||
ELF_RUNTIME_STR_REPEAT,
|
||||
ELF_RUNTIME_STR_TRIM_OP,
|
||||
ELF_RUNTIME_STR_PAIR_OP,
|
||||
ELF_RUNTIME_STR_COUNT_BYTE,
|
||||
ELF_RUNTIME_STR_WORD_COUNT_ASCII,
|
||||
ELF_RUNTIME_CRYPTO_DIGEST,
|
||||
ELF_RUNTIME_CRYPTO_HMAC_SHA256,
|
||||
ELF_RUNTIME_CRYPTO_HMAC_SHA256_HEX,
|
||||
ELF_RUNTIME_FS_DIR_ENTRY_NAME,
|
||||
ELF_RUNTIME_ARGS_FIND,
|
||||
ELF_RUNTIME_PARSE_I32,
|
||||
ELF_RUNTIME_PARSE_U32,
|
||||
ELF_RUNTIME_FMT_BOOL,
|
||||
ELF_RUNTIME_FMT_HEX_LOWER_U32,
|
||||
ELF_RUNTIME_FMT_I32,
|
||||
ELF_RUNTIME_FMT_U32,
|
||||
ELF_RUNTIME_FMT_USIZE,
|
||||
ELF_RUNTIME_PROC_SPAWN_INHERIT,
|
||||
ELF_RUNTIME_PROC_SPAWN_INHERIT_ARGS,
|
||||
ELF_RUNTIME_PROC_CAPTURE,
|
||||
ELF_RUNTIME_PROC_CAPTURE_ARGS,
|
||||
ELF_RUNTIME_PROC_CAPTURE_FILES,
|
||||
ELF_RUNTIME_PROC_CAPTURE_FILES_ARGS,
|
||||
ELF_RUNTIME_PROC_SPAWN_CHILD,
|
||||
ELF_RUNTIME_PROC_SPAWN_CHILD_IN,
|
||||
ELF_RUNTIME_PROC_SPAWN_CHILD_IN_ENV,
|
||||
ELF_RUNTIME_PROC_SPAWN_CHILD_ARGS,
|
||||
ELF_RUNTIME_PROC_CHILD_OP,
|
||||
ELF_RUNTIME_PROC_CHILD_IO,
|
||||
ELF_RUNTIME_PTY_SPAWN,
|
||||
ELF_RUNTIME_PTY_SPAWN_IN,
|
||||
ELF_RUNTIME_PTY_SPAWN_IN_ENV,
|
||||
ELF_RUNTIME_PTY_SPAWN_ARGS,
|
||||
ELF_RUNTIME_PTY_RESIZE,
|
||||
ELF_RUNTIME_HELPER_COUNT
|
||||
} ElfRuntimeHelper;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
} ElfPatch;
|
||||
|
||||
typedef struct {
|
||||
ElfPatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} ElfPatchList;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned callee_index;
|
||||
unsigned external_index;
|
||||
bool external_call;
|
||||
} ElfCallPatch;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned data_offset;
|
||||
} ElfRodataPatch;
|
||||
|
||||
typedef struct {
|
||||
const IrProgram *ir;
|
||||
size_t *function_offsets;
|
||||
size_t function_count;
|
||||
ElfCallPatch *call_patches;
|
||||
size_t call_patch_len;
|
||||
size_t call_patch_cap;
|
||||
ElfRodataPatch *rodata_patches;
|
||||
size_t rodata_patch_len;
|
||||
size_t rodata_patch_cap;
|
||||
ElfPatchList runtime_patches[ELF_RUNTIME_HELPER_COUNT];
|
||||
bool emit_rodata_relocations;
|
||||
bool seed_main_process_args;
|
||||
unsigned rodata_base_offset;
|
||||
uint64_t rodata_addr;
|
||||
ZDirectTrapMessages trap_messages;
|
||||
ZDirectTrapBranchList trap_branches[Z_DIRECT_TRAP_KIND_COUNT];
|
||||
ZDirectLoopFrame *loop;
|
||||
} ElfEmitContext;
|
||||
|
||||
const char *z_elf_runtime_helper_symbol(ElfRuntimeHelper helper);
|
||||
void z_elf_emit_context_free(ElfEmitContext *ctx);
|
||||
bool z_elf_record_call_patch(ElfEmitContext *ctx, size_t patch_offset, unsigned callee_index, ZDiag *diag, const IrValue *value);
|
||||
bool z_elf_record_rodata_patch(ElfEmitContext *ctx, size_t patch_offset, unsigned data_offset, ZDiag *diag, const IrValue *value);
|
||||
bool z_elf_record_value_runtime_patch(ElfEmitContext *ctx, ElfRuntimeHelper helper, size_t patch_offset, ZDiag *diag, const IrValue *value);
|
||||
size_t z_elf_runtime_patch_count(const ElfEmitContext *ctx, ElfRuntimeHelper helper);
|
||||
bool z_elf_has_runtime_patches(const ElfEmitContext *ctx);
|
||||
void z_elf_patch_call_patches(ZBuf *code, const ElfEmitContext *ctx);
|
||||
void z_elf_patch_rodata_patches(ZBuf *code, const ElfEmitContext *ctx);
|
||||
void z_elf_append_rodata_relocations(ZBuf *rela_text, const ElfEmitContext *ctx, uint32_t rodata_symbol);
|
||||
void z_elf_append_external_call_relocations(ZBuf *rela_text, const ElfEmitContext *ctx, uint32_t external_symbol_base);
|
||||
void z_elf_append_runtime_relocations(ZBuf *rela_text, const ElfEmitContext *ctx, ElfRuntimeHelper helper, uint32_t runtime_symbol);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "elf_format.h"
|
||||
|
||||
size_t z_elf_align(size_t value, size_t alignment) {
|
||||
size_t remainder = alignment ? value % alignment : 0;
|
||||
return remainder == 0 ? value : value + (alignment - remainder);
|
||||
}
|
||||
|
||||
void z_elf_append_u8(ZBuf *buf, unsigned value) {
|
||||
zbuf_append_char(buf, (char)(value & 0xff));
|
||||
}
|
||||
|
||||
void z_elf_append_u16(ZBuf *buf, uint16_t value) {
|
||||
z_elf_append_u8(buf, value);
|
||||
z_elf_append_u8(buf, value >> 8);
|
||||
}
|
||||
|
||||
void z_elf_append_u32(ZBuf *buf, uint32_t value) {
|
||||
z_elf_append_u8(buf, value);
|
||||
z_elf_append_u8(buf, value >> 8);
|
||||
z_elf_append_u8(buf, value >> 16);
|
||||
z_elf_append_u8(buf, value >> 24);
|
||||
}
|
||||
|
||||
void z_elf_append_u64(ZBuf *buf, uint64_t value) {
|
||||
z_elf_append_u32(buf, (uint32_t)value);
|
||||
z_elf_append_u32(buf, (uint32_t)(value >> 32));
|
||||
}
|
||||
|
||||
void z_elf_append_bytes(ZBuf *buf, const unsigned char *bytes, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) z_elf_append_u8(buf, bytes[i]);
|
||||
}
|
||||
|
||||
void z_elf_append_zeros(ZBuf *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) z_elf_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_elf_pad_to(ZBuf *buf, size_t offset) {
|
||||
while (buf->len < offset) z_elf_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_elf_append_symbol(ZBuf *symtab, uint32_t name, unsigned char info, uint16_t shndx, uint64_t value, uint64_t size) {
|
||||
z_elf_append_u32(symtab, name);
|
||||
z_elf_append_u8(symtab, info);
|
||||
z_elf_append_u8(symtab, 0);
|
||||
z_elf_append_u16(symtab, shndx);
|
||||
z_elf_append_u64(symtab, value);
|
||||
z_elf_append_u64(symtab, size);
|
||||
}
|
||||
|
||||
void z_elf_append_rela(ZBuf *rela, uint64_t offset, uint32_t sym, uint32_t type, int64_t addend) {
|
||||
z_elf_append_u64(rela, offset);
|
||||
z_elf_append_u64(rela, ((uint64_t)sym << 32) | type);
|
||||
z_elf_append_u64(rela, (uint64_t)addend);
|
||||
}
|
||||
|
||||
static void elf_append_section_header(ZBuf *out, uint32_t name, uint32_t type, uint64_t flags, uint64_t offset, uint64_t size, uint32_t link, uint32_t info, uint64_t align, uint64_t entsize) {
|
||||
z_elf_append_u32(out, name);
|
||||
z_elf_append_u32(out, type);
|
||||
z_elf_append_u64(out, flags);
|
||||
z_elf_append_u64(out, 0);
|
||||
z_elf_append_u64(out, offset);
|
||||
z_elf_append_u64(out, size);
|
||||
z_elf_append_u32(out, link);
|
||||
z_elf_append_u32(out, info);
|
||||
z_elf_append_u64(out, align);
|
||||
z_elf_append_u64(out, entsize);
|
||||
}
|
||||
|
||||
void z_elf_write_object64(ZBuf *out, const ZElfObjectImage *image) {
|
||||
const ZBuf empty = {0};
|
||||
const ZBuf *text = image && image->text ? image->text : ∅
|
||||
const ZBuf *rodata = image && image->rodata ? image->rodata : ∅
|
||||
const ZBuf *rela_text = image && image->rela_text ? image->rela_text : ∅
|
||||
const ZBuf *symtab = image && image->symtab ? image->symtab : ∅
|
||||
const ZBuf *strtab = image && image->strtab ? image->strtab : ∅
|
||||
bool has_rodata = rodata->len > 0;
|
||||
bool has_rela_text = rela_text->len > 0;
|
||||
size_t text_align = image && image->text_align ? image->text_align : 16;
|
||||
size_t rodata_align = image && image->rodata_align ? image->rodata_align : 8;
|
||||
|
||||
ZBuf shstrtab;
|
||||
zbuf_init(&shstrtab);
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
uint32_t sh_name_text = (uint32_t)shstrtab.len;
|
||||
zbuf_append(&shstrtab, ".text");
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
uint32_t sh_name_rodata = 0;
|
||||
uint32_t sh_name_rela_text = 0;
|
||||
if (has_rodata) {
|
||||
sh_name_rodata = (uint32_t)shstrtab.len;
|
||||
zbuf_append(&shstrtab, ".rodata");
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
}
|
||||
if (has_rela_text) {
|
||||
sh_name_rela_text = (uint32_t)shstrtab.len;
|
||||
zbuf_append(&shstrtab, ".rela.text");
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
}
|
||||
uint32_t sh_name_symtab = (uint32_t)shstrtab.len;
|
||||
zbuf_append(&shstrtab, ".symtab");
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
uint32_t sh_name_strtab = (uint32_t)shstrtab.len;
|
||||
zbuf_append(&shstrtab, ".strtab");
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
uint32_t sh_name_shstrtab = (uint32_t)shstrtab.len;
|
||||
zbuf_append(&shstrtab, ".shstrtab");
|
||||
z_elf_append_u8(&shstrtab, 0);
|
||||
|
||||
const size_t ehdr_size = 64;
|
||||
const size_t shnum = 5 + (has_rodata ? 1 : 0) + (has_rela_text ? 1 : 0);
|
||||
const uint16_t symtab_shndx = (uint16_t)(2 + (has_rodata ? 1 : 0) + (has_rela_text ? 1 : 0));
|
||||
const uint16_t strtab_shndx = (uint16_t)(symtab_shndx + 1);
|
||||
const uint16_t shstrtab_shndx = (uint16_t)(strtab_shndx + 1);
|
||||
size_t text_offset = z_elf_align(ehdr_size, text_align);
|
||||
size_t rodata_offset = has_rodata ? z_elf_align(text_offset + text->len, rodata_align) : 0;
|
||||
size_t rela_text_offset = has_rela_text ? z_elf_align((has_rodata ? rodata_offset + rodata->len : text_offset + text->len), 8) : 0;
|
||||
size_t symtab_offset = z_elf_align((has_rela_text ? rela_text_offset + rela_text->len : (has_rodata ? rodata_offset + rodata->len : text_offset + text->len)), 8);
|
||||
size_t strtab_offset = symtab_offset + symtab->len;
|
||||
size_t shstrtab_offset = strtab_offset + strtab->len;
|
||||
size_t shoff = z_elf_align(shstrtab_offset + shstrtab.len, 8);
|
||||
|
||||
zbuf_init(out);
|
||||
const unsigned char ident[] = {0x7f, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
z_elf_append_bytes(out, ident, sizeof(ident));
|
||||
z_elf_append_u16(out, 1);
|
||||
z_elf_append_u16(out, image ? (uint16_t)image->machine : 0);
|
||||
z_elf_append_u32(out, 1);
|
||||
z_elf_append_u64(out, 0);
|
||||
z_elf_append_u64(out, 0);
|
||||
z_elf_append_u64(out, shoff);
|
||||
z_elf_append_u32(out, 0);
|
||||
z_elf_append_u16(out, 64);
|
||||
z_elf_append_u16(out, 0);
|
||||
z_elf_append_u16(out, 0);
|
||||
z_elf_append_u16(out, 64);
|
||||
z_elf_append_u16(out, (uint16_t)shnum);
|
||||
z_elf_append_u16(out, shstrtab_shndx);
|
||||
|
||||
z_elf_pad_to(out, text_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)text->data, text->len);
|
||||
if (has_rodata) {
|
||||
z_elf_pad_to(out, rodata_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)rodata->data, rodata->len);
|
||||
}
|
||||
if (has_rela_text) {
|
||||
z_elf_pad_to(out, rela_text_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)rela_text->data, rela_text->len);
|
||||
}
|
||||
z_elf_pad_to(out, symtab_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)symtab->data, symtab->len);
|
||||
z_elf_pad_to(out, strtab_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)strtab->data, strtab->len);
|
||||
z_elf_pad_to(out, shstrtab_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)shstrtab.data, shstrtab.len);
|
||||
z_elf_pad_to(out, shoff);
|
||||
|
||||
elf_append_section_header(out, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
elf_append_section_header(out, sh_name_text, 1, 0x6, text_offset, text->len, 0, 0, text_align, 0);
|
||||
if (has_rodata) elf_append_section_header(out, sh_name_rodata, 1, 0x2, rodata_offset, rodata->len, 0, 0, rodata_align, 0);
|
||||
if (has_rela_text) elf_append_section_header(out, sh_name_rela_text, 4, 0, rela_text_offset, rela_text->len, symtab_shndx, 1, 8, 24);
|
||||
elf_append_section_header(out, sh_name_symtab, 2, 0, symtab_offset, symtab->len, strtab_shndx, image ? image->local_symbol_count : 1, 8, 24);
|
||||
elf_append_section_header(out, sh_name_strtab, 3, 0, strtab_offset, strtab->len, 0, 0, 1, 0);
|
||||
elf_append_section_header(out, sh_name_shstrtab, 3, 0, shstrtab_offset, shstrtab.len, 0, 0, 1, 0);
|
||||
zbuf_free(&shstrtab);
|
||||
}
|
||||
|
||||
void z_elf_write_executable64(ZBuf *out, const ZElfExecutableImage *image) {
|
||||
const ZBuf empty = {0};
|
||||
const ZBuf *text = image && image->text ? image->text : ∅
|
||||
const ZBuf *rodata = image && image->rodata ? image->rodata : ∅
|
||||
bool has_rodata = image && image->rodata && rodata->len > 0;
|
||||
uint64_t file_size = has_rodata ? image->rodata_offset + rodata->len : image->text_offset + text->len;
|
||||
|
||||
zbuf_init(out);
|
||||
const unsigned char ident[] = {0x7f, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
z_elf_append_bytes(out, ident, sizeof(ident));
|
||||
z_elf_append_u16(out, 2);
|
||||
z_elf_append_u16(out, image ? (uint16_t)image->machine : 0);
|
||||
z_elf_append_u32(out, 1);
|
||||
z_elf_append_u64(out, image ? image->entry_addr : 0);
|
||||
z_elf_append_u64(out, 64);
|
||||
z_elf_append_u64(out, 0);
|
||||
z_elf_append_u32(out, 0);
|
||||
z_elf_append_u16(out, 64);
|
||||
z_elf_append_u16(out, 56);
|
||||
z_elf_append_u16(out, 1);
|
||||
z_elf_append_u16(out, 0);
|
||||
z_elf_append_u16(out, 0);
|
||||
z_elf_append_u16(out, 0);
|
||||
|
||||
z_elf_append_u32(out, 1);
|
||||
z_elf_append_u32(out, 5);
|
||||
z_elf_append_u64(out, 0);
|
||||
z_elf_append_u64(out, image ? image->base_addr : 0);
|
||||
z_elf_append_u64(out, image ? image->base_addr : 0);
|
||||
z_elf_append_u64(out, file_size);
|
||||
z_elf_append_u64(out, file_size);
|
||||
z_elf_append_u64(out, image && image->segment_align ? image->segment_align : 0x1000);
|
||||
|
||||
z_elf_pad_to(out, image ? image->text_offset : 0);
|
||||
z_elf_append_bytes(out, (const unsigned char *)text->data, text->len);
|
||||
if (has_rodata) {
|
||||
z_elf_pad_to(out, image->rodata_offset);
|
||||
z_elf_append_bytes(out, (const unsigned char *)rodata->data, rodata->len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef ZERO_C_ELF_FORMAT_H
|
||||
#define ZERO_C_ELF_FORMAT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
Z_ELF_MACHINE_X86_64 = 62,
|
||||
Z_ELF_MACHINE_AARCH64 = 183
|
||||
} ZElfMachine;
|
||||
|
||||
typedef struct {
|
||||
ZElfMachine machine;
|
||||
const ZBuf *text;
|
||||
size_t text_align;
|
||||
const ZBuf *rodata;
|
||||
size_t rodata_align;
|
||||
const ZBuf *rela_text;
|
||||
const ZBuf *symtab;
|
||||
const ZBuf *strtab;
|
||||
uint32_t local_symbol_count;
|
||||
} ZElfObjectImage;
|
||||
|
||||
typedef struct {
|
||||
ZElfMachine machine;
|
||||
uint64_t base_addr;
|
||||
uint64_t entry_addr;
|
||||
size_t text_offset;
|
||||
const ZBuf *text;
|
||||
const ZBuf *rodata;
|
||||
size_t rodata_offset;
|
||||
uint64_t segment_align;
|
||||
} ZElfExecutableImage;
|
||||
|
||||
size_t z_elf_align(size_t value, size_t alignment);
|
||||
void z_elf_append_u8(ZBuf *buf, unsigned value);
|
||||
void z_elf_append_u16(ZBuf *buf, uint16_t value);
|
||||
void z_elf_append_u32(ZBuf *buf, uint32_t value);
|
||||
void z_elf_append_u64(ZBuf *buf, uint64_t value);
|
||||
void z_elf_append_bytes(ZBuf *buf, const unsigned char *bytes, size_t len);
|
||||
void z_elf_append_zeros(ZBuf *buf, size_t len);
|
||||
void z_elf_pad_to(ZBuf *buf, size_t offset);
|
||||
void z_elf_append_symbol(ZBuf *symtab, uint32_t name, unsigned char info, uint16_t shndx, uint64_t value, uint64_t size);
|
||||
void z_elf_append_rela(ZBuf *rela, uint64_t offset, uint32_t sym, uint32_t type, int64_t addend);
|
||||
void z_elf_write_object64(ZBuf *out, const ZElfObjectImage *image);
|
||||
void z_elf_write_executable64(ZBuf *out, const ZElfExecutableImage *image);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Generated from Zero runtime sources. Run node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/embed-runtime-sources.mts to refresh. */
|
||||
#ifndef ZERO_EMBEDDED_RUNTIME_SOURCES_INC
|
||||
#define ZERO_EMBEDDED_RUNTIME_SOURCES_INC
|
||||
|
||||
static const char *const zero_embedded_zero_runtime_h[] = {
|
||||
"#ifndef ZERO_C_RUNTIME_H\n#define ZERO_C_RUNTIME_H\n\n#include <stddef.h>\n#include <stdint.h>\n\ntypedef struct {\n const unsigned char *ptr;\n size_t len;\n} ZeroByteView;\n\ntypedef struct {\n unsigned char *ptr;\n size_t len;\n} ZeroMutByteView;\n\ntypedef struct {\n uint64_t has;\n uint64_t value;\n} ZeroMaybeUsize;\n\ntypedef enum {\n ZERO_HTTP_OK = 0,\n ZERO_HTTP_INVALID_URL = 1,\n ZERO_HTTP_UNSUPPORTED_PROTOCOL = 2,\n ZERO_HTTP_DNS = 3,\n ZERO_HTTP_CONNECT = 4,\n ZERO_HTTP_TLS = 5,\n ZERO_HTTP_TIMEOUT = 6,\n ZERO_HTTP_TOO_LARGE = 7,\n ZERO_HTTP_PROVIDER_UNAVAILABLE = 8,\n ZERO_HTTP_IO = 9,\n ZERO_HTTP_INVALID_REQUEST = 10\n} ZeroHttpError;\n\ntypedef enum {\n ZERO_STR_OP_REVERSE = 0,\n ZERO_STR_OP_COPY = 1,\n ZERO_STR_OP_CONCAT = 2,\n ZERO_STR_OP_REPEAT = 3,\n ZERO_STR_OP_TO_LOWER_ASCII = 4,\n ZERO_STR_OP_TO_UPPER_ASCII = 5,\n ZERO_STR_OP_TRIM_ASCII = 6,\n ZERO_STR_OP_TRIM_START_ASCII = 7,\n ZERO_STR_OP_TRIM_END_ASCII = 8,\n ZERO_STR_OP_COUNT_BYTE = 9,\n ZERO_STR_OP_STARTS_WITH = 10,\n ZERO_STR_OP_ENDS_WITH = 11,\n ZERO_STR_OP_CONTAINS = 12,\n ZERO_STR_OP_COUNT = 13,\n ZERO_STR_OP_INDEX_OF = 14,\n ZERO_STR_OP_LAST_INDEX_OF = 15,\n ZERO_STR_OP_EQL_IGNORE_ASCII_CASE = 16,\n ZERO_STR_OP_WORD_COUNT_ASCII = 17,\n ZERO_STR_OP_PATH_BASENAME = 18,\n ZERO_STR_OP_PATH_DIRNAME = 19,\n ZERO_STR_OP_PATH_EXTENSION = 20,\n ZERO_STR_OP_PARSE_TOKEN_ASCII = 21\n} ZeroStrOp;\n\ntypedef enum {\n ZERO_CRYPTO_DIGEST_SHA256 = 0,\n ZERO_CRYPTO_DIGEST_SHA256_HEX = 1\n} ZeroCryptoDigestOp;\n\ntypedef enum {\n ZERO_ASCII_OP_IS_DIGIT = 0,\n ZERO_ASCII_OP_IS_LOWER = 1,\n ZERO_ASCII_OP_IS_UPPER = 2,\n ZERO_ASCII_OP_IS_ALPHA = 3,\n ZERO_ASCII_OP_IS_ALNUM = 4,\n ZERO_ASCII_OP_IS_WHITESPACE = 5,\n ZERO_ASCII_OP_IS_HEX_DIGIT = 6,\n ZERO_ASCII_OP_TO_LOWER = 7,\n ZERO_ASCII_OP_TO_UPPER = 8,\n ZERO_ASCII_OP_DIGIT_VALUE = 9,\n ZERO_ASCII_OP_HEX_VALUE = 10\n} ZeroAsciiOp;\n\ntypedef enum {\n ZERO_TEXT_OP_IS_ASCII = 0,\n ZERO_TEXT_OP_UTF8_VALID = 1,\n ZERO_TEXT_OP_UTF8_LEN = 2\n} ZeroTextOp;\n\ntypedef enum {\n ZERO_PARSE_OP_IS_ASCII_DIGIT = 0,\n ZERO_PARSE_OP_IS_ASCII_ALPHA = 1,\n ZERO_PARSE_OP_IS_IDENTIFIER_START = 2,\n ZERO_PARSE_OP_IS_WHITESPACE = 3,\n ZERO_PARSE_OP_SCAN_DIGITS = 4,\n ZERO_PARSE_OP_SCAN_IDENTIFIER = 5,\n ZERO_PARSE_OP_SCAN_UNTIL_BYTE = 6,\n ZERO_PARSE_OP_SCAN_WHITESPACE = 7,\n ZERO_PARSE_OP_PARSE_BOOL = 8,\n ZERO_PARSE_OP_PARSE_U8 = 9,\n ZERO_PARSE_OP_PARSE_U16 = 10,\n ZERO_PARSE_OP_PARSE_USIZE = 11,\n ZERO_PARSE_OP_TERM_KEY_CODE = 12,\n ZERO_PARSE_OP_TERM_KEY_BYTE_LEN = 13\n} ZeroParseOp;\n\ntypedef enum {\n ZERO_TIME_OP_AS_US_FLOOR = 0,\n ZERO_TIME_OP_AS_MS_FLOOR = 1,\n ZERO_TIME_OP_AS_SECONDS_FLOOR = 2,\n ZERO_TIME_OP_MIN = 3,\n ZERO_TIME_OP_MAX = 4,\n ZERO_TIME_OP_CLAMP = 5,\n ZERO_TIME_OP_SLEEP = 6,\n ZERO_TIME_OP_WALL_SECONDS = 7,\n ZERO_TIME_OP_MONOTONIC = 8\n} ZeroTimeOp;\n\ntypedef enum {\n ZERO_MATH_OP_MIN_I32 = 0,\n ZERO_MATH_OP_MAX_I32 = 1,\n",
|
||||
" ZERO_MATH_OP_CLAMP_I32 = 2,\n ZERO_MATH_OP_MIN_I64 = 3,\n ZERO_MATH_OP_MAX_I64 = 4,\n ZERO_MATH_OP_CLAMP_I64 = 5,\n ZERO_MATH_OP_MIN_U32 = 6,\n ZERO_MATH_OP_MAX_U32 = 7,\n ZERO_MATH_OP_CLAMP_U32 = 8,\n ZERO_MATH_OP_MIN_U64 = 9,\n ZERO_MATH_OP_MAX_U64 = 10,\n ZERO_MATH_OP_CLAMP_U64 = 11,\n ZERO_MATH_OP_MIN_USIZE = 12,\n ZERO_MATH_OP_MAX_USIZE = 13,\n ZERO_MATH_OP_CLAMP_USIZE = 14,\n ZERO_MATH_OP_ABS_I32 = 15,\n ZERO_MATH_OP_ABS_I64 = 16,\n ZERO_MATH_OP_CHECKED_ADD_U32 = 17,\n ZERO_MATH_OP_CHECKED_SUB_U32 = 18,\n ZERO_MATH_OP_CHECKED_MUL_U32 = 19,\n ZERO_MATH_OP_SATURATING_ADD_U32 = 20,\n ZERO_MATH_OP_SATURATING_SUB_U32 = 21,\n ZERO_MATH_OP_SATURATING_MUL_U32 = 22,\n ZERO_MATH_OP_CHECKED_ADD_I32 = 23,\n ZERO_MATH_OP_CHECKED_SUB_I32 = 24,\n ZERO_MATH_OP_CHECKED_MUL_I32 = 25,\n ZERO_MATH_OP_SATURATING_ADD_I32 = 26,\n ZERO_MATH_OP_SATURATING_SUB_I32 = 27,\n ZERO_MATH_OP_SATURATING_MUL_I32 = 28,\n ZERO_MATH_OP_GCD_U32 = 29,\n ZERO_MATH_OP_LCM_U32 = 30,\n ZERO_MATH_OP_CHECKED_LCM_U32 = 31,\n ZERO_MATH_OP_POW_U32 = 32,\n ZERO_MATH_OP_CHECKED_POW_U32 = 33,\n ZERO_MATH_OP_MOD_POW_U32 = 34,\n ZERO_MATH_OP_IS_PRIME_U32 = 35,\n ZERO_MATH_OP_SQRT_FLOOR_U32 = 36,\n ZERO_MATH_OP_FACTORIAL_U32 = 37,\n ZERO_MATH_OP_BINOMIAL_U32 = 38,\n ZERO_MATH_OP_DIVISOR_COUNT_U32 = 39,\n ZERO_MATH_OP_PROPER_DIVISOR_SUM_U32 = 40,\n ZERO_MATH_OP_CHECKED_ADD_USIZE = 41,\n ZERO_MATH_OP_CHECKED_SUB_USIZE = 42,\n ZERO_MATH_OP_CHECKED_MUL_USIZE = 43,\n ZERO_MATH_OP_SATURATING_ADD_USIZE = 44,\n ZERO_MATH_OP_SATURATING_SUB_USIZE = 45,\n ZERO_MATH_OP_SATURATING_MUL_USIZE = 46\n} ZeroMathOp;\n\ntypedef enum {\n ZERO_SEARCH_OP_LOWER_BOUND_I32 = 0,\n ZERO_SEARCH_OP_BINARY_I32 = 1,\n ZERO_SEARCH_OP_LOWER_BOUND_U32 = 2,\n ZERO_SEARCH_OP_BINARY_U32 = 3,\n ZERO_SEARCH_OP_LOWER_BOUND_USIZE = 4,\n ZERO_SEARCH_OP_BINARY_USIZE = 5,\n ZERO_SEARCH_OP_UPPER_BOUND_I32 = 6,\n ZERO_SEARCH_OP_UPPER_BOUND_U32 = 7,\n ZERO_SEARCH_OP_UPPER_BOUND_USIZE = 8\n} ZeroSearchOp;\n\ntypedef enum {\n ZERO_SORT_OP_INSERTION_I32 = 0,\n ZERO_SORT_OP_IS_SORTED_I32 = 1,\n ZERO_SORT_OP_INSERTION_U32 = 2,\n ZERO_SORT_OP_IS_SORTED_U32 = 3,\n ZERO_SORT_OP_INSERTION_USIZE = 4,\n ZERO_SORT_OP_IS_SORTED_USIZE = 5\n} ZeroSortOp;\n\ntypedef enum {\n ZERO_TERM_OP_STDIN_IS_TTY = 0,\n ZERO_TERM_OP_STDOUT_IS_TTY = 1,\n ZERO_TERM_OP_WIDTH_OR = 2,\n ZERO_TERM_OP_HEIGHT_OR = 3,\n ZERO_TERM_OP_ENTER_RAW_MODE = 4,\n ZERO_TERM_OP_LEAVE_RAW_MODE = 5,\n ZERO_TERM_OP_READ_INPUT = 6\n} ZeroTermOp;\n\ntypedef enum {\n ZERO_PROC_CHILD_OP_RUNNING = 0,\n ZERO_PROC_CHILD_OP_WAIT = 1,\n ZERO_PROC_CHILD_OP_KILL = 2,\n ZERO_PROC_CHILD_OP_CLOSE = 3,\n ZERO_PROC_CHILD_OP_VALID = 4,\n ZERO_PROC_CHILD_OP_PID = 5,\n ZERO_PROC_CHILD_OP_INTERRUPT = 6,\n ZERO_PROC_CHILD_OP_CLOSE_STDIN = 7,\n ZERO_PROC_CHILD_OP_PID_RUNNING = 8,\n ZERO_PROC_CHILD_OP_KILL_PID = 9,\n",
|
||||
" ZERO_PROC_CHILD_OP_INTERRUPT_PID = 10, ZERO_PROC_CHILD_OP_KILL_GROUP_PID = 11, ZERO_PROC_CHILD_OP_INTERRUPT_GROUP_PID = 12\n} ZeroProcChildOp;\n\ntypedef enum {\n ZERO_PROC_CHILD_IO_READ_STDOUT = 0,\n ZERO_PROC_CHILD_IO_READ_STDERR = 1,\n ZERO_PROC_CHILD_IO_WRITE_STDIN = 2\n} ZeroProcChildIoOp;\n\ntypedef enum {\n ZERO_FS_PATH_EXISTS = 0,\n ZERO_FS_PATH_IS_DIR = 1,\n ZERO_FS_PATH_MAKE_DIR = 2,\n ZERO_FS_PATH_REMOVE_DIR = 3,\n ZERO_FS_PATH_REMOVE = 4\n} ZeroFsPathOp;\n\n#define ZERO_HTTP_RESPONSE_META_BYTES 24u\n\nint zero_world_write(int fd, const char *buf, unsigned len);\n\nZeroMaybeUsize zero_fs_read_bytes(ZeroByteView path, ZeroMutByteView buffer);\nZeroMaybeUsize zero_fs_read_bytes_at(ZeroByteView path, uint64_t offset, ZeroMutByteView buffer);\nZeroMaybeUsize zero_fs_write_bytes(ZeroByteView path, ZeroByteView bytes);\nZeroMaybeUsize zero_fs_append_bytes(ZeroByteView path, ZeroByteView bytes);\nZeroMaybeUsize zero_fs_dir_entry_count(ZeroByteView path);\nuint64_t zero_fs_dir_entry_name(ZeroMutByteView buffer, ZeroByteView path, uint64_t index);\nuint32_t zero_fs_path_op(ZeroByteView path, uint32_t op);\nuint32_t zero_fs_rename(ZeroByteView from_path, ZeroByteView to_path);\nuint32_t zero_fs_atomic_write(ZeroByteView path, ZeroByteView temp_path, ZeroByteView bytes);\nint32_t zero_proc_spawn_inherit(ZeroByteView command);\nint32_t zero_proc_spawn_inherit_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env);\nZeroMaybeUsize zero_proc_capture(ZeroByteView command, ZeroMutByteView buffer);\nZeroMaybeUsize zero_proc_capture_args(ZeroByteView program, ZeroByteView args, ZeroMutByteView buffer);\nint32_t zero_proc_capture_files(ZeroByteView command, ZeroByteView stdout_path, ZeroByteView stderr_path);\nint32_t zero_proc_capture_files_args(ZeroByteView program, ZeroByteView args, ZeroByteView stdout_path, ZeroByteView stderr_path);\nint32_t zero_proc_spawn_child(ZeroByteView command);\nint32_t zero_proc_spawn_child_in(ZeroByteView command, ZeroByteView cwd);\nint32_t zero_proc_spawn_child_in_env(ZeroByteView command, ZeroByteView cwd, ZeroByteView env);\nint32_t zero_proc_spawn_child_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env);\nint32_t zero_proc_child_op(int32_t child, uint32_t op);\nZeroMaybeUsize zero_proc_child_io(int32_t child, ZeroMutByteView buffer, uint32_t op);\nint32_t zero_pty_spawn(ZeroByteView command);\nint32_t zero_pty_spawn_in(ZeroByteView command, ZeroByteView cwd);\nint32_t zero_pty_spawn_in_env(ZeroByteView command, ZeroByteView cwd, ZeroByteView env);\nint32_t zero_pty_spawn_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env);\nint32_t zero_pty_resize(int32_t child, uint64_t columns, uint64_t rows);\n\nint64_t zero_json_parse_bytes(ZeroByteView input);\nuint64_t zero_json_diagnostic(ZeroByteView input, uint32_t op);\nuint64_t zero_json_field(ZeroByteView input, ZeroByteView key);\n",
|
||||
"uint64_t zero_json_lookup_scalar(ZeroByteView input, ZeroByteView key, uint32_t op);\nuint64_t zero_json_string_decode(ZeroMutByteView buffer, ZeroByteView raw);\nuint64_t zero_json_string_field(ZeroMutByteView buffer, ZeroByteView input, ZeroByteView key);\nuint64_t zero_json_write_string(ZeroMutByteView buffer, ZeroByteView text);\nuint64_t zero_json_write_field_raw(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value);\nuint64_t zero_json_write_field_string(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value);\nuint64_t zero_json_write_field_u32(ZeroMutByteView buffer, ZeroByteView key, uint32_t value);\nuint64_t zero_json_write_field_bool(ZeroMutByteView buffer, ZeroByteView key, uint32_t value);\nuint64_t zero_json_write_object1_string(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value);\nuint64_t zero_json_write_object1_u32(ZeroMutByteView buffer, ZeroByteView key, uint32_t value);\nuint64_t zero_json_write_object1_bool(ZeroMutByteView buffer, ZeroByteView key, uint32_t value);\nuint64_t zero_json_write_object2_fields(ZeroMutByteView buffer, ZeroByteView field0, ZeroByteView field1);\nuint64_t zero_json_write_object2_string_field(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, ZeroByteView field1);\nuint64_t zero_json_write_object2_u32_field(ZeroMutByteView buffer, ZeroByteView key, uint32_t value, ZeroByteView field1);\nuint64_t zero_json_write_object2_bool_field(ZeroMutByteView buffer, ZeroByteView key, uint32_t value, ZeroByteView field1);\nuint64_t zero_json_write_array2_strings(ZeroMutByteView buffer, ZeroByteView value0, ZeroByteView value1);\nuint64_t zero_json_write_array2_u32(ZeroMutByteView buffer, uint32_t value0, uint32_t value1);\nuint64_t zero_json_write_array2_bools(ZeroMutByteView buffer, uint32_t value0, uint32_t value1);\nuint32_t zero_ascii_op(uint32_t byte, uint32_t op);\nuint64_t zero_text_op(ZeroByteView text, uint32_t op);\nuint64_t zero_parse_op(ZeroByteView text, uint32_t arg, uint32_t op);\nZeroMaybeUsize zero_parse_usize(ZeroByteView text);\nint64_t zero_time_op(int64_t a, int64_t b, int64_t c, uint32_t op);\nuint32_t zero_rand_entropy_u32(void);\nuint64_t zero_math_op(int64_t a, int64_t b, int64_t c, uint32_t op);\nZeroMaybeUsize zero_math_usize_op(uint64_t a, uint64_t b, uint64_t c, uint32_t op);\nuint64_t zero_search_op(ZeroByteView items, int64_t needle, uint32_t op);\nvoid zero_sort_op(ZeroMutByteView items, uint32_t op);\nuint32_t zero_sort_is_sorted_op(ZeroByteView items, uint32_t op);\nuint64_t zero_term_op(uint64_t fallback, uint32_t op);\nZeroMaybeUsize zero_term_read_input(ZeroMutByteView buffer);\nuint32_t zero_str_contains(ZeroByteView text, ZeroByteView needle);\nuint32_t zero_str_buffer_op(ZeroMutByteView buffer, ZeroByteView text, uint32_t op);\nuint32_t zero_str_concat(ZeroMutByteView buffer, ZeroByteView left, ZeroByteView right);\nuint32_t zero_str_repeat(ZeroMutByteView buffer, ZeroByteView text, uint64_t count);\n",
|
||||
"uint64_t zero_str_trim_op(ZeroByteView text, uint32_t op);\nuint64_t zero_str_pair_op(ZeroByteView text, ZeroByteView needle, uint32_t op);\nuint64_t zero_str_count_byte(ZeroByteView text, uint32_t byte);\nuint64_t zero_str_word_count_ascii(ZeroByteView text);\nuint32_t zero_crypto_digest(ZeroMutByteView buffer, ZeroByteView bytes, uint32_t op);\nuint32_t zero_crypto_hmac_sha256(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView bytes);\nuint32_t zero_crypto_hmac_sha256_hex(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView bytes);\nuint64_t zero_args_find(uint64_t argc, const char *const *argv, ZeroByteView name);\nuint64_t zero_parse_i32(ZeroByteView text);\nuint64_t zero_parse_u32(ZeroByteView text);\nuint32_t zero_fmt_bool(ZeroMutByteView buffer, uint32_t value);\nuint32_t zero_fmt_hex_lower_u32(ZeroMutByteView buffer, uint32_t value);\nuint32_t zero_fmt_i32(ZeroMutByteView buffer, int32_t value);\nuint32_t zero_fmt_u32(ZeroMutByteView buffer, uint32_t value);\nuint32_t zero_fmt_usize(ZeroMutByteView buffer, uint64_t value);\n\nuint64_t zero_http_fetch_result(\n ZeroByteView request,\n ZeroMutByteView response_out,\n int64_t timeout_ns\n);\n\nuint32_t zero_http_result_ok(uint64_t result);\nuint32_t zero_http_result_status(uint64_t result);\nuint32_t zero_http_result_body_len(uint64_t result);\nuint32_t zero_http_result_error(uint64_t result);\n\nuint32_t zero_http_response_len(ZeroByteView response);\nuint32_t zero_http_response_headers_len(ZeroByteView response);\nuint32_t zero_http_response_body_offset(ZeroByteView response);\nuint64_t zero_http_header_value(ZeroByteView headers, ZeroByteView name);\nuint32_t zero_http_header_found(uint64_t value);\nuint32_t zero_http_header_offset(uint64_t value);\nuint32_t zero_http_header_len(uint64_t value);\nuint32_t zero_http_write_json_response(ZeroMutByteView buffer, uint32_t status, ZeroByteView body);\nuint64_t zero_http_request_method_name(ZeroByteView request);\nuint64_t zero_http_request_path(ZeroByteView request);\nuint32_t zero_http_request_matches(ZeroByteView request, ZeroByteView method, ZeroByteView path);\nuint64_t zero_http_request_body_within(ZeroByteView request, uint64_t max, uint32_t require_json);\n\n#endif\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_zero_runtime_c[] = {
|
||||
"#if !defined(_WIN32) && !defined(_XOPEN_SOURCE)\n#define _XOPEN_SOURCE 700\n#endif\n#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE)\n#define _POSIX_C_SOURCE 200809L\n#endif\n\n#include \"zero_runtime.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#if defined(_WIN32)\n#include <conio.h>\n#include <direct.h>\n#include <io.h>\n#include <process.h>\n#include <windows.h>\ntypedef int ZeroWriteResult;\ntypedef int ZeroReadResult;\ntypedef struct _stat ZeroRuntimeStat;\n#define ZERO_RUNTIME_WRITE _write\n#define ZERO_RUNTIME_OPEN _open\n#define ZERO_RUNTIME_READ _read\n#define ZERO_RUNTIME_CLOSE _close\n#define ZERO_RUNTIME_FSTAT _fstat\n#define ZERO_RUNTIME_STAT _stat\n#define ZERO_RUNTIME_LSEEK _lseeki64\n#define ZERO_RUNTIME_CHDIR _chdir\n#define ZERO_RUNTIME_OPEN_FLAGS (_O_RDONLY | _O_BINARY)\n#define ZERO_RUNTIME_IS_REGULAR(mode) (((mode) & _S_IFREG) != 0)\n#define ZERO_RUNTIME_IS_DIR(mode) (((mode) & _S_IFDIR) != 0)\n#define ZERO_RUNTIME_ISATTY _isatty\n#define ZERO_RUNTIME_STDIN_FD 0\n#define ZERO_RUNTIME_STDOUT_FD 1\n#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT\n#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200\n#endif\n#else\n#include <dirent.h>\n#include <poll.h>\n#include <signal.h>\n#include <sys/select.h>\n#include <sys/ioctl.h>\n#include <sys/wait.h>\n#include <termios.h>\n#include <time.h>\n#include <unistd.h>\n#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)\n#include <util.h>\n#else\n#include <pty.h>\n#endif\ntypedef ssize_t ZeroWriteResult;\ntypedef ssize_t ZeroReadResult;\ntypedef struct stat ZeroRuntimeStat;\n#define ZERO_RUNTIME_WRITE write\n#define ZERO_RUNTIME_OPEN open\n#define ZERO_RUNTIME_READ read\n#define ZERO_RUNTIME_CLOSE close\n#define ZERO_RUNTIME_FSTAT fstat\n#define ZERO_RUNTIME_STAT stat\n#define ZERO_RUNTIME_LSEEK lseek\n#define ZERO_RUNTIME_CHDIR chdir\n#define ZERO_RUNTIME_OPEN_FLAGS O_RDONLY\n#define ZERO_RUNTIME_IS_REGULAR(mode) S_ISREG(mode)\n#define ZERO_RUNTIME_IS_DIR(mode) S_ISDIR(mode)\n#define ZERO_RUNTIME_ISATTY isatty\n#define ZERO_RUNTIME_STDIN_FD STDIN_FILENO\n#define ZERO_RUNTIME_STDOUT_FD STDOUT_FILENO\n#endif\n\n#define ZERO_RUNTIME_PATH_BYTES 4096u\n#define ZERO_RUNTIME_READ_CHUNK 1048576u\n#define ZERO_RUNTIME_PROC_COMMAND_BYTES 4096u\n#define ZERO_RUNTIME_PROC_ARGS_BYTES 4096u\n#define ZERO_RUNTIME_PROC_ENV_BYTES 4096u\n#define ZERO_RUNTIME_PROC_MAX_ARGS 64u\n#define ZERO_RUNTIME_PROC_CHILD_MAX 64u\n#define ZERO_RUNTIME_PROC_CHILD_SLOT_BITS 7u\n#define ZERO_RUNTIME_PROC_CHILD_SLOT_MASK ((1u << ZERO_RUNTIME_PROC_CHILD_SLOT_BITS) - 1u)\n#define ZERO_RUNTIME_PROC_CHILD_GENERATION_MAX ((uint32_t)INT32_MAX >> ZERO_RUNTIME_PROC_CHILD_SLOT_BITS)\n#define ZERO_RUNTIME_PROC_DRAIN_CHUNK 4096u\n#define ZERO_RUNTIME_PROC_BUFFER_MAX (16u * 1024u * 1024u)\n\nstatic uint64_t zero_runtime_pack_span(int has, size_t offset, size_t len);\n\n",
|
||||
"uint32_t zero_rand_entropy_u32(void) {\n#if defined(_WIN32)\n LARGE_INTEGER counter;\n QueryPerformanceCounter(&counter);\n uint64_t value = (uint64_t)counter.QuadPart ^ (uint64_t)GetTickCount64() ^ ((uint64_t)GetCurrentProcessId() << 32);\n value ^= value >> 33;\n value *= 0xff51afd7ed558ccdull;\n value ^= value >> 33;\n return (uint32_t)value;\n#else\n uint32_t value = 0;\n int fd = open(\"/dev/urandom\", O_RDONLY);\n if (fd < 0) return 0;\n ssize_t got = read(fd, &value, sizeof(value));\n close(fd);\n return got == (ssize_t)sizeof(value) ? value : 0;\n#endif\n}\n\nstatic ZeroMaybeUsize zero_runtime_none_usize(void) {\n return (ZeroMaybeUsize){0, 0};\n}\n\nstatic int zero_runtime_path_copy(ZeroByteView path, char out[ZERO_RUNTIME_PATH_BYTES]) {\n if ((!path.ptr && path.len > 0) || path.len == 0 || path.len >= ZERO_RUNTIME_PATH_BYTES) return 0;\n memcpy(out, path.ptr, path.len);\n out[path.len] = '\\0';\n return 1;\n}\n\nstatic int zero_runtime_open_readonly(const char *path) {\n int fd;\n do {\n fd = ZERO_RUNTIME_OPEN(path, ZERO_RUNTIME_OPEN_FLAGS);\n } while (fd < 0 && errno == EINTR);\n return fd;\n}\n\nstatic int zero_runtime_open_write_truncate(const char *path) {\n#if defined(_WIN32)\n int fd;\n do {\n fd = ZERO_RUNTIME_OPEN(path, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, _S_IREAD | _S_IWRITE);\n } while (fd < 0 && errno == EINTR);\n return fd;\n#else\n int fd;\n do {\n fd = ZERO_RUNTIME_OPEN(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n } while (fd < 0 && errno == EINTR);\n return fd;\n#endif\n}\n\nstatic int zero_runtime_open_write_append(const char *path) {\n#if defined(_WIN32)\n int fd;\n do {\n fd = ZERO_RUNTIME_OPEN(path, _O_WRONLY | _O_CREAT | _O_APPEND | _O_BINARY, _S_IREAD | _S_IWRITE);\n } while (fd < 0 && errno == EINTR);\n return fd;\n#else\n int fd;\n do {\n fd = ZERO_RUNTIME_OPEN(path, O_WRONLY | O_CREAT | O_APPEND, 0666);\n } while (fd < 0 && errno == EINTR);\n return fd;\n#endif\n}\n\nstatic int zero_runtime_fd_regular_size(int fd, uint64_t *size_out) {\n ZeroRuntimeStat st;\n if (ZERO_RUNTIME_FSTAT(fd, &st) != 0) return 0;\n if (!ZERO_RUNTIME_IS_REGULAR(st.st_mode)) return 0;\n if (size_out) *size_out = st.st_size < 0 ? 0 : (uint64_t)st.st_size;\n return 1;\n}\n\nstatic int zero_runtime_path_is_dir(const char *path) {\n ZeroRuntimeStat st;\n return path && ZERO_RUNTIME_STAT(path, &st) == 0 && ZERO_RUNTIME_IS_DIR(st.st_mode);\n}\n\nstatic int zero_runtime_close_fd(int fd) {\n return ZERO_RUNTIME_CLOSE(fd) == 0;\n}\n\nstatic int zero_runtime_read_fd(int fd, ZeroMutByteView buffer, uint64_t *read_len_out) {\n size_t total = 0;\n while (total < buffer.len) {\n size_t remaining = buffer.len - total;\n unsigned chunk = remaining > ZERO_RUNTIME_READ_CHUNK ? ZERO_RUNTIME_READ_CHUNK : (unsigned)remaining;\n ZeroReadResult read_len = ZERO_RUNTIME_READ(fd, buffer.ptr + total, chunk);\n if (read_len < 0) {\n if (errno == EINTR) continue;\n",
|
||||
" return 0;\n }\n if (read_len == 0) break;\n total += (size_t)read_len;\n }\n if (read_len_out) *read_len_out = (uint64_t)total;\n return 1;\n}\n\nint zero_world_write(int fd, const char *buf, unsigned len) {\n if (len == 0) return 0;\n if (!buf) return 1;\n const char *cursor = buf;\n unsigned remaining = len;\n while (remaining > 0) {\n ZeroWriteResult written = ZERO_RUNTIME_WRITE(fd, cursor, remaining);\n if (written < 0) {\n if (errno == EINTR) continue;\n return 1;\n }\n if (written == 0) return 1;\n cursor += (unsigned)written;\n remaining -= (unsigned)written;\n }\n return 0;\n}\n\nZeroMaybeUsize zero_fs_read_bytes(ZeroByteView path, ZeroMutByteView buffer) {\n if (!buffer.ptr) return zero_runtime_none_usize();\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();\n\n int fd = zero_runtime_open_readonly(path_buf);\n if (fd < 0) return zero_runtime_none_usize();\n\n uint64_t total = 0;\n uint64_t read_len = 0;\n int ok = zero_runtime_fd_regular_size(fd, &total) && zero_runtime_read_fd(fd, buffer, &read_len);\n int closed = zero_runtime_close_fd(fd);\n if (!ok || !closed) return zero_runtime_none_usize();\n /* snprintf convention: report the total file size so callers can detect\n truncation when the value exceeds the buffer length. */\n if (read_len > total) total = read_len;\n return (ZeroMaybeUsize){1, total};\n}\n\nZeroMaybeUsize zero_fs_read_bytes_at(ZeroByteView path, uint64_t offset, ZeroMutByteView buffer) {\n if (!buffer.ptr) return zero_runtime_none_usize();\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();\n\n int fd = zero_runtime_open_readonly(path_buf);\n if (fd < 0) return zero_runtime_none_usize();\n\n uint64_t total = 0;\n uint64_t read_len = 0;\n int ok = zero_runtime_fd_regular_size(fd, &total);\n if (ok && offset < total && buffer.len > 0) {\n ok = ZERO_RUNTIME_LSEEK(fd, (int64_t)offset, SEEK_SET) == (int64_t)offset &&\n zero_runtime_read_fd(fd, buffer, &read_len);\n }\n int closed = zero_runtime_close_fd(fd);\n if (!ok || !closed) return zero_runtime_none_usize();\n /* snprintf convention: report the total file size so callers can loop\n offset += len(buffer) until offset reaches the returned total. */\n if (read_len > 0 && offset + read_len > total) total = offset + read_len;\n return (ZeroMaybeUsize){1, total};\n}\n\nstatic ZeroMaybeUsize zero_runtime_write_bytes_to_path(ZeroByteView path, ZeroByteView bytes, int append) {\n if (!bytes.ptr && bytes.len > 0) return zero_runtime_none_usize();\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();\n int fd = append ? zero_runtime_open_write_append(path_buf) : zero_runtime_open_write_truncate(path_buf);\n if (fd < 0) return zero_runtime_none_usize();\n\n",
|
||||
" size_t total = 0;\n int ok = 1;\n while (total < bytes.len) {\n size_t remaining = bytes.len - total;\n unsigned chunk = remaining > ZERO_RUNTIME_READ_CHUNK ? ZERO_RUNTIME_READ_CHUNK : (unsigned)remaining;\n ZeroWriteResult written = ZERO_RUNTIME_WRITE(fd, bytes.ptr + total, chunk);\n if (written < 0) {\n if (errno == EINTR) continue;\n ok = 0;\n break;\n }\n if (written == 0) {\n ok = 0;\n break;\n }\n total += (size_t)written;\n }\n int closed = zero_runtime_close_fd(fd);\n if (!ok || !closed) return zero_runtime_none_usize();\n return (ZeroMaybeUsize){1, (uint64_t)total};\n}\n\nZeroMaybeUsize zero_fs_write_bytes(ZeroByteView path, ZeroByteView bytes) {\n return zero_runtime_write_bytes_to_path(path, bytes, 0);\n}\n\nZeroMaybeUsize zero_fs_append_bytes(ZeroByteView path, ZeroByteView bytes) {\n return zero_runtime_write_bytes_to_path(path, bytes, 1);\n}\n\nZeroMaybeUsize zero_fs_dir_entry_count(ZeroByteView path) {\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();\n#if defined(_WIN32)\n char search_buf[ZERO_RUNTIME_PATH_BYTES + 3u];\n size_t path_len = strlen(path_buf);\n if (path_len + 3u >= sizeof(search_buf)) return zero_runtime_none_usize();\n memcpy(search_buf, path_buf, path_len);\n if (path_len > 0 && search_buf[path_len - 1u] != '\\\\' && search_buf[path_len - 1u] != '/') {\n search_buf[path_len++] = '\\\\';\n }\n search_buf[path_len++] = '*';\n search_buf[path_len] = '\\0';\n\n WIN32_FIND_DATAA data;\n HANDLE handle = FindFirstFileA(search_buf, &data);\n if (handle == INVALID_HANDLE_VALUE) return zero_runtime_none_usize();\n uint64_t count = 0;\n do {\n const char *name = data.cFileName;\n if (strcmp(name, \".\") == 0 || strcmp(name, \"..\") == 0) continue;\n count++;\n } while (FindNextFileA(handle, &data));\n FindClose(handle);\n return (ZeroMaybeUsize){1, count};\n#else\n DIR *dir = opendir(path_buf);\n if (!dir) return zero_runtime_none_usize();\n uint64_t count = 0;\n struct dirent *entry = NULL;\n while ((entry = readdir(dir)) != NULL) {\n const char *name = entry->d_name;\n if (strcmp(name, \".\") == 0 || strcmp(name, \"..\") == 0) continue;\n count++;\n }\n closedir(dir);\n return (ZeroMaybeUsize){1, count};\n#endif\n}\n\nuint64_t zero_fs_dir_entry_name(ZeroMutByteView buffer, ZeroByteView path, uint64_t index) {\n if ((!buffer.ptr && buffer.len > 0) || index > UINT32_MAX) return 0;\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf)) return 0;\n#if defined(_WIN32)\n char search_buf[ZERO_RUNTIME_PATH_BYTES + 3u];\n size_t path_len = strlen(path_buf);\n if (path_len + 3u >= sizeof(search_buf)) return 0;\n memcpy(search_buf, path_buf, path_len);\n if (path_len > 0 && search_buf[path_len - 1u] != '\\\\' && search_buf[path_len - 1u] != '/') {\n search_buf[path_len++] = '\\\\';\n }\n",
|
||||
" search_buf[path_len++] = '*';\n search_buf[path_len] = '\\0';\n\n WIN32_FIND_DATAA data;\n HANDLE handle = FindFirstFileA(search_buf, &data);\n if (handle == INVALID_HANDLE_VALUE) return 0;\n uint64_t visible = 0;\n do {\n const char *name = data.cFileName;\n if (strcmp(name, \".\") == 0 || strcmp(name, \"..\") == 0) continue;\n if (visible == index) {\n size_t len = strlen(name);\n if (len > buffer.len || len > 0x7fffffffu) {\n FindClose(handle);\n return 0;\n }\n if (len > 0) memcpy(buffer.ptr, name, len);\n FindClose(handle);\n return zero_runtime_pack_span(1, 0, len);\n }\n visible++;\n } while (FindNextFileA(handle, &data));\n FindClose(handle);\n return 0;\n#else\n DIR *dir = opendir(path_buf);\n if (!dir) return 0;\n uint64_t visible = 0;\n struct dirent *entry = NULL;\n while ((entry = readdir(dir)) != NULL) {\n const char *name = entry->d_name;\n if (strcmp(name, \".\") == 0 || strcmp(name, \"..\") == 0) continue;\n if (visible == index) {\n size_t len = strlen(name);\n if (len > buffer.len || len > 0x7fffffffu) {\n closedir(dir);\n return 0;\n }\n if (len > 0) memcpy(buffer.ptr, name, len);\n closedir(dir);\n return zero_runtime_pack_span(1, 0, len);\n }\n visible++;\n }\n closedir(dir);\n return 0;\n#endif\n}\n\nstatic int zero_runtime_stat_path(const char *path, ZeroRuntimeStat *st) {\n if (!path || !path[0] || !st) return 0;\n#if defined(_WIN32)\n return _stat(path, st) == 0;\n#else\n return stat(path, st) == 0;\n#endif\n}\n\nuint32_t zero_fs_path_op(ZeroByteView path, uint32_t op) {\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf)) return 0;\n switch ((ZeroFsPathOp)op) {\n case ZERO_FS_PATH_EXISTS: {\n ZeroRuntimeStat st;\n return zero_runtime_stat_path(path_buf, &st) ? 1u : 0u;\n }\n case ZERO_FS_PATH_IS_DIR: {\n ZeroRuntimeStat st;\n if (!zero_runtime_stat_path(path_buf, &st)) return 0;\n#if defined(_WIN32)\n return (st.st_mode & _S_IFDIR) != 0 ? 1u : 0u;\n#else\n return S_ISDIR(st.st_mode) ? 1u : 0u;\n#endif\n }\n case ZERO_FS_PATH_MAKE_DIR:\n#if defined(_WIN32)\n return _mkdir(path_buf) == 0 ? 1u : 0u;\n#else\n return mkdir(path_buf, 0777) == 0 ? 1u : 0u;\n#endif\n case ZERO_FS_PATH_REMOVE_DIR:\n#if defined(_WIN32)\n return _rmdir(path_buf) == 0 ? 1u : 0u;\n#else\n return rmdir(path_buf) == 0 ? 1u : 0u;\n#endif\n case ZERO_FS_PATH_REMOVE:\n#if defined(_WIN32)\n return _unlink(path_buf) == 0 ? 1u : 0u;\n#else\n return unlink(path_buf) == 0 ? 1u : 0u;\n#endif\n }\n return 0;\n}\n\nuint32_t zero_fs_rename(ZeroByteView from_path, ZeroByteView to_path) {\n char from_buf[ZERO_RUNTIME_PATH_BYTES];\n char to_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(from_path, from_buf) || !zero_runtime_path_copy(to_path, to_buf)) return 0;\n",
|
||||
" return rename(from_buf, to_buf) == 0 ? 1u : 0u;\n}\n\nuint32_t zero_fs_atomic_write(ZeroByteView path, ZeroByteView temp_path, ZeroByteView bytes) {\n char path_buf[ZERO_RUNTIME_PATH_BYTES];\n char temp_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(path, path_buf) || !zero_runtime_path_copy(temp_path, temp_buf)) return 0;\n ZeroMaybeUsize wrote = zero_runtime_write_bytes_to_path(temp_path, bytes, 0);\n if (!wrote.has || wrote.value != bytes.len) {\n remove(temp_buf);\n return 0;\n }\n#if defined(_WIN32)\n remove(path_buf);\n#endif\n if (rename(temp_buf, path_buf) != 0) {\n remove(temp_buf);\n return 0;\n }\n return 1u;\n}\n\nstatic int zero_runtime_ascii_space(unsigned char byte) {\n return byte == ' ' || byte == '\\t' || byte == '\\n' || byte == '\\r' || byte == '\\f' || byte == '\\v';\n}\n\nstatic int zero_runtime_proc_parse_command(ZeroByteView command, char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES], char *argv[ZERO_RUNTIME_PROC_MAX_ARGS]) {\n if (!command.ptr || command.len == 0 || command.len >= ZERO_RUNTIME_PROC_COMMAND_BYTES) return 0;\n for (size_t i = 0; i < command.len; i++) {\n if (command.ptr[i] == 0) return 0;\n }\n\n size_t read = 0;\n size_t write = 0;\n size_t argc = 0;\n while (read < command.len) {\n while (read < command.len && zero_runtime_ascii_space(command.ptr[read])) read++;\n if (read >= command.len) break;\n if (argc + 1 >= ZERO_RUNTIME_PROC_MAX_ARGS) return 0;\n\n size_t token_start = write;\n argv[argc++] = &storage[token_start];\n while (read < command.len && !zero_runtime_ascii_space(command.ptr[read])) {\n if (command.ptr[read] == '\\'') {\n read++;\n int closed = 0;\n while (read < command.len) {\n if (command.ptr[read] == '\\'') {\n closed = 1;\n read++;\n break;\n }\n storage[write++] = (char)command.ptr[read++];\n }\n if (!closed) return 0;\n } else if (command.ptr[read] == '\\\\' && read + 1 < command.len) {\n read++;\n storage[write++] = (char)command.ptr[read++];\n } else {\n storage[write++] = (char)command.ptr[read++];\n }\n }\n if (write == token_start) {\n argc--;\n } else {\n storage[write++] = 0;\n }\n }\n if (argc == 0) return 0;\n argv[argc] = NULL;\n return 1;\n}\n\nstatic int zero_runtime_proc_build_argv(ZeroByteView program, ZeroByteView args, char storage[ZERO_RUNTIME_PROC_ARGS_BYTES], char *argv[ZERO_RUNTIME_PROC_MAX_ARGS]) {\n if (!program.ptr || program.len == 0 || (!args.ptr && args.len > 0)) return 0;\n for (size_t i = 0; i < program.len; i++) {\n if (program.ptr[i] == 0) return 0;\n }\n for (size_t i = 0; i < args.len; i++) {\n if (args.ptr[i] == 0) return 0;\n }\n\n size_t write = 0;\n size_t argc = 0;\n if (program.len >= ZERO_RUNTIME_PROC_ARGS_BYTES) return 0;\n argv[argc++] = &storage[write];\n memcpy(&storage[write], program.ptr, program.len);\n",
|
||||
" write += program.len;\n storage[write++] = 0;\n\n size_t line_start = 0;\n while (line_start < args.len) {\n size_t line_end = line_start;\n while (line_end < args.len && args.ptr[line_end] != '\\n') line_end++;\n if (line_end > line_start) {\n if (argc + 1 >= ZERO_RUNTIME_PROC_MAX_ARGS) return 0;\n size_t line_len = line_end - line_start;\n if (write + line_len >= ZERO_RUNTIME_PROC_ARGS_BYTES) return 0;\n argv[argc++] = &storage[write];\n memcpy(&storage[write], &args.ptr[line_start], line_len);\n write += line_len;\n storage[write++] = 0;\n }\n line_start = line_end + 1;\n }\n\n argv[argc] = NULL;\n return 1;\n}\n\nstatic int zero_runtime_proc_copy_env_block(ZeroByteView env, char storage[ZERO_RUNTIME_PROC_ENV_BYTES]) {\n if (!env.ptr && env.len > 0) return 0;\n if (env.len >= ZERO_RUNTIME_PROC_ENV_BYTES) return 0;\n for (size_t i = 0; i < env.len; i++) {\n if (env.ptr[i] == 0) return 0;\n }\n size_t line_start = 0;\n while (line_start < env.len) {\n size_t line_end = line_start;\n while (line_end < env.len && env.ptr[line_end] != '\\n') line_end++;\n if (line_end > line_start) {\n size_t equals = line_start;\n while (equals < line_end && env.ptr[equals] != '=') equals++;\n if (equals == line_start || equals == line_end) return 0;\n }\n line_start = line_end + 1;\n }\n if (env.len > 0) memcpy(storage, env.ptr, env.len);\n storage[env.len] = '\\0';\n return 1;\n}\n\nstatic int zero_runtime_proc_set_env(const char *name, const char *value) {\n#if defined(_WIN32)\n return SetEnvironmentVariableA(name, value ? value : \"\") != 0;\n#else\n return setenv(name, value ? value : \"\", 1) == 0;\n#endif\n}\n\nstatic int zero_runtime_proc_apply_env_block(char *env) {\n if (!env || !env[0]) return 1;\n char *cursor = env;\n while (*cursor) {\n char *line = cursor;\n while (*cursor && *cursor != '\\n') cursor++;\n if (*cursor == '\\n') {\n *cursor = '\\0';\n cursor++;\n }\n if (!line[0]) continue;\n char *equals = strchr(line, '=');\n if (!equals || equals == line) return 0;\n *equals = '\\0';\n if (!zero_runtime_proc_set_env(line, equals + 1)) return 0;\n }\n return 1;\n}\n\n#if !defined(_WIN32)\nstatic int zero_runtime_set_fd_nonblock(int fd) {\n int flags = fcntl(fd, F_GETFL, 0);\n if (flags < 0) return 0;\n return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;\n}\n#endif\n\nstatic int32_t zero_proc_spawn_inherit_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env) {\n#if defined(_WIN32)\n (void)argv;\n (void)cwd;\n (void)env;\n return 127;\n#else\n pid_t pid = fork();\n if (pid < 0) return 127;\n\n if (pid == 0) {\n if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);\n if (!zero_runtime_proc_apply_env_block(env)) _exit(127);\n execvp(argv[0], argv);\n _exit(127);\n }\n\n int status = 0;\n while (waitpid(pid, &status, 0) < 0) {\n",
|
||||
" if (errno == EINTR) continue;\n return 127;\n }\n if (WIFEXITED(status)) return (int32_t)WEXITSTATUS(status);\n if (WIFSIGNALED(status)) return (int32_t)(128 + WTERMSIG(status));\n return 127;\n#endif\n}\n\nint32_t zero_proc_spawn_inherit(ZeroByteView command) {\n char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n if (!zero_runtime_proc_parse_command(command, storage, argv)) return 127;\n return zero_proc_spawn_inherit_argv_impl(argv, NULL, NULL);\n}\n\nint32_t zero_proc_spawn_inherit_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env) {\n char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];\n if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||\n !zero_runtime_path_copy(cwd, cwd_buf) ||\n !zero_runtime_proc_copy_env_block(env, env_buf)) return 127;\n return zero_proc_spawn_inherit_argv_impl(argv, cwd_buf, env_buf);\n}\n\nstatic ZeroMaybeUsize zero_proc_capture_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env, ZeroMutByteView buffer) {\n if (!buffer.ptr) return zero_runtime_none_usize();\n\n#if defined(_WIN32)\n (void)argv;\n (void)cwd;\n (void)env;\n return zero_runtime_none_usize();\n#else\n int pipe_fd[2];\n if (pipe(pipe_fd) != 0) return zero_runtime_none_usize();\n\n pid_t pid = fork();\n if (pid < 0) {\n ZERO_RUNTIME_CLOSE(pipe_fd[0]);\n ZERO_RUNTIME_CLOSE(pipe_fd[1]);\n return zero_runtime_none_usize();\n }\n\n if (pid == 0) {\n if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);\n if (!zero_runtime_proc_apply_env_block(env)) _exit(127);\n ZERO_RUNTIME_CLOSE(pipe_fd[0]);\n if (dup2(pipe_fd[1], STDOUT_FILENO) < 0) _exit(127);\n ZERO_RUNTIME_CLOSE(pipe_fd[1]);\n execvp(argv[0], argv);\n _exit(127);\n }\n\n ZERO_RUNTIME_CLOSE(pipe_fd[1]);\n if (!zero_runtime_set_fd_nonblock(pipe_fd[0])) {\n ZERO_RUNTIME_CLOSE(pipe_fd[0]);\n int ignored_status = 0;\n while (waitpid(pid, &ignored_status, 0) < 0 && errno == EINTR) {}\n return zero_runtime_none_usize();\n }\n uint64_t total = 0;\n int too_large = 0;\n int read_ok = 1;\n int wait_ok = 1;\n int status = 0;\n int reaped = 0;\n unsigned char chunk[4096];\n while (!reaped) {\n if (pipe_fd[0] >= 0) {\n for (;;) {\n ZeroReadResult n = ZERO_RUNTIME_READ(pipe_fd[0], chunk, sizeof(chunk));\n if (n < 0) {\n if (errno == EINTR) continue;\n if (errno == EAGAIN || errno == EWOULDBLOCK) break;\n read_ok = 0;\n break;\n }\n if (n == 0) {\n ZERO_RUNTIME_CLOSE(pipe_fd[0]);\n pipe_fd[0] = -1;\n break;\n }\n for (ZeroReadResult i = 0; i < n; i++) {\n if (total < buffer.len) {\n buffer.ptr[total] = chunk[i];\n } else {\n",
|
||||
" too_large = 1;\n }\n total++;\n }\n }\n }\n if (!read_ok) break;\n for (;;) {\n pid_t got = waitpid(pid, &status, WNOHANG);\n if (got == 0) break;\n if (got < 0) {\n if (errno == EINTR) continue;\n wait_ok = 0;\n } else {\n reaped = 1;\n }\n break;\n }\n if (!wait_ok || reaped) break;\n if (pipe_fd[0] >= 0) {\n struct pollfd fd = {.fd = pipe_fd[0], .events = POLLIN | POLLHUP | POLLERR};\n int rc = 0;\n do {\n rc = poll(&fd, 1, 50);\n } while (rc < 0 && errno == EINTR);\n if (rc < 0) {\n read_ok = 0;\n break;\n }\n } else {\n (void)poll(NULL, 0, 50);\n }\n }\n\n if (pipe_fd[0] >= 0) {\n if (reaped) {\n struct pollfd fd = {.fd = pipe_fd[0], .events = POLLIN | POLLHUP | POLLERR};\n int rc = 0;\n do {\n rc = poll(&fd, 1, 50);\n } while (rc < 0 && errno == EINTR);\n if (rc < 0) read_ok = 0;\n }\n while (read_ok) {\n ZeroReadResult n = ZERO_RUNTIME_READ(pipe_fd[0], chunk, sizeof(chunk));\n if (n < 0) {\n if (errno == EINTR) continue;\n if (errno == EAGAIN || errno == EWOULDBLOCK) break;\n read_ok = 0;\n break;\n }\n if (n == 0) break;\n for (ZeroReadResult i = 0; i < n; i++) {\n if (total < buffer.len) {\n buffer.ptr[total] = chunk[i];\n } else {\n too_large = 1;\n }\n total++;\n }\n }\n ZERO_RUNTIME_CLOSE(pipe_fd[0]);\n }\n\n if (!reaped) {\n while (waitpid(pid, &status, 0) < 0) {\n if (errno == EINTR) continue;\n wait_ok = 0;\n break;\n }\n }\n if (!read_ok || !wait_ok || too_large) return zero_runtime_none_usize();\n if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return zero_runtime_none_usize();\n return (ZeroMaybeUsize){1, total};\n#endif\n}\n\nZeroMaybeUsize zero_proc_capture(ZeroByteView command, ZeroMutByteView buffer) {\n char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n if (!zero_runtime_proc_parse_command(command, storage, argv)) return zero_runtime_none_usize();\n return zero_proc_capture_argv_impl(argv, NULL, NULL, buffer);\n}\n\nZeroMaybeUsize zero_proc_capture_args(ZeroByteView program, ZeroByteView args, ZeroMutByteView buffer) {\n char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv)) return zero_runtime_none_usize();\n return zero_proc_capture_argv_impl(argv, NULL, NULL, buffer);\n}\n\nstatic int32_t zero_proc_capture_files_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env, const char *stdout_buf, const char *stderr_buf) {\n#if defined(_WIN32)\n (void)argv;\n (void)cwd;\n (void)env;\n (void)stdout_buf;\n (void)stderr_buf;\n return 127;\n#else\n",
|
||||
" int stdout_fd = zero_runtime_open_write_truncate(stdout_buf);\n if (stdout_fd < 0) return 127;\n int stderr_fd = zero_runtime_open_write_truncate(stderr_buf);\n if (stderr_fd < 0) {\n ZERO_RUNTIME_CLOSE(stdout_fd);\n return 127;\n }\n\n pid_t pid = fork();\n if (pid < 0) {\n ZERO_RUNTIME_CLOSE(stdout_fd);\n ZERO_RUNTIME_CLOSE(stderr_fd);\n return 127;\n }\n\n if (pid == 0) {\n if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);\n if (!zero_runtime_proc_apply_env_block(env)) _exit(127);\n if (dup2(stdout_fd, STDOUT_FILENO) < 0) _exit(127);\n if (dup2(stderr_fd, STDERR_FILENO) < 0) _exit(127);\n ZERO_RUNTIME_CLOSE(stdout_fd);\n ZERO_RUNTIME_CLOSE(stderr_fd);\n execvp(argv[0], argv);\n _exit(127);\n }\n\n ZERO_RUNTIME_CLOSE(stdout_fd);\n ZERO_RUNTIME_CLOSE(stderr_fd);\n\n int status = 0;\n while (waitpid(pid, &status, 0) < 0) {\n if (errno == EINTR) continue;\n return 127;\n }\n if (WIFEXITED(status)) return (int32_t)WEXITSTATUS(status);\n if (WIFSIGNALED(status)) return (int32_t)(128 + WTERMSIG(status));\n return 127;\n#endif\n}\n\nint32_t zero_proc_capture_files(ZeroByteView command, ZeroByteView stdout_path, ZeroByteView stderr_path) {\n char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n char stdout_buf[ZERO_RUNTIME_PATH_BYTES];\n char stderr_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_proc_parse_command(command, storage, argv) ||\n !zero_runtime_path_copy(stdout_path, stdout_buf) ||\n !zero_runtime_path_copy(stderr_path, stderr_buf)) {\n return 127;\n }\n\n return zero_proc_capture_files_argv_impl(argv, NULL, NULL, stdout_buf, stderr_buf);\n}\n\nint32_t zero_proc_capture_files_args(ZeroByteView program, ZeroByteView args, ZeroByteView stdout_path, ZeroByteView stderr_path) {\n char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n char stdout_buf[ZERO_RUNTIME_PATH_BYTES];\n char stderr_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||\n !zero_runtime_path_copy(stdout_path, stdout_buf) ||\n !zero_runtime_path_copy(stderr_path, stderr_buf)) return 127;\n return zero_proc_capture_files_argv_impl(argv, NULL, NULL, stdout_buf, stderr_buf);\n}\n\n#if !defined(_WIN32)\ntypedef struct {\n unsigned char *data;\n size_t offset;\n size_t len;\n size_t cap;\n int discard;\n} ZeroRuntimeProcPipeBuffer;\n\ntypedef struct {\n int active;\n uint32_t generation;\n int reaped;\n int status;\n pid_t pid;\n int stdin_fd;\n int stdout_fd;\n int stderr_fd;\n int pty_fd;\n int pty;\n ZeroRuntimeProcPipeBuffer stdout_buffer;\n ZeroRuntimeProcPipeBuffer stderr_buffer;\n ZeroRuntimeProcPipeBuffer pty_buffer;\n} ZeroRuntimeProcChild;\n\nstatic ZeroRuntimeProcChild zero_proc_children[ZERO_RUNTIME_PROC_CHILD_MAX];\nstatic int zero_proc_sigpipe_ignored;\n\n",
|
||||
"static int zero_proc_signal_child(ZeroRuntimeProcChild *child, int signal_number);\n\nstatic uint32_t zero_proc_child_next_generation(uint32_t generation) {\n generation++;\n if (generation == 0 || generation > ZERO_RUNTIME_PROC_CHILD_GENERATION_MAX) return 1;\n return generation;\n}\n\nstatic int32_t zero_proc_child_handle(int index, uint32_t generation) {\n if (index < 0 || index >= (int)ZERO_RUNTIME_PROC_CHILD_MAX || generation == 0) return 0;\n return (int32_t)((generation << ZERO_RUNTIME_PROC_CHILD_SLOT_BITS) | ((uint32_t)index + 1u));\n}\n\nstatic int zero_proc_child_index(int32_t child) {\n if (child <= 0) return -1;\n uint32_t handle = (uint32_t)child;\n uint32_t slot = handle & ZERO_RUNTIME_PROC_CHILD_SLOT_MASK;\n uint32_t generation = handle >> ZERO_RUNTIME_PROC_CHILD_SLOT_BITS;\n if (slot == 0 || slot > ZERO_RUNTIME_PROC_CHILD_MAX || generation == 0) return -1;\n int index = (int)(slot - 1u);\n ZeroRuntimeProcChild *candidate = &zero_proc_children[index];\n return candidate->active && candidate->generation == generation ? index : -1;\n}\n\nstatic int zero_proc_child_alloc(void) {\n for (int i = 0; i < (int)ZERO_RUNTIME_PROC_CHILD_MAX; i++) {\n if (!zero_proc_children[i].active) return i;\n }\n return -1;\n}\n\nstatic void zero_proc_child_close_fd(int *fd) {\n if (*fd >= 0) {\n ZERO_RUNTIME_CLOSE(*fd);\n *fd = -1;\n }\n}\n\nstatic void zero_proc_child_close_pair(int fds[2]) {\n if (!fds) return;\n zero_proc_child_close_fd(&fds[0]);\n zero_proc_child_close_fd(&fds[1]);\n}\n\nstatic void zero_proc_child_close_pipes(ZeroRuntimeProcChild *child) {\n if (!child) return;\n zero_proc_child_close_fd(&child->stdin_fd);\n zero_proc_child_close_fd(&child->stdout_fd);\n zero_proc_child_close_fd(&child->stderr_fd);\n zero_proc_child_close_fd(&child->pty_fd);\n}\n\nstatic void zero_proc_pipe_buffer_free(ZeroRuntimeProcPipeBuffer *buffer) {\n if (!buffer) return;\n free(buffer->data);\n *buffer = (ZeroRuntimeProcPipeBuffer){0};\n}\n\nstatic void zero_proc_child_free_buffers(ZeroRuntimeProcChild *child) {\n if (!child) return;\n zero_proc_pipe_buffer_free(&child->stdout_buffer);\n zero_proc_pipe_buffer_free(&child->stderr_buffer);\n zero_proc_pipe_buffer_free(&child->pty_buffer);\n}\n\nstatic void zero_proc_pipe_buffer_compact(ZeroRuntimeProcPipeBuffer *buffer) {\n if (!buffer || buffer->offset == 0) return;\n if (buffer->offset >= buffer->len) {\n buffer->offset = 0;\n buffer->len = 0;\n return;\n }\n memmove(buffer->data, buffer->data + buffer->offset, buffer->len - buffer->offset);\n buffer->len -= buffer->offset;\n buffer->offset = 0;\n}\n\nstatic int zero_proc_pipe_buffer_append(ZeroRuntimeProcPipeBuffer *buffer, const unsigned char *data, size_t len) {\n if (!buffer || (!data && len > 0)) return 0;\n if (len == 0 || buffer->discard) return 1;\n zero_proc_pipe_buffer_compact(buffer);\n if (len > ZERO_RUNTIME_PROC_BUFFER_MAX - buffer->len) {\n",
|
||||
" size_t available = ZERO_RUNTIME_PROC_BUFFER_MAX - buffer->len;\n if (available > 0 && !zero_proc_pipe_buffer_append(buffer, data, available)) return 0;\n buffer->discard = 1;\n return 1;\n }\n size_t need = buffer->len + len;\n if (need > buffer->cap) {\n size_t cap = buffer->cap ? buffer->cap : 4096u;\n while (cap < need) {\n if (cap > ZERO_RUNTIME_PROC_BUFFER_MAX / 2u) {\n cap = ZERO_RUNTIME_PROC_BUFFER_MAX;\n break;\n }\n cap *= 2u;\n }\n unsigned char *next = (unsigned char *)realloc(buffer->data, cap);\n if (!next) {\n buffer->discard = 1;\n return 1;\n }\n buffer->data = next;\n buffer->cap = cap;\n }\n memcpy(buffer->data + buffer->len, data, len);\n buffer->len += len;\n return 1;\n}\n\nstatic ZeroMaybeUsize zero_proc_pipe_buffer_read(ZeroRuntimeProcPipeBuffer *buffer, ZeroMutByteView out) {\n if (!buffer || buffer->offset >= buffer->len || out.len == 0) return zero_runtime_none_usize();\n size_t available = buffer->len - buffer->offset;\n size_t take = available < out.len ? available : out.len;\n memcpy(out.ptr, buffer->data + buffer->offset, take);\n buffer->offset += take;\n if (buffer->offset == buffer->len) {\n buffer->offset = 0;\n buffer->len = 0;\n }\n return (ZeroMaybeUsize){1, (uint64_t)take};\n}\n\nstatic void zero_proc_child_drain_fd(int *fd, ZeroRuntimeProcPipeBuffer *buffer) {\n if (!fd || *fd < 0 || !buffer) return;\n unsigned char chunk[ZERO_RUNTIME_PROC_DRAIN_CHUNK];\n for (;;) {\n ZeroReadResult got = ZERO_RUNTIME_READ(*fd, chunk, sizeof(chunk));\n if (got > 0) {\n (void)zero_proc_pipe_buffer_append(buffer, chunk, (size_t)got);\n continue;\n }\n if (got == 0) {\n zero_proc_child_close_fd(fd);\n return;\n }\n if (errno == EINTR) continue;\n if (errno == EAGAIN || errno == EWOULDBLOCK) return;\n zero_proc_child_close_fd(fd);\n return;\n }\n}\n\nstatic void zero_proc_child_drain_output(ZeroRuntimeProcChild *child) {\n if (!child) return;\n if (child->pty) {\n zero_proc_child_drain_fd(&child->pty_fd, &child->pty_buffer);\n } else {\n zero_proc_child_drain_fd(&child->stdout_fd, &child->stdout_buffer);\n zero_proc_child_drain_fd(&child->stderr_fd, &child->stderr_buffer);\n }\n}\n\nstatic int zero_proc_child_output_open(const ZeroRuntimeProcChild *child) {\n if (!child) return 0;\n if (child->pty) return child->pty_fd >= 0;\n return child->stdout_fd >= 0 || child->stderr_fd >= 0;\n}\n\nstatic int zero_proc_child_set_nonblock(int fd) {\n return zero_runtime_set_fd_nonblock(fd);\n}\n\nstatic int zero_proc_child_set_cloexec(int fd) {\n int flags = fcntl(fd, F_GETFD, 0);\n if (flags < 0) return 0;\n return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0;\n}\n\nstatic int32_t zero_proc_status_from_wait(int status) {\n if (WIFEXITED(status)) return (int32_t)WEXITSTATUS(status);\n if (WIFSIGNALED(status)) return (int32_t)(128 + WTERMSIG(status));\n return 127;\n}\n\n",
|
||||
"static int zero_proc_child_reap(ZeroRuntimeProcChild *child, int nohang) {\n if (!child || !child->active) return 0;\n if (child->reaped) return 1;\n int raw = 0;\n for (;;) {\n pid_t got = waitpid(child->pid, &raw, nohang ? WNOHANG : 0);\n if (got == 0) return 0;\n if (got < 0) {\n if (errno == EINTR) continue;\n child->status = 127;\n child->reaped = 1;\n return 1;\n }\n child->status = zero_proc_status_from_wait(raw);\n child->reaped = 1;\n return 1;\n }\n}\n\nstatic void zero_proc_child_poll_output(ZeroRuntimeProcChild *child, int timeout_ms) {\n if (!child) return;\n struct pollfd fds[2];\n nfds_t count = 0;\n if (child->pty) {\n if (child->pty_fd >= 0) {\n fds[count++] = (struct pollfd){.fd = child->pty_fd, .events = POLLIN | POLLHUP | POLLERR};\n }\n } else {\n if (child->stdout_fd >= 0) {\n fds[count++] = (struct pollfd){.fd = child->stdout_fd, .events = POLLIN | POLLHUP | POLLERR};\n }\n if (child->stderr_fd >= 0) {\n fds[count++] = (struct pollfd){.fd = child->stderr_fd, .events = POLLIN | POLLHUP | POLLERR};\n }\n }\n if (count == 0) {\n if (timeout_ms < 0) {\n (void)zero_proc_child_reap(child, 0);\n } else if (timeout_ms > 0) {\n (void)poll(NULL, 0, timeout_ms);\n }\n return;\n }\n int rc = 0;\n do {\n rc = poll(fds, count, timeout_ms);\n } while (rc < 0 && errno == EINTR);\n if (rc > 0) zero_proc_child_drain_output(child);\n}\n\nstatic int zero_proc_child_wait_with_drain(ZeroRuntimeProcChild *child, int timeout_ms) {\n if (!child || !child->active) return 0;\n int elapsed_ms = 0;\n for (;;) {\n zero_proc_child_drain_output(child);\n if (zero_proc_child_reap(child, 1)) {\n zero_proc_child_drain_output(child);\n return 1;\n }\n if (timeout_ms == 0) return 0;\n int slice_ms = 50;\n if (timeout_ms > 0) {\n if (elapsed_ms >= timeout_ms) return 0;\n slice_ms = timeout_ms - elapsed_ms;\n if (slice_ms > 50) slice_ms = 50;\n }\n zero_proc_child_poll_output(child, slice_ms);\n if (slice_ms > 0) elapsed_ms += slice_ms;\n }\n}\n\nstatic int zero_proc_child_wait_output_closed(ZeroRuntimeProcChild *child, int timeout_ms) {\n if (!child || !child->active) return 0;\n int elapsed_ms = 0;\n for (;;) {\n zero_proc_child_drain_output(child);\n if (!zero_proc_child_output_open(child)) return 1;\n if (timeout_ms == 0) return 0;\n int slice_ms = 50;\n if (timeout_ms > 0) {\n if (elapsed_ms >= timeout_ms) return 0;\n slice_ms = timeout_ms - elapsed_ms;\n if (slice_ms > 50) slice_ms = 50;\n }\n zero_proc_child_poll_output(child, slice_ms);\n if (slice_ms > 0) elapsed_ms += slice_ms;\n }\n}\n\nstatic int zero_proc_child_close_handle(ZeroRuntimeProcChild *child) {\n if (!child || !child->active) return 0;\n if (!child->reaped && !zero_proc_child_wait_with_drain(child, 0)) {\n (void)zero_proc_signal_child(child, SIGTERM);\n",
|
||||
" (void)zero_proc_child_wait_with_drain(child, 250);\n if (!child->reaped) {\n (void)zero_proc_signal_child(child, SIGKILL);\n (void)zero_proc_child_wait_with_drain(child, -1);\n }\n }\n zero_proc_child_drain_output(child);\n if (zero_proc_child_output_open(child)) {\n (void)zero_proc_signal_child(child, SIGTERM);\n (void)zero_proc_child_wait_output_closed(child, 250);\n if (zero_proc_child_output_open(child)) {\n (void)zero_proc_signal_child(child, SIGKILL);\n (void)zero_proc_child_wait_output_closed(child, 250);\n }\n }\n zero_proc_child_close_pipes(child);\n zero_proc_child_free_buffers(child);\n child->active = 0;\n return 1;\n}\n\nstatic int zero_proc_pid_running(int32_t pid) {\n if (pid <= 0) return 0;\n if (kill((pid_t)pid, 0) == 0) return 1;\n return errno == EPERM ? 1 : 0;\n}\n\nstatic int zero_proc_signal_pid(int32_t pid, int signal_number) {\n if (pid <= 0) return 0;\n return kill((pid_t)pid, signal_number) == 0 ? 1 : 0;\n}\n\nstatic int zero_proc_signal_group_pid(int32_t pid, int signal_number) {\n if (pid <= 1) return 0;\n return kill(-(pid_t)pid, signal_number) == 0 ? 1 : 0;\n}\n\nstatic void zero_proc_kill_and_reap_pid(pid_t pid) {\n if (pid <= 0) return;\n (void)zero_proc_signal_group_pid((int32_t)pid, SIGTERM);\n (void)zero_proc_signal_pid((int32_t)pid, SIGTERM);\n\n int status = 0;\n for (;;) {\n pid_t got = waitpid(pid, &status, WNOHANG);\n if (got == pid) return;\n if (got < 0) {\n if (errno == EINTR) continue;\n return;\n }\n break;\n }\n\n (void)zero_proc_signal_group_pid((int32_t)pid, SIGKILL);\n (void)zero_proc_signal_pid((int32_t)pid, SIGKILL);\n for (;;) {\n pid_t got = waitpid(pid, &status, 0);\n if (got == pid) return;\n if (got < 0 && errno == EINTR) continue;\n return;\n }\n}\n\nstatic int zero_proc_signal_child(ZeroRuntimeProcChild *child, int signal_number) {\n if (!child || child->pid <= 0) return 0;\n if (zero_proc_signal_group_pid((int32_t)child->pid, signal_number)) return 1;\n return zero_proc_signal_pid((int32_t)child->pid, signal_number);\n}\n#endif\n\nstatic int32_t zero_proc_spawn_child_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env) {\n#if defined(_WIN32)\n (void)argv;\n (void)cwd;\n (void)env;\n return 0;\n#else\n if (cwd && !zero_runtime_path_is_dir(cwd)) return 0;\n int index = zero_proc_child_alloc();\n if (index < 0) return 0;\n\n int stdin_pipe[2] = {-1, -1};\n int stdout_pipe[2] = {-1, -1};\n int stderr_pipe[2] = {-1, -1};\n if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0 || pipe(stderr_pipe) != 0) {\n zero_proc_child_close_pair(stdin_pipe);\n zero_proc_child_close_pair(stdout_pipe);\n zero_proc_child_close_pair(stderr_pipe);\n return 0;\n }\n if (!zero_proc_child_set_cloexec(stdin_pipe[0]) ||\n !zero_proc_child_set_cloexec(stdin_pipe[1]) ||\n !zero_proc_child_set_cloexec(stdout_pipe[0]) ||\n",
|
||||
" !zero_proc_child_set_cloexec(stdout_pipe[1]) ||\n !zero_proc_child_set_cloexec(stderr_pipe[0]) ||\n !zero_proc_child_set_cloexec(stderr_pipe[1])) {\n zero_proc_child_close_pair(stdin_pipe);\n zero_proc_child_close_pair(stdout_pipe);\n zero_proc_child_close_pair(stderr_pipe);\n return 0;\n }\n\n pid_t pid = fork();\n if (pid < 0) {\n zero_proc_child_close_pair(stdin_pipe);\n zero_proc_child_close_pair(stdout_pipe);\n zero_proc_child_close_pair(stderr_pipe);\n return 0;\n }\n\n if (pid == 0) {\n (void)setpgid(0, 0);\n if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);\n if (!zero_runtime_proc_apply_env_block(env)) _exit(127);\n ZERO_RUNTIME_CLOSE(stdin_pipe[1]);\n ZERO_RUNTIME_CLOSE(stdout_pipe[0]);\n ZERO_RUNTIME_CLOSE(stderr_pipe[0]);\n if (dup2(stdin_pipe[0], STDIN_FILENO) < 0) _exit(127);\n if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) _exit(127);\n if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) _exit(127);\n ZERO_RUNTIME_CLOSE(stdin_pipe[0]);\n ZERO_RUNTIME_CLOSE(stdout_pipe[1]);\n ZERO_RUNTIME_CLOSE(stderr_pipe[1]);\n execvp(argv[0], argv);\n _exit(127);\n }\n\n (void)setpgid(pid, pid);\n ZERO_RUNTIME_CLOSE(stdin_pipe[0]);\n ZERO_RUNTIME_CLOSE(stdout_pipe[1]);\n ZERO_RUNTIME_CLOSE(stderr_pipe[1]);\n if (!zero_proc_child_set_nonblock(stdin_pipe[1]) ||\n !zero_proc_child_set_nonblock(stdout_pipe[0]) ||\n !zero_proc_child_set_nonblock(stderr_pipe[0])) {\n ZERO_RUNTIME_CLOSE(stdin_pipe[1]);\n ZERO_RUNTIME_CLOSE(stdout_pipe[0]);\n ZERO_RUNTIME_CLOSE(stderr_pipe[0]);\n zero_proc_kill_and_reap_pid(pid);\n return 0;\n }\n if (!zero_proc_sigpipe_ignored) {\n signal(SIGPIPE, SIG_IGN);\n zero_proc_sigpipe_ignored = 1;\n }\n\n uint32_t generation = zero_proc_child_next_generation(zero_proc_children[index].generation);\n zero_proc_children[index] = (ZeroRuntimeProcChild){\n .active = 1,\n .generation = generation,\n .reaped = 0,\n .status = 127,\n .pid = pid,\n .stdin_fd = stdin_pipe[1],\n .stdout_fd = stdout_pipe[0],\n .stderr_fd = stderr_pipe[0],\n .pty_fd = -1,\n .pty = 0,\n };\n return zero_proc_child_handle(index, generation);\n#endif\n}\n\nstatic int32_t zero_proc_spawn_child_impl(ZeroByteView command, const char *cwd, char *env) {\n char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n if (!zero_runtime_proc_parse_command(command, storage, argv)) return 0;\n return zero_proc_spawn_child_argv_impl(argv, cwd, env);\n}\n\nint32_t zero_proc_spawn_child(ZeroByteView command) {\n return zero_proc_spawn_child_impl(command, NULL, NULL);\n}\n\nint32_t zero_proc_spawn_child_in(ZeroByteView command, ZeroByteView cwd) {\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(cwd, cwd_buf)) return 0;\n return zero_proc_spawn_child_impl(command, cwd_buf, NULL);\n}\n\n",
|
||||
"int32_t zero_proc_spawn_child_in_env(ZeroByteView command, ZeroByteView cwd, ZeroByteView env) {\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];\n if (!zero_runtime_path_copy(cwd, cwd_buf) || !zero_runtime_proc_copy_env_block(env, env_buf)) return 0;\n return zero_proc_spawn_child_impl(command, cwd_buf, env_buf);\n}\n\nint32_t zero_proc_spawn_child_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env) {\n char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];\n if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||\n !zero_runtime_path_copy(cwd, cwd_buf) ||\n !zero_runtime_proc_copy_env_block(env, env_buf)) return 0;\n return zero_proc_spawn_child_argv_impl(argv, cwd_buf, env_buf);\n}\n\nstatic int32_t zero_pty_spawn_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env) {\n#if defined(_WIN32)\n (void)argv;\n (void)cwd;\n (void)env;\n return 0;\n#else\n if (cwd && !zero_runtime_path_is_dir(cwd)) return 0;\n int index = zero_proc_child_alloc();\n if (index < 0) return 0;\n\n int master_fd = -1;\n pid_t pid = forkpty(&master_fd, NULL, NULL, NULL);\n if (pid < 0) {\n return 0;\n }\n\n if (pid == 0) {\n (void)setpgid(0, 0);\n if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);\n if (!zero_runtime_proc_apply_env_block(env)) _exit(127);\n execvp(argv[0], argv);\n _exit(127);\n }\n\n (void)setpgid(pid, pid);\n if (!zero_proc_child_set_cloexec(master_fd) || !zero_proc_child_set_nonblock(master_fd)) {\n ZERO_RUNTIME_CLOSE(master_fd);\n zero_proc_kill_and_reap_pid(pid);\n return 0;\n }\n if (!zero_proc_sigpipe_ignored) {\n signal(SIGPIPE, SIG_IGN);\n zero_proc_sigpipe_ignored = 1;\n }\n\n uint32_t generation = zero_proc_child_next_generation(zero_proc_children[index].generation);\n zero_proc_children[index] = (ZeroRuntimeProcChild){\n .active = 1,\n .generation = generation,\n .reaped = 0,\n .status = 127,\n .pid = pid,\n .stdin_fd = -1,\n .stdout_fd = -1,\n .stderr_fd = -1,\n .pty_fd = master_fd,\n .pty = 1,\n };\n return zero_proc_child_handle(index, generation);\n#endif\n}\n\nstatic int32_t zero_pty_spawn_impl(ZeroByteView command, const char *cwd, char *env) {\n char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n if (!zero_runtime_proc_parse_command(command, storage, argv)) return 0;\n return zero_pty_spawn_argv_impl(argv, cwd, env);\n}\n\nint32_t zero_pty_spawn(ZeroByteView command) {\n return zero_pty_spawn_impl(command, NULL, NULL);\n}\n\nint32_t zero_pty_spawn_in(ZeroByteView command, ZeroByteView cwd) {\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n if (!zero_runtime_path_copy(cwd, cwd_buf)) return 0;\n return zero_pty_spawn_impl(command, cwd_buf, NULL);\n}\n\n",
|
||||
"int32_t zero_pty_spawn_in_env(ZeroByteView command, ZeroByteView cwd, ZeroByteView env) {\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];\n if (!zero_runtime_path_copy(cwd, cwd_buf) || !zero_runtime_proc_copy_env_block(env, env_buf)) return 0;\n return zero_pty_spawn_impl(command, cwd_buf, env_buf);\n}\n\nint32_t zero_pty_spawn_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env) {\n char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];\n char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];\n char cwd_buf[ZERO_RUNTIME_PATH_BYTES];\n char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];\n if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||\n !zero_runtime_path_copy(cwd, cwd_buf) ||\n !zero_runtime_proc_copy_env_block(env, env_buf)) return 0;\n return zero_pty_spawn_argv_impl(argv, cwd_buf, env_buf);\n}\n\nint32_t zero_pty_resize(int32_t child, uint64_t columns, uint64_t rows) {\n#if defined(_WIN32)\n (void)child;\n (void)columns;\n (void)rows;\n return 0;\n#else\n int index = zero_proc_child_index(child);\n if (index < 0 || columns == 0 || rows == 0 || columns > UINT16_MAX || rows > UINT16_MAX) return 0;\n ZeroRuntimeProcChild *slot = &zero_proc_children[index];\n if (!slot->pty || slot->pty_fd < 0) return 0;\n#if defined(TIOCSWINSZ)\n struct winsize size;\n memset(&size, 0, sizeof(size));\n size.ws_col = (unsigned short)columns;\n size.ws_row = (unsigned short)rows;\n return ioctl(slot->pty_fd, TIOCSWINSZ, &size) == 0 ? 1 : 0;\n#else\n return 0;\n#endif\n#endif\n}\n\nint32_t zero_proc_child_op(int32_t child, uint32_t op) {\n#if defined(_WIN32)\n (void)child;\n (void)op;\n return 0;\n#else\n switch ((ZeroProcChildOp)op) {\n case ZERO_PROC_CHILD_OP_PID_RUNNING:\n return zero_proc_pid_running(child);\n case ZERO_PROC_CHILD_OP_KILL_PID:\n return zero_proc_signal_pid(child, SIGTERM);\n case ZERO_PROC_CHILD_OP_INTERRUPT_PID:\n return zero_proc_signal_pid(child, SIGINT);\n case ZERO_PROC_CHILD_OP_KILL_GROUP_PID:\n return zero_proc_signal_group_pid(child, SIGTERM);\n case ZERO_PROC_CHILD_OP_INTERRUPT_GROUP_PID:\n return zero_proc_signal_group_pid(child, SIGINT);\n default:\n break;\n }\n int index = zero_proc_child_index(child);\n if (index < 0) return op == ZERO_PROC_CHILD_OP_WAIT ? 127 : 0;\n ZeroRuntimeProcChild *slot = &zero_proc_children[index];\n switch ((ZeroProcChildOp)op) {\n case ZERO_PROC_CHILD_OP_VALID:\n return 1;\n case ZERO_PROC_CHILD_OP_PID:\n return (int32_t)slot->pid;\n case ZERO_PROC_CHILD_OP_RUNNING:\n return zero_proc_child_reap(slot, 1) ? 0 : 1;\n case ZERO_PROC_CHILD_OP_WAIT:\n zero_proc_child_wait_with_drain(slot, -1);\n return slot->status;\n case ZERO_PROC_CHILD_OP_KILL:\n if (zero_proc_child_reap(slot, 1)) return 1;\n return zero_proc_signal_child(slot, SIGTERM);\n case ZERO_PROC_CHILD_OP_INTERRUPT:\n",
|
||||
" if (zero_proc_child_reap(slot, 1)) return 1;\n return zero_proc_signal_child(slot, SIGINT);\n case ZERO_PROC_CHILD_OP_CLOSE_STDIN:\n if (slot->pty) return 1;\n zero_proc_child_close_fd(&slot->stdin_fd);\n return 1;\n case ZERO_PROC_CHILD_OP_CLOSE:\n return zero_proc_child_close_handle(slot);\n default:\n return 0;\n }\n#endif\n}\n\nZeroMaybeUsize zero_proc_child_io(int32_t child, ZeroMutByteView buffer, uint32_t op) {\n if (!buffer.ptr && buffer.len > 0) return zero_runtime_none_usize();\n#if defined(_WIN32)\n (void)child;\n (void)op;\n return zero_runtime_none_usize();\n#else\n int index = zero_proc_child_index(child);\n if (index < 0) return zero_runtime_none_usize();\n ZeroRuntimeProcChild *slot = &zero_proc_children[index];\n int fd = -1;\n if (slot->pty) {\n if (op == ZERO_PROC_CHILD_IO_READ_STDOUT || op == ZERO_PROC_CHILD_IO_WRITE_STDIN) fd = slot->pty_fd;\n else return zero_runtime_none_usize();\n } else {\n if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) fd = slot->stdout_fd;\n else if (op == ZERO_PROC_CHILD_IO_READ_STDERR) fd = slot->stderr_fd;\n else if (op == ZERO_PROC_CHILD_IO_WRITE_STDIN) fd = slot->stdin_fd;\n else return zero_runtime_none_usize();\n }\n if (op == ZERO_PROC_CHILD_IO_WRITE_STDIN) {\n if (fd < 0) return zero_runtime_none_usize();\n if (buffer.len == 0) return (ZeroMaybeUsize){1, 0};\n ZeroWriteResult written = ZERO_RUNTIME_WRITE(fd, buffer.ptr, buffer.len > (size_t)UINT_MAX ? UINT_MAX : (unsigned)buffer.len);\n if (written < 0) {\n if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return zero_runtime_none_usize();\n if (slot->pty) zero_proc_child_close_fd(&slot->pty_fd);\n else zero_proc_child_close_fd(&slot->stdin_fd);\n return zero_runtime_none_usize();\n }\n return (ZeroMaybeUsize){1, (uint64_t)written};\n }\n\n if (buffer.len == 0) return zero_runtime_none_usize();\n ZeroRuntimeProcPipeBuffer *pending = NULL;\n if (slot->pty) pending = &slot->pty_buffer;\n else if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) pending = &slot->stdout_buffer;\n else if (op == ZERO_PROC_CHILD_IO_READ_STDERR) pending = &slot->stderr_buffer;\n ZeroMaybeUsize buffered = zero_proc_pipe_buffer_read(pending, buffer);\n if (buffered.has) return buffered;\n if (fd < 0) return zero_runtime_none_usize();\n\n ZeroReadResult got = ZERO_RUNTIME_READ(fd, buffer.ptr, buffer.len > (size_t)UINT_MAX ? UINT_MAX : (unsigned)buffer.len);\n if (got < 0) {\n if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return zero_runtime_none_usize();\n if (slot->pty) zero_proc_child_close_fd(&slot->pty_fd);\n else if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) zero_proc_child_close_fd(&slot->stdout_fd);\n else zero_proc_child_close_fd(&slot->stderr_fd);\n return zero_runtime_none_usize();\n }\n if (got == 0) {\n if (slot->pty) zero_proc_child_close_fd(&slot->pty_fd);\n",
|
||||
" else if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) zero_proc_child_close_fd(&slot->stdout_fd);\n else zero_proc_child_close_fd(&slot->stderr_fd);\n return zero_runtime_none_usize();\n }\n return (ZeroMaybeUsize){1, (uint64_t)got};\n#endif\n}\n\nuint64_t zero_parse_u32(ZeroByteView text) {\n if ((!text.ptr && text.len > 0) || text.len == 0) return 0;\n uint64_t total = 0;\n for (size_t i = 0; i < text.len; i++) {\n unsigned char byte = text.ptr[i];\n if (byte < '0' || byte > '9') return 0;\n uint64_t digit = (uint64_t)(byte - '0');\n if (total > (UINT32_MAX - digit) / 10u) return 0;\n total = total * 10u + digit;\n }\n return (1ull << 32) | total;\n}\n\nuint64_t zero_parse_i32(ZeroByteView text) {\n if ((!text.ptr && text.len > 0) || text.len == 0) return 0;\n size_t index = 0;\n uint32_t limit = 2147483647u;\n uint32_t negative = 0;\n if (text.ptr[0] == '-') {\n negative = 1;\n limit = 2147483648u;\n index = 1;\n } else if (text.ptr[0] == '+') {\n index = 1;\n }\n if (index == text.len) return 0;\n uint64_t total = 0;\n for (; index < text.len; index++) {\n unsigned char byte = text.ptr[index];\n if (byte < '0' || byte > '9') return 0;\n uint64_t digit = (uint64_t)(byte - '0');\n if (total > ((uint64_t)limit - digit) / 10u) return 0;\n total = total * 10u + digit;\n }\n int32_t value = negative ? (total == 2147483648ull ? INT32_MIN : -(int32_t)total) : (int32_t)total;\n return (1ull << 32) | (uint32_t)value;\n}\n\nuint32_t zero_fmt_u32(ZeroMutByteView buffer, uint32_t value) {\n if (!buffer.ptr) return 0;\n unsigned char tmp[10];\n size_t len = 0;\n do {\n tmp[len++] = (unsigned char)('0' + (value % 10u));\n value /= 10u;\n } while (value > 0 && len < sizeof(tmp));\n if (len > buffer.len) return 0;\n for (size_t i = 0; i < len; i++) buffer.ptr[i] = tmp[len - 1 - i];\n return (uint32_t)len;\n}\n\nuint32_t zero_fmt_bool(ZeroMutByteView buffer, uint32_t value) {\n static const unsigned char true_text[] = {'t', 'r', 'u', 'e'};\n static const unsigned char false_text[] = {'f', 'a', 'l', 's', 'e'};\n const unsigned char *text = value ? true_text : false_text;\n size_t len = value ? sizeof(true_text) : sizeof(false_text);\n if (!buffer.ptr || len > buffer.len) return 0;\n memcpy(buffer.ptr, text, len);\n return (uint32_t)len;\n}\n\nuint32_t zero_fmt_hex_lower_u32(ZeroMutByteView buffer, uint32_t value) {\n if (!buffer.ptr) return 0;\n static const unsigned char digits[] = \"0123456789abcdef\";\n unsigned char tmp[8];\n size_t len = 0;\n do {\n tmp[len++] = digits[value & 0xfu];\n value >>= 4;\n } while (value > 0 && len < sizeof(tmp));\n if (len > buffer.len) return 0;\n for (size_t i = 0; i < len; i++) buffer.ptr[i] = tmp[len - 1 - i];\n return (uint32_t)len;\n}\n\nuint32_t zero_fmt_i32(ZeroMutByteView buffer, int32_t value) {\n if (!buffer.ptr) return 0;\n if (value >= 0) return zero_fmt_u32(buffer, (uint32_t)value);\n",
|
||||
" if (buffer.len == 0) return 0;\n buffer.ptr[0] = '-';\n uint32_t magnitude = value == INT32_MIN ? 2147483648u : (uint32_t)(-value);\n ZeroMutByteView rest = {buffer.ptr + 1, buffer.len - 1};\n uint32_t written = zero_fmt_u32(rest, magnitude);\n return written ? written + 1 : 0;\n}\n\nuint32_t zero_fmt_usize(ZeroMutByteView buffer, uint64_t value) {\n if (!buffer.ptr) return 0;\n unsigned char tmp[20];\n size_t len = 0;\n do {\n tmp[len++] = (unsigned char)('0' + (value % 10u));\n value /= 10u;\n } while (value > 0 && len < sizeof(tmp));\n if (len > buffer.len) return 0;\n for (size_t i = 0; i < len; i++) buffer.ptr[i] = tmp[len - 1 - i];\n return (uint32_t)len;\n}\n\nuint64_t zero_args_find(uint64_t argc, const char *const *argv, ZeroByteView name) {\n if (!argv || (!name.ptr && name.len > 0)) return 0;\n for (uint64_t i = 1; i < argc; i++) {\n const char *arg = argv[i];\n if (!arg) continue;\n size_t len = strlen(arg);\n if (len == name.len && memcmp(arg, name.ptr, len) == 0) {\n return (1ull << 32) | (i & 0xffffffffull);\n }\n }\n return 0;\n}\n\nuint32_t zero_ascii_op(uint32_t byte, uint32_t op) {\n byte &= 0xffu;\n uint32_t is_digit = byte >= '0' && byte <= '9';\n uint32_t is_lower = byte >= 'a' && byte <= 'z';\n uint32_t is_upper = byte >= 'A' && byte <= 'Z';\n uint32_t is_alpha = is_lower || is_upper;\n switch (op) {\n case ZERO_ASCII_OP_IS_DIGIT:\n return is_digit;\n case ZERO_ASCII_OP_IS_LOWER:\n return is_lower;\n case ZERO_ASCII_OP_IS_UPPER:\n return is_upper;\n case ZERO_ASCII_OP_IS_ALPHA:\n return is_alpha;\n case ZERO_ASCII_OP_IS_ALNUM:\n return is_alpha || is_digit;\n case ZERO_ASCII_OP_IS_WHITESPACE:\n return byte == ' ' || byte == '\\t' || byte == '\\n' || byte == '\\r';\n case ZERO_ASCII_OP_IS_HEX_DIGIT:\n return is_digit || (byte >= 'A' && byte <= 'F') || (byte >= 'a' && byte <= 'f');\n case ZERO_ASCII_OP_TO_LOWER:\n return is_upper ? byte + ('a' - 'A') : byte;\n case ZERO_ASCII_OP_TO_UPPER:\n return is_lower ? byte - ('a' - 'A') : byte;\n case ZERO_ASCII_OP_DIGIT_VALUE:\n return is_digit ? 0x100u | (byte - '0') : 0;\n case ZERO_ASCII_OP_HEX_VALUE:\n if (is_digit) return 0x100u | (byte - '0');\n if (byte >= 'A' && byte <= 'F') return 0x100u | (byte - 'A' + 10u);\n if (byte >= 'a' && byte <= 'f') return 0x100u | (byte - 'a' + 10u);\n return 0;\n default:\n return 0;\n }\n}\n\nstatic int zero_str_view_valid(ZeroByteView view) {\n return view.ptr || view.len == 0;\n}\n\nstatic uint32_t zero_str_some_len(size_t len) {\n if (len > UINT32_MAX - 1u) return 0;\n return (uint32_t)(len + 1u);\n}\n\nstatic unsigned char zero_str_lower(unsigned char byte) {\n return byte >= 'A' && byte <= 'Z' ? (unsigned char)(byte + ('a' - 'A')) : byte;\n}\n\nstatic unsigned char zero_str_upper(unsigned char byte) {\n",
|
||||
" return byte >= 'a' && byte <= 'z' ? (unsigned char)(byte - ('a' - 'A')) : byte;\n}\n\ntypedef struct {\n uint32_t state[8];\n uint64_t bit_len;\n unsigned char block[64];\n size_t block_len;\n} ZeroSha256;\n\nstatic uint32_t zero_sha256_rotr(uint32_t value, unsigned bits) {\n return (value >> bits) | (value << (32u - bits));\n}\n\nstatic uint32_t zero_sha256_load_be32(const unsigned char *ptr) {\n return ((uint32_t)ptr[0] << 24) |\n ((uint32_t)ptr[1] << 16) |\n ((uint32_t)ptr[2] << 8) |\n (uint32_t)ptr[3];\n}\n\nstatic void zero_sha256_store_be32(unsigned char *ptr, uint32_t value) {\n ptr[0] = (unsigned char)((value >> 24) & 0xffu);\n ptr[1] = (unsigned char)((value >> 16) & 0xffu);\n ptr[2] = (unsigned char)((value >> 8) & 0xffu);\n ptr[3] = (unsigned char)(value & 0xffu);\n}\n\nstatic void zero_sha256_transform(ZeroSha256 *sha, const unsigned char block[64]) {\n static const uint32_t k[64] = {\n UINT32_C(0x428a2f98), UINT32_C(0x71374491), UINT32_C(0xb5c0fbcf), UINT32_C(0xe9b5dba5),\n UINT32_C(0x3956c25b), UINT32_C(0x59f111f1), UINT32_C(0x923f82a4), UINT32_C(0xab1c5ed5),\n UINT32_C(0xd807aa98), UINT32_C(0x12835b01), UINT32_C(0x243185be), UINT32_C(0x550c7dc3),\n UINT32_C(0x72be5d74), UINT32_C(0x80deb1fe), UINT32_C(0x9bdc06a7), UINT32_C(0xc19bf174),\n UINT32_C(0xe49b69c1), UINT32_C(0xefbe4786), UINT32_C(0x0fc19dc6), UINT32_C(0x240ca1cc),\n UINT32_C(0x2de92c6f), UINT32_C(0x4a7484aa), UINT32_C(0x5cb0a9dc), UINT32_C(0x76f988da),\n UINT32_C(0x983e5152), UINT32_C(0xa831c66d), UINT32_C(0xb00327c8), UINT32_C(0xbf597fc7),\n UINT32_C(0xc6e00bf3), UINT32_C(0xd5a79147), UINT32_C(0x06ca6351), UINT32_C(0x14292967),\n UINT32_C(0x27b70a85), UINT32_C(0x2e1b2138), UINT32_C(0x4d2c6dfc), UINT32_C(0x53380d13),\n UINT32_C(0x650a7354), UINT32_C(0x766a0abb), UINT32_C(0x81c2c92e), UINT32_C(0x92722c85),\n UINT32_C(0xa2bfe8a1), UINT32_C(0xa81a664b), UINT32_C(0xc24b8b70), UINT32_C(0xc76c51a3),\n UINT32_C(0xd192e819), UINT32_C(0xd6990624), UINT32_C(0xf40e3585), UINT32_C(0x106aa070),\n UINT32_C(0x19a4c116), UINT32_C(0x1e376c08), UINT32_C(0x2748774c), UINT32_C(0x34b0bcb5),\n UINT32_C(0x391c0cb3), UINT32_C(0x4ed8aa4a), UINT32_C(0x5b9cca4f), UINT32_C(0x682e6ff3),\n UINT32_C(0x748f82ee), UINT32_C(0x78a5636f), UINT32_C(0x84c87814), UINT32_C(0x8cc70208),\n UINT32_C(0x90befffa), UINT32_C(0xa4506ceb), UINT32_C(0xbef9a3f7), UINT32_C(0xc67178f2)\n };\n uint32_t w[64];\n for (size_t i = 0; i < 16; i++) w[i] = zero_sha256_load_be32(block + i * 4u);\n for (size_t i = 16; i < 64; i++) {\n uint32_t s0 = zero_sha256_rotr(w[i - 15u], 7) ^ zero_sha256_rotr(w[i - 15u], 18) ^ (w[i - 15u] >> 3);\n uint32_t s1 = zero_sha256_rotr(w[i - 2u], 17) ^ zero_sha256_rotr(w[i - 2u], 19) ^ (w[i - 2u] >> 10);\n w[i] = w[i - 16u] + s0 + w[i - 7u] + s1;\n }\n\n uint32_t a = sha->state[0];\n uint32_t b = sha->state[1];\n uint32_t c = sha->state[2];\n uint32_t d = sha->state[3];\n",
|
||||
" uint32_t e = sha->state[4];\n uint32_t f = sha->state[5];\n uint32_t g = sha->state[6];\n uint32_t h = sha->state[7];\n\n for (size_t i = 0; i < 64; i++) {\n uint32_t s1 = zero_sha256_rotr(e, 6) ^ zero_sha256_rotr(e, 11) ^ zero_sha256_rotr(e, 25);\n uint32_t ch = (e & f) ^ ((~e) & g);\n uint32_t temp1 = h + s1 + ch + k[i] + w[i];\n uint32_t s0 = zero_sha256_rotr(a, 2) ^ zero_sha256_rotr(a, 13) ^ zero_sha256_rotr(a, 22);\n uint32_t maj = (a & b) ^ (a & c) ^ (b & c);\n uint32_t temp2 = s0 + maj;\n h = g;\n g = f;\n f = e;\n e = d + temp1;\n d = c;\n c = b;\n b = a;\n a = temp1 + temp2;\n }\n\n sha->state[0] += a;\n sha->state[1] += b;\n sha->state[2] += c;\n sha->state[3] += d;\n sha->state[4] += e;\n sha->state[5] += f;\n sha->state[6] += g;\n sha->state[7] += h;\n}\n\nstatic void zero_sha256_init(ZeroSha256 *sha) {\n sha->state[0] = UINT32_C(0x6a09e667);\n sha->state[1] = UINT32_C(0xbb67ae85);\n sha->state[2] = UINT32_C(0x3c6ef372);\n sha->state[3] = UINT32_C(0xa54ff53a);\n sha->state[4] = UINT32_C(0x510e527f);\n sha->state[5] = UINT32_C(0x9b05688c);\n sha->state[6] = UINT32_C(0x1f83d9ab);\n sha->state[7] = UINT32_C(0x5be0cd19);\n sha->bit_len = 0;\n sha->block_len = 0;\n}\n\nstatic void zero_sha256_update(ZeroSha256 *sha, const unsigned char *bytes, size_t len) {\n if (!bytes && len > 0) return;\n while (len > 0) {\n size_t take = 64u - sha->block_len;\n if (take > len) take = len;\n memcpy(sha->block + sha->block_len, bytes, take);\n sha->block_len += take;\n bytes += take;\n len -= take;\n if (sha->block_len == 64u) {\n zero_sha256_transform(sha, sha->block);\n sha->bit_len += 512u;\n sha->block_len = 0;\n }\n }\n}\n\nstatic void zero_sha256_final(ZeroSha256 *sha, unsigned char digest[32]) {\n uint64_t total_bits = sha->bit_len + (uint64_t)sha->block_len * 8u;\n sha->block[sha->block_len++] = 0x80u;\n if (sha->block_len > 56u) {\n while (sha->block_len < 64u) sha->block[sha->block_len++] = 0;\n zero_sha256_transform(sha, sha->block);\n sha->block_len = 0;\n }\n while (sha->block_len < 56u) sha->block[sha->block_len++] = 0;\n for (size_t i = 0; i < 8; i++) sha->block[63u - i] = (unsigned char)((total_bits >> (i * 8u)) & 0xffu);\n zero_sha256_transform(sha, sha->block);\n for (size_t i = 0; i < 8; i++) zero_sha256_store_be32(digest + i * 4u, sha->state[i]);\n}\n\nstatic void zero_sha256_hash(ZeroByteView bytes, unsigned char digest[32]) {\n ZeroSha256 sha;\n zero_sha256_init(&sha);\n zero_sha256_update(&sha, bytes.ptr, bytes.len);\n zero_sha256_final(&sha, digest);\n}\n\nstatic void zero_sha256_hmac(ZeroByteView key, ZeroByteView bytes, unsigned char digest[32]) {\n unsigned char key_block[64];\n unsigned char key_hash[32];\n unsigned char ipad[64];\n unsigned char opad[64];\n unsigned char inner[32];\n memset(key_block, 0, sizeof(key_block));\n if (key.len > 64u) {\n zero_sha256_hash(key, key_hash);\n",
|
||||
" memcpy(key_block, key_hash, sizeof(key_hash));\n } else if (key.len > 0) {\n memcpy(key_block, key.ptr, key.len);\n }\n for (size_t i = 0; i < 64u; i++) {\n ipad[i] = (unsigned char)(key_block[i] ^ 0x36u);\n opad[i] = (unsigned char)(key_block[i] ^ 0x5cu);\n }\n\n ZeroSha256 sha;\n zero_sha256_init(&sha);\n zero_sha256_update(&sha, ipad, sizeof(ipad));\n zero_sha256_update(&sha, bytes.ptr, bytes.len);\n zero_sha256_final(&sha, inner);\n\n zero_sha256_init(&sha);\n zero_sha256_update(&sha, opad, sizeof(opad));\n zero_sha256_update(&sha, inner, sizeof(inner));\n zero_sha256_final(&sha, digest);\n}\n\nstatic uint32_t zero_crypto_write_digest_hex(ZeroMutByteView buffer, const unsigned char digest[32]) {\n if (buffer.len < 64u) return 0;\n static const unsigned char hex[] = \"0123456789abcdef\";\n for (size_t i = 0; i < 32u; i++) {\n buffer.ptr[i * 2u] = hex[digest[i] >> 4];\n buffer.ptr[i * 2u + 1u] = hex[digest[i] & 0x0fu];\n }\n return zero_str_some_len(64u);\n}\n\nuint32_t zero_crypto_digest(ZeroMutByteView buffer, ZeroByteView bytes, uint32_t op) {\n if (!buffer.ptr || !zero_str_view_valid(bytes)) return 0;\n unsigned char digest[32];\n zero_sha256_hash(bytes, digest);\n if (op == ZERO_CRYPTO_DIGEST_SHA256) {\n if (buffer.len < 32u) return 0;\n memcpy(buffer.ptr, digest, 32u);\n return zero_str_some_len(32u);\n }\n if (op == ZERO_CRYPTO_DIGEST_SHA256_HEX) {\n return zero_crypto_write_digest_hex(buffer, digest);\n }\n return 0;\n}\n\nuint32_t zero_crypto_hmac_sha256(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView bytes) {\n if (!buffer.ptr || !zero_str_view_valid(key) || !zero_str_view_valid(bytes) || buffer.len < 32u) return 0;\n unsigned char digest[32];\n zero_sha256_hmac(key, bytes, digest);\n memcpy(buffer.ptr, digest, sizeof(digest));\n return zero_str_some_len(sizeof(digest));\n}\n\nuint32_t zero_crypto_hmac_sha256_hex(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView bytes) {\n if (!buffer.ptr || !zero_str_view_valid(key) || !zero_str_view_valid(bytes)) return 0;\n unsigned char digest[32];\n zero_sha256_hmac(key, bytes, digest);\n return zero_crypto_write_digest_hex(buffer, digest);\n}\n\nstatic int zero_str_ascii_space(unsigned char byte) {\n return byte == ' ' || byte == '\\t' || byte == '\\n' || byte == '\\r';\n}\n\nstatic size_t zero_text_utf8_sequence_len(unsigned char first) {\n if (first < 128u) return 1;\n if (first >= 194u && first <= 223u) return 2;\n if (first >= 224u && first <= 239u) return 3;\n if (first >= 240u && first <= 244u) return 4;\n return 0;\n}\n\nstatic int zero_text_is_cont(unsigned char byte) {\n return byte >= 128u && byte <= 191u;\n}\n\nstatic uint64_t zero_text_utf8_len_encoded(ZeroByteView text, int count_codepoints) {\n if (!zero_str_view_valid(text)) return 0;\n size_t index = 0;\n uint64_t count = 0;\n while (index < text.len) {\n unsigned char first = text.ptr[index];\n",
|
||||
" size_t width = zero_text_utf8_sequence_len(first);\n if (width == 0 || index + width > text.len) return 0;\n if (width == 2) {\n if (!zero_text_is_cont(text.ptr[index + 1])) return 0;\n } else if (width == 3) {\n unsigned char second = text.ptr[index + 1];\n if (!zero_text_is_cont(second) || !zero_text_is_cont(text.ptr[index + 2])) return 0;\n if (first == 224u && second < 160u) return 0;\n if (first == 237u && second > 159u) return 0;\n } else if (width == 4) {\n unsigned char second = text.ptr[index + 1];\n if (!zero_text_is_cont(second) || !zero_text_is_cont(text.ptr[index + 2]) || !zero_text_is_cont(text.ptr[index + 3])) return 0;\n if (first == 240u && second < 144u) return 0;\n if (first == 244u && second > 143u) return 0;\n }\n index += width;\n count++;\n }\n if (!count_codepoints) return 1;\n if (count == UINT64_MAX) return 0;\n return count + 1u;\n}\n\nuint64_t zero_text_op(ZeroByteView text, uint32_t op) {\n switch (op) {\n case ZERO_TEXT_OP_IS_ASCII:\n if (!zero_str_view_valid(text)) return 0;\n for (size_t i = 0; i < text.len; i++) {\n if (text.ptr[i] >= 128u) return 0;\n }\n return 1;\n case ZERO_TEXT_OP_UTF8_VALID:\n return zero_text_utf8_len_encoded(text, 0);\n case ZERO_TEXT_OP_UTF8_LEN:\n return zero_text_utf8_len_encoded(text, 1);\n default:\n return 0;\n }\n}\n\n#define ZERO_TERM_KEY_ARROW_UP UINT32_C(1114113)\n#define ZERO_TERM_KEY_ARROW_DOWN UINT32_C(1114114)\n#define ZERO_TERM_KEY_ARROW_RIGHT UINT32_C(1114115)\n#define ZERO_TERM_KEY_ARROW_LEFT UINT32_C(1114116)\n#define ZERO_TERM_KEY_DELETE UINT32_C(1114117)\n#define ZERO_TERM_KEY_HOME UINT32_C(1114118)\n#define ZERO_TERM_KEY_END UINT32_C(1114119)\n#define ZERO_TERM_KEY_PAGE_UP UINT32_C(1114120)\n#define ZERO_TERM_KEY_PAGE_DOWN UINT32_C(1114121)\n#define ZERO_TERM_KEY_INSERT UINT32_C(1114122)\n#define ZERO_TERM_KEY_SHIFT_TAB UINT32_C(1114123)\n#define ZERO_TERM_KEY_F1 UINT32_C(1114124)\n#define ZERO_TERM_KEY_F2 UINT32_C(1114125)\n#define ZERO_TERM_KEY_F3 UINT32_C(1114126)\n#define ZERO_TERM_KEY_F4 UINT32_C(1114127)\n#define ZERO_TERM_KEY_F5 UINT32_C(1114128)\n#define ZERO_TERM_KEY_F6 UINT32_C(1114129)\n#define ZERO_TERM_KEY_F7 UINT32_C(1114130)\n#define ZERO_TERM_KEY_F8 UINT32_C(1114131)\n#define ZERO_TERM_KEY_F9 UINT32_C(1114132)\n#define ZERO_TERM_KEY_F10 UINT32_C(1114133)\n#define ZERO_TERM_KEY_F11 UINT32_C(1114134)\n#define ZERO_TERM_KEY_F12 UINT32_C(1114135)\n#define ZERO_TERM_KEY_PASTE_START UINT32_C(1114136)\n#define ZERO_TERM_KEY_PASTE_END UINT32_C(1114137)\n\nstatic uint32_t zero_term_utf8_key(ZeroByteView text, size_t *width_out) {\n if (text.len == 0 || !text.ptr) return 0;\n unsigned char first = text.ptr[0];\n size_t width = zero_text_utf8_sequence_len(first);\n if (width == 0 || width > text.len) return 0;\n uint32_t code = first;\n if (width == 2) {\n unsigned char second = text.ptr[1];\n",
|
||||
" if (!zero_text_is_cont(second)) return 0;\n code = ((uint32_t)(first & 0x1fu) << 6) | (uint32_t)(second & 0x3fu);\n } else if (width == 3) {\n unsigned char second = text.ptr[1];\n unsigned char third = text.ptr[2];\n if (!zero_text_is_cont(second) || !zero_text_is_cont(third)) return 0;\n if (first == 224u && second < 160u) return 0;\n if (first == 237u && second > 159u) return 0;\n code = ((uint32_t)(first & 0x0fu) << 12) | ((uint32_t)(second & 0x3fu) << 6) | (uint32_t)(third & 0x3fu);\n } else if (width == 4) {\n unsigned char second = text.ptr[1];\n unsigned char third = text.ptr[2];\n unsigned char fourth = text.ptr[3];\n if (!zero_text_is_cont(second) || !zero_text_is_cont(third) || !zero_text_is_cont(fourth)) return 0;\n if (first == 240u && second < 144u) return 0;\n if (first == 244u && second > 143u) return 0;\n code = ((uint32_t)(first & 0x07u) << 18) | ((uint32_t)(second & 0x3fu) << 12) | ((uint32_t)(third & 0x3fu) << 6) | (uint32_t)(fourth & 0x3fu);\n }\n if (width_out) *width_out = width;\n return code;\n}\n\nstatic uint32_t zero_term_key_decode(ZeroByteView text, int return_len) {\n if (!zero_str_view_valid(text) || text.len == 0) return 0;\n unsigned char first = text.ptr[0];\n if (first == 0x1bu) {\n if (text.len == 1) return return_len ? 1u : 27u;\n if (text.len >= 3 && (text.ptr[1] == '[' || text.ptr[1] == 'O')) {\n unsigned char code = text.ptr[2];\n if (code == 'A') return return_len ? 3u : ZERO_TERM_KEY_ARROW_UP;\n if (code == 'B') return return_len ? 3u : ZERO_TERM_KEY_ARROW_DOWN;\n if (code == 'C') return return_len ? 3u : ZERO_TERM_KEY_ARROW_RIGHT;\n if (code == 'D') return return_len ? 3u : ZERO_TERM_KEY_ARROW_LEFT;\n if (code == 'H') return return_len ? 3u : ZERO_TERM_KEY_HOME;\n if (code == 'F') return return_len ? 3u : ZERO_TERM_KEY_END;\n if (code == 'Z' && text.ptr[1] == '[') return return_len ? 3u : ZERO_TERM_KEY_SHIFT_TAB;\n if (code == 'P' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F1;\n if (code == 'Q' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F2;\n if (code == 'R' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F3;\n if (code == 'S' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F4;\n }\n if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '2' && text.ptr[3] == '~') {\n return return_len ? 4u : ZERO_TERM_KEY_INSERT;\n }\n if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '3' && text.ptr[3] == '~') {\n return return_len ? 4u : ZERO_TERM_KEY_DELETE;\n }\n if (text.len >= 4 && text.ptr[1] == '[' && (text.ptr[2] == '1' || text.ptr[2] == '7') && text.ptr[3] == '~') {\n return return_len ? 4u : ZERO_TERM_KEY_HOME;\n }\n if (text.len >= 4 && text.ptr[1] == '[' && (text.ptr[2] == '4' || text.ptr[2] == '8') && text.ptr[3] == '~') {\n",
|
||||
" return return_len ? 4u : ZERO_TERM_KEY_END;\n }\n if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '5' && text.ptr[3] == '~') {\n return return_len ? 4u : ZERO_TERM_KEY_PAGE_UP;\n }\n if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '6' && text.ptr[3] == '~') {\n return return_len ? 4u : ZERO_TERM_KEY_PAGE_DOWN;\n }\n if (text.len >= 5 && text.ptr[1] == '[' && text.ptr[4] == '~') {\n unsigned char a = text.ptr[2];\n unsigned char b = text.ptr[3];\n if (a == '1' && b == '1') return return_len ? 5u : ZERO_TERM_KEY_F1;\n if (a == '1' && b == '2') return return_len ? 5u : ZERO_TERM_KEY_F2;\n if (a == '1' && b == '3') return return_len ? 5u : ZERO_TERM_KEY_F3;\n if (a == '1' && b == '4') return return_len ? 5u : ZERO_TERM_KEY_F4;\n if (a == '1' && b == '5') return return_len ? 5u : ZERO_TERM_KEY_F5;\n if (a == '1' && b == '7') return return_len ? 5u : ZERO_TERM_KEY_F6;\n if (a == '1' && b == '8') return return_len ? 5u : ZERO_TERM_KEY_F7;\n if (a == '1' && b == '9') return return_len ? 5u : ZERO_TERM_KEY_F8;\n if (a == '2' && b == '0') return return_len ? 5u : ZERO_TERM_KEY_F9;\n if (a == '2' && b == '1') return return_len ? 5u : ZERO_TERM_KEY_F10;\n if (a == '2' && b == '3') return return_len ? 5u : ZERO_TERM_KEY_F11;\n if (a == '2' && b == '4') return return_len ? 5u : ZERO_TERM_KEY_F12;\n }\n if (text.len >= 6 && text.ptr[1] == '[' && text.ptr[2] == '2' && text.ptr[3] == '0' && text.ptr[5] == '~') {\n if (text.ptr[4] == '0') return return_len ? 6u : ZERO_TERM_KEY_PASTE_START;\n if (text.ptr[4] == '1') return return_len ? 6u : ZERO_TERM_KEY_PASTE_END;\n }\n return 0;\n }\n if (first == '\\r' || first == '\\n') return return_len ? 1u : 13u;\n if (first == '\\t') return return_len ? 1u : 9u;\n if (first == 0x7fu || first == 0x08u) return return_len ? 1u : 127u;\n if (first < 32u) return return_len ? 1u : (uint32_t)first;\n if (first < 128u) return return_len ? 1u : (uint32_t)first;\n size_t width = 0;\n uint32_t code = zero_term_utf8_key(text, &width);\n if (!code) return 0;\n return return_len ? (uint32_t)width : code;\n}\n\nstatic uint64_t zero_term_size_or(uint64_t fallback, int height) {\n#if !defined(_WIN32) && defined(TIOCGWINSZ)\n struct winsize size;\n if (ioctl(ZERO_RUNTIME_STDOUT_FD, TIOCGWINSZ, &size) == 0) {\n unsigned value = height ? size.ws_row : size.ws_col;\n if (value > 0) return value;\n }\n#endif\n return fallback;\n}\n\n#if defined(_WIN32)\nstatic DWORD zero_term_original_mode;\nstatic int zero_term_raw_active;\nstatic int zero_term_restore_registered;\n\nstatic void zero_term_restore_at_exit(void) {\n if (!zero_term_raw_active) return;\n HANDLE input = GetStdHandle(STD_INPUT_HANDLE);\n if (input == INVALID_HANDLE_VALUE) return;\n SetConsoleMode(input, zero_term_original_mode);\n zero_term_raw_active = 0;\n}\n\nstatic uint64_t zero_term_enter_raw_mode(void) {\n",
|
||||
" HANDLE input = GetStdHandle(STD_INPUT_HANDLE);\n if (input == INVALID_HANDLE_VALUE) return 0;\n DWORD mode = 0;\n if (!GetConsoleMode(input, &mode)) return 0;\n if (zero_term_raw_active) return 1;\n DWORD raw = mode;\n raw &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);\n raw |= ENABLE_VIRTUAL_TERMINAL_INPUT;\n if (!SetConsoleMode(input, raw)) return 0;\n zero_term_original_mode = mode;\n zero_term_raw_active = 1;\n if (!zero_term_restore_registered) {\n atexit(zero_term_restore_at_exit);\n zero_term_restore_registered = 1;\n }\n return 1;\n}\n\nstatic uint64_t zero_term_leave_raw_mode(void) {\n if (!zero_term_raw_active) return 1;\n HANDLE input = GetStdHandle(STD_INPUT_HANDLE);\n if (input == INVALID_HANDLE_VALUE) return 0;\n if (!SetConsoleMode(input, zero_term_original_mode)) return 0;\n zero_term_raw_active = 0;\n return 1;\n}\n#else\nstatic struct termios zero_term_original_mode;\nstatic int zero_term_raw_active;\nstatic int zero_term_restore_registered;\n\nstatic void zero_term_restore_at_exit(void) {\n if (!zero_term_raw_active) return;\n tcsetattr(ZERO_RUNTIME_STDIN_FD, TCSAFLUSH, &zero_term_original_mode);\n zero_term_raw_active = 0;\n}\n\nstatic uint64_t zero_term_enter_raw_mode(void) {\n if (!ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDIN_FD)) return 0;\n struct termios mode;\n if (tcgetattr(ZERO_RUNTIME_STDIN_FD, &mode) != 0) return 0;\n if (zero_term_raw_active) return 1;\n zero_term_original_mode = mode;\n mode.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);\n mode.c_oflag &= ~(OPOST);\n mode.c_cflag |= CS8;\n#if defined(IEXTEN)\n mode.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);\n#else\n mode.c_lflag &= ~(ECHO | ICANON | ISIG);\n#endif\n mode.c_cc[VMIN] = 0;\n mode.c_cc[VTIME] = 0;\n if (tcsetattr(ZERO_RUNTIME_STDIN_FD, TCSAFLUSH, &mode) != 0) return 0;\n zero_term_raw_active = 1;\n if (!zero_term_restore_registered) {\n atexit(zero_term_restore_at_exit);\n zero_term_restore_registered = 1;\n }\n return 1;\n}\n\nstatic uint64_t zero_term_leave_raw_mode(void) {\n if (!zero_term_raw_active) return 1;\n if (tcsetattr(ZERO_RUNTIME_STDIN_FD, TCSAFLUSH, &zero_term_original_mode) != 0) return 0;\n zero_term_raw_active = 0;\n return 1;\n}\n#endif\n\nstatic size_t zero_term_read_chunk_len(size_t len) {\n return len > (size_t)UINT_MAX ? (size_t)UINT_MAX : len;\n}\n\nZeroMaybeUsize zero_term_read_input(ZeroMutByteView buffer) {\n if (!buffer.ptr || buffer.len == 0) return zero_runtime_none_usize();\n size_t chunk = zero_term_read_chunk_len(buffer.len);\n#if defined(_WIN32)\n HANDLE input = GetStdHandle(STD_INPUT_HANDLE);\n if (input == INVALID_HANDLE_VALUE) return zero_runtime_none_usize();\n if (ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDIN_FD)) {\n if (!_kbhit()) return zero_runtime_none_usize();\n int first = _getch();\n if (first == 0 || first == 0xe0) {\n if (!_kbhit()) return zero_runtime_none_usize();\n int second = _getch();\n",
|
||||
" const unsigned char *sequence = NULL;\n size_t len = 0;\n static const unsigned char up[] = {0x1b, '[', 'A'};\n static const unsigned char down[] = {0x1b, '[', 'B'};\n static const unsigned char right[] = {0x1b, '[', 'C'};\n static const unsigned char left[] = {0x1b, '[', 'D'};\n static const unsigned char del[] = {0x1b, '[', '3', '~'};\n if (second == 72) { sequence = up; len = sizeof(up); }\n else if (second == 80) { sequence = down; len = sizeof(down); }\n else if (second == 77) { sequence = right; len = sizeof(right); }\n else if (second == 75) { sequence = left; len = sizeof(left); }\n else if (second == 83) { sequence = del; len = sizeof(del); }\n if (!sequence || len > buffer.len) return zero_runtime_none_usize();\n memcpy(buffer.ptr, sequence, len);\n return (ZeroMaybeUsize){1, (uint64_t)len};\n }\n buffer.ptr[0] = (unsigned char)first;\n return (ZeroMaybeUsize){1, 1};\n }\n DWORD type = GetFileType(input);\n if (type == FILE_TYPE_PIPE) {\n DWORD available = 0;\n if (!PeekNamedPipe(input, NULL, 0, NULL, &available, NULL) || available == 0) return zero_runtime_none_usize();\n if ((uint64_t)chunk > (uint64_t)available) chunk = (size_t)available;\n } else if (type != FILE_TYPE_DISK) {\n return zero_runtime_none_usize();\n }\n int got = ZERO_RUNTIME_READ(ZERO_RUNTIME_STDIN_FD, buffer.ptr, (unsigned)chunk);\n if (got <= 0) return zero_runtime_none_usize();\n return (ZeroMaybeUsize){1, (uint64_t)got};\n#else\n for (;;) {\n fd_set read_set;\n FD_ZERO(&read_set);\n FD_SET(ZERO_RUNTIME_STDIN_FD, &read_set);\n struct timeval timeout;\n timeout.tv_sec = 0;\n timeout.tv_usec = 0;\n int ready = select(ZERO_RUNTIME_STDIN_FD + 1, &read_set, NULL, NULL, &timeout);\n if (ready < 0) {\n if (errno == EINTR) continue;\n return zero_runtime_none_usize();\n }\n if (ready == 0) return zero_runtime_none_usize();\n ZeroReadResult got = ZERO_RUNTIME_READ(ZERO_RUNTIME_STDIN_FD, buffer.ptr, (unsigned)chunk);\n if (got < 0) {\n if (errno == EINTR) continue;\n if (errno == EAGAIN || errno == EWOULDBLOCK) return zero_runtime_none_usize();\n return zero_runtime_none_usize();\n }\n if (got == 0) return zero_runtime_none_usize();\n return (ZeroMaybeUsize){1, (uint64_t)got};\n }\n#endif\n}\n\nuint64_t zero_term_op(uint64_t fallback, uint32_t op) {\n switch ((ZeroTermOp)op) {\n case ZERO_TERM_OP_STDIN_IS_TTY:\n return ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDIN_FD) ? 1u : 0u;\n case ZERO_TERM_OP_STDOUT_IS_TTY:\n return ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDOUT_FD) ? 1u : 0u;\n case ZERO_TERM_OP_WIDTH_OR:\n return zero_term_size_or(fallback, 0);\n case ZERO_TERM_OP_HEIGHT_OR:\n return zero_term_size_or(fallback, 1);\n case ZERO_TERM_OP_ENTER_RAW_MODE:\n return zero_term_enter_raw_mode();\n case ZERO_TERM_OP_LEAVE_RAW_MODE:\n",
|
||||
" return zero_term_leave_raw_mode();\n default:\n return fallback;\n }\n}\n\nstatic int zero_parse_ascii_digit(unsigned char byte) {\n return byte >= '0' && byte <= '9';\n}\n\nstatic int zero_parse_ascii_alpha(unsigned char byte) {\n return (byte >= 'A' && byte <= 'Z') || (byte >= 'a' && byte <= 'z');\n}\n\nstatic int zero_parse_identifier_start(unsigned char byte) {\n return zero_parse_ascii_alpha(byte) || byte == '_';\n}\n\nstatic int zero_parse_identifier_byte(unsigned char byte) {\n return zero_parse_identifier_start(byte) || zero_parse_ascii_digit(byte);\n}\n\nstatic uint64_t zero_parse_u32_max(ZeroByteView text, uint32_t max) {\n if (!zero_str_view_valid(text) || text.len == 0) return 0;\n uint64_t total = 0;\n for (size_t i = 0; i < text.len; i++) {\n unsigned char byte = text.ptr[i];\n if (!zero_parse_ascii_digit(byte)) return 0;\n uint64_t digit = (uint64_t)(byte - '0');\n if (total > ((uint64_t)max - digit) / 10u) return 0;\n total = total * 10u + digit;\n }\n return (1ull << 32) | total;\n}\n\nuint64_t zero_parse_op(ZeroByteView text, uint32_t arg, uint32_t op) {\n if (!zero_str_view_valid(text)) return 0;\n unsigned char first = text.len > 0 ? text.ptr[0] : 0;\n switch (op) {\n case ZERO_PARSE_OP_IS_ASCII_DIGIT:\n return text.len > 0 && zero_parse_ascii_digit(first) ? 1 : 0;\n case ZERO_PARSE_OP_IS_ASCII_ALPHA:\n return text.len > 0 && zero_parse_ascii_alpha(first) ? 1 : 0;\n case ZERO_PARSE_OP_IS_IDENTIFIER_START:\n return text.len > 0 && zero_parse_identifier_start(first) ? 1 : 0;\n case ZERO_PARSE_OP_IS_WHITESPACE:\n return text.len > 0 && zero_str_ascii_space(first) ? 1 : 0;\n case ZERO_PARSE_OP_SCAN_DIGITS: {\n size_t index = 0;\n while (index < text.len && zero_parse_ascii_digit(text.ptr[index])) index++;\n return index;\n }\n case ZERO_PARSE_OP_SCAN_IDENTIFIER: {\n if (text.len == 0 || !zero_parse_identifier_start(first)) return 0;\n size_t index = 1;\n while (index < text.len && zero_parse_identifier_byte(text.ptr[index])) index++;\n return index;\n }\n case ZERO_PARSE_OP_SCAN_UNTIL_BYTE: {\n unsigned char target = (unsigned char)(arg & 0xffu);\n for (size_t i = 0; i < text.len; i++) {\n if (text.ptr[i] == target) return i;\n }\n return text.len;\n }\n case ZERO_PARSE_OP_SCAN_WHITESPACE: {\n size_t index = 0;\n while (index < text.len && zero_str_ascii_space(text.ptr[index])) index++;\n return index;\n }\n case ZERO_PARSE_OP_PARSE_BOOL:\n if (text.len == 4 && memcmp(text.ptr, \"true\", 4) == 0) return (1ull << 32) | 1u;\n if (text.len == 5 && memcmp(text.ptr, \"false\", 5) == 0) return (1ull << 32);\n return 0;\n case ZERO_PARSE_OP_PARSE_U8:\n return zero_parse_u32_max(text, UINT8_MAX);\n case ZERO_PARSE_OP_PARSE_U16:\n return zero_parse_u32_max(text, UINT16_MAX);\n case ZERO_PARSE_OP_TERM_KEY_CODE:\n",
|
||||
" return zero_term_key_decode(text, 0);\n case ZERO_PARSE_OP_TERM_KEY_BYTE_LEN:\n return zero_term_key_decode(text, 1);\n default:\n return 0;\n }\n}\n\nZeroMaybeUsize zero_parse_usize(ZeroByteView text) {\n if (!zero_str_view_valid(text) || text.len == 0) return (ZeroMaybeUsize){0, 0};\n uint64_t total = 0;\n for (size_t i = 0; i < text.len; i++) {\n unsigned char byte = text.ptr[i];\n if (!zero_parse_ascii_digit(byte)) return (ZeroMaybeUsize){0, 0};\n uint64_t digit = (uint64_t)(byte - '0');\n if (total > (UINT64_MAX - digit) / 10u) return (ZeroMaybeUsize){0, 0};\n total = total * 10u + digit;\n }\n return (ZeroMaybeUsize){1, total};\n}\n\nstatic int64_t zero_time_floor_div(int64_t value, int64_t divisor) {\n int64_t quotient = value / divisor;\n int64_t remainder = value % divisor;\n if (value < 0 && remainder != 0) return quotient - 1;\n return quotient;\n}\n\nstatic int64_t zero_time_sleep_ns(int64_t ns) {\n if (ns <= 0) return 1;\n#if defined(_WIN32)\n uint64_t remaining_ms = ((uint64_t)ns + 999999u) / 1000000u;\n while (remaining_ms > 0) {\n DWORD chunk = remaining_ms > 0xffffffffu ? 0xffffffffu : (DWORD)remaining_ms;\n Sleep(chunk);\n remaining_ms -= chunk;\n }\n return 1;\n#else\n struct timespec req;\n req.tv_sec = (time_t)(ns / 1000000000);\n req.tv_nsec = (long)(ns % 1000000000);\n while (nanosleep(&req, &req) != 0) {\n if (errno == EINTR) continue;\n return 0;\n }\n return 1;\n#endif\n}\n\nstatic int64_t zero_time_wall_seconds(void) {\n#if defined(_WIN32)\n FILETIME ft;\n GetSystemTimeAsFileTime(&ft);\n uint64_t ticks = ((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime;\n if (ticks < 116444736000000000ull) return 0;\n return (int64_t)((ticks - 116444736000000000ull) / 10000000ull);\n#else\n return (int64_t)time(NULL);\n#endif\n}\n\n#if defined(_WIN32)\nstatic uint64_t zero_time_scale_qpc_remainder_ns(uint64_t remainder, uint64_t frequency) {\n if (frequency == 0) return 0;\n uint64_t quotient = 0;\n uint64_t rem = 0;\n uint64_t term_quotient = remainder / frequency;\n uint64_t term_rem = remainder % frequency;\n uint64_t multiplier = 1000000000ull;\n while (multiplier > 0) {\n if ((multiplier & 1ull) != 0) {\n if (quotient > UINT64_MAX - term_quotient) return UINT64_MAX;\n quotient += term_quotient;\n uint64_t gap = frequency - term_rem;\n if (term_rem != 0 && rem >= gap) {\n if (quotient == UINT64_MAX) return UINT64_MAX;\n quotient++;\n rem -= gap;\n } else {\n rem += term_rem;\n }\n }\n multiplier >>= 1;\n if (multiplier == 0) break;\n if (term_quotient > UINT64_MAX / 2u) return UINT64_MAX;\n term_quotient *= 2u;\n uint64_t gap = frequency - term_rem;\n if (term_rem != 0 && term_rem >= gap) {\n if (term_quotient == UINT64_MAX) return UINT64_MAX;\n term_quotient++;\n term_rem -= gap;\n } else {\n term_rem += term_rem;\n }\n",
|
||||
" }\n return quotient;\n}\n#endif\n\nstatic int64_t zero_time_monotonic_ns(void) {\n#if defined(_WIN32)\n LARGE_INTEGER frequency;\n LARGE_INTEGER counter;\n if (!QueryPerformanceFrequency(&frequency) || !QueryPerformanceCounter(&counter) || frequency.QuadPart <= 0 || counter.QuadPart < 0) return 0;\n uint64_t qpc_frequency = (uint64_t)frequency.QuadPart;\n uint64_t qpc_ticks = (uint64_t)counter.QuadPart;\n uint64_t seconds = qpc_ticks / qpc_frequency;\n uint64_t remainder_ns = zero_time_scale_qpc_remainder_ns(qpc_ticks % qpc_frequency, qpc_frequency);\n if (seconds > (uint64_t)(INT64_MAX / 1000000000ll)) return INT64_MAX;\n uint64_t whole_ns = seconds * 1000000000ull;\n if (remainder_ns > (uint64_t)INT64_MAX - whole_ns) return INT64_MAX;\n return (int64_t)(whole_ns + remainder_ns);\n#else\n struct timespec ts;\n if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;\n return (int64_t)ts.tv_sec * 1000000000ll + (int64_t)ts.tv_nsec;\n#endif\n}\n\nint64_t zero_time_op(int64_t a, int64_t b, int64_t c, uint32_t op) {\n switch (op) {\n case ZERO_TIME_OP_AS_US_FLOOR:\n return zero_time_floor_div(a, 1000);\n case ZERO_TIME_OP_AS_MS_FLOOR:\n return zero_time_floor_div(a, 1000000);\n case ZERO_TIME_OP_AS_SECONDS_FLOOR:\n return zero_time_floor_div(a, 1000000000);\n case ZERO_TIME_OP_MIN:\n return a < b ? a : b;\n case ZERO_TIME_OP_MAX:\n return a < b ? b : a;\n case ZERO_TIME_OP_CLAMP: {\n int64_t lower = b < c ? b : c;\n int64_t upper = b < c ? c : b;\n if (a < lower) return lower;\n if (upper < a) return upper;\n return a;\n }\n case ZERO_TIME_OP_SLEEP:\n return zero_time_sleep_ns(a);\n case ZERO_TIME_OP_WALL_SECONDS:\n (void)a;\n (void)b;\n (void)c;\n return zero_time_wall_seconds();\n case ZERO_TIME_OP_MONOTONIC:\n (void)a;\n (void)b;\n (void)c;\n return zero_time_monotonic_ns();\n default:\n return 0;\n }\n}\n\nstatic uint64_t zero_math_clamp_u64(uint64_t value, uint64_t low, uint64_t high) {\n uint64_t lower = low < high ? low : high;\n uint64_t upper = low < high ? high : low;\n if (value < lower) return lower;\n if (upper < value) return upper;\n return value;\n}\n\nstatic int64_t zero_math_clamp_i64(int64_t value, int64_t low, int64_t high) {\n int64_t lower = low < high ? low : high;\n int64_t upper = low < high ? high : low;\n if (value < lower) return lower;\n if (upper < value) return upper;\n return value;\n}\n\nstatic uint64_t zero_math_some_u32(uint32_t value) {\n return (1ull << 32) | (uint64_t)value;\n}\n\nstatic ZeroMaybeUsize zero_math_some_usize(uint64_t value) {\n return (ZeroMaybeUsize){1, value};\n}\n\nstatic uint64_t zero_math_checked_add_u32(uint32_t left, uint32_t right) {\n if (left > UINT32_MAX - right) return 0;\n return zero_math_some_u32(left + right);\n}\n\nstatic uint64_t zero_math_checked_sub_u32(uint32_t left, uint32_t right) {\n",
|
||||
" if (left < right) return 0;\n return zero_math_some_u32(left - right);\n}\n\nstatic uint64_t zero_math_checked_mul_u32(uint32_t left, uint32_t right) {\n if (left != 0 && right > UINT32_MAX / left) return 0;\n return zero_math_some_u32(left * right);\n}\n\nstatic uint64_t zero_math_checked_add_i32(int32_t left, int32_t right) {\n int64_t wide = (int64_t)left + (int64_t)right;\n if (wide > INT32_MAX || wide < INT32_MIN) return 0;\n return zero_math_some_u32((uint32_t)(int32_t)wide);\n}\n\nstatic uint64_t zero_math_checked_sub_i32(int32_t left, int32_t right) {\n int64_t wide = (int64_t)left - (int64_t)right;\n if (wide > INT32_MAX || wide < INT32_MIN) return 0;\n return zero_math_some_u32((uint32_t)(int32_t)wide);\n}\n\nstatic uint64_t zero_math_checked_mul_i32(int32_t left, int32_t right) {\n int64_t wide = (int64_t)left * (int64_t)right;\n if (wide > INT32_MAX || wide < INT32_MIN) return 0;\n return zero_math_some_u32((uint32_t)(int32_t)wide);\n}\n\nstatic uint32_t zero_math_gcd_u32(uint32_t a, uint32_t b) {\n while (b != 0) {\n uint32_t next = a % b;\n a = b;\n b = next;\n }\n return a;\n}\n\nstatic uint64_t zero_math_checked_lcm_u32(uint32_t left, uint32_t right) {\n if (left == 0 || right == 0) return zero_math_some_u32(0);\n uint32_t gcd = zero_math_gcd_u32(left, right);\n return zero_math_checked_mul_u32(left / gcd, right);\n}\n\nstatic uint64_t zero_math_checked_pow_u32(uint32_t base, uint32_t exponent) {\n uint32_t result = 1;\n uint32_t current = base;\n uint32_t exp = exponent;\n while (exp > 0) {\n if (exp % 2 == 1) {\n uint64_t next_result = zero_math_checked_mul_u32(result, current);\n if ((next_result >> 32) == 0) return 0;\n result = (uint32_t)next_result;\n }\n exp = exp / 2;\n if (exp > 0) {\n uint64_t next_current = zero_math_checked_mul_u32(current, current);\n if ((next_current >> 32) == 0) return 0;\n current = (uint32_t)next_current;\n }\n }\n return zero_math_some_u32(result);\n}\n\nstatic uint32_t zero_math_pow_u32(uint32_t base, uint32_t exponent) {\n uint32_t result = 1;\n uint32_t current = base;\n uint32_t exp = exponent;\n while (exp > 0) {\n if (exp % 2 == 1) result = result * current;\n current = current * current;\n exp = exp / 2;\n }\n return result;\n}\n\nstatic uint32_t zero_math_mod_pow_u32(uint32_t base, uint32_t exponent, uint32_t modulus) {\n if (modulus == 0) return 0;\n uint64_t wide_modulus = modulus;\n uint64_t result = 1ull % wide_modulus;\n uint64_t current = ((uint64_t)base) % wide_modulus;\n uint32_t exp = exponent;\n while (exp > 0) {\n if (exp % 2 == 1) result = result * current % wide_modulus;\n current = current * current % wide_modulus;\n exp = exp / 2;\n }\n return (uint32_t)result;\n}\n\nstatic uint32_t zero_math_is_prime_u32(uint32_t value) {\n if (value < 2) return 0;\n if (value == 2) return 1;\n if (value % 2 == 0) return 0;\n uint32_t divisor = 3;\n",
|
||||
" while (divisor <= value / divisor) {\n if (value % divisor == 0) return 0;\n divisor += 2;\n }\n return 1;\n}\n\nstatic uint32_t zero_math_sqrt_floor_u32(uint32_t value) {\n uint32_t low = 0;\n uint32_t high = 65536;\n while (low + 1 < high) {\n uint32_t mid = low + (high - low) / 2;\n if (mid <= value / mid) low = mid;\n else high = mid;\n }\n return low;\n}\n\nstatic uint64_t zero_math_factorial_u32(uint32_t value) {\n uint32_t result = 1;\n uint32_t next = 2;\n while (next <= value) {\n uint64_t multiplied = zero_math_checked_mul_u32(result, next);\n if ((multiplied >> 32) == 0) return 0;\n result = (uint32_t)multiplied;\n next++;\n }\n return zero_math_some_u32(result);\n}\n\nstatic uint64_t zero_math_binomial_u32(uint32_t n, uint32_t k0) {\n if (k0 > n) return 0;\n uint32_t k = k0;\n if (k > n - k) k = n - k;\n uint32_t result = 1;\n uint32_t i = 1;\n while (i <= k) {\n uint32_t numerator = n - k + i;\n uint32_t denominator = i;\n uint32_t numerator_gcd = zero_math_gcd_u32(numerator, denominator);\n numerator = numerator / numerator_gcd;\n denominator = denominator / numerator_gcd;\n uint32_t result_gcd = zero_math_gcd_u32(result, denominator);\n result = result / result_gcd;\n denominator = denominator / result_gcd;\n if (denominator != 1) return 0;\n uint64_t multiplied = zero_math_checked_mul_u32(result, numerator);\n if ((multiplied >> 32) == 0) return 0;\n result = (uint32_t)multiplied;\n i++;\n }\n return zero_math_some_u32(result);\n}\n\nstatic uint32_t zero_math_divisor_count_u32(uint32_t value) {\n if (value == 0) return 0;\n uint32_t count = 0;\n uint32_t divisor = 1;\n while (divisor <= value / divisor) {\n if (value % divisor == 0) {\n uint32_t paired = value / divisor;\n count += paired == divisor ? 1u : 2u;\n }\n divisor++;\n }\n return count;\n}\n\nstatic uint32_t zero_math_proper_divisor_sum_u32(uint32_t value) {\n if (value <= 1) return 0;\n uint32_t sum = 1;\n uint32_t divisor = 2;\n while (divisor <= value / divisor) {\n if (value % divisor == 0) {\n uint32_t paired = value / divisor;\n sum += divisor;\n if (paired != divisor) sum += paired;\n }\n divisor++;\n }\n return sum;\n}\n\nstatic ZeroMaybeUsize zero_math_checked_add_usize(uint64_t left, uint64_t right) {\n if (left > UINT64_MAX - right) return (ZeroMaybeUsize){0, 0};\n return zero_math_some_usize(left + right);\n}\n\nstatic ZeroMaybeUsize zero_math_checked_sub_usize(uint64_t left, uint64_t right) {\n if (left < right) return (ZeroMaybeUsize){0, 0};\n return zero_math_some_usize(left - right);\n}\n\nstatic ZeroMaybeUsize zero_math_checked_mul_usize(uint64_t left, uint64_t right) {\n if (left != 0 && right > UINT64_MAX / left) return (ZeroMaybeUsize){0, 0};\n return zero_math_some_usize(left * right);\n}\n\nZeroMaybeUsize zero_math_usize_op(uint64_t a, uint64_t b, uint64_t c, uint32_t op) {\n (void)c;\n",
|
||||
" switch (op) {\n case ZERO_MATH_OP_CHECKED_ADD_USIZE:\n return zero_math_checked_add_usize(a, b);\n case ZERO_MATH_OP_CHECKED_SUB_USIZE:\n return zero_math_checked_sub_usize(a, b);\n case ZERO_MATH_OP_CHECKED_MUL_USIZE:\n return zero_math_checked_mul_usize(a, b);\n default:\n return (ZeroMaybeUsize){0, 0};\n }\n}\n\nuint64_t zero_math_op(int64_t a, int64_t b, int64_t c, uint32_t op) {\n switch (op) {\n case ZERO_MATH_OP_MIN_I32: {\n int32_t left = (int32_t)a;\n int32_t right = (int32_t)b;\n return (uint64_t)(int64_t)(left < right ? left : right);\n }\n case ZERO_MATH_OP_MAX_I32: {\n int32_t left = (int32_t)a;\n int32_t right = (int32_t)b;\n return (uint64_t)(int64_t)(left > right ? left : right);\n }\n case ZERO_MATH_OP_CLAMP_I32: {\n int32_t value = (int32_t)a;\n int32_t low = (int32_t)b;\n int32_t high = (int32_t)c;\n int32_t lower = low < high ? low : high;\n int32_t upper = low < high ? high : low;\n if (value < lower) return (uint64_t)(int64_t)lower;\n if (upper < value) return (uint64_t)(int64_t)upper;\n return (uint64_t)(int64_t)value;\n }\n case ZERO_MATH_OP_MIN_I64:\n return (uint64_t)(a < b ? a : b);\n case ZERO_MATH_OP_MAX_I64:\n return (uint64_t)(a > b ? a : b);\n case ZERO_MATH_OP_CLAMP_I64:\n return (uint64_t)zero_math_clamp_i64(a, b, c);\n case ZERO_MATH_OP_MIN_U32: {\n uint32_t left = (uint32_t)a;\n uint32_t right = (uint32_t)b;\n return left < right ? left : right;\n }\n case ZERO_MATH_OP_MAX_U32: {\n uint32_t left = (uint32_t)a;\n uint32_t right = (uint32_t)b;\n return left > right ? left : right;\n }\n case ZERO_MATH_OP_CLAMP_U32:\n return (uint32_t)zero_math_clamp_u64((uint32_t)a, (uint32_t)b, (uint32_t)c);\n case ZERO_MATH_OP_MIN_U64:\n case ZERO_MATH_OP_MIN_USIZE: {\n uint64_t left = (uint64_t)a;\n uint64_t right = (uint64_t)b;\n return left < right ? left : right;\n }\n case ZERO_MATH_OP_MAX_U64:\n case ZERO_MATH_OP_MAX_USIZE: {\n uint64_t left = (uint64_t)a;\n uint64_t right = (uint64_t)b;\n return left > right ? left : right;\n }\n case ZERO_MATH_OP_CLAMP_U64:\n case ZERO_MATH_OP_CLAMP_USIZE:\n return zero_math_clamp_u64((uint64_t)a, (uint64_t)b, (uint64_t)c);\n case ZERO_MATH_OP_ABS_I32: {\n int32_t value = (int32_t)a;\n if (value == INT32_MIN) return 2147483648u;\n return value < 0 ? (uint32_t)(-value) : (uint32_t)value;\n }\n case ZERO_MATH_OP_ABS_I64:\n if (a == INT64_MIN) return 9223372036854775808ull;\n return a < 0 ? (uint64_t)(-a) : (uint64_t)a;\n case ZERO_MATH_OP_CHECKED_ADD_U32:\n return zero_math_checked_add_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_CHECKED_SUB_U32:\n return zero_math_checked_sub_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_CHECKED_MUL_U32:\n",
|
||||
" return zero_math_checked_mul_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_SATURATING_ADD_U32: {\n uint64_t result = zero_math_checked_add_u32((uint32_t)a, (uint32_t)b);\n return (result >> 32) ? (uint32_t)result : UINT32_MAX;\n }\n case ZERO_MATH_OP_SATURATING_SUB_U32: {\n uint64_t result = zero_math_checked_sub_u32((uint32_t)a, (uint32_t)b);\n return (result >> 32) ? (uint32_t)result : 0;\n }\n case ZERO_MATH_OP_SATURATING_MUL_U32: {\n uint64_t result = zero_math_checked_mul_u32((uint32_t)a, (uint32_t)b);\n return (result >> 32) ? (uint32_t)result : UINT32_MAX;\n }\n case ZERO_MATH_OP_CHECKED_ADD_I32:\n return zero_math_checked_add_i32((int32_t)a, (int32_t)b);\n case ZERO_MATH_OP_CHECKED_SUB_I32:\n return zero_math_checked_sub_i32((int32_t)a, (int32_t)b);\n case ZERO_MATH_OP_CHECKED_MUL_I32:\n return zero_math_checked_mul_i32((int32_t)a, (int32_t)b);\n case ZERO_MATH_OP_SATURATING_ADD_I32: {\n int64_t wide = (int64_t)(int32_t)a + (int64_t)(int32_t)b;\n if (wide > INT32_MAX) return (uint32_t)INT32_MAX;\n if (wide < INT32_MIN) return (uint64_t)(int64_t)INT32_MIN;\n return (uint64_t)(int64_t)(int32_t)wide;\n }\n case ZERO_MATH_OP_SATURATING_SUB_I32: {\n int64_t wide = (int64_t)(int32_t)a - (int64_t)(int32_t)b;\n if (wide > INT32_MAX) return (uint32_t)INT32_MAX;\n if (wide < INT32_MIN) return (uint64_t)(int64_t)INT32_MIN;\n return (uint64_t)(int64_t)(int32_t)wide;\n }\n case ZERO_MATH_OP_SATURATING_MUL_I32: {\n int32_t left = (int32_t)a;\n int32_t right = (int32_t)b;\n int64_t wide = (int64_t)left * (int64_t)right;\n if (wide > INT32_MAX) return (uint32_t)INT32_MAX;\n if (wide < INT32_MIN) return (uint64_t)(int64_t)INT32_MIN;\n return (uint64_t)(int64_t)(int32_t)wide;\n }\n case ZERO_MATH_OP_GCD_U32:\n return zero_math_gcd_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_LCM_U32: {\n uint32_t left = (uint32_t)a;\n uint32_t right = (uint32_t)b;\n if (left == 0 || right == 0) return 0;\n return (uint32_t)((left / zero_math_gcd_u32(left, right)) * right);\n }\n case ZERO_MATH_OP_CHECKED_LCM_U32:\n return zero_math_checked_lcm_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_POW_U32:\n return zero_math_pow_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_CHECKED_POW_U32:\n return zero_math_checked_pow_u32((uint32_t)a, (uint32_t)b);\n case ZERO_MATH_OP_MOD_POW_U32:\n return zero_math_mod_pow_u32((uint32_t)a, (uint32_t)b, (uint32_t)c);\n case ZERO_MATH_OP_IS_PRIME_U32:\n return zero_math_is_prime_u32((uint32_t)a);\n case ZERO_MATH_OP_SQRT_FLOOR_U32:\n return zero_math_sqrt_floor_u32((uint32_t)a);\n case ZERO_MATH_OP_FACTORIAL_U32:\n return zero_math_factorial_u32((uint32_t)a);\n case ZERO_MATH_OP_BINOMIAL_U32:\n return zero_math_binomial_u32((uint32_t)a, (uint32_t)b);\n",
|
||||
" case ZERO_MATH_OP_DIVISOR_COUNT_U32:\n return zero_math_divisor_count_u32((uint32_t)a);\n case ZERO_MATH_OP_PROPER_DIVISOR_SUM_U32:\n return zero_math_proper_divisor_sum_u32((uint32_t)a);\n case ZERO_MATH_OP_SATURATING_ADD_USIZE: {\n ZeroMaybeUsize result = zero_math_checked_add_usize((uint64_t)a, (uint64_t)b);\n return result.has ? result.value : UINT64_MAX;\n }\n case ZERO_MATH_OP_SATURATING_SUB_USIZE: {\n ZeroMaybeUsize result = zero_math_checked_sub_usize((uint64_t)a, (uint64_t)b);\n return result.has ? result.value : 0;\n }\n case ZERO_MATH_OP_SATURATING_MUL_USIZE: {\n ZeroMaybeUsize result = zero_math_checked_mul_usize((uint64_t)a, (uint64_t)b);\n return result.has ? result.value : UINT64_MAX;\n }\n default:\n return 0;\n }\n}\n\nstatic size_t zero_search_lower_bound_i32(const int32_t *items, size_t len, int32_t needle) {\n size_t low = 0;\n size_t high = len;\n while (low < high) {\n size_t mid = low + (high - low) / 2u;\n if (items[mid] < needle) low = mid + 1u;\n else high = mid;\n }\n return low;\n}\n\nstatic size_t zero_search_upper_bound_i32(const int32_t *items, size_t len, int32_t needle) {\n size_t low = 0;\n size_t high = len;\n while (low < high) {\n size_t mid = low + (high - low) / 2u;\n if (items[mid] <= needle) low = mid + 1u;\n else high = mid;\n }\n return low;\n}\n\nstatic size_t zero_search_lower_bound_u32(const uint32_t *items, size_t len, uint32_t needle) {\n size_t low = 0;\n size_t high = len;\n while (low < high) {\n size_t mid = low + (high - low) / 2u;\n if (items[mid] < needle) low = mid + 1u;\n else high = mid;\n }\n return low;\n}\n\nstatic size_t zero_search_upper_bound_u32(const uint32_t *items, size_t len, uint32_t needle) {\n size_t low = 0;\n size_t high = len;\n while (low < high) {\n size_t mid = low + (high - low) / 2u;\n if (items[mid] <= needle) low = mid + 1u;\n else high = mid;\n }\n return low;\n}\n\nstatic size_t zero_search_lower_bound_usize(const uint64_t *items, size_t len, uint64_t needle) {\n size_t low = 0;\n size_t high = len;\n while (low < high) {\n size_t mid = low + (high - low) / 2u;\n if (items[mid] < needle) low = mid + 1u;\n else high = mid;\n }\n return low;\n}\n\nstatic size_t zero_search_upper_bound_usize(const uint64_t *items, size_t len, uint64_t needle) {\n size_t low = 0;\n size_t high = len;\n while (low < high) {\n size_t mid = low + (high - low) / 2u;\n if (items[mid] <= needle) low = mid + 1u;\n else high = mid;\n }\n return low;\n}\n\nuint64_t zero_search_op(ZeroByteView items, int64_t needle, uint32_t op) {\n if (!zero_str_view_valid(items)) return 0;\n switch (op) {\n case ZERO_SEARCH_OP_LOWER_BOUND_I32:\n return zero_search_lower_bound_i32((const int32_t *)items.ptr, items.len, (int32_t)needle);\n case ZERO_SEARCH_OP_BINARY_I32: {\n",
|
||||
" size_t index = zero_search_lower_bound_i32((const int32_t *)items.ptr, items.len, (int32_t)needle);\n return index < items.len && ((const int32_t *)items.ptr)[index] == (int32_t)needle ? index : items.len;\n }\n case ZERO_SEARCH_OP_LOWER_BOUND_U32:\n return zero_search_lower_bound_u32((const uint32_t *)items.ptr, items.len, (uint32_t)needle);\n case ZERO_SEARCH_OP_BINARY_U32: {\n size_t index = zero_search_lower_bound_u32((const uint32_t *)items.ptr, items.len, (uint32_t)needle);\n return index < items.len && ((const uint32_t *)items.ptr)[index] == (uint32_t)needle ? index : items.len;\n }\n case ZERO_SEARCH_OP_LOWER_BOUND_USIZE:\n return zero_search_lower_bound_usize((const uint64_t *)items.ptr, items.len, (uint64_t)needle);\n case ZERO_SEARCH_OP_BINARY_USIZE: {\n size_t index = zero_search_lower_bound_usize((const uint64_t *)items.ptr, items.len, (uint64_t)needle);\n return index < items.len && ((const uint64_t *)items.ptr)[index] == (uint64_t)needle ? index : items.len;\n }\n case ZERO_SEARCH_OP_UPPER_BOUND_I32:\n return zero_search_upper_bound_i32((const int32_t *)items.ptr, items.len, (int32_t)needle);\n case ZERO_SEARCH_OP_UPPER_BOUND_U32:\n return zero_search_upper_bound_u32((const uint32_t *)items.ptr, items.len, (uint32_t)needle);\n case ZERO_SEARCH_OP_UPPER_BOUND_USIZE:\n return zero_search_upper_bound_usize((const uint64_t *)items.ptr, items.len, (uint64_t)needle);\n default:\n return 0;\n }\n}\n\nstatic void zero_sort_insertion_i32(int32_t *items, size_t len) {\n for (size_t index = 1; index < len; index++) {\n int32_t value = items[index];\n size_t cursor = index;\n while (cursor > 0 && items[cursor - 1u] > value) {\n items[cursor] = items[cursor - 1u];\n cursor--;\n }\n items[cursor] = value;\n }\n}\n\nstatic void zero_sort_insertion_u32(uint32_t *items, size_t len) {\n for (size_t index = 1; index < len; index++) {\n uint32_t value = items[index];\n size_t cursor = index;\n while (cursor > 0 && items[cursor - 1u] > value) {\n items[cursor] = items[cursor - 1u];\n cursor--;\n }\n items[cursor] = value;\n }\n}\n\nstatic void zero_sort_insertion_usize(uint64_t *items, size_t len) {\n for (size_t index = 1; index < len; index++) {\n uint64_t value = items[index];\n size_t cursor = index;\n while (cursor > 0 && items[cursor - 1u] > value) {\n items[cursor] = items[cursor - 1u];\n cursor--;\n }\n items[cursor] = value;\n }\n}\n\nvoid zero_sort_op(ZeroMutByteView items, uint32_t op) {\n if ((!items.ptr && items.len > 0)) return;\n switch (op) {\n case ZERO_SORT_OP_INSERTION_I32:\n zero_sort_insertion_i32((int32_t *)items.ptr, items.len);\n break;\n case ZERO_SORT_OP_INSERTION_U32:\n zero_sort_insertion_u32((uint32_t *)items.ptr, items.len);\n break;\n case ZERO_SORT_OP_INSERTION_USIZE:\n",
|
||||
" zero_sort_insertion_usize((uint64_t *)items.ptr, items.len);\n break;\n default:\n break;\n }\n}\n\nuint32_t zero_sort_is_sorted_op(ZeroByteView items, uint32_t op) {\n if (!zero_str_view_valid(items)) return 0;\n switch (op) {\n case ZERO_SORT_OP_IS_SORTED_I32: {\n const int32_t *values = (const int32_t *)items.ptr;\n for (size_t i = 1; i < items.len; i++) if (values[i] < values[i - 1u]) return 0;\n return 1;\n }\n case ZERO_SORT_OP_IS_SORTED_U32: {\n const uint32_t *values = (const uint32_t *)items.ptr;\n for (size_t i = 1; i < items.len; i++) if (values[i] < values[i - 1u]) return 0;\n return 1;\n }\n case ZERO_SORT_OP_IS_SORTED_USIZE: {\n const uint64_t *values = (const uint64_t *)items.ptr;\n for (size_t i = 1; i < items.len; i++) if (values[i] < values[i - 1u]) return 0;\n return 1;\n }\n default:\n return 0;\n }\n}\n\nstatic int zero_str_eq_at(ZeroByteView text, size_t start, ZeroByteView needle) {\n if (start > text.len || needle.len > text.len - start) return 0;\n for (size_t i = 0; i < needle.len; i++) {\n if (text.ptr[start + i] != needle.ptr[i]) return 0;\n }\n return 1;\n}\n\nuint32_t zero_str_buffer_op(ZeroMutByteView buffer, ZeroByteView text, uint32_t op) {\n if (!buffer.ptr || !zero_str_view_valid(text) || text.len > buffer.len) return 0;\n uint32_t some_len = zero_str_some_len(text.len);\n if (!some_len) return 0;\n for (size_t i = 0; i < text.len; i++) {\n unsigned char byte = text.ptr[i];\n switch (op) {\n case ZERO_STR_OP_REVERSE:\n byte = text.ptr[text.len - 1u - i];\n break;\n case ZERO_STR_OP_COPY:\n break;\n case ZERO_STR_OP_TO_LOWER_ASCII:\n byte = zero_str_lower(byte);\n break;\n case ZERO_STR_OP_TO_UPPER_ASCII:\n byte = zero_str_upper(byte);\n break;\n default:\n return 0;\n }\n buffer.ptr[i] = byte;\n }\n return some_len;\n}\n\nuint32_t zero_str_concat(ZeroMutByteView buffer, ZeroByteView left, ZeroByteView right) {\n if (!buffer.ptr || !zero_str_view_valid(left) || !zero_str_view_valid(right)) return 0;\n if (left.len > buffer.len || right.len > buffer.len - left.len) return 0;\n uint32_t some_len = zero_str_some_len(left.len + right.len);\n if (!some_len) return 0;\n if (left.len > 0) memcpy(buffer.ptr, left.ptr, left.len);\n if (right.len > 0) memcpy(buffer.ptr + left.len, right.ptr, right.len);\n return some_len;\n}\n\nuint32_t zero_str_repeat(ZeroMutByteView buffer, ZeroByteView text, uint64_t count) {\n if (!buffer.ptr || !zero_str_view_valid(text)) return 0;\n if (text.len > 0 && count > (uint64_t)(buffer.len / text.len)) return 0;\n uint64_t total64 = (uint64_t)text.len * count;\n if (total64 > UINT32_MAX - 1u) return 0;\n size_t total = (size_t)total64;\n uint32_t some_len = zero_str_some_len(total);\n if (!some_len) return 0;\n size_t out = 0;\n for (uint64_t r = 0; r < count; r++) {\n",
|
||||
" if (text.len > 0) memcpy(buffer.ptr + out, text.ptr, text.len);\n out += text.len;\n }\n return some_len;\n}\n\nuint64_t zero_str_trim_op(ZeroByteView text, uint32_t op) {\n if (!zero_str_view_valid(text) || text.len > UINT32_MAX) return 0;\n size_t start = 0;\n size_t end = text.len;\n if (op == ZERO_STR_OP_PATH_BASENAME ||\n op == ZERO_STR_OP_PATH_DIRNAME ||\n op == ZERO_STR_OP_PATH_EXTENSION) {\n while (end > 0 && (text.ptr[end - 1u] == '/' || text.ptr[end - 1u] == '\\\\')) end--;\n if (op == ZERO_STR_OP_PATH_BASENAME) {\n start = end;\n while (start > 0 && text.ptr[start - 1u] != '/' && text.ptr[start - 1u] != '\\\\') start--;\n return ((uint64_t)(uint32_t)start << 32) | (uint32_t)(end - start);\n }\n if (op == ZERO_STR_OP_PATH_DIRNAME) {\n size_t root_len = text.len > 0 && (text.ptr[0] == '/' || text.ptr[0] == '\\\\') ? 1u : 0u;\n if (root_len > 0 && end == 0) return (uint32_t)root_len;\n size_t split = end;\n while (split > 0 && text.ptr[split - 1u] != '/' && text.ptr[split - 1u] != '\\\\') split--;\n while (split > root_len && (text.ptr[split - 1u] == '/' || text.ptr[split - 1u] == '\\\\')) split--;\n return (uint32_t)split;\n }\n start = end;\n while (start > 0 && text.ptr[start - 1u] != '/' && text.ptr[start - 1u] != '\\\\') start--;\n size_t dot = end;\n size_t scan = end;\n while (scan > start) {\n scan--;\n if (text.ptr[scan] == '.') {\n dot = scan;\n scan = start;\n }\n }\n if (dot == end || dot == start) return ((uint64_t)(uint32_t)end << 32);\n return ((uint64_t)(uint32_t)(dot + 1u) << 32) | (uint32_t)(end - dot - 1u);\n }\n if (op == ZERO_STR_OP_PARSE_TOKEN_ASCII) {\n while (start < end && zero_str_ascii_space(text.ptr[start])) start++;\n size_t token_end = start;\n while (token_end < end && !zero_str_ascii_space(text.ptr[token_end])) token_end++;\n return ((uint64_t)(uint32_t)start << 32) | (uint32_t)(token_end - start);\n }\n if (op == ZERO_STR_OP_TRIM_ASCII || op == ZERO_STR_OP_TRIM_START_ASCII) {\n while (start < end && zero_str_ascii_space(text.ptr[start])) start++;\n }\n if (op == ZERO_STR_OP_TRIM_ASCII || op == ZERO_STR_OP_TRIM_END_ASCII) {\n while (end > start && zero_str_ascii_space(text.ptr[end - 1u])) end--;\n }\n return ((uint64_t)(uint32_t)start << 32) | (uint32_t)(end - start);\n}\n\nuint64_t zero_str_pair_op(ZeroByteView text, ZeroByteView needle, uint32_t op) {\n if (!zero_str_view_valid(text) || !zero_str_view_valid(needle)) return 0;\n switch (op) {\n case ZERO_STR_OP_STARTS_WITH:\n return needle.len <= text.len && zero_str_eq_at(text, 0, needle) ? 1 : 0;\n case ZERO_STR_OP_ENDS_WITH:\n return needle.len <= text.len && zero_str_eq_at(text, text.len - needle.len, needle) ? 1 : 0;\n case ZERO_STR_OP_CONTAINS:\n return zero_str_pair_op(text, needle, ZERO_STR_OP_INDEX_OF) != text.len || needle.len == 0 ? 1 : 0;\n",
|
||||
" case ZERO_STR_OP_INDEX_OF:\n if (needle.len == 0) return 0;\n if (needle.len > text.len) return text.len;\n for (size_t start = 0; start <= text.len - needle.len; start++) {\n if (zero_str_eq_at(text, start, needle)) return start;\n }\n return text.len;\n case ZERO_STR_OP_LAST_INDEX_OF:\n if (needle.len == 0 || needle.len > text.len) return text.len;\n for (size_t start = text.len - needle.len + 1u; start > 0; start--) {\n size_t candidate = start - 1u;\n if (zero_str_eq_at(text, candidate, needle)) return candidate;\n }\n return text.len;\n case ZERO_STR_OP_COUNT: {\n if (needle.len == 0) return text.len + 1u;\n size_t index = 0;\n uint64_t count = 0;\n while (index <= text.len) {\n ZeroByteView rest = { text.ptr ? text.ptr + index : NULL, text.len - index };\n uint64_t found = zero_str_pair_op(rest, needle, ZERO_STR_OP_INDEX_OF);\n if (found == rest.len) return count;\n count++;\n index += (size_t)found + needle.len;\n }\n return count;\n }\n case ZERO_STR_OP_EQL_IGNORE_ASCII_CASE:\n if (text.len != needle.len) return 0;\n for (size_t i = 0; i < text.len; i++) {\n if (zero_str_lower(text.ptr[i]) != zero_str_lower(needle.ptr[i])) return 0;\n }\n return 1;\n default:\n return 0;\n }\n}\n\nuint64_t zero_str_count_byte(ZeroByteView text, uint32_t byte) {\n if (!zero_str_view_valid(text)) return 0;\n uint64_t count = 0;\n unsigned char target = (unsigned char)(byte & 0xffu);\n for (size_t i = 0; i < text.len; i++) {\n if (text.ptr[i] == target) count++;\n }\n return count;\n}\n\nuint64_t zero_str_word_count_ascii(ZeroByteView text) {\n if (!zero_str_view_valid(text)) return 0;\n uint64_t count = 0;\n int in_word = 0;\n for (size_t i = 0; i < text.len; i++) {\n if (zero_str_ascii_space(text.ptr[i])) {\n in_word = 0;\n } else if (!in_word) {\n count++;\n in_word = 1;\n }\n }\n return count;\n}\n\ntypedef struct {\n const unsigned char *ptr;\n size_t len;\n size_t pos;\n size_t tokens;\n size_t error_pos;\n} ZeroJsonScanner;\n\nstatic int zero_json_fail(ZeroJsonScanner *scanner, size_t pos) {\n if (scanner) scanner->error_pos = pos;\n return 0;\n}\n\nstatic int zero_json_hex_value(unsigned char ch) {\n if (ch >= '0' && ch <= '9') return (int)(ch - '0');\n if (ch >= 'a' && ch <= 'f') return (int)(ch - 'a' + 10);\n if (ch >= 'A' && ch <= 'F') return (int)(ch - 'A' + 10);\n return -1;\n}\n\nstatic int zero_json_hex4_value(const unsigned char *ptr, size_t len, size_t pos, uint16_t *out) {\n if (!ptr || pos + 4 > len) return 0;\n uint16_t value = 0;\n for (size_t i = 0; i < 4; i++) {\n int digit = zero_json_hex_value(ptr[pos + i]);\n if (digit < 0) return 0;\n value = (uint16_t)((value << 4) | (uint16_t)digit);\n }\n if (out) *out = value;\n return 1;\n}\n\nstatic int zero_json_high_surrogate(uint16_t value) {\n",
|
||||
" return value >= 0xd800u && value <= 0xdbffu;\n}\n\nstatic int zero_json_low_surrogate(uint16_t value) {\n return value >= 0xdc00u && value <= 0xdfffu;\n}\n\nstatic uint32_t zero_json_surrogate_codepoint(uint16_t high, uint16_t low) {\n uint32_t high_part = (uint32_t)high - 0xd800u;\n uint32_t low_part = (uint32_t)low - 0xdc00u;\n return 0x10000u + ((high_part << 10) | low_part);\n}\n\nstatic size_t zero_json_write_utf8_bytes(uint32_t codepoint, unsigned char out[4]) {\n if (codepoint <= 0x7fu) {\n out[0] = (unsigned char)codepoint;\n return 1;\n }\n if (codepoint <= 0x7ffu) {\n out[0] = (unsigned char)(0xc0u | (codepoint >> 6));\n out[1] = (unsigned char)(0x80u | (codepoint & 0x3fu));\n return 2;\n }\n if (codepoint <= 0xffffu) {\n out[0] = (unsigned char)(0xe0u | (codepoint >> 12));\n out[1] = (unsigned char)(0x80u | ((codepoint >> 6) & 0x3fu));\n out[2] = (unsigned char)(0x80u | (codepoint & 0x3fu));\n return 3;\n }\n if (codepoint <= 0x10ffffu) {\n out[0] = (unsigned char)(0xf0u | (codepoint >> 18));\n out[1] = (unsigned char)(0x80u | ((codepoint >> 12) & 0x3fu));\n out[2] = (unsigned char)(0x80u | ((codepoint >> 6) & 0x3fu));\n out[3] = (unsigned char)(0x80u | (codepoint & 0x3fu));\n return 4;\n }\n return 0;\n}\n\nstatic void zero_json_skip_ws(ZeroJsonScanner *scanner) {\n while (scanner->pos < scanner->len) {\n unsigned char ch = scanner->ptr[scanner->pos];\n if (ch != ' ' && ch != '\\n' && ch != '\\r' && ch != '\\t') return;\n scanner->pos++;\n }\n}\n\nstatic size_t zero_json_skip_ws_at(ZeroByteView input, size_t pos) {\n while (pos < input.len) {\n unsigned char ch = input.ptr[pos];\n if (ch != ' ' && ch != '\\n' && ch != '\\r' && ch != '\\t') break;\n pos++;\n }\n return pos;\n}\n\nstatic int zero_json_parse_value(ZeroJsonScanner *scanner, unsigned depth);\nstatic size_t zero_json_utf8_char_len(const unsigned char *ptr, size_t len, size_t pos);\n\nstatic int zero_json_parse_string(ZeroJsonScanner *scanner) {\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != '\"') return zero_json_fail(scanner, scanner->pos);\n scanner->pos++;\n while (scanner->pos < scanner->len) {\n size_t char_pos = scanner->pos;\n unsigned char ch = scanner->ptr[scanner->pos++];\n if (ch == '\"') return 1;\n if (ch < 0x20u) return zero_json_fail(scanner, char_pos);\n if (ch >= 0x80u) {\n size_t width = zero_json_utf8_char_len(scanner->ptr, scanner->len, char_pos);\n if (width == 0) return zero_json_fail(scanner, char_pos);\n scanner->pos = char_pos + width;\n continue;\n }\n if (ch != '\\\\') continue;\n if (scanner->pos >= scanner->len) return zero_json_fail(scanner, scanner->pos);\n size_t escape_pos = scanner->pos;\n unsigned char esc = scanner->ptr[scanner->pos++];\n if (esc == '\"' || esc == '\\\\' || esc == '/' || esc == 'b' || esc == 'f' || esc == 'n' || esc == 'r' || esc == 't') continue;\n",
|
||||
" if (esc != 'u') return zero_json_fail(scanner, escape_pos);\n uint16_t unit = 0;\n if (!zero_json_hex4_value(scanner->ptr, scanner->len, scanner->pos, &unit)) return zero_json_fail(scanner, scanner->pos);\n scanner->pos += 4;\n if (zero_json_high_surrogate(unit)) {\n size_t pair_pos = scanner->pos;\n if (scanner->pos + 6 > scanner->len || scanner->ptr[scanner->pos] != '\\\\' || scanner->ptr[scanner->pos + 1] != 'u') return zero_json_fail(scanner, pair_pos);\n scanner->pos += 2;\n uint16_t low = 0;\n if (!zero_json_hex4_value(scanner->ptr, scanner->len, scanner->pos, &low) || !zero_json_low_surrogate(low)) return zero_json_fail(scanner, scanner->pos);\n scanner->pos += 4;\n } else if (zero_json_low_surrogate(unit)) {\n return zero_json_fail(scanner, escape_pos);\n }\n }\n return zero_json_fail(scanner, scanner->len);\n}\n\nstatic int zero_json_match_literal(ZeroJsonScanner *scanner, const char *literal) {\n size_t start = scanner->pos;\n for (size_t i = 0; literal[i]; i++) {\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != (unsigned char)literal[i]) {\n size_t error_pos = scanner->pos;\n scanner->pos = start;\n return zero_json_fail(scanner, error_pos);\n }\n scanner->pos++;\n }\n return 1;\n}\n\nstatic int zero_json_parse_number(ZeroJsonScanner *scanner) {\n size_t start = scanner->pos;\n if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '-') scanner->pos++;\n if (scanner->pos >= scanner->len) {\n size_t error_pos = scanner->pos;\n scanner->pos = start;\n return zero_json_fail(scanner, error_pos);\n }\n if (scanner->ptr[scanner->pos] == '0') {\n scanner->pos++;\n } else if (scanner->ptr[scanner->pos] >= '1' && scanner->ptr[scanner->pos] <= '9') {\n while (scanner->pos < scanner->len && scanner->ptr[scanner->pos] >= '0' && scanner->ptr[scanner->pos] <= '9') scanner->pos++;\n } else {\n size_t error_pos = scanner->pos;\n scanner->pos = start;\n return zero_json_fail(scanner, error_pos);\n }\n if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '.') {\n scanner->pos++;\n size_t digits = scanner->pos;\n while (scanner->pos < scanner->len && scanner->ptr[scanner->pos] >= '0' && scanner->ptr[scanner->pos] <= '9') scanner->pos++;\n if (scanner->pos == digits) {\n size_t error_pos = scanner->pos;\n scanner->pos = start;\n return zero_json_fail(scanner, error_pos);\n }\n }\n if (scanner->pos < scanner->len && (scanner->ptr[scanner->pos] == 'e' || scanner->ptr[scanner->pos] == 'E')) {\n scanner->pos++;\n if (scanner->pos < scanner->len && (scanner->ptr[scanner->pos] == '+' || scanner->ptr[scanner->pos] == '-')) scanner->pos++;\n size_t digits = scanner->pos;\n while (scanner->pos < scanner->len && scanner->ptr[scanner->pos] >= '0' && scanner->ptr[scanner->pos] <= '9') scanner->pos++;\n if (scanner->pos == digits) {\n",
|
||||
" size_t error_pos = scanner->pos;\n scanner->pos = start;\n return zero_json_fail(scanner, error_pos);\n }\n }\n return 1;\n}\n\nstatic int zero_json_parse_array(ZeroJsonScanner *scanner, unsigned depth) {\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != '[') return zero_json_fail(scanner, scanner->pos);\n scanner->tokens++;\n scanner->pos++;\n zero_json_skip_ws(scanner);\n if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == ']') {\n scanner->pos++;\n return 1;\n }\n for (;;) {\n if (!zero_json_parse_value(scanner, depth + 1)) return 0;\n zero_json_skip_ws(scanner);\n if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == ']') {\n scanner->pos++;\n return 1;\n }\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != ',') return zero_json_fail(scanner, scanner->pos);\n scanner->pos++;\n zero_json_skip_ws(scanner);\n }\n}\n\nstatic int zero_json_parse_object(ZeroJsonScanner *scanner, unsigned depth) {\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != '{') return zero_json_fail(scanner, scanner->pos);\n scanner->tokens++;\n scanner->pos++;\n zero_json_skip_ws(scanner);\n if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '}') {\n scanner->pos++;\n return 1;\n }\n for (;;) {\n if (!zero_json_parse_string(scanner)) return 0;\n scanner->tokens++;\n zero_json_skip_ws(scanner);\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != ':') return zero_json_fail(scanner, scanner->pos);\n scanner->pos++;\n if (!zero_json_parse_value(scanner, depth + 1)) return 0;\n zero_json_skip_ws(scanner);\n if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '}') {\n scanner->pos++;\n return 1;\n }\n if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != ',') return zero_json_fail(scanner, scanner->pos);\n scanner->pos++;\n zero_json_skip_ws(scanner);\n }\n}\n\nstatic int zero_json_parse_value(ZeroJsonScanner *scanner, unsigned depth) {\n if (depth > 64) return zero_json_fail(scanner, scanner->pos);\n zero_json_skip_ws(scanner);\n if (scanner->pos >= scanner->len) return zero_json_fail(scanner, scanner->pos);\n unsigned char ch = scanner->ptr[scanner->pos];\n if (ch == '{') return zero_json_parse_object(scanner, depth);\n if (ch == '[') return zero_json_parse_array(scanner, depth);\n if (ch == '\"') {\n scanner->tokens++;\n return zero_json_parse_string(scanner);\n }\n if (ch == 't') {\n scanner->tokens++;\n return zero_json_match_literal(scanner, \"true\");\n }\n if (ch == 'f') {\n scanner->tokens++;\n return zero_json_match_literal(scanner, \"false\");\n }\n if (ch == 'n') {\n scanner->tokens++;\n return zero_json_match_literal(scanner, \"null\");\n }\n if (ch == '-' || (ch >= '0' && ch <= '9')) {\n scanner->tokens++;\n return zero_json_parse_number(scanner);\n }\n",
|
||||
" return zero_json_fail(scanner, scanner->pos);\n}\n\nint64_t zero_json_parse_bytes(ZeroByteView input) {\n if (!input.ptr || input.len == 0) return -1;\n ZeroJsonScanner scanner = {input.ptr, input.len, 0, 0, 0};\n if (!zero_json_parse_value(&scanner, 0)) return -1;\n zero_json_skip_ws(&scanner);\n if (scanner.pos != scanner.len || scanner.tokens > INT64_MAX) return scanner.pos != scanner.len ? -2 : -1;\n return (int64_t)scanner.tokens;\n}\n\nstatic uint64_t zero_json_error_offset(ZeroByteView input, uint32_t *status_out) {\n if (!input.ptr || input.len == 0) {\n if (status_out) *status_out = 1;\n return 0;\n }\n ZeroJsonScanner scanner = {input.ptr, input.len, 0, 0, 0};\n if (!zero_json_parse_value(&scanner, 0)) {\n if (status_out) *status_out = 1;\n return scanner.error_pos;\n }\n zero_json_skip_ws(&scanner);\n if (scanner.pos != scanner.len || scanner.tokens > INT64_MAX) {\n if (status_out) *status_out = scanner.pos != scanner.len ? 2u : 1u;\n return scanner.pos;\n }\n if (status_out) *status_out = 0;\n return input.len;\n}\n\nuint64_t zero_json_diagnostic(ZeroByteView input, uint32_t op) {\n uint32_t status = 0;\n uint64_t offset = zero_json_error_offset(input, &status);\n if (op == 0) return status;\n if (op == 1) return offset;\n uint64_t line = 1;\n uint64_t column = 1;\n uint64_t start = 0;\n if (input.ptr) {\n for (uint64_t index = 0; index < offset && index < input.len; index++) {\n if (input.ptr[index] == '\\n') {\n line++;\n start = index + 1;\n }\n }\n column = offset >= start ? offset - start + 1 : 1;\n }\n if (op == 2) return line;\n if (op == 3) return column;\n return status;\n}\n\nstatic uint64_t zero_runtime_pack_span(int has, size_t offset, size_t len) {\n if (!has || offset > UINT32_MAX || len > 0x7fffffffu) return 0;\n return (1ull << 63) | ((uint64_t)len << 32) | (uint64_t)(uint32_t)offset;\n}\n\nstatic uint64_t zero_json_pack_span(int has, size_t offset, size_t len) {\n return zero_runtime_pack_span(has, offset, len);\n}\n\nstatic int zero_json_key_matches(ZeroByteView input, size_t start, size_t end, ZeroByteView key) {\n if (!input.ptr || start >= end || input.ptr[start] != '\"') return 0;\n size_t pos = start + 1;\n size_t key_pos = 0;\n while (pos + 1 < end) {\n unsigned char decoded = input.ptr[pos];\n unsigned char scratch[4] = {0};\n size_t scratch_len = 0;\n if (decoded == '\\\\') {\n pos++;\n if (pos + 1 >= end) return 0;\n unsigned char escaped = input.ptr[pos];\n if (escaped == '\"' || escaped == '\\\\' || escaped == '/') {\n decoded = escaped;\n } else if (escaped == 'b') {\n decoded = 8;\n } else if (escaped == 'f') {\n decoded = 12;\n } else if (escaped == 'n') {\n decoded = 10;\n } else if (escaped == 'r') {\n decoded = 13;\n } else if (escaped == 't') {\n decoded = 9;\n } else if (escaped == 'u') {\n",
|
||||
" uint16_t unit = 0;\n if (!zero_json_hex4_value(input.ptr, input.len, pos + 1, &unit)) return 0;\n uint32_t codepoint = unit;\n if (zero_json_high_surrogate(unit)) {\n if (pos + 10 >= end || input.ptr[pos + 5] != '\\\\' || input.ptr[pos + 6] != 'u') return 0;\n uint16_t low = 0;\n if (!zero_json_hex4_value(input.ptr, input.len, pos + 7, &low) || !zero_json_low_surrogate(low)) return 0;\n codepoint = zero_json_surrogate_codepoint(unit, low);\n pos += 10;\n } else if (zero_json_low_surrogate(unit)) {\n return 0;\n } else {\n pos += 4;\n }\n scratch_len = zero_json_write_utf8_bytes(codepoint, scratch);\n if (scratch_len == 0) return 0;\n } else {\n return 0;\n }\n }\n if (scratch_len > 0) {\n for (size_t i = 0; i < scratch_len; i++) {\n if (key_pos >= key.len || (key.len > 0 && key.ptr[key_pos] != scratch[i])) return 0;\n key_pos++;\n }\n } else {\n if (key_pos >= key.len || (key.len > 0 && key.ptr[key_pos] != decoded)) return 0;\n key_pos++;\n }\n pos++;\n }\n return key_pos == key.len;\n}\n\nuint64_t zero_json_field(ZeroByteView input, ZeroByteView key) {\n if (!input.ptr || (key.len > 0 && !key.ptr)) return 0;\n size_t start = zero_json_skip_ws_at(input, 0);\n if (start >= input.len || input.ptr[start] != '{') return 0;\n size_t pos = zero_json_skip_ws_at(input, start + 1);\n if (pos < input.len && input.ptr[pos] == '}') {\n size_t trailing = zero_json_skip_ws_at(input, pos + 1);\n return trailing == input.len ? 0 : 0;\n }\n int found = 0;\n size_t found_start = 0;\n size_t found_end = 0;\n while (pos < input.len) {\n ZeroJsonScanner key_scanner = {input.ptr, input.len, pos, 0, pos};\n if (!zero_json_parse_string(&key_scanner)) return 0;\n size_t key_end = key_scanner.pos;\n int matched = zero_json_key_matches(input, pos, key_end, key);\n pos = zero_json_skip_ws_at(input, key_end);\n if (pos >= input.len || input.ptr[pos] != ':') return 0;\n size_t value_start = zero_json_skip_ws_at(input, pos + 1);\n ZeroJsonScanner value_scanner = {input.ptr, input.len, value_start, 0, value_start};\n if (!zero_json_parse_value(&value_scanner, 0)) return 0;\n size_t value_end = value_scanner.pos;\n if (matched && !found) {\n found = 1;\n found_start = value_start;\n found_end = value_end;\n }\n pos = zero_json_skip_ws_at(input, value_end);\n if (pos < input.len && input.ptr[pos] == '}') {\n size_t trailing = zero_json_skip_ws_at(input, pos + 1);\n if (trailing != input.len) return 0;\n return zero_json_pack_span(found, found_start, found_end - found_start);\n }\n if (pos >= input.len || input.ptr[pos] != ',') return 0;\n pos = zero_json_skip_ws_at(input, pos + 1);\n if (pos >= input.len || input.ptr[pos] == '}') return 0;\n }\n return 0;\n}\n\n",
|
||||
"uint64_t zero_json_lookup_scalar(ZeroByteView input, ZeroByteView key, uint32_t op) {\n uint64_t packed = zero_json_field(input, key);\n if ((packed >> 63) == 0) return 0;\n size_t offset = (size_t)(uint32_t)packed;\n size_t len = (size_t)((packed >> 32) & 0x7fffffffu);\n if (offset > input.len || len > input.len - offset) return 0;\n ZeroByteView raw = {input.ptr + offset, len};\n if (op == 0) return zero_parse_u32(raw);\n if (op == 1) {\n static const unsigned char true_lit[] = {'t', 'r', 'u', 'e'};\n static const unsigned char false_lit[] = {'f', 'a', 'l', 's', 'e'};\n if (raw.len == sizeof(true_lit) && memcmp(raw.ptr, true_lit, sizeof(true_lit)) == 0) return (1ull << 32) | 1u;\n if (raw.len == sizeof(false_lit) && memcmp(raw.ptr, false_lit, sizeof(false_lit)) == 0) return (1ull << 32);\n }\n return 0;\n}\n\nstatic int zero_json_continuation(unsigned char ch) {\n return (ch & 0xc0u) == 0x80u;\n}\n\nstatic size_t zero_json_utf8_char_len(const unsigned char *ptr, size_t len, size_t pos) {\n if (!ptr || pos >= len) return 0;\n unsigned char ch = ptr[pos];\n if (ch < 0x80u) return 1;\n if (ch >= 0xc2u && ch <= 0xdfu) {\n return pos + 1 < len && zero_json_continuation(ptr[pos + 1]) ? 2 : 0;\n }\n if (ch == 0xe0u) {\n return pos + 2 < len && ptr[pos + 1] >= 0xa0u && ptr[pos + 1] <= 0xbfu && zero_json_continuation(ptr[pos + 2]) ? 3 : 0;\n }\n if ((ch >= 0xe1u && ch <= 0xecu) || (ch >= 0xeeu && ch <= 0xefu)) {\n return pos + 2 < len && zero_json_continuation(ptr[pos + 1]) && zero_json_continuation(ptr[pos + 2]) ? 3 : 0;\n }\n if (ch == 0xedu) {\n return pos + 2 < len && ptr[pos + 1] >= 0x80u && ptr[pos + 1] <= 0x9fu && zero_json_continuation(ptr[pos + 2]) ? 3 : 0;\n }\n if (ch == 0xf0u) {\n return pos + 3 < len && ptr[pos + 1] >= 0x90u && ptr[pos + 1] <= 0xbfu && zero_json_continuation(ptr[pos + 2]) && zero_json_continuation(ptr[pos + 3]) ? 4 : 0;\n }\n if (ch >= 0xf1u && ch <= 0xf3u) {\n return pos + 3 < len && zero_json_continuation(ptr[pos + 1]) && zero_json_continuation(ptr[pos + 2]) && zero_json_continuation(ptr[pos + 3]) ? 4 : 0;\n }\n if (ch == 0xf4u) {\n return pos + 3 < len && ptr[pos + 1] >= 0x80u && ptr[pos + 1] <= 0x8fu && zero_json_continuation(ptr[pos + 2]) && zero_json_continuation(ptr[pos + 3]) ? 4 : 0;\n }\n return 0;\n}\n\nuint64_t zero_json_string_decode(ZeroMutByteView buffer, ZeroByteView raw) {\n if ((!buffer.ptr && buffer.len > 0) || !raw.ptr || raw.len < 2 || raw.ptr[0] != '\"') return 0;\n size_t pos = 1;\n size_t out = 0;\n while (pos < raw.len) {\n unsigned char ch = raw.ptr[pos++];\n if (ch == '\"') {\n return pos == raw.len ? zero_json_pack_span(1, 0, out) : 0;\n }\n if (ch < 0x20u) return 0;\n if (ch != '\\\\') {\n size_t char_len = ch < 0x80u ? 1 : zero_json_utf8_char_len(raw.ptr, raw.len, pos - 1);\n if (char_len == 0 || out + char_len > buffer.len) return 0;\n",
|
||||
" for (size_t i = 0; i < char_len; i++) buffer.ptr[out++] = raw.ptr[pos - 1 + i];\n pos += char_len - 1;\n continue;\n }\n if (pos >= raw.len) return 0;\n unsigned char escaped = raw.ptr[pos++];\n unsigned char decoded = 0;\n if (escaped == '\"' || escaped == '\\\\' || escaped == '/') {\n decoded = escaped;\n } else if (escaped == 'b') {\n decoded = 8;\n } else if (escaped == 'f') {\n decoded = 12;\n } else if (escaped == 'n') {\n decoded = 10;\n } else if (escaped == 'r') {\n decoded = 13;\n } else if (escaped == 't') {\n decoded = 9;\n } else if (escaped == 'u') {\n uint16_t unit = 0;\n if (!zero_json_hex4_value(raw.ptr, raw.len, pos, &unit)) return 0;\n uint32_t codepoint = unit;\n pos += 4;\n if (zero_json_high_surrogate(unit)) {\n if (pos + 6 > raw.len || raw.ptr[pos] != '\\\\' || raw.ptr[pos + 1] != 'u') return 0;\n pos += 2;\n uint16_t low = 0;\n if (!zero_json_hex4_value(raw.ptr, raw.len, pos, &low) || !zero_json_low_surrogate(low)) return 0;\n codepoint = zero_json_surrogate_codepoint(unit, low);\n pos += 4;\n } else if (zero_json_low_surrogate(unit)) {\n return 0;\n }\n unsigned char scratch[4] = {0};\n size_t scratch_len = zero_json_write_utf8_bytes(codepoint, scratch);\n if (scratch_len == 0 || out + scratch_len > buffer.len) return 0;\n for (size_t i = 0; i < scratch_len; i++) buffer.ptr[out++] = scratch[i];\n continue;\n } else {\n return 0;\n }\n if (out >= buffer.len) return 0;\n buffer.ptr[out++] = decoded;\n }\n return 0;\n}\n\nuint64_t zero_json_string_field(ZeroMutByteView buffer, ZeroByteView input, ZeroByteView key) {\n uint64_t packed = zero_json_field(input, key);\n if ((packed >> 63) == 0) return 0;\n size_t offset = (size_t)(uint32_t)packed;\n size_t len = (size_t)((packed >> 32) & 0x7fffffffu);\n if (offset > input.len || len > input.len - offset) return 0;\n return zero_json_string_decode(buffer, (ZeroByteView){input.ptr + offset, len});\n}\n\nstatic int zero_json_write_byte(ZeroMutByteView buffer, size_t *out, unsigned char byte) {\n if (!out || *out >= buffer.len) return 0;\n buffer.ptr[*out] = byte;\n *out += 1;\n return 1;\n}\n\nstatic int zero_json_write_escape_pair(ZeroMutByteView buffer, size_t *out, unsigned char escaped) {\n return zero_json_write_byte(buffer, out, '\\\\') && zero_json_write_byte(buffer, out, escaped);\n}\n\nstatic int zero_json_write_u00_escape(ZeroMutByteView buffer, size_t *out, unsigned char byte) {\n static const unsigned char hex[] = \"0123456789abcdef\";\n return zero_json_write_byte(buffer, out, '\\\\') &&\n zero_json_write_byte(buffer, out, 'u') &&\n zero_json_write_byte(buffer, out, '0') &&\n zero_json_write_byte(buffer, out, '0') &&\n zero_json_write_byte(buffer, out, hex[(byte >> 4) & 0xfu]) &&\n",
|
||||
" zero_json_write_byte(buffer, out, hex[byte & 0xfu]);\n}\n\nuint64_t zero_json_write_string(ZeroMutByteView buffer, ZeroByteView text) {\n if (!buffer.ptr || (text.len > 0 && !text.ptr)) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '\"')) return 0;\n size_t pos = 0;\n while (pos < text.len) {\n unsigned char byte = text.ptr[pos];\n if (byte == '\"') {\n if (!zero_json_write_escape_pair(buffer, &out, '\"')) return 0;\n pos++;\n } else if (byte == '\\\\') {\n if (!zero_json_write_escape_pair(buffer, &out, '\\\\')) return 0;\n pos++;\n } else if (byte == '\\b') {\n if (!zero_json_write_escape_pair(buffer, &out, 'b')) return 0;\n pos++;\n } else if (byte == '\\f') {\n if (!zero_json_write_escape_pair(buffer, &out, 'f')) return 0;\n pos++;\n } else if (byte == '\\n') {\n if (!zero_json_write_escape_pair(buffer, &out, 'n')) return 0;\n pos++;\n } else if (byte == '\\r') {\n if (!zero_json_write_escape_pair(buffer, &out, 'r')) return 0;\n pos++;\n } else if (byte == '\\t') {\n if (!zero_json_write_escape_pair(buffer, &out, 't')) return 0;\n pos++;\n } else if (byte < 0x20u) {\n if (!zero_json_write_u00_escape(buffer, &out, byte)) return 0;\n pos++;\n } else {\n size_t width = zero_json_utf8_char_len(text.ptr, text.len, pos);\n if (width == 0 || out + width > buffer.len) return 0;\n for (size_t i = 0; i < width; i++) buffer.ptr[out++] = text.ptr[pos + i];\n pos += width;\n }\n }\n if (!zero_json_write_byte(buffer, &out, '\"')) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nstatic int zero_json_write_span_bytes(ZeroMutByteView buffer, size_t *out, ZeroByteView bytes) {\n if (!out || (bytes.len > 0 && !bytes.ptr) || *out > buffer.len || bytes.len > buffer.len - *out) return 0;\n if (bytes.len > 0) memcpy(buffer.ptr + *out, bytes.ptr, bytes.len);\n *out += bytes.len;\n return 1;\n}\n\nstatic int zero_json_write_u32_value(ZeroMutByteView buffer, size_t *out, uint32_t value) {\n unsigned char digits[10];\n size_t len = 0;\n do {\n digits[len++] = (unsigned char)('0' + (value % 10u));\n value /= 10u;\n } while (value != 0);\n while (len > 0) {\n if (!zero_json_write_byte(buffer, out, digits[--len])) return 0;\n }\n return 1;\n}\n\nstatic int zero_json_write_bool_value(ZeroMutByteView buffer, size_t *out, uint32_t value) {\n static const unsigned char true_lit[] = {'t', 'r', 'u', 'e'};\n static const unsigned char false_lit[] = {'f', 'a', 'l', 's', 'e'};\n ZeroByteView lit = value ? (ZeroByteView){true_lit, sizeof(true_lit)} : (ZeroByteView){false_lit, sizeof(false_lit)};\n return zero_json_write_span_bytes(buffer, out, lit);\n}\n\nstatic int zero_json_write_string_value_at(ZeroMutByteView buffer, size_t *out, ZeroByteView text) {\n if (!out || *out > buffer.len) return 0;\n",
|
||||
" uint64_t packed = zero_json_write_string((ZeroMutByteView){buffer.ptr + *out, buffer.len - *out}, text);\n if ((packed >> 63) == 0) return 0;\n size_t len = (size_t)((packed >> 32) & 0x7fffffffu);\n *out += len;\n return 1;\n}\n\ntypedef enum {\n ZERO_JSON_WRITE_FIELD_RAW = 0,\n ZERO_JSON_WRITE_FIELD_STRING = 1,\n ZERO_JSON_WRITE_FIELD_U32 = 2,\n ZERO_JSON_WRITE_FIELD_BOOL = 3\n} ZeroJsonWriteFieldKind;\n\nstatic int zero_json_write_field_at(ZeroMutByteView buffer, size_t *out, ZeroByteView key, ZeroByteView value, uint64_t scalar, ZeroJsonWriteFieldKind kind) {\n if (!zero_json_write_string_value_at(buffer, out, key)) return 0;\n if (!zero_json_write_byte(buffer, out, ':')) return 0;\n switch (kind) {\n case ZERO_JSON_WRITE_FIELD_RAW:\n if (zero_json_parse_bytes(value) < 0) return 0;\n return zero_json_write_span_bytes(buffer, out, value);\n case ZERO_JSON_WRITE_FIELD_STRING:\n return zero_json_write_string_value_at(buffer, out, value);\n case ZERO_JSON_WRITE_FIELD_U32:\n return zero_json_write_u32_value(buffer, out, (uint32_t)scalar);\n case ZERO_JSON_WRITE_FIELD_BOOL:\n return zero_json_write_bool_value(buffer, out, scalar ? 1u : 0u);\n }\n return 0;\n}\n\nstatic uint64_t zero_json_write_field_result(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, uint64_t scalar, ZeroJsonWriteFieldKind kind) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_field_at(buffer, &out, key, value, scalar, kind)) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nstatic uint64_t zero_json_write_object1_result(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, uint64_t scalar, ZeroJsonWriteFieldKind kind) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '{')) return 0;\n if (!zero_json_write_field_at(buffer, &out, key, value, scalar, kind)) return 0;\n if (!zero_json_write_byte(buffer, &out, '}')) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_field_raw(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value) {\n return zero_json_write_field_result(buffer, key, value, 0, ZERO_JSON_WRITE_FIELD_RAW);\n}\n\nuint64_t zero_json_write_field_string(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value) {\n return zero_json_write_field_result(buffer, key, value, 0, ZERO_JSON_WRITE_FIELD_STRING);\n}\n\nuint64_t zero_json_write_field_u32(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {\n return zero_json_write_field_result(buffer, key, (ZeroByteView){0}, value, ZERO_JSON_WRITE_FIELD_U32);\n}\n\nuint64_t zero_json_write_field_bool(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {\n return zero_json_write_field_result(buffer, key, (ZeroByteView){0}, value ? 1u : 0u, ZERO_JSON_WRITE_FIELD_BOOL);\n}\n\nuint64_t zero_json_write_object1_string(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value) {\n",
|
||||
" return zero_json_write_object1_result(buffer, key, value, 0, ZERO_JSON_WRITE_FIELD_STRING);\n}\n\nuint64_t zero_json_write_object1_u32(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {\n return zero_json_write_object1_result(buffer, key, (ZeroByteView){0}, value, ZERO_JSON_WRITE_FIELD_U32);\n}\n\nuint64_t zero_json_write_object1_bool(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {\n return zero_json_write_object1_result(buffer, key, (ZeroByteView){0}, value ? 1u : 0u, ZERO_JSON_WRITE_FIELD_BOOL);\n}\n\nuint64_t zero_json_write_object2_fields(ZeroMutByteView buffer, ZeroByteView field0, ZeroByteView field1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '{')) return 0;\n if (!zero_json_write_span_bytes(buffer, &out, field0)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;\n if (!zero_json_write_byte(buffer, &out, '}')) return 0;\n ZeroByteView candidate = {buffer.ptr, out};\n if (zero_json_parse_bytes(candidate) < 0) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_object2_string_field(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, ZeroByteView field1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '{')) return 0;\n if (!zero_json_write_field_at(buffer, &out, key, value, 0, ZERO_JSON_WRITE_FIELD_STRING)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;\n if (!zero_json_write_byte(buffer, &out, '}')) return 0;\n ZeroByteView candidate = {buffer.ptr, out};\n if (zero_json_parse_bytes(candidate) < 0) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_object2_u32_field(ZeroMutByteView buffer, ZeroByteView key, uint32_t value, ZeroByteView field1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '{')) return 0;\n if (!zero_json_write_field_at(buffer, &out, key, (ZeroByteView){0}, value, ZERO_JSON_WRITE_FIELD_U32)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;\n if (!zero_json_write_byte(buffer, &out, '}')) return 0;\n ZeroByteView candidate = {buffer.ptr, out};\n if (zero_json_parse_bytes(candidate) < 0) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_object2_bool_field(ZeroMutByteView buffer, ZeroByteView key, uint32_t value, ZeroByteView field1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '{')) return 0;\n if (!zero_json_write_field_at(buffer, &out, key, (ZeroByteView){0}, value ? 1u : 0u, ZERO_JSON_WRITE_FIELD_BOOL)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n",
|
||||
" if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;\n if (!zero_json_write_byte(buffer, &out, '}')) return 0;\n ZeroByteView candidate = {buffer.ptr, out};\n if (zero_json_parse_bytes(candidate) < 0) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_array2_strings(ZeroMutByteView buffer, ZeroByteView value0, ZeroByteView value1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '[')) return 0;\n if (!zero_json_write_string_value_at(buffer, &out, value0)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n if (!zero_json_write_string_value_at(buffer, &out, value1)) return 0;\n if (!zero_json_write_byte(buffer, &out, ']')) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_array2_u32(ZeroMutByteView buffer, uint32_t value0, uint32_t value1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '[')) return 0;\n if (!zero_json_write_u32_value(buffer, &out, value0)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n if (!zero_json_write_u32_value(buffer, &out, value1)) return 0;\n if (!zero_json_write_byte(buffer, &out, ']')) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint64_t zero_json_write_array2_bools(ZeroMutByteView buffer, uint32_t value0, uint32_t value1) {\n if (!buffer.ptr) return 0;\n size_t out = 0;\n if (!zero_json_write_byte(buffer, &out, '[')) return 0;\n if (!zero_json_write_bool_value(buffer, &out, value0 ? 1u : 0u)) return 0;\n if (!zero_json_write_byte(buffer, &out, ',')) return 0;\n if (!zero_json_write_bool_value(buffer, &out, value1 ? 1u : 0u)) return 0;\n if (!zero_json_write_byte(buffer, &out, ']')) return 0;\n return zero_json_pack_span(1, 0, out);\n}\n\nuint32_t zero_str_contains(ZeroByteView text, ZeroByteView needle) {\n return (uint32_t)zero_str_pair_op(text, needle, ZERO_STR_OP_CONTAINS);\n}\n\nstatic int zero_http_header_token_char(unsigned char ch) {\n return (ch >= 'A' && ch <= 'Z') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= '0' && ch <= '9') ||\n ch == '!' || ch == '#' || ch == '$' || ch == '%' ||\n ch == '&' || ch == '\\'' || ch == '*' || ch == '+' ||\n ch == '-' || ch == '.' || ch == '^' || ch == '_' ||\n ch == '`' || ch == '|' || ch == '~';\n}\n\nstatic unsigned char zero_http_header_lower(unsigned char ch) {\n return ch >= 'A' && ch <= 'Z' ? (unsigned char)(ch + ('a' - 'A')) : ch;\n}\n\nstatic int zero_http_header_name_equal(const unsigned char *ptr, size_t len, ZeroByteView name) {\n if (!ptr || !name.ptr || len != name.len || len == 0) return 0;\n for (size_t i = 0; i < len; i++) {\n unsigned char a = ptr[i];\n unsigned char b = name.ptr[i];\n if (!zero_http_header_token_char(b)) return 0;\n if (zero_http_header_lower(a) != zero_http_header_lower(b)) return 0;\n }\n return 1;\n}\n\n",
|
||||
"static uint64_t zero_http_header_pack(size_t offset, size_t len) {\n if (offset > 0x7fffffffu || len > 0xffffffffu) return 0;\n return (1ull << 63) | ((uint64_t)(uint32_t)offset << 32) | (uint64_t)(uint32_t)len;\n}\n\nstatic uint64_t zero_http_span_pack(size_t offset, size_t len) {\n if (offset > 0xffffffffu || len > 0x7fffffffu) return 0;\n return (1ull << 63) | ((uint64_t)(uint32_t)len << 32) | (uint64_t)(uint32_t)offset;\n}\n\nstatic uint32_t zero_http_read_u32le(const unsigned char *ptr) {\n return (uint32_t)ptr[0] |\n ((uint32_t)ptr[1] << 8) |\n ((uint32_t)ptr[2] << 16) |\n ((uint32_t)ptr[3] << 24);\n}\n\nstatic int zero_http_response_has_meta(ZeroByteView response) {\n if (!response.ptr || response.len < ZERO_HTTP_RESPONSE_META_BYTES) return 0;\n return response.ptr[0] == 'Z' &&\n response.ptr[1] == 'H' &&\n response.ptr[2] == 'R' &&\n response.ptr[3] == '1';\n}\n\nstatic size_t zero_http_raw_header_len(ZeroByteView response) {\n if (!response.ptr || response.len == 0) return 0;\n size_t pos = 0;\n while (pos < response.len) {\n size_t line_start = pos;\n while (pos < response.len && response.ptr[pos] != '\\n') pos++;\n size_t line_end = pos;\n if (line_end > line_start && response.ptr[line_end - 1] == '\\r') line_end--;\n size_t next = pos < response.len ? pos + 1 : pos;\n if (line_end == line_start) return next;\n pos = next;\n }\n return response.len;\n}\n\nuint32_t zero_http_response_len(ZeroByteView response) {\n if (!zero_http_response_has_meta(response)) {\n return response.len > 0xffffffffu ? 0xffffffffu : (uint32_t)response.len;\n }\n uint32_t headers_len = zero_http_read_u32le(response.ptr + 8);\n uint32_t body_len = zero_http_read_u32le(response.ptr + 12);\n if (headers_len > 0xffffffffu - body_len) return 0xffffffffu;\n return headers_len + body_len;\n}\n\nuint32_t zero_http_response_headers_len(ZeroByteView response) {\n if (zero_http_response_has_meta(response)) return zero_http_read_u32le(response.ptr + 8);\n size_t len = zero_http_raw_header_len(response);\n return len > 0xffffffffu ? 0xffffffffu : (uint32_t)len;\n}\n\nuint32_t zero_http_response_body_offset(ZeroByteView response) {\n if (zero_http_response_has_meta(response)) {\n uint32_t headers_len = zero_http_read_u32le(response.ptr + 8);\n return ZERO_HTTP_RESPONSE_META_BYTES + headers_len;\n }\n size_t offset = zero_http_raw_header_len(response);\n return offset > 0xffffffffu ? 0xffffffffu : (uint32_t)offset;\n}\n\nuint64_t zero_http_header_value(ZeroByteView headers, ZeroByteView name) {\n if (!headers.ptr || !name.ptr || headers.len == 0 || name.len == 0) return 0;\n for (size_t i = 0; i < name.len; i++) {\n if (!zero_http_header_token_char(name.ptr[i])) return 0;\n }\n\n size_t header_offset = 0;\n size_t header_len = headers.len;\n if (zero_http_response_has_meta(headers)) {\n header_offset = ZERO_HTTP_RESPONSE_META_BYTES;\n",
|
||||
" header_len = zero_http_read_u32le(headers.ptr + 8);\n if (header_offset > headers.len || header_len > headers.len - header_offset) return 0;\n }\n\n size_t pos = header_offset;\n size_t limit = header_offset + header_len;\n while (pos < limit) {\n size_t line_start = pos;\n while (pos < limit && headers.ptr[pos] != '\\n') pos++;\n size_t line_end = pos;\n if (line_end > line_start && headers.ptr[line_end - 1] == '\\r') line_end--;\n if (pos < limit) pos++;\n\n if (line_end == line_start) break;\n size_t colon = line_start;\n while (colon < line_end && headers.ptr[colon] != ':') colon++;\n if (colon == line_start || colon >= line_end) continue;\n if (!zero_http_header_name_equal(headers.ptr + line_start, colon - line_start, name)) continue;\n\n size_t value_start = colon + 1;\n while (value_start < line_end && (headers.ptr[value_start] == ' ' || headers.ptr[value_start] == '\\t')) value_start++;\n size_t value_end = line_end;\n while (value_end > value_start && (headers.ptr[value_end - 1] == ' ' || headers.ptr[value_end - 1] == '\\t')) value_end--;\n return zero_http_header_pack(value_start, value_end - value_start);\n }\n return 0;\n}\n\nuint32_t zero_http_header_found(uint64_t value) {\n return (value >> 63) ? 1u : 0u;\n}\n\nuint32_t zero_http_header_offset(uint64_t value) {\n return zero_http_header_found(value) ? (uint32_t)((value >> 32) & 0x7fffffffu) : 0u;\n}\n\nuint32_t zero_http_header_len(uint64_t value) {\n return zero_http_header_found(value) ? (uint32_t)(value & 0xffffffffu) : 0u;\n}\n\nstatic int zero_http_token_char(unsigned char ch) {\n return (ch >= 'A' && ch <= 'Z') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= '0' && ch <= '9') ||\n ch == '!' || ch == '#' || ch == '$' || ch == '%' ||\n ch == '&' || ch == '\\'' || ch == '*' || ch == '+' ||\n ch == '-' || ch == '.' || ch == '^' || ch == '_' ||\n ch == '`' || ch == '|' || ch == '~';\n}\n\nstatic int zero_http_method_valid(ZeroByteView method) {\n if (!method.ptr || method.len == 0) return 0;\n for (size_t i = 0; i < method.len; i++) {\n if (!zero_http_token_char(method.ptr[i])) return 0;\n }\n return 1;\n}\n\nstatic ZeroByteView zero_http_trim_trailing_cr(ZeroByteView view) {\n if (view.len > 0 && view.ptr[view.len - 1] == '\\r') view.len--;\n return view;\n}\n\nstatic int zero_http_first_line(ZeroByteView request, ZeroByteView *line_out) {\n if (line_out) *line_out = (ZeroByteView){0};\n if (!request.ptr || request.len == 0) return 0;\n size_t line_end = 0;\n while (line_end < request.len && request.ptr[line_end] != '\\n') line_end++;\n if (line_end >= request.len) return 0;\n ZeroByteView line = zero_http_trim_trailing_cr((ZeroByteView){request.ptr, line_end});\n if (line.len == 0) return 0;\n if (line_out) *line_out = line;\n return 1;\n}\n\nstatic int zero_http_request_target(ZeroByteView request, size_t *offset_out, size_t *len_out) {\n",
|
||||
" if (offset_out) *offset_out = 0;\n if (len_out) *len_out = 0;\n ZeroByteView line = {0};\n if (!zero_http_first_line(request, &line)) return 0;\n size_t space = 0;\n while (space < line.len && line.ptr[space] != ' ') space++;\n if (space == 0 || space + 1 >= line.len) return 0;\n ZeroByteView method = {line.ptr, space};\n if (!zero_http_method_valid(method)) return 0;\n size_t start = space + 1;\n size_t end = start;\n while (end < line.len && line.ptr[end] != ' ') {\n if (line.ptr[end] <= 32u) return 0;\n end++;\n }\n if (end == start) return 0;\n if (end < line.len) {\n size_t version_start = end;\n while (version_start < line.len && line.ptr[version_start] == ' ') version_start++;\n if (version_start < line.len) {\n ZeroByteView version = {line.ptr + version_start, line.len - version_start};\n if (!((version.len == 8 && memcmp(version.ptr, \"HTTP/1.0\", 8) == 0) ||\n (version.len == 8 && memcmp(version.ptr, \"HTTP/1.1\", 8) == 0))) {\n return 0;\n }\n }\n }\n if (offset_out) *offset_out = (size_t)(line.ptr + start - request.ptr);\n if (len_out) *len_out = end - start;\n return 1;\n}\n\nuint64_t zero_http_request_method_name(ZeroByteView request) {\n ZeroByteView line = {0};\n if (!zero_http_first_line(request, &line)) return 0;\n size_t space = 0;\n while (space < line.len && line.ptr[space] != ' ') space++;\n if (space == 0 || space >= line.len) return 0;\n ZeroByteView method = {line.ptr, space};\n if (!zero_http_method_valid(method)) return 0;\n return zero_http_span_pack(0, space);\n}\n\nuint64_t zero_http_request_path(ZeroByteView request) {\n size_t target_offset = 0;\n size_t target_len = 0;\n if (!zero_http_request_target(request, &target_offset, &target_len)) return 0;\n const unsigned char *target = request.ptr + target_offset;\n\n size_t path_start = 0;\n if (target_len >= 7 && memcmp(target, \"http://\", 7) == 0) {\n path_start = 7;\n while (path_start < target_len && target[path_start] != '/') path_start++;\n if (path_start >= target_len) return zero_http_span_pack(target_offset + 5, 1);\n } else if (target_len >= 8 && memcmp(target, \"https://\", 8) == 0) {\n path_start = 8;\n while (path_start < target_len && target[path_start] != '/') path_start++;\n if (path_start >= target_len) return zero_http_span_pack(target_offset + 6, 1);\n }\n\n size_t path_end = path_start;\n while (path_end < target_len && target[path_end] != '?' && target[path_end] != '#') path_end++;\n if (path_end == path_start) return 0;\n return zero_http_span_pack(target_offset + path_start, path_end - path_start);\n}\n\nuint32_t zero_http_request_matches(ZeroByteView request, ZeroByteView method, ZeroByteView path) {\n if ((!method.ptr && method.len > 0) || (!path.ptr && path.len > 0)) return 0;\n uint64_t method_span = zero_http_request_method_name(request);\n if ((method_span >> 63) == 0) return 0;\n",
|
||||
" size_t method_len = (size_t)((method_span >> 32) & 0x7fffffffu);\n size_t method_offset = (size_t)(method_span & 0xffffffffu);\n if (method_offset > request.len || method_len > request.len - method_offset) return 0;\n if (method_len != method.len || (method_len > 0 && memcmp(request.ptr + method_offset, method.ptr, method_len) != 0)) return 0;\n\n uint64_t path_span = zero_http_request_path(request);\n if ((path_span >> 63) == 0) return 0;\n size_t path_len = (size_t)((path_span >> 32) & 0x7fffffffu);\n size_t path_offset = (size_t)(path_span & 0xffffffffu);\n if (path_offset > request.len || path_len > request.len - path_offset) return 0;\n return path_len == path.len && (path_len == 0 || memcmp(request.ptr + path_offset, path.ptr, path_len) == 0);\n}\n\nstatic int zero_http_body_start(ZeroByteView request, size_t *start_out) {\n if (start_out) *start_out = 0;\n if (!request.ptr) return 0;\n size_t start = 0;\n while (start < request.len) {\n size_t end = start;\n while (end < request.len && request.ptr[end] != '\\n') end++;\n size_t line_end = end;\n if (line_end > start && request.ptr[line_end - 1] == '\\r') line_end--;\n size_t next = end < request.len ? end + 1 : end;\n if (line_end == start) {\n if (start_out) *start_out = next;\n return 1;\n }\n start = next;\n }\n return 0;\n}\n\nstatic int zero_http_value_starts_with_ignore_ascii_case(ZeroByteView value, const char *prefix, size_t prefix_len) {\n if (!prefix || value.len < prefix_len) return 0;\n for (size_t i = 0; i < prefix_len; i++) {\n if (zero_http_header_lower(value.ptr[i]) != zero_http_header_lower((unsigned char)prefix[i])) return 0;\n }\n return 1;\n}\n\nstatic int zero_http_request_has_json_content_type(ZeroByteView request) {\n static const unsigned char header_name[] = \"content-type\";\n static const char json_prefix[] = \"application/json\";\n uint64_t header = zero_http_header_value(request, (ZeroByteView){header_name, sizeof(header_name) - 1});\n if ((header >> 63) == 0) return 0;\n size_t offset = (size_t)((header >> 32) & 0x7fffffffu);\n size_t len = (size_t)(header & 0xffffffffu);\n if (offset > request.len || len > request.len - offset) return 0;\n ZeroByteView value = {request.ptr + offset, len};\n if (!zero_http_value_starts_with_ignore_ascii_case(value, json_prefix, sizeof(json_prefix) - 1)) return 0;\n return value.len == sizeof(json_prefix) - 1 || value.ptr[sizeof(json_prefix) - 1] == ';';\n}\n\nuint64_t zero_http_request_body_within(ZeroByteView request, uint64_t max, uint32_t require_json) {\n size_t body_start = 0;\n if (!zero_http_body_start(request, &body_start)) return 0;\n if (body_start > request.len) return 0;\n size_t body_len = request.len - body_start;\n if (body_len > max) return 0;\n ZeroByteView body = {request.ptr + body_start, body_len};\n if (require_json) {\n if (!zero_http_request_has_json_content_type(request)) return 0;\n",
|
||||
" if (zero_json_parse_bytes(body) < 0) return 0;\n }\n return zero_http_span_pack(body_start, body_len);\n}\n\nstatic const char *zero_http_status_reason(uint32_t status) {\n switch (status) {\n case 100: return \"Continue\";\n case 200: return \"OK\";\n case 201: return \"Created\";\n case 202: return \"Accepted\";\n case 204: return \"No Content\";\n case 301: return \"Moved Permanently\";\n case 302: return \"Found\";\n case 304: return \"Not Modified\";\n case 400: return \"Bad Request\";\n case 401: return \"Unauthorized\";\n case 403: return \"Forbidden\";\n case 404: return \"Not Found\";\n case 405: return \"Method Not Allowed\";\n case 409: return \"Conflict\";\n case 422: return \"Unprocessable Content\";\n case 429: return \"Too Many Requests\";\n case 500: return \"Internal Server Error\";\n case 502: return \"Bad Gateway\";\n case 503: return \"Service Unavailable\";\n default: return \"Unknown\";\n }\n}\n\nstatic int zero_http_write_bytes(ZeroMutByteView buffer, size_t *offset, const unsigned char *bytes, size_t len) {\n if (!offset || !buffer.ptr || (!bytes && len > 0)) return 0;\n if (*offset > buffer.len || len > buffer.len - *offset) return 0;\n if (len > 0) memcpy(buffer.ptr + *offset, bytes, len);\n *offset += len;\n return 1;\n}\n\nstatic int zero_http_write_literal(ZeroMutByteView buffer, size_t *offset, const char *text) {\n return zero_http_write_bytes(buffer, offset, (const unsigned char *)text, strlen(text));\n}\n\nstatic int zero_http_write_u32_decimal(ZeroMutByteView buffer, size_t *offset, uint32_t value) {\n unsigned char tmp[10];\n size_t len = 0;\n do {\n tmp[len++] = (unsigned char)('0' + (value % 10u));\n value /= 10u;\n } while (value > 0 && len < sizeof(tmp));\n if (!offset || *offset > buffer.len || len > buffer.len - *offset) return 0;\n for (size_t i = 0; i < len; i++) buffer.ptr[*offset + i] = tmp[len - 1 - i];\n *offset += len;\n return 1;\n}\n\nuint32_t zero_http_write_json_response(ZeroMutByteView buffer, uint32_t status, ZeroByteView body) {\n if (!buffer.ptr || (!body.ptr && body.len > 0) || body.len > 0xffffffffu) return 0;\n size_t offset = 0;\n const char *reason = zero_http_status_reason(status);\n if (!zero_http_write_literal(buffer, &offset, \"HTTP/1.1 \") ||\n !zero_http_write_u32_decimal(buffer, &offset, status) ||\n !zero_http_write_literal(buffer, &offset, \" \") ||\n !zero_http_write_literal(buffer, &offset, reason) ||\n !zero_http_write_literal(buffer, &offset, \"\\r\\ncontent-type: application/json\\r\\nconnection: close\\r\\ncontent-length: \") ||\n !zero_http_write_u32_decimal(buffer, &offset, (uint32_t)body.len) ||\n !zero_http_write_literal(buffer, &offset, \"\\r\\n\\r\\n\") ||\n !zero_http_write_bytes(buffer, &offset, body.ptr, body.len) ||\n offset > 0xffffffffu) {\n return 0;\n }\n return (uint32_t)offset;\n}\n\n",
|
||||
"uint32_t zero_http_result_ok(uint64_t result) {\n uint32_t status = zero_http_result_status(result);\n return zero_http_result_error(result) == ZERO_HTTP_OK && status >= 200 && status < 300 ? 1u : 0u;\n}\n\nuint32_t zero_http_result_status(uint64_t result) {\n return (uint32_t)((result >> 32) & 0xffffu);\n}\n\nuint32_t zero_http_result_body_len(uint64_t result) {\n return (uint32_t)(result & 0xffffffffu);\n}\n\nuint32_t zero_http_result_error(uint64_t result) {\n return (uint32_t)((result >> 48) & 0xffffu);\n}\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_zero_http_curl_c[] = {
|
||||
"#include \"zero_runtime.h\"\n\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n\n#if !defined(__has_include)\n#define __has_include(x) 0\n#endif\n\n#if !defined(ZERO_RUNTIME_NO_CURL) && __has_include(<curl/curl.h>)\n#define ZERO_RUNTIME_HAS_CURL 1\n#include <curl/curl.h>\n#endif\n\nstatic uint64_t zero_http_result_pack(uint32_t error, uint16_t status, uint32_t body_len) {\n return ((uint64_t)(error & 0xffffu) << 48) |\n ((uint64_t)status << 32) |\n (uint64_t)body_len;\n}\n\nstatic void zero_http_write_u16le(unsigned char *ptr, uint16_t value) {\n ptr[0] = (unsigned char)(value & 0xffu);\n ptr[1] = (unsigned char)((value >> 8) & 0xffu);\n}\n\nstatic void zero_http_write_u32le(unsigned char *ptr, uint32_t value) {\n ptr[0] = (unsigned char)(value & 0xffu);\n ptr[1] = (unsigned char)((value >> 8) & 0xffu);\n ptr[2] = (unsigned char)((value >> 16) & 0xffu);\n ptr[3] = (unsigned char)((value >> 24) & 0xffu);\n}\n\nstatic void zero_http_write_response_meta(\n ZeroMutByteView response,\n uint16_t status,\n uint32_t error,\n uint32_t headers_len,\n uint32_t body_len\n) {\n if (!response.ptr || response.len < ZERO_HTTP_RESPONSE_META_BYTES) return;\n response.ptr[0] = 'Z';\n response.ptr[1] = 'H';\n response.ptr[2] = 'R';\n response.ptr[3] = '1';\n zero_http_write_u16le(response.ptr + 4, status);\n zero_http_write_u16le(response.ptr + 6, (uint16_t)(error & 0xffffu));\n zero_http_write_u32le(response.ptr + 8, headers_len);\n zero_http_write_u32le(response.ptr + 12, body_len);\n zero_http_write_u32le(response.ptr + 16, headers_len > 0xffffffffu - body_len ? 0xffffffffu : headers_len + body_len);\n zero_http_write_u32le(response.ptr + 20, 0);\n}\n\n#if ZERO_RUNTIME_HAS_CURL\ntypedef struct {\n unsigned char *ptr;\n size_t cap;\n size_t headers_len;\n size_t body_len;\n int too_large;\n} ZeroCurlResponseBuf;\n\ntypedef struct {\n ZeroByteView method;\n ZeroByteView url;\n ZeroByteView headers;\n ZeroByteView body;\n} ZeroParsedHttpRequest;\n\nstatic CURLcode zero_curl_global_status = CURLE_FAILED_INIT;\nstatic int zero_curl_global_initialized = 0;\n\nstatic void zero_runtime_curl_global_cleanup(void) {\n curl_global_cleanup();\n}\n\nstatic void zero_runtime_curl_global_init(void) {\n if (zero_curl_global_initialized) return;\n zero_curl_global_status = curl_global_init(CURL_GLOBAL_DEFAULT);\n zero_curl_global_initialized = 1;\n if (zero_curl_global_status == CURLE_OK) {\n (void)atexit(zero_runtime_curl_global_cleanup);\n }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n__attribute__((constructor))\nstatic void zero_runtime_curl_global_constructor(void) {\n zero_runtime_curl_global_init();\n}\n#endif\n\nstatic int zero_runtime_curl_global_ok(void) {\n zero_runtime_curl_global_init();\n return zero_curl_global_status == CURLE_OK;\n}\n\nstatic int zero_curl_response_header_starts_block(const char *ptr, size_t n) {\n",
|
||||
" return n >= 5 && ptr[0] == 'H' && ptr[1] == 'T' && ptr[2] == 'T' && ptr[3] == 'P' && ptr[4] == '/';\n}\n\nstatic size_t zero_curl_response_append(ZeroCurlResponseBuf *out, const char *ptr, size_t n, int is_header) {\n if (!out || !ptr) return 0;\n if (is_header) {\n if (out->body_len > 0) return n;\n /* libcurl emits separate blocks for interim and final responses; keep the final block. */\n if (zero_curl_response_header_starts_block(ptr, n)) out->headers_len = 0;\n }\n size_t used = out->headers_len + out->body_len;\n if (used > out->cap || n > out->cap - used) {\n out->too_large = 1;\n return 0;\n }\n if (n > 0) memcpy(out->ptr + used, ptr, n);\n if (is_header) out->headers_len += n;\n else out->body_len += n;\n return n;\n}\n\nstatic size_t zero_curl_response_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {\n size_t n = size * nmemb;\n if (size != 0 && n / size != nmemb) return 0;\n return zero_curl_response_append((ZeroCurlResponseBuf *)userdata, ptr, n, 1);\n}\n\nstatic size_t zero_curl_response_body_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {\n size_t n = size * nmemb;\n if (size != 0 && n / size != nmemb) return 0;\n return zero_curl_response_append((ZeroCurlResponseBuf *)userdata, ptr, n, 0);\n}\n\nstatic int zero_runtime_view_has_nul(ZeroByteView view) {\n for (size_t i = 0; i < view.len; i++) {\n if (view.ptr[i] == 0) return 1;\n }\n return 0;\n}\n\nstatic ZeroByteView zero_runtime_trim_trailing_cr(ZeroByteView view) {\n if (view.len > 0 && view.ptr[view.len - 1] == '\\r') view.len--;\n return view;\n}\n\nstatic int zero_runtime_url_supported(ZeroByteView url) {\n static const unsigned char http[] = \"http://\";\n static const unsigned char https[] = \"https://\";\n return (url.len >= sizeof(http) - 1 && memcmp(url.ptr, http, sizeof(http) - 1) == 0) ||\n (url.len >= sizeof(https) - 1 && memcmp(url.ptr, https, sizeof(https) - 1) == 0);\n}\n\nstatic int zero_runtime_http_token_char(unsigned char ch) {\n return (ch >= 'A' && ch <= 'Z') ||\n (ch >= 'a' && ch <= 'z') ||\n (ch >= '0' && ch <= '9') ||\n ch == '!' || ch == '#' || ch == '$' || ch == '%' ||\n ch == '&' || ch == '\\'' || ch == '*' || ch == '+' ||\n ch == '-' || ch == '.' || ch == '^' || ch == '_' ||\n ch == '`' || ch == '|' || ch == '~';\n}\n\nstatic int zero_runtime_method_valid(ZeroByteView method) {\n if (!method.ptr || method.len == 0) return 0;\n for (size_t i = 0; i < method.len; i++) {\n if (!zero_runtime_http_token_char(method.ptr[i])) return 0;\n }\n return 1;\n}\n\nstatic int zero_runtime_method_is(ZeroByteView method, const char *name) {\n size_t len = strlen(name);\n return method.len == len && memcmp(method.ptr, name, len) == 0;\n}\n\nstatic char *zero_runtime_dup_view(ZeroByteView view) {\n if (view.len == (size_t)-1) return NULL;\n char *out = (char *)malloc(view.len + 1);\n if (!out) return NULL;\n",
|
||||
" if (view.len > 0) memcpy(out, view.ptr, view.len);\n out[view.len] = 0;\n return out;\n}\n\nstatic int zero_http_parse_request(ZeroByteView request, ZeroParsedHttpRequest *out) {\n if (!out) return 0;\n *out = (ZeroParsedHttpRequest){0};\n if (!request.ptr || request.len == 0) return 0;\n\n size_t first_line_end = 0;\n while (first_line_end < request.len && request.ptr[first_line_end] != '\\n') first_line_end++;\n if (first_line_end >= request.len) return 0;\n ZeroByteView line = zero_runtime_trim_trailing_cr((ZeroByteView){request.ptr, first_line_end});\n if (line.len == 0 || zero_runtime_view_has_nul(line)) return 0;\n\n size_t space = 0;\n while (space < line.len && line.ptr[space] != ' ') space++;\n if (space == 0 || space >= line.len - 1) return 0;\n out->method = (ZeroByteView){line.ptr, space};\n out->url = (ZeroByteView){line.ptr + space + 1, line.len - space - 1};\n if (!zero_runtime_method_valid(out->method) || zero_runtime_view_has_nul(out->url)) return 0;\n for (size_t i = 0; i < out->url.len; i++) {\n if (out->url.ptr[i] <= ' ') return 0;\n }\n\n size_t pos = first_line_end < request.len ? first_line_end + 1 : first_line_end;\n size_t header_start = pos;\n while (pos < request.len) {\n size_t line_start = pos;\n while (pos < request.len && request.ptr[pos] != '\\n') pos++;\n size_t line_len = pos - line_start;\n if (line_len > 0 && request.ptr[line_start + line_len - 1] == '\\r') line_len--;\n size_t next = pos < request.len ? pos + 1 : pos;\n if (line_len == 0) {\n out->headers = (ZeroByteView){request.ptr + header_start, line_start - header_start};\n out->body = (ZeroByteView){request.ptr + next, request.len - next};\n return 1;\n }\n pos = next;\n }\n\n (void)header_start;\n return 0;\n}\n\nstatic int zero_runtime_header_line_valid(ZeroByteView line) {\n if (!line.ptr || line.len == 0 || zero_runtime_view_has_nul(line)) return 0;\n size_t colon = 0;\n while (colon < line.len && line.ptr[colon] != ':') colon++;\n if (colon == 0 || colon >= line.len) return 0;\n for (size_t i = 0; i < colon; i++) {\n if (!zero_runtime_http_token_char(line.ptr[i])) return 0;\n }\n for (size_t i = colon + 1; i < line.len; i++) {\n unsigned char ch = line.ptr[i];\n if (ch == '\\r' || ch == '\\n') return 0;\n if (ch < 0x20u && ch != '\\t') return 0;\n }\n return 1;\n}\n\nstatic uint32_t zero_runtime_curl_headers(ZeroByteView headers, struct curl_slist **out) {\n if (!out) return ZERO_HTTP_IO;\n *out = NULL;\n size_t pos = 0;\n while (pos < headers.len) {\n size_t line_start = pos;\n while (pos < headers.len && headers.ptr[pos] != '\\n') pos++;\n size_t line_len = pos - line_start;\n if (line_len > 0 && headers.ptr[line_start + line_len - 1] == '\\r') line_len--;\n if (pos < headers.len) pos++;\n ZeroByteView line = {headers.ptr + line_start, line_len};\n if (line.len == 0) continue;\n if (!zero_runtime_header_line_valid(line)) {\n",
|
||||
" curl_slist_free_all(*out);\n *out = NULL;\n return ZERO_HTTP_INVALID_REQUEST;\n }\n char *header = zero_runtime_dup_view(line);\n if (!header) {\n curl_slist_free_all(*out);\n *out = NULL;\n return ZERO_HTTP_IO;\n }\n struct curl_slist *next = curl_slist_append(*out, header);\n free(header);\n if (!next) {\n curl_slist_free_all(*out);\n *out = NULL;\n return ZERO_HTTP_IO;\n }\n *out = next;\n }\n return ZERO_HTTP_OK;\n}\n\nstatic long zero_runtime_timeout_ms(int64_t timeout_ns) {\n if (timeout_ns <= 0) return 0;\n uint64_t ns = (uint64_t)timeout_ns;\n uint64_t ms = ns / 1000000ull;\n if (ms == 0) ms = 1;\n if (ms > (uint64_t)LONG_MAX) return LONG_MAX;\n return (long)ms;\n}\n\nstatic const char *zero_runtime_test_ca_bundle(void) {\n const char *path = getenv(\"ZERO_HTTP_TEST_CA_BUNDLE\");\n return path && path[0] ? path : NULL;\n}\n\nstatic uint32_t zero_runtime_curl_error(CURLcode code) {\n switch (code) {\n case CURLE_OK:\n return ZERO_HTTP_OK;\n case CURLE_URL_MALFORMAT:\n return ZERO_HTTP_INVALID_URL;\n case CURLE_UNSUPPORTED_PROTOCOL:\n return ZERO_HTTP_UNSUPPORTED_PROTOCOL;\n case CURLE_COULDNT_RESOLVE_HOST:\n return ZERO_HTTP_DNS;\n case CURLE_COULDNT_CONNECT:\n return ZERO_HTTP_CONNECT;\n case CURLE_OPERATION_TIMEDOUT:\n return ZERO_HTTP_TIMEOUT;\n case CURLE_SSL_CONNECT_ERROR:\n case CURLE_PEER_FAILED_VERIFICATION:\n case CURLE_SSL_CACERT_BADFILE:\n return ZERO_HTTP_TLS;\n case CURLE_WRITE_ERROR:\n return ZERO_HTTP_TOO_LARGE;\n default:\n return ZERO_HTTP_IO;\n }\n}\n\nstatic int zero_http_perform_fetch(\n ZeroByteView method,\n ZeroByteView url,\n ZeroByteView request_headers,\n ZeroByteView request_body,\n ZeroMutByteView response_out,\n int64_t timeout_ns,\n uint16_t *status_out,\n uint32_t *headers_len_out,\n uint32_t *body_len_out,\n uint32_t *error_out\n) {\n if (status_out) *status_out = 0;\n if (headers_len_out) *headers_len_out = 0;\n if (body_len_out) *body_len_out = 0;\n if (error_out) *error_out = ZERO_HTTP_OK;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_OK, 0, 0);\n if (!response_out.ptr || response_out.len < ZERO_HTTP_RESPONSE_META_BYTES) {\n if (error_out) *error_out = ZERO_HTTP_TOO_LARGE;\n return -1;\n }\n if (!zero_runtime_method_valid(method)) {\n if (error_out) *error_out = ZERO_HTTP_INVALID_REQUEST;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_INVALID_REQUEST, 0, 0);\n return -1;\n }\n if (!url.ptr || url.len == 0 || zero_runtime_view_has_nul(url)) {\n if (error_out) *error_out = ZERO_HTTP_INVALID_URL;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_INVALID_URL, 0, 0);\n return -1;\n }\n if (!zero_runtime_url_supported(url)) {\n if (error_out) *error_out = ZERO_HTTP_UNSUPPORTED_PROTOCOL;\n",
|
||||
" zero_http_write_response_meta(response_out, 0, ZERO_HTTP_UNSUPPORTED_PROTOCOL, 0, 0);\n return -1;\n }\n if (!request_body.ptr && request_body.len > 0) {\n if (error_out) *error_out = ZERO_HTTP_IO;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_IO, 0, 0);\n return -1;\n }\n\n struct curl_slist *header_list = NULL;\n uint32_t header_error = zero_runtime_curl_headers(request_headers, &header_list);\n if (header_error != ZERO_HTTP_OK) {\n if (error_out) *error_out = header_error;\n zero_http_write_response_meta(response_out, 0, header_error, 0, 0);\n return -1;\n }\n\n char *method_c = zero_runtime_dup_view(method);\n char *url_c = zero_runtime_dup_view(url);\n if (!method_c || !url_c) {\n curl_slist_free_all(header_list);\n free(method_c);\n free(url_c);\n if (error_out) *error_out = ZERO_HTTP_IO;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_IO, 0, 0);\n return -1;\n }\n\n if (!zero_runtime_curl_global_ok()) {\n curl_slist_free_all(header_list);\n free(method_c);\n free(url_c);\n if (error_out) *error_out = ZERO_HTTP_PROVIDER_UNAVAILABLE;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_PROVIDER_UNAVAILABLE, 0, 0);\n return -1;\n }\n CURL *curl = curl_easy_init();\n if (!curl) {\n curl_slist_free_all(header_list);\n free(method_c);\n free(url_c);\n if (error_out) *error_out = ZERO_HTTP_PROVIDER_UNAVAILABLE;\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_PROVIDER_UNAVAILABLE, 0, 0);\n return -1;\n }\n\n ZeroCurlResponseBuf response = {\n .ptr = response_out.ptr + ZERO_HTTP_RESPONSE_META_BYTES,\n .cap = response_out.len - ZERO_HTTP_RESPONSE_META_BYTES,\n .headers_len = 0,\n .body_len = 0,\n .too_large = 0\n };\n\n curl_easy_setopt(curl, CURLOPT_URL, url_c);\n if (zero_runtime_method_is(method, \"GET\") && request_body.len == 0) {\n curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);\n } else if (zero_runtime_method_is(method, \"HEAD\") && request_body.len == 0) {\n curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method_c);\n curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);\n } else {\n static const char empty_body[] = \"\";\n curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method_c);\n if (request_body.len > 0 ||\n zero_runtime_method_is(method, \"POST\") ||\n zero_runtime_method_is(method, \"PUT\") ||\n zero_runtime_method_is(method, \"PATCH\")) {\n curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)request_body.len);\n curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_body.len > 0 ? (const char *)request_body.ptr : empty_body);\n }\n }\n if (header_list) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);\n#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x075500\n curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, \"http,https\");\n curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, \"http,https\");\n#else\n",
|
||||
" curl_easy_setopt(curl, CURLOPT_PROTOCOLS, (long)(CURLPROTO_HTTP | CURLPROTO_HTTPS));\n curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, (long)(CURLPROTO_HTTP | CURLPROTO_HTTPS));\n#endif\n curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);\n curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);\n curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);\n const char *test_ca_bundle = zero_runtime_test_ca_bundle();\n if (test_ca_bundle) curl_easy_setopt(curl, CURLOPT_CAINFO, test_ca_bundle);\n curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, zero_runtime_timeout_ms(timeout_ns));\n curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, zero_curl_response_header_cb);\n curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response);\n curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, zero_curl_response_body_cb);\n curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);\n\n CURLcode result = curl_easy_perform(curl);\n long status = 0;\n curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);\n curl_easy_cleanup(curl);\n curl_slist_free_all(header_list);\n free(method_c);\n free(url_c);\n\n uint16_t status16 = status > 0 && status <= 65535 ? (uint16_t)status : 0;\n if (status_out) *status_out = status16;\n uint32_t mapped = zero_runtime_curl_error(result);\n if (response.too_large) mapped = ZERO_HTTP_TOO_LARGE;\n if (response.headers_len > 0xffffffffu || response.body_len > 0xffffffffu ||\n response.headers_len > 0xffffffffu - response.body_len) {\n mapped = ZERO_HTTP_TOO_LARGE;\n }\n uint32_t headers_len = mapped == ZERO_HTTP_OK ? (uint32_t)response.headers_len : 0;\n uint32_t body_len = mapped == ZERO_HTTP_OK ? (uint32_t)response.body_len : 0;\n if (headers_len_out) *headers_len_out = headers_len;\n if (body_len_out) *body_len_out = body_len;\n if (error_out) *error_out = mapped;\n zero_http_write_response_meta(response_out, status16, mapped, headers_len, body_len);\n if (mapped != ZERO_HTTP_OK) return -1;\n return 0;\n}\n\nuint64_t zero_http_fetch_result(\n ZeroByteView request,\n ZeroMutByteView response_out,\n int64_t timeout_ns\n) {\n uint16_t status = 0;\n uint32_t headers_len = 0;\n uint32_t body_len = 0;\n uint32_t error = ZERO_HTTP_OK;\n ZeroParsedHttpRequest parsed;\n if (!zero_http_parse_request(request, &parsed)) {\n zero_http_write_response_meta(response_out, 0, ZERO_HTTP_INVALID_REQUEST, 0, 0);\n return zero_http_result_pack(ZERO_HTTP_INVALID_REQUEST, 0, 0);\n }\n int ok = zero_http_perform_fetch(\n parsed.method,\n parsed.url,\n parsed.headers,\n parsed.body,\n response_out,\n timeout_ns,\n &status,\n &headers_len,\n &body_len,\n &error\n );\n (void)headers_len;\n if (ok < 0) body_len = 0;\n return zero_http_result_pack(error, status, body_len);\n}\n\n#else\nuint64_t zero_http_fetch_result(\n ZeroByteView request,\n ZeroMutByteView response_out,\n int64_t timeout_ns\n) {\n (void)request;\n (void)timeout_ns;\n",
|
||||
" zero_http_write_response_meta(response_out, 0, ZERO_HTTP_PROVIDER_UNAVAILABLE, 0, 0);\n return zero_http_result_pack(ZERO_HTTP_PROVIDER_UNAVAILABLE, 0, 0);\n}\n\n#endif\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
/* Generated from Zero skill data. Run node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/embed-skill-data.mts to refresh. */
|
||||
#ifndef ZERO_EMBEDDED_SKILLS_INC
|
||||
#define ZERO_EMBEDDED_SKILLS_INC
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *description;
|
||||
bool hidden;
|
||||
const char *const *content;
|
||||
} ZeroEmbeddedSkill;
|
||||
|
||||
static const char *const zero_embedded_skill_agent_chunks[] = {
|
||||
"---\nname: agent\ndescription: Graph-first agent workflow for making focused Zero changes with CLI feedback.\n---\n\n# Zero Agent Workflow\n\nUse this when editing Zero code. `zero.graph` is compiler input; `.0` is the human projection. Use JSON only when another tool must parse stable fields.\n\n## Edit Through Patch\n\nAnchored edits win. Do not retype a function for one line or rewrite `.0` for one declaration.\n\n1. `--replace-in-fn`: edit one function's canonical body text.\n\n```sh\nzero patch . --replace-in-fn handleLine --old 'limit + 1' --new 'limit + 2'\n```\n\n`--old` must match `zero view --fn <name>` output exactly once.\n\n2. `--replace-fn` for one whole body:\n\n```sh\nzero patch . --replace-fn greet --body-file - <<'EOF'\ncheck world.out.write(\"hello agent\\n\")\nEOF\n```\n\n3. Declaration work stays in ops; call sites update:\n\n```sh\nzero patch . --op 'setConst name=\"limit\" value=\"64\"'\nzero patch . --op 'addParamTo fn=\"scan\" name=\"bias\" type=\"i32\" default=\"0\"' # updates every call site\nzero patch . --op 'setReturnType fn=\"scan\" type=\"i64\"'\n```\n\n4. New helpers stay graph-native:\n\n```text\nzero-program-graph-patch v1\nupsertFunction handle\nfn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>> {\n return null\n}\nend\n```\n\nPass a patch file, or stream full `zero-program-graph-patch v1` text with `zero patch . --patch-text -`.\n\nUse `addReturnExpr fn=\"maybe\" expr=\"null\"` for non-id returns and `appendStmt fn=\"main\" stmt=\"check std.http.listen(world, 3000_u16)\"` for one stmt. For pure helper tests, use `addTest name=\"addition works\" call=\"add\" arg0=\"2\" arg1=\"3\" expect=\"5\" type=\"i32\"`; reserve `addTestBody name=\"api add\" ... end` for custom bodies and remove bad ones with `deleteTest name=\"api add\"`. Labels are display names, not `__zero_test_*`.\n\nRunnable CLIs keep `World` on `pub fn main`; helpers are value-based. HTTP uses `handle(request, response)`.\n\nAfter `validated: check-equivalent`, the graph is saved and checked. Do not run `zero check`, `zero view`, or `zero export` just to confirm. `zero run . -- <args>` / `zero test` prove behavior or debug. Export only for requested `.0` review. Repeat `--op` to batch edits. For rewrites/handles: `zero skills get graph`.\n\nRead only for current code or handles:\n\n- `zero view --fn <name>`: one function source.\n- `zero view --fn <name> --around <text>`: enclosing block only.\n- `zero view --outline <module-or-file>`: signatures plus one-line docs.\n\nFor a new package: `zero init`, then `zero patch --op 'addMain'`.\n\n## zero query\n\n```text\nzero query [--json] [--fn <name>] [--find <text>] [--refs <name>] [--calls <name>]\n [--node <id>] [--depth <n>] [--full] [--handles] [--no-help] [graph-input|name]\n```\n\n- bare name that is not an existing path: runs `--find` against the current package\n- `zero query --fn <name> --handles`: patch handles for one function\n",
|
||||
"- add `--no-help` when you need handles without the patch-operation footer\n- `--find <text>`: search names, ids, types, values, and node kinds; prints matches with spans\n- `--calls <name>` / `--refs <name>`: resolved calls and semantic references\n- `--node <id>`: one node's span, parents, and children; short handles resolve here too\n\nImport/export, identity recovery, structural edits, and merge live in `graph`. Direct `.0` edits are a last resort; never delete `zero.graph`.\n\n## Verify Before Done\n\nAfter a fix works, exercise typical and boundary inputs.\n\n```sh\nzero run . -- <typical input>\nzero run . -- <empty or boundary input>\nzero test\n```\n\nIf behavior changed, add or update a `test` block. On a diagnostic, run `zero explain <code>`.\n\n## Rules\n\n- Treat effects as capabilities, not ambient globals: `World`, `std.fs`, `std.args`, `std.env`.\n- Use `Maybe<T>`, explicit `raises` / `raises [...]`, and `check` / `rescue` instead of hidden failure.\n- Do not invent syntax or CLI fields; load `language` when unsure.\n- Check `stdlib` before hand-writing parsing or validation; it ships validators such as `std.time`, `std.inet`, `std.regex`, and `std.unicode`. Fetch one module with `zero skills get stdlib --topic std.time`.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_builds_chunks[] = {
|
||||
"---\nname: builds\ndescription: Build, run, target, and profile Zero programs.\n---\n\n# Zero Builds\n\nUse this when an agent needs to run, build, cross-build, inspect artifacts, or explain target support for a Zero program.\n\n## Inputs\n\nMost build commands accept one of these graph-backed inputs:\n\n- a direct `.graph` or `.program-graph` artifact\n- a package directory containing `zero.toml` or `zero.json`\n- a direct path to `zero.toml` or `zero.json`\n\nWhen both manifests are present in the same package root, Zero uses\n`zero.toml`. Prefer one checked-in manifest unless testing precedence.\n\nFor packages, normal check, build, run, test, size, and mem commands compile from the checked-in `zero.graph` store. When the `.0` source projection was edited, those commands refresh the stale store from source first and note it on stderr; set `ZERO_STALE=fail` to make staleness an error (RGP008) instead. They never rewrite `.0` files. Use `zero verify-projection` when CI or review needs projection drift to fail, and `zero export` only when a human-readable projection needs regeneration. When already inside a package, omit the input and commands default to the current directory.\n\n## Run\n\nUse `zero run` for the host development loop:\n\n```sh\nzero run\nzero run -- input.txt\nzero run examples/hello.graph\nzero run examples/cli-file.graph -- input.txt\n```\n\nArguments after `--` are passed to the Zero program.\n\n## Build\n\nUse `zero build` when the user asks for an artifact. It is the normal command\nfor executables, object files, LLVM IR, cross-target artifacts, and CI outputs.\nUse direct emitters. The removed generated-C backend is not a fallback path.\n\n```sh\nzero build --emit exe --out .zero/out/app\nzero build --emit obj --out .zero/out/app.o\nzero build --emit exe examples/hello.graph --out .zero/out/hello\nzero build --emit obj examples/hello.graph --out .zero/out/hello.o\n```\n\nUse LLVM only when the request is explicit. LLVM is experimental: it is not the\ndefault backend and not release eligible. Textual IR is inspectable with\n`--emit llvm-ir`; host executable builds require a ready clang toolchain. LLVM\ncurrently lowers scalar code, direct calls, branches, loops, primitive fixed\narrays, byte views, readonly strings, and primitive `std.mem` helpers:\n\n```sh\nzero build --backend llvm --emit llvm-ir examples/hello.graph --out .zero/out/hello.ll\nzero build --backend llvm --emit exe examples/hello.graph --out .zero/out/hello-llvm\nzero run --backend llvm examples/hello.graph\n```\n\nUse `--json` when a tool will read exact build fields:\n\n```sh\nzero build --json --target linux-musl-x64 examples/memory-package\n```\n\nUseful JSON fields include `artifact`, `sizeBytes`, `toolchain`, `releaseTargetContract`, selected target facts, linker flavor, and sysroot status.\n\n## Graph Inputs\n\nWhen an agent is authoring a repository graph package, patch the package graph\n",
|
||||
"and use normal build/run commands. They compile from `zero.graph` and do not\nrequire `.0` projections to exist:\n\n```sh\nzero patch --op 'addMain'\nzero run\nzero build --out .zero/out/app\n```\n\nUse `zero export` when humans need checked-in `.0` projections. After a human edits a projection, the next graph-store compile refreshes the store automatically, or run `zero import` to refresh it explicitly. `zero status` reports the active store format.\n\nBuild, run, test, size, and mem commands maintain a derived final-MIR cache in the native cache, keyed by graph hash, compiler version, target, emit kind, and backend request. Agents should not patch `.zmir` files; JSON outputs report cache reuse in a `mappedFinalMir` row.\n\nIf another tool hands you a standalone `.program-graph`, normal `zero build`\nand `zero run` can validate it as an interchange artifact. Do not create a\nstandalone graph artifact for the ordinary package loop; use the package path so\nthe compiler reads `zero.graph` directly.\n\n## Targets\n\nInspect target names and capability facts before cross-building:\n\n```sh\nzero targets\nzero check --target linux-musl-x64 examples/memory-package\nzero inspect --target linux-musl-x64 examples/memory-package\n```\n\nHosted APIs such as process args, environment, filesystem, net, and proc are target-gated. A non-host target may reject code that checks on the host.\n\n## Profiles\n\nCommon profile names are `debug`, `dev`, `release-fast`, `release-small`, `tiny`, and `audit`.\n\n```sh\nzero build --profile release-small examples/hello.graph\nzero size --profile tiny examples/hello.graph\n```\n\nUse `zero size` to explain retained functions, sections, literals, runtime shims, imports, debug metadata, and optimization hints. Add `--json` when a tool needs exact fields.\nUse `zero size --backend llvm` when the question is specifically about the explicit LLVM backend; the report includes LLVM target triple, optimization level, retained runtime/helper facts, toolchain readiness, and direct-vs-LLVM comparison rows.\n\n## Troubleshooting\n\n- `zero doctor` checks host and target readiness.\n- `zero doctor --json` reports `llvmToolchain` readiness for explicit LLVM host builds.\n- LLVM JSON facts include `backendLifecycle` so tools can distinguish explicit experimental readiness from release support.\n- `BLD003` means an old backend flag was requested; remove it.\n- `BLD004` with `backendBlocker.backend: \"llvm\"` means the selected LLVM artifact, target, command, MIR subset, or clang toolchain is not ready.\n- Missing sysroot facts identify the required `ZERO_SYSROOT_*` variable.\n- Unsupported targets fail explicitly instead of silently choosing another backend.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_diagnostics_chunks[] = {
|
||||
"---\nname: diagnostics\ndescription: Read Zero diagnostics, explanations, and typed fix plans.\n---\n\n# Zero Diagnostics\n\nUse this when Zero code fails to parse, typecheck, build, test, or target-check.\nZero diagnostics are intended for agents: start with the readable command\noutput. Use JSON only when an automation tool needs stable fields or a debugging\nsession needs exact spans, repair metadata, or machine-readable diagnostics.\n\n## Commands\n\n```sh\nzero check\nzero explain <diagnostic-code>\n```\n\nUse machine-readable output when a tool needs exact fields:\n\n```sh\nzero check --json\nzero explain --json <diagnostic-code>\nzero fix --plan --json\n```\n\n`zero fix` reads graph-backed inputs. It reports candidate repairs for graph diagnostics; projection-only source must be imported before repair planning.\n\n## Diagnostic Shape\n\nImportant fields from `zero check --json`:\n\n- `code`: stable diagnostic code such as `NAM003` or `TAR002`\n- `message`: short human summary\n- `path`, `line`, `column`, `length`: source span\n- `expected` and `actual`: structured mismatch facts when available\n- `help`: concise next action\n- `fixSafety`: safety label for an agent repair\n- `repair`: optional repair id and summary\n- `related`: extra spans or facts\n\nDo not scrape terminal prose for automation. Use JSON when a script or tool needs stable fields.\n\n## Fix Safety\n\n`zero fix --plan --json` reports `safetyLevels` and per-fix `safety`:\n\n- `format-only`: formatting or trivia only\n- `behavior-preserving`: intended not to change runtime behavior\n- `api-changing`: signatures, exports, or call sites may change\n- `target-changing`: target support or capability use may change\n- `requires-human-review`: the compiler cannot prove the edit is safe\n\nApply only the edit you can justify from the source and fix plan. Treat `requires-human-review` as a planning hint, not an automatic patch.\n\n## Common Codes\n\n- `NAM003`: unknown name; declare it, import it, or fix spelling.\n- `IMP001`: unknown package-local import.\n- `IMP002`: package-local import cycle.\n- `PKG001`: local dependency path lacks `zero.toml` or a compatibility\n `zero.json`.\n- `PKG002`: package dependency cycle.\n- `PKG003`: one package name resolves to conflicting versions.\n- `PKG004`: selected target is not supported by a dependency.\n- `TAR001`: unknown target; inspect `zero targets`.\n- `TAR002`: capability unavailable for selected target.\n- `BLD003`: removed backend flag; use direct emitters.\n- `STD002`: unknown standard-library helper; use a documented `std.<module>.<helper>` name.\n- `STD003`: standard-library capability or contract mismatch; inspect the helper signature and required capability.\n- `TYP009`: immutable value used where a mutable destination is required; make the binding `var` or pass mutable storage.\n",
|
||||
"- `MEM003`: one function's fixed locals exceed the 128 KiB frame limit; split the buffer into smaller buffers in helper functions, or process the data in fixed-size chunks.\n- `RGP007`: ambiguous source identity during import; split the text edit into smaller passes or make the change with `zero patch`.\n- `RGP008`: stale package projection while `ZERO_STALE=fail` is set; run `zero import`, or unset the variable to let the command refresh automatically.\n- `RGP009`: binary `zero.graph` store unreadable by this compiler, usually written by a different zero build; rebuild it with this binary via `zero import .` or install the matching compiler (compare `zero --version` build hashes).\n\n## Agent Triage\n\n1. Run the failing command normally first.\n2. If a debugging session needs exact machine fields, rerun with `--json` and use the span to inspect only the relevant source.\n3. Run `zero explain <code>` before broad refactors.\n4. If multiple diagnostics share a root cause, fix the earliest source issue.\n5. Re-run the same command after the patch.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_graph_chunks[] = {
|
||||
"---\nname: graph\ndescription: Use ProgramGraph commands as the primary agent authoring and inspection surface.\n---\n\n# Zero Graph Authoring\n\nUse this when creating, inspecting, patching, importing, or exporting Zero programs through the graph interface, the primary agent authoring surface. `zero.graph` is the repository graph store for packages, `.0` files are the human-readable source projection, and `.program-graph` files are derived debug/interchange artifacts.\n\n## Source Boundary\n\n- Normal `zero check`, `zero run`, `zero test`, `zero build`, `zero size`, and `zero mem` compile packages from `zero.graph` (`zero.toml` takes precedence over `zero.json`). When the `.0` projection was edited, those commands refresh the stale store from source first and note it on stderr; `ZERO_STALE=fail` turns that refresh into an RGP008 error instead.\n- `zero import` refreshes `zero.graph` from edited source explicitly. It accepts the package root, manifest, or any source path inside the package, updates an existing store in place, and preserves node handles where the edit is unambiguous. When several edited nodes could claim one handle, import picks the structurally closest match and notes it on stderr. A whole-file rewrite that keeps the file's function set (names and signatures) is accepted in one pass with regenerated node identities for that file, noted on stderr; only ties that span files or change the function set fail (RGP007) with a split-the-edit strategy. Never delete the store to force a reimport, and omit `--out` for package imports.\n- `zero export [package]` materializes `.0` projections for human review; do not export just to silence stale-projection notes or after every patch. Compiler commands report projection state but never rewrite `.0` files. `zero verify-projection [package]` fails on drift without writing anything.\n- `zero.graph` is binary by default. Reads auto-detect text and binary stores, writes preserve the existing encoding, and `zero status` reports `store format: text|binary`. Use `--format text` only for a deliberately readable debug store. Stdlib `std/*.graph` stores are binary; sibling `std/*.0` files are projections, not the stdlib compile source.\n\n## Create\n\n```sh\nzero init\nzero patch --op 'addMain'\n```\n\n`zero init` defaults to the current directory and that folder's name. Use `zero init app` for a new subdirectory, `--manifest json` only for explicit compatibility, and `--template cli|lib|package` only when the user asks for starter files.\n\n## Inspect\n\n```sh\nzero query userTotals # bare name that is not a path = --find in the current package\nzero query --fn main # one function's signature and call summary\nzero query --fn main --handles # adds stmt/param patch handles; use before patching\nzero query --calls std # resolved call targets\nzero query --refs add # semantic references\nzero query --node '#expr_2cad38f9' --depth 2 # node-scoped: span, parents, children\n",
|
||||
"zero view --fn main # one function's canonical source\nzero view --fn main --handles # the same source with a trailing // #handle per statement\nzero view --fn main --around minLength # only the enclosing block containing the text\nzero view --outline src/main.0 # signatures plus one-line docs, no bodies\nzero status # store format and projection state\n```\n\n`--node` defaults to depth 1; add `--full` for the whole-module report. Use handles from `zero view --fn <name> --handles`, `--find`, or `--fn <name> --handles` for checked edits (`set`, `insert`, `insertEdge`, `replace`, `replaceExpr`, `rename`, `delete`); delete compacts ordered graph groups so valid sibling order is preserved. Handles accept short forms: the id segment (`#55ae541c`), any unique prefix (`#55ae`), or `#head..tail` for ids with long shared prefixes; `--handles` views print the shortest form that resolves, and a missing handle fails with the nearest existing one. Reserve unfiltered `zero query` dumps for tools that need every node and edge.\n\n## Patches\n\nEdit through the graph: `zero patch` covers everything from one-line changes (`addCheckWrite`, `rename`, `set`) to whole function bodies (`--replace-fn <fn> --body-file -` with a heredoc). Direct `.0` text edits are a last resort for changes no patch op expresses; the compiler will refresh the graph from edited source, but patch keeps the loop faster (0.2s validation, no reconcile pass) and preserves node identity for queries.\n\nA successful patch loads, applies, validates, and saves `zero.graph`, and prints the saved path, new graph hash, functions, and tests. Do not run `zero check`, `zero view`, `zero query`, or `zero export` just to confirm that the patch applied. Use those only when you need current text/handles, projection review, or a diagnostic loop.\n\n```sh\nzero patch \\\n --op 'addFunction name=\"add\" ret=\"i32\"' \\\n --op 'addParam fn=\"add\" name=\"left\" type=\"i32\"' \\\n --op 'addParam fn=\"add\" name=\"right\" type=\"i32\"' \\\n --op 'addReturnBinary fn=\"add\" name=\"+\" left=\"left\" right=\"right\" type=\"i32\"' \\\n --op 'addTest name=\"addition works\" call=\"add\" arg0=\"40\" arg1=\"2\" expect=\"42\" type=\"i32\"'\n```\n\nUse `addTest` for one pure helper call; reserve `addTestBody` for custom body rows. Test labels are display names, not `__zero_test_*` function names. If a custom test fails as an unknown function label, delete it and recreate simple coverage with `addTest` instead of renaming the label.\n\n",
|
||||
"For declaration-level edits, stay in patch ops instead of rewriting files. `setConst name=\"limit\" value=\"64\"` replaces a top-level const's initializer by package-scoped name. `addParamTo fn=\"scan\" name=\"bias\" type=\"i32\" default=\"0\"` appends a parameter to an existing function and updates every call site in the package (nested calls included) to pass the default explicitly, reporting `updated N call sites`; without `default` it fails with the call-site count. `setReturnType fn=\"scan\" type=\"i64\"` changes a declared return type. All three revalidate and batch like any other op.\n\nFor a new or replacement multi-statement helper, use complete source through `upsertFunction` instead of editing `.0` and importing:\n\n```text\nzero-program-graph-patch v1\nupsertFunction handle\nfn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>> {\n return null\n}\nend\n```\n\nFor runnable CLI programs, keep `World` on `pub fn main`; value-based helpers build and run more reliably. HTTP server helpers use `handle(request, response)`.\n\n`upsertFunction` parses exactly one complete function declaration, inserts it if missing, and replaces the prior declaration and body if it already exists. For smaller append-only work, `appendStmt fn=\"main\" stmt=\"check std.http.listen(world, 3000_u16)\"` appends one canonical statement, and `addReturnExpr fn=\"maybe\" expr=\"null\"` appends a return statement for any expression. `addReturnValue` is only for identifier returns.\n\nFor sub-line edits, think in graph: take a handle from `zero view --fn <name> --handles` and change exactly one thing. `set` edits one field (a literal `value`, a declared `type`, a `name`/operator); `replaceExpr` swaps any expression subtree, and aimed at a statement handle it replaces that statement's expression (initializer, condition, return value). Repeat `--op` to batch several micro-ops into one patch with a single revalidation:\n\n```sh\nzero patch . \\\n --op 'set node=\"#a647\" field=\"value\" expect=\"1\" value=\"8\"' \\\n --op 'replaceExpr node=\"#5f15\" with=\"i < k + 1\"'\n```\n\nTo express one cross-cutting transformation instead of editing N sites, use structural rewrite by example. `--rewrite '<pattern>' --to '<template>'` matches canonical projection expressions structurally; `$A`, `$B` bind arbitrary subtrees and the same metavariable twice must match equal subtrees. The default is a dry run that lists every site as `path fn:handle` with rendered before/after; `--apply` rewrites all sites in one batch with one revalidation, and `--fn <name>` scopes matching to one function. Patterns are expression-level only; unsupported subtree kinds are skipped and counted.\n\n```sh\nzero patch . --rewrite 'bnCmp($A, $B) == 0' --to 'bnEq($A, $B)' # dry run\nzero patch . --rewrite 'bnCmp($A, $B) == 0' --to 'bnEq($A, $B)' --apply # rewrite every site\n```\n\n",
|
||||
"For multi-statement bodies, use `replaceFunctionBody` for a whole function or `replaceBlockBody` for one selected `Block` node. Body rows accept canonical projection syntax, the same text `zero view` prints:\n\n```text\nzero-program-graph-patch v1\nexpect graphHash \"graph:a7f7e6899a73f3b4\"\nreplaceFunctionBody main\n let name: Maybe<String> = std.args.get(1)\n if name.has {\n check world.out.write(\"hello \")\n check world.out.write(name.value)\n check world.out.write(\"\\n\")\n } else {\n check world.out.write(\"hello anonymous\\n\")\n }\nend\n```\n\nTo replace one function body without patch syntax or shell quoting, use `--replace-fn` with `--body-file`. `--body-file -` reads the body rows from stdin, so a heredoc does the whole edit in one call:\n\n```sh\nzero patch --replace-fn main --body-file - <<'EOF'\n let name: Maybe<String> = std.args.get(1)\n if name.has {\n check world.out.write(\"hello \")\n check world.out.write(name.value)\n check world.out.write(\"\\n\")\n } else {\n check world.out.write(\"hello anonymous\\n\")\n }\nEOF\n```\n\nThe body holds only the new rows, exactly what `zero view --fn <name>` prints between the signature braces (no header, no `end`). Quotes, `$variables`, and backslashes pass through a quoted heredoc untouched. The alternative is a file path: `zero patch --replace-fn <name> --body-file /tmp/main.body`.\n\nTo change a few characters inside a large function, do not retype the body: `--replace-in-fn` replaces one unique literal occurrence of `--old` in the function's canonical body text with `--new` (Edit semantics), then revalidates exactly like `--replace-fn`:\n\n```sh\nzero patch --replace-in-fn handleLine --old 'limit + 1' --new 'limit + 2'\n```\n\nA missing or non-unique `--old` fails with the occurrence count; extend `--old` with surrounding lines from `zero view --fn <name>` until it matches once. Inline `--old`/`--new` accept `\\n` escapes; `--old-file`/`--new-file <file|->` read multi-line text from a file or stdin.\n\nTo patch one branch instead of rewriting the whole function, find the block handle first:\n\n```sh\nzero query --find Block\n```\n\n```text\nzero-program-graph-patch v1\nreplaceBlockBody #block_32cefdd9\n check world.out.write(\"updated\\n\")\nend\n```\n\nPreview without writing, and list operation shapes without loading a graph:\n\n```sh\nzero patch --check-only /tmp/body.patch\nzero patch --dry-run --json /tmp/body.patch\nzero patch --op help\n```\n\nSupported graph patch operations (authoring ops first; node-handle ops are the advanced surface):\n\n```text\naddMain\naddCheckWrite fn=\"main\" text=\"hello\\n\"\naddFunction name=\"add\" ret=\"i32\"\naddParam fn=\"add\" name=\"left\" type=\"i32\"\naddParamTo fn=\"add\" name=\"bias\" type=\"i32\" default=\"0\"\nsetConst name=\"limit\" value=\"64\"\nsetReturnType fn=\"add\" type=\"i64\"\naddReturnBinary fn=\"add\" name=\"+\" left=\"left\" right=\"right\" type=\"i32\"\n",
|
||||
"addLetLiteral fn=\"main\" name=\"count\" type=\"u32\" value=\"0\"\naddLetBinary fn=\"add\" name=\"sum\" type=\"i32\" operator=\"+\" left=\"left\" right=\"right\"\naddReturnValue fn=\"identity\" value=\"input\" type=\"i32\"\naddReturnExpr fn=\"maybe\" expr=\"null\"\nappendStmt fn=\"main\" stmt=\"check std.http.listen(world, 3000_u16)\"\naddCheckWriteValue fn=\"main\" value=\"message\" type=\"String\"\naddTest name=\"addition works\" call=\"add\" arg0=\"40\" arg1=\"2\" expect=\"42\" type=\"i32\"\naddTestBody name=\"api add\"\n expect apiAddOk()\nend\nrenameTest name=\"api add\" value=\"api add route\"\ndeleteTest name=\"api add\"\nupsertFunction handle\nfn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>> {\n return null\n}\nend\nreplaceFunctionBody main\n let name Maybe<String> = std.args.get 1\n if name.has\n check world.out.write \"hello \"\n check world.out.write name.value\n check world.out.write \"\\n\"\n else\n check world.out.write \"hello anonymous\\n\"\nend\nreplaceBlockBody #block_id\n check world.out.write \"updated\\n\"\nend\nexpect graphHash \"graph:a7f7e6899a73f3b4\"\nset node=\"#id\" field=\"value\" expect=\"old\" value=\"new\"\ninsert node=\"#id\" kind=\"Literal\" parent=\"#parent\" edge=\"arg\" order=\"0\" type=\"String\" value=\"text\"\ninsertEdge from=\"#from\" to=\"#to\" edge=\"arg\" target=\"node\" order=\"0\"\nreplace node=\"#id\" expect=\"nodehash:abc123\" kind=\"Literal\" type=\"String\" value=\"text\"\nreplaceExpr node=\"#id\" with=\"left + 1\"\ndelete node=\"#id\" expect=\"nodehash:abc123\"\ndelete node=\"#id\"\nrename node=\"#id\" expect=\"old\" value=\"new\"\n```\n\n`insert` and `insertEdge` default `order` to `0`, which fits singular edges like `expr`, `left`, and `declaredType`. For precise existing-node edits, pin the graph hash and node facts:\n\n```sh\nzero patch \\\n --expect-graph-hash graph:a7f7e6899a73f3b4 \\\n --op 'set node=\"#expr_653eeb6e\" field=\"value\" expect=\"hello from zero\\n\" value=\"hello agent\\n\"'\n```\n\nFor larger edits, write a patch file under `/tmp` or pass `--patch-text`; `--patch-text -` reads a complete `zero-program-graph-patch v1` patch from stdin. Always include `expect graphHash` when a patch is carried across tool calls.\n\n## Artifacts, Reconcile, And Diff\n\nCreate a derived artifact only to carry a graph between tools, and validate it before applying accepted changes back to a package store:\n\n```sh\nzero dump --out .zero/agent/app.program-graph\nzero validate .zero/agent/app.program-graph\nzero view .zero/agent/app.program-graph\n```\n\nDo not commit `.program-graph` files unless the user explicitly asks. `zero source-map` connects graph nodes to source ranges. When a human edited source after a graph was captured, reconcile before relying on old node IDs:\n\n```sh\nzero reconcile .zero/agent/app.before.program-graph --source <projection-or-package>\n```\n\n",
|
||||
"`zero reconcile` reports unchanged, edited, inserted, deleted, ambiguous, and identity-changed nodes; ambiguous matches fail instead of assigning stale handles.\n\nFor readable Git diffs of `.graph` files, mark them with `*.graph diff=zero-graph` in `.gitattributes` and set `git config diff.zero-graph.textconv 'zero diff'` (`bin/zero diff` inside a Zero checkout). `zero diff` prints canonical review text for textconv, and `zero diff --fn <name>` scopes it to one function; keep using `zero query`, `zero inspect`, and `zero patch` for graph work.\n\n`zero merge --base <base-zero.graph> --left <left-zero.graph> --right <right-zero.graph> <package>` combines independent node-hash edits and writes only the target store; run `export` separately if a human needs the refreshed projection. Build and run commands may also write a derived final-MIR cache in the native cache; agents should not patch `.zmir` files.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_language_chunks[] = {
|
||||
"---\nname: language\ndescription: Compact zerolang syntax and semantics guide for agents.\n---\n\n# zerolang Language\n\nUse this when reading or writing Zero source. `.0` files are the human-readable projection syntax, not the package compiler input: package commands compile from `zero.graph` and refresh it automatically after `.0` edits. Read one function with `zero view --fn <name>` instead of whole files.\n\n## Minimal Program\n\n```zero\npub fn main(world: World) -> Void raises {\n check world.out.write(\"hello from zerolang\\n\")\n}\n```\n\n`pub fn` exports a function. `World` carries runtime capabilities. `raises` marks a fallible function. `check` calls a fallible operation and propagates failure.\n\n## Declarations\n\n- `use std.mem` or `use helpers`\n- `const answer: i32 = 42` (top-level consts work in functions and tests)\n- `type Point { x: i32, y: i32, }`\n- `enum Mode { fast, small, }`\n- `choice Result { ok: i32, err: String, }`\n- `fn answer() -> i32 { return 42 }`\n- `pub fn main(world: World) -> Void raises { ... }`\n- `test \"name\" { expect true }`\n\n## Values, Mutation, And Control Flow\n\nUse `let` by default and `var` only when a binding changes. Conditions are `Bool`; do not rely on truthy integers or strings. Operators are normal infix expressions: `a + b`, `a % b`, `a == b`, `a < b`, `a && b`. Comments start with `//`.\n\n```zero\nfn sum_odds(n: i32) -> i32 {\n var i: i32 = 0\n var total: i32 = 0\n while i < n {\n i = i + 1\n if i % 2 == 0 {\n continue\n }\n if total > 100 {\n break\n }\n total = total + i\n }\n return total\n}\n```\n\n`break` exits the nearest loop and `continue` skips to its next iteration.\n\n## Types\n\nPrimitive and scalar types:\n\n```text\nBool Void String char Type\ni8 i16 i32 i64 isize\nu8 u16 u32 u64 usize\nf32 f64\n```\n\nBare integer literals adopt the other operand's integer type when in range, so `i * 12` works for `i: usize`. Use suffixes such as `_u8` or `_usize` only when no operand gives context, and `as` for intentional casts.\n\nCore capability, resource, and helper types recognized by the compiler:\n\n```text\nWorld WorldStream\nFs File ByteBuf\nNullAlloc FixedBufAlloc PageAlloc GeneralAlloc\nVec Duration RandSource ProcStatus ProcChild\nAddress Net Conn Listener\nHttpMethod HttpClient HttpServer HttpResult HttpError HttpHeaderValue\nJsonDoc BufferedReader BufferedWriter FixedReader FixedWriter\nEnv Args Clock Rand Proc Alloc\nMaybe<T> Span<T> MutSpan<T>\nref<T> mutref<T> owned<T>\n```\n\n## Shapes, Enums, And Choices\n\n```zero\nlet point: Point = Point { x: 1, y: 2 }\nlet result: Result = Result.ok(42)\n```\n\nMatches must be exhaustive unless they use the fallback arm `_`:\n\n```zero\nmatch result {\n .ok(value) {\n expect value == 42\n }\n .err(message) {\n expect true\n }\n}\n```\n\n## Errors\n\n```zero\nfn value(i: i32) -> i32 raises [Odd] {\n if i % 2 == 0 {\n",
|
||||
" return i\n }\n raise Odd\n}\n\npub fn main(world: World) -> Void raises {\n let item: i32 = rescue value(3) err 1\n if item == 1 {\n check world.out.write(\"fallible ok\\n\")\n }\n}\n```\n\n`raises [Error]` restricts the error set. Plain `raises` is open. Calling a fallible function requires `check` or `rescue`.\n\n## Borrowing And Memory Views\n\n- `ref<T>` is a read-only borrow, passed with `&value`.\n- `mutref<T>` is a mutable borrow, passed with `&mut value`. Shape parameters such as `mutref<Point>` work, including field mutation and nested calls.\n- `[N]T` is a fixed array; `Span<T>` is a read-only view; `MutSpan<T>` is a writable view.\n- Returning a span backed by local fixed-array storage is rejected; return an owned value or keep the view local.\n- `Maybe<T>` represents absence; read `.value` only inside a visible `.has` guard, or use `check` / `rescue`. `var name: Maybe<String> = null` declares an absent local to assign or return later.\n- `owned<T>` marks explicit resource ownership.\n- One function's fixed locals are limited to 128 KiB (MEM003). Split large buffers into smaller buffers in helper functions, or process data in fixed-size chunks.\n\n```zero\nfn bump(point: mutref<Point>) -> Void {\n point.x = point.x + 1\n}\n\npub fn main() -> Void {\n var storage: [4]u8 = [1, 2, 3, 4]\n let view: MutSpan<u8> = storage\n let first: u8 = storage[0]\n storage[0] = 9\n}\n```\n\nArray and span indexing is bounds-checked at runtime; an out-of-range index traps with a signal exit and no output, so exercise boundary inputs before declaring a change done.\n\n## Generics\n\n```zero\ntype Box<T: Type> {\n value: T,\n}\n\nfn id<T: Type>(value: T) -> T {\n return value\n}\n\ntype FixedVec<T: Type, static N: usize> {\n len: usize,\n items: [N]T,\n}\n```\n\n## Standard Library Call Shapes\n\nLoad `zero skills get stdlib` for the full signature catalog. Beyond the `std.mem`/`std.parse`/`std.rand`/`std.json` helpers shown below, it ships ready-made validators: `std.time` (RFC 3339 date/time incl. the exact leap-second rule), `std.inet` (IPv4/IPv6/hostname), `std.regex` (ECMA subset), and `std.unicode` (strict UTF-8). Check the catalog before hand-writing parsing or validation logic. Target-neutral helpers follow these shapes:\n\n```zero\npub fn main() -> Void {\n let bytes: Span<u8> = std.mem.span(\"zero\")\n let n: usize = std.mem.len(bytes)\n let same: Bool = std.mem.eql(\"zero\", \"zero\")\n\n let parsed: Maybe<u16> = std.parse.parseU16(\"8080\")\n if parsed.has {\n expect parsed.value == 8080\n }\n\n var rng: RandSource = std.rand.seed(7_u32)\n let random: u32 = std.rand.nextU32(&mut rng)\n let count: Maybe<u32> = std.json.u32(\"{\\\"count\\\":42}\", \"count\")\n expect same && random == 1025555898_u32 && count.has\n}\n```\n\n",
|
||||
"Hosted helpers are capability-gated by target. There is no stdin; hosted programs take input from `std.args` and files through `std.fs`:\n\n```zero\npub fn main(world: World) -> Void raises {\n let first: Maybe<String> = std.args.get(1)\n if first.has {\n check world.out.write(first.value)\n }\n\n var path_storage: [16]u8 = [0; 16]\n let path: String = check std.path.join(path_storage, \".zero\", \"x\")\n let fs: Fs = std.fs.host()\n var file: owned<File> = check std.fs.createOrRaise(fs, path)\n check std.fs.writeAllOrRaise(&mut file, std.mem.span(\"hello\\n\"))\n}\n```\n\nIf unsure, run `zero check` instead of inventing syntax, and pair behavior changes with a `test` block:\n\n```zero\nfn point_sum(point: Point) -> i32 {\n return point.x + point.y\n}\n\ntest \"shape\" {\n let point: Point = Point { x: 40, y: 2 }\n expect point_sum(point) == 42\n}\n```\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_packages_chunks[] = {
|
||||
"---\nname: packages\ndescription: Create, inspect, and repair Zero packages and manifests.\n---\n\n# Zero Packages\n\nUse this when working with `zero.toml`, compatibility `zero.json`, package-local modules, package tests, or multi-file Zero projects.\n\n## Create\n\nFor agent-authored packages, start graph-first:\n\n```sh\nzero init\nzero patch --op 'addMain'\n```\n\n`zero.graph` is the package graph store and compiler input. `.0` files are the\nhuman-readable projection; export them only when a human asks to review or edit\nthe projection. `zero init` defaults to the current directory and uses that\ndirectory's folder name as the package name. Use `zero init app` when the\nuser asks for a new subdirectory. It writes TOML metadata by default; use\n`zero init --manifest json [package]` only for explicit compatibility cases.\nUse `zero init --template cli|lib|package [package]` only when the user\nexplicitly asks for starter files.\n\n## Manifest\n\nMinimal executable package in TOML:\n\n```toml\n[package]\nname = \"hello\"\nversion = \"0.1.0\"\n\n[targets.cli]\nkind = \"exe\"\nmain = \"src/main.0\"\n```\n\nThe target `main` path names the human-readable projection for source maps,\nexport/import, and review. Normal package commands compile from `zero.graph`.\n\nJSON is also accepted for compatibility, but new agent-authored packages should\nuse TOML:\n\n```json\n{\n \"package\": { \"name\": \"hello\", \"version\": \"0.1.0\" },\n \"targets\": { \"cli\": { \"kind\": \"exe\", \"main\": \"src/main.0\" } }\n}\n```\n\nIf both `zero.toml` and `zero.json` exist in the same package root, Zero uses\n`zero.toml`. Keep one manifest checked in unless the task is specifically\ntesting precedence.\n\nPass either the package directory or manifest to commands:\n\n```sh\nzero check\nzero check zero.toml\nzero run examples/systems-package\n```\n\n## Module Imports\n\nPackage-local imports resolve from `src/`:\n\n- `use helpers` resolves `src/helpers.0`\n- `use config.parser` resolves `src/config/parser.0`\n- `use config.parser` may also resolve `src/config/parser/mod.0`\n\nStandard library imports use the same `use` form:\n\n```zero\nuse std.mem\nuse std.parse\n```\n\nAvoid implicit files. If an import is unknown, run:\n\n```sh\nzero check\nzero inspect\n```\n\n## Dependencies\n\nCurrent packages support local path dependencies and registry metadata. Local\ndependencies must point at a directory containing `zero.toml` or compatibility\n`zero.json`; `zero.toml` takes precedence.\n\nTOML dependency metadata:\n\n```toml\n[dependencies.local-tools]\npath = \"../local-tools\"\nversion = \"0.1.0\"\n```\n\n```json\n{\n \"dependencies\": {\n \"local-tools\": { \"path\": \"../local-tools\", \"version\": \"0.1.0\" }\n }\n}\n```\n\nThe resolver is declarative; it records deterministic lock facts under `.zero/package-locks/` and does not fetch remote package code.\n\n",
|
||||
"Package compiler commands validate and compile from a checked-in `zero.graph` store, including target and package metadata, and can operate when `.0` source projections are missing. When the projection was edited, commands that consume the store (`zero check`, `zero build`, `zero run`, `zero test`, `zero query`, `zero view`, `zero diff`, and friends) refresh the stale store from source and note it on stderr (`ZERO_STALE=fail` makes that an error); they never rewrite `.0` files. When `zero.graph` is the newer side, for example right after `zero patch`, they keep using the graph and note that too; when both sides changed independently they fail with `RGP006` and offer `zero import` or `zero export` as repairs. Drift is classified by content (each store write records a source projection hash), never by file timestamps, so staged or cloned workspaces behave identically. Use `zero verify-projection` when drift must fail the workflow, and `zero export` only when a human-readable projection needs regeneration.\n\n## Inspect\n\n```sh\nzero inspect\nzero doc\nzero dev\n```\n\nUse `--json` when a tool needs exact graph, doc, or dev fields. Useful `graph`\nfacts include modules, source paths, import edges, public and private symbol\ncounts, function effects, required capabilities, target facts, dependency\nfacts, and package cache key inputs.\n\n## Graph Authoring\n\nFor agent-authored packages, prefer the repository graph surface:\n\n```sh\nzero init\nzero patch --op 'addMain'\n```\n\nInspect and patch existing packages through the graph. Create an artifact under\n`.zero/` only when another tool needs a file artifact:\n\n```sh\nzero view\nzero patch --op 'addMain'\n```\n\nPackage-level patches write `zero.graph`; successful patch output includes the new graph hash and top-level symbols. Use `zero export` only to materialize `.0` for human review, and `zero import` after humans edit that projection; import accepts the package root or any source path inside it and updates the existing store in place. Keep derived graph artifacts out of the package source unless the user explicitly asks for them.\n\nRepository graph stores are binary by default. Use `zero init --format text` or\n`zero import --format text [package]` only when the package\nintentionally needs a readable debug store. Normal reads auto-detect both\nencodings, and normal writes preserve an existing text or binary store. Stdlib\n`std/*.graph` stores are binary graph stores; `std/*.0` siblings are human\nprojections and are not used as the stdlib compile source.\n\n## Common Repairs\n\n- `IMP001`: create the imported module, fix its path, or adjust `use`.\n- `IMP002`: break a direct import cycle.\n- `PKG001`: fix a local dependency path so it contains `zero.toml` or a compatibility `zero.json`.\n- `PKG002`: break a package dependency cycle.\n- `PKG003`: avoid resolving one package name to multiple versions.\n- `PKG004`: update target metadata or choose a supported target.\n\n",
|
||||
"Prefer a package-local fix over moving unrelated files.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_stdlib_chunks[] = {
|
||||
"---\nname: stdlib\ndescription: Use Zero standard library modules and target-gated capabilities.\n---\n\n# Zero Standard Library\n\nUse this for common library calls, memory helpers, hosted I/O, or target-capability guidance.\n\n## Import\n\n```zero\nuse std.mem\n\nuse std.parse\n```\n\nCall functions with their module path, such as `std.mem.len(value)`.\n\n## Target-Neutral Helpers\n\n- `std.mem`: spans, byte copy/fill, non-owned scalar item copy/fill/search/compare, scalar item slicing, chunking, cursor helpers, length, safe indexed `get`, fixed-buffer allocation, byte buffers, and caller-owned vectors.\n- `std.collections`: fixed-capacity push/pop, deque front/back operations, first/last access, indexed insert/replace/remove, unique insert, append, clear, truncate, fill, reverse, rotate, live-prefix and set/map views, `FixedSet<T>`, `FixedDeque<T>`, `FixedRingBuffer<T>`, and `FixedMap<K,V>` storage wrappers, set/map/ring-buffer capacity state, count, contains, value removal, swap, swap-remove, move-to-front, and parallel key/value map helpers over caller-owned array or allocator-returned span storage plus explicit lengths.\n- `std.search`: generic scalar index search plus typed min/max, lower-bound, upper-bound, and binary-search helpers.\n- `std.sort`: in-place insertion, stable, unstable, swap, rotate, reverse, partition, sorted dedupe, and sortedness helpers for ascending and descending `i32`, `u32`, and `usize` storage.\n- `std.ascii`: ASCII byte predicates, case conversion, and digit value helpers.\n- `std.fmt`: caller-buffer and fixed-writer formatting for booleans, 32-bit and 64-bit integer text, integer bases, signs, and padding.\n- `std.text`: ASCII and UTF-8 byte-backed text validation.\n- `std.unicode`: strict UTF-8 codepoint decode/encode iteration, cursor status helpers, and codepoint-class helpers; pair with `std.text.utf8Valid`/`std.text.utf8Len` for whole-span validation and counting.\n- `std.math`: fixed-width min/max/clamp, checked and saturating integer arithmetic, GCD/LCM, powers, modular power, roots, combinatorics, primality, and divisor routines.\n- `std.path`: target-neutral lexical path basename, dirname, extension, stem, component, abs, join, normalize, and relative helpers.\n- `std.codec`: endian reads/writes, unsigned and signed varints, base32/base64/hex encode/decode, CRC helpers, and byte checksums.\n- `std.csv`: allocation-free CSV validation, record scanning, field decoding, and fixed-arity writers.\n- `std.parse`: byte scanners plus decimal, radix, prefix integer width, bool, duration, and byte-size parsers returning `Maybe<T>`.\n- `std.regex`: compile-once and one-shot regular expression matching/search/split/replace for a documented ECMA-262-leaning subset (literals, classes, anchors, word boundaries, greedy quantifiers, alternation, groups); unsupported constructs fail with structured status codes and offsets.\n",
|
||||
"- `std.inet`: target-neutral IPv4/IPv6/hostname literal validation and parsing; no network capability needed.\n- `std.time`: duration construction, conversion, comparison, elapsed-window helpers, hosted sleep, RFC 3339 date/time validation and epoch parsing, and target-gated clock helpers.\n- `std.rand`: explicit deterministic random sources, random bits, unbiased bounded/range helpers, target entropy helpers, and caller-buffer entropy IDs.\n- `std.crypto`: small hashes, SHA-256 digest writers, fixed-width hash text, byte-oriented crypto helpers, and caller-buffer IDs.\n- `std.json`: explicit-buffer JSON validation, structured status/location diagnostics, object/array cursor lookup, typed scalar decode, parsing, and string/object writing helpers.\n- `std.toml`: no-allocation TOML validation, shallow/dotted field lookup, and typed scalar decode helpers.\n- `std.url`: target-neutral URL splitting, percent/query/form encoding and decoding, query/form lookup, and query append helpers.\n- `std.str`: byte-span string helpers, including non-overlapping reverse, copy/concat/repeat/replace, prefix/suffix, split, fields, lines, trim, and word counts.\n- `std.io`: buffered reader/writer surfaces, cursor writes, line scanning, and byte copy over caller-owned storage.\n- `std.testing`: Bool-returning helpers for test blocks and byte-output checks.\n- `std.term`: ANSI terminal style, cursor, clear, alternate-screen sequence helpers, target-neutral key-byte decoding, and hosted TTY/size/raw-mode/input helpers.\n- `std.log`: explicit-buffer JSON Lines record formatting.\n\nPrefer `Maybe<T>` return checks over assuming an operation succeeded.\n\n## Hosted Capabilities\n\nThese modules depend on host or runtime capabilities:\n\n- `std.args`: process arguments\n- `std.cli`: command-line flag, option, and typed option helpers over process arguments\n- `std.env`: process environment lookup, comparisons, and typed fallback parsing\n- `std.fs`: hosted filesystem, explicit `Fs` or `owned<File>` handles, and file-level byte helpers\n- `std.net`: bootstrap network handles\n- `std.http`: HTTP request/response helpers and loopback listeners\n- `std.proc`: process execution and exit-status helpers\n- `std.pty`: hosted pseudoterminal child processes\n- `World.out` and `World.err`: program output capabilities\n\nNon-host targets may reject these APIs with target diagnostics. Inspect target facts before cross-building:\n\n```sh\nzero targets\nzero check --target linux-musl-x64 [graph-input]\nzero inspect --target linux-musl-x64 [graph-input]\n```\n\nAdd `--json` only when a tool needs exact target facts or diagnostics.\n\n## Memory Pattern\n\n```zero\nuse std.mem\n\npub fn main(world: World) -> Void raises {\n let bytes: Span<u8> = std.mem.span(\"zero\")\n if std.mem.len(bytes) == 4 {\n check world.out.write(\"memory ok\\n\")\n }\n}\n```\n\nFor writable buffers, use caller-owned fixed arrays and `MutSpan<T>`:\n\n```zero\npub fn main() -> Void {\n",
|
||||
" var storage: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]\n let writable: MutSpan<u8> = storage\n let copied: usize = std.mem.copy(writable, std.mem.span(\"zero\"))\n}\n```\n\nFor non-byte scalar item storage, use the generic item helpers. Current direct\ntargets support `Bool`, `u8`, `u16`, `usize`, `i32`, `u32`, `i64`, and `u64`\nelements for these helpers.\n\n```zero\npub fn main() -> Void {\n var values: [4]i32 = [1, 2, 3, 4]\n var scratch: [4]i32 = [0, 0, 0, 0]\n let copied: usize = std.mem.copyItems(scratch, values)\n let prefix: Span<i32> = std.mem.prefix(scratch, 2)\n let suffix: Span<i32> = std.mem.suffix(scratch, 2)\n let before: Span<i32> = std.mem.splitBefore(scratch, 3)\n let after: Span<i32> = std.mem.splitAfter(scratch, 3)\n let middle: Span<i32> = std.mem.slice(scratch, 1, 2)\n let chunk: Span<i32> = std.mem.chunk(scratch, 1_usize, 2_usize)\n let sliding: Span<i32> = std.mem.window(scratch, 1_usize, 2_usize)\n let cursor: usize = std.mem.advance(scratch, 0_usize, 2_usize)\n let rest: Span<i32> = std.mem.remaining(scratch, cursor)\n expect copied == 4 && std.mem.contains(prefix, 1) && std.mem.compareI32(prefix, suffix) < 0 && std.mem.len(suffix) == 2 && std.mem.len(before) == 2 && std.mem.len(after) == 1 && std.mem.len(middle) == 2 && std.mem.len(chunk) == 2 && std.mem.len(sliding) == 2 && std.mem.len(rest) == 2\n}\n```\n\nFixed-capacity collection helpers keep storage and length explicit:\n\n```zero\npub fn main() -> Void {\n var values: [4]i32 = [0, 0, 0, 0]\n var len: usize = 0\n len = std.collections.push(values, len, 3)\n len = std.collections.push(values, len, 1)\n len = std.collections.setInsert(values, len, 5)\n len = std.collections.insertAt(values, len, 1_usize, 4)\n let replaced: Bool = std.collections.replaceAt(values, len, 1_usize, 9)\n let swapped: Bool = std.collections.swapAt(values, len, 0_usize, 1_usize)\n let reversed: Bool = std.collections.reverse(values, len)\n let filled: Bool = std.collections.fill(values, 2_usize, 7)\n let rotated_left: Bool = std.collections.rotateLeft(values, len, 1_usize)\n let rotated_right: Bool = std.collections.rotateRight(values, len, 1_usize)\n len = std.collections.removeAt(values, len, 2_usize)\n let last: Maybe<i32> = std.collections.last(values, len)\n len = std.collections.dequePushFront(values, len, 2)\n let front: Maybe<i32> = std.collections.dequeFront(values, len)\n len = std.collections.dequePopFront(values, len)\n let back_len: usize = std.collections.dequePopBack(values, len)\n len = std.collections.truncate(values, len, 3)\n let set_live: Span<i32> = std.collections.setView(values, len)\n let set_remaining: usize = std.collections.setRemaining(values, len)\n let live: Span<i32> = std.collections.view(values, len)\n var fixed_storage: [4]i32 = [1, 2, 0, 0]\n var fixed_set: FixedSet<i32> = std.collections.fixedSet(fixed_storage, 2_usize)\n",
|
||||
" let fixed_inserted: Bool = std.collections.fixedSetInsert(&mut fixed_set, 3)\n let fixed_removed: Bool = std.collections.fixedSetRemove(&mut fixed_set, 1)\n let fixed_live: Span<i32> = std.collections.fixedSetView(&fixed_set)\n var fixed_deque_storage: [4]i32 = [0, 0, 0, 0]\n var fixed_deque: FixedDeque<i32> = std.collections.fixedDeque(fixed_deque_storage, 0_usize)\n let fixed_deque_back_pushed: Bool = std.collections.fixedDequePushBack(&mut fixed_deque, 2)\n let fixed_deque_front_pushed: Bool = std.collections.fixedDequePushFront(&mut fixed_deque, 1)\n let fixed_deque_front: Maybe<i32> = std.collections.fixedDequeFront(&fixed_deque)\n let fixed_deque_popped: Maybe<i32> = std.collections.fixedDequePopBack(&mut fixed_deque)\n var fixed_ring_storage: [4]i32 = [0, 0, 0, 0]\n var fixed_ring: FixedRingBuffer<i32> = std.collections.fixedRingBuffer(fixed_ring_storage, 0_usize, 0_usize)\n let fixed_ring_back_pushed: Bool = std.collections.fixedRingBufferPushBack(&mut fixed_ring, 2)\n let fixed_ring_front_pushed: Bool = std.collections.fixedRingBufferPushFront(&mut fixed_ring, 1)\n let fixed_ring_front: Maybe<i32> = std.collections.fixedRingBufferFront(&fixed_ring)\n let fixed_ring_popped: Maybe<i32> = std.collections.fixedRingBufferPopBack(&mut fixed_ring)\n var fixed_keys: [3]u8 = [1_u8, 2_u8, 0_u8]\n var fixed_scores: [3]u16 = [10_u16, 20_u16, 0_u16]\n var fixed_map: FixedMap<u8, u16> = std.collections.fixedMap(fixed_keys, fixed_scores, 2_usize)\n let fixed_map_added: Bool = std.collections.fixedMapPut(&mut fixed_map, 3_u8, 30_u16)\n let fixed_map_score: Maybe<u16> = std.collections.fixedMapGet(&fixed_map, 3_u8)\n var keys: [3]u8 = [1_u8, 2_u8, 0_u8]\n var scores: [3]u16 = [10_u16, 20_u16, 0_u16]\n var map_len: usize = 2\n map_len = std.collections.mapPut(keys, scores, map_len, 3_u8, 30_u16)\n let has_score: Bool = std.collections.mapContains(keys, map_len, 3_u8)\n let score: Maybe<u16> = std.collections.mapGet(keys, scores, map_len, 3_u8)\n let live_keys: Span<u8> = std.collections.mapKeys(keys, map_len)\n let live_scores: Span<u16> = std.collections.mapValues(keys, scores, map_len)\n",
|
||||
" expect replaced && swapped && reversed && filled && rotated_left && rotated_right && std.collections.clear(values, len) == 0 && std.collections.setClear(values, len) == 0 && std.collections.pop(values, len) == 2 && last.has && last.value == 5 && front.has && front.value == 2 && back_len == 2 && std.collections.setContains(values, len, 5) && set_remaining == 1 && std.mem.len(set_live) == 3 && std.mem.len(live) == 3 && fixed_inserted && fixed_removed && std.mem.len(fixed_live) == 2 && std.collections.fixedSetRemaining(&fixed_set) == 2 && std.collections.fixedSetLen(&fixed_set) == 2 && fixed_deque_back_pushed && fixed_deque_front_pushed && fixed_deque_front.has && fixed_deque_front.value == 1 && fixed_deque_popped.has && fixed_deque_popped.value == 2 && fixed_map_added && fixed_map_score.has && fixed_map_score.value == 30_u16 && map_len == 3 && std.collections.mapRemaining(keys, scores, map_len) == 0 && std.collections.mapIsFull(keys, scores, map_len) && std.collections.mapClear(keys, scores, map_len) == 0 && has_score && score.has && score.value == 30_u16 && std.mem.len(live_keys) == 3 && std.mem.len(live_scores) == 3\n}\n```\n\nFor byte-oriented dynamic storage, request mutable byte spans explicitly with\n`std.mem.allocBytes`. The fixed collection wrappers can use those returned spans\nas backing storage:\n\n```zero\npub fn main() -> Void {\n var key_storage: [4]u8 = [0_u8; 4]\n var value_storage: [4]u8 = [0_u8; 4]\n var key_alloc: FixedBufAlloc = std.mem.fixedBufAlloc(key_storage)\n var value_alloc: FixedBufAlloc = std.mem.fixedBufAlloc(value_storage)\n let keys_maybe: Maybe<MutSpan<u8>> = std.mem.allocBytes(key_alloc, 4_usize)\n let values_maybe: Maybe<MutSpan<u8>> = std.mem.allocBytes(value_alloc, 4_usize)\n if keys_maybe.has && values_maybe.has {\n var map: FixedMap<u8, u8> = std.collections.fixedMap(keys_maybe.value, values_maybe.value, 0_usize)\n let ok: Bool = std.collections.fixedMapPut(&mut map, 7_u8, 42_u8)\n let value: Maybe<u8> = std.collections.fixedMapGet(&map, 7_u8)\n expect ok && value.has && value.value == 42_u8\n }\n}\n```\n\nUse `std.sort` and `std.search` for common scalar algorithms instead of\nhand-rolling loops:\n\n```zero\npub fn main() -> Void {\n var values: [5]i32 = [5, 1, 4, 2, 3]\n std.sort.stableI32(values)\n expect std.sort.isSortedI32(values)\n std.sort.unstableI32(values)\n expect std.sort.isSortedI32(values)\n std.sort.reverseI32(values)\n expect std.sort.isSortedDescI32(values)\n let swapped: Bool = std.sort.swapI32(values, 0_usize, 4_usize)\n expect swapped\n std.sort.rotateLeftI32(values, 2_usize)\n expect values[0] == 3\n std.sort.rotateRightI32(values, 2_usize)\n expect values[0] == 1\n std.sort.insertionDescI32(values)\n expect std.sort.isSortedDescI32(values)\n std.sort.stableDescI32(values)\n expect std.sort.isSortedDescI32(values)\n std.sort.unstableDescI32(values)\n",
|
||||
" expect std.sort.isSortedDescI32(values)\n std.sort.insertionI32(values)\n expect std.search.binaryI32(values, 4) == 3\n expect std.search.containsSortedI32(values, 4)\n expect std.search.upperBoundI32(values, 4) == 4\n expect std.search.countSortedI32(values, 4) == 1\n std.sort.insertionDescI32(values)\n expect std.search.binaryDescI32(values, 4) == 1\n expect std.search.containsSortedDescI32(values, 4)\n expect std.search.upperBoundDescI32(values, 4) == 2\n expect std.search.countSortedDescI32(values, 4) == 1\n let high_len: usize = std.sort.partitionDescI32(values, 3)\n expect high_len == 2\n let minimum: Maybe<i32> = std.search.minI32(values)\n expect minimum.has && minimum.value == 1\n expect std.search.maxIndexI32(values) == 0\n}\n```\n\nString helpers are byte-oriented and allocation-free. `std.str.reverse` writes\ninto caller storage and requires that destination storage does not overlap the\ninput text:\n\n```zero\npub fn main() -> Void {\n var reversed: [4]u8 = [0, 0, 0, 0]\n let out: Maybe<Span<u8>> = std.str.reverse(reversed, \"zero\")\n if out.has {\n expect std.mem.eql(out.value, \"orez\")\n }\n}\n```\n\nUse `std.parse` and `std.fmt` instead of hand-rolled decimal loops in ordinary\nCLIs and examples:\n\n```zero\npub fn main() -> Void {\n let parsed: Maybe<i32> = std.parse.parseI32(\"-42\")\n var out: [12]u8 = [0_u8; 12]\n if parsed.has {\n let formatted: Maybe<Span<u8>> = std.fmt.i32(out, parsed.value)\n expect formatted.has && std.mem.eql(formatted.value, \"-42\")\n }\n}\n```\n\nUse codec, JSON, and URL helpers for common wire-format work instead of\nhand-rolled loops:\n\n```zero\npub fn main() -> Void {\n var decoded: [4]u8 = [0_u8; 4]\n let text: Maybe<Span<u8>> = std.codec.base64Decode(decoded, \"emVybw==\")\n\n let input: Span<u8> = \"{\\\"count\\\":42,\\\"ok\\\":true}\"\n let count: Maybe<u32> = std.json.u32(input, \"count\")\n\n var url_buf: [48]u8 = [0_u8; 48]\n var param_buf: [16]u8 = [0_u8; 16]\n let param: Maybe<Span<u8>> = std.url.writeQueryParam(param_buf, \"q\", \"zero lang\")\n var url: Maybe<Span<u8>> = null\n if param.has {\n url = std.url.appendQuery(url_buf, \"https://example.com/path\", param.value)\n }\n\n expect text.has && count.has && url.has\n}\n```\n\nUse `std.math` checked helpers when overflow is a normal input outcome:\n\n```zero\npub fn main() -> Void {\n let value: Maybe<u32> = std.math.checkedMulU32(6_u32, 7_u32)\n if value.has {\n expect value.value == 42_u32\n }\n expect std.math.sqrtFloorU32(99) == 9\n}\n```\n\nKeep random sources explicit and durations typed:\n\n```zero\npub fn main() -> Void {\n var rng: RandSource = std.rand.seed(7_u32)\n let first: u32 = std.rand.nextU32(&mut rng)\n let bit: Bool = std.rand.nextBool(&mut rng)\n let bounded: Maybe<u32> = std.rand.nextBelow(&mut rng, 10_u32)\n",
|
||||
" let ranged: Maybe<u32> = std.rand.rangeU32(&mut rng, 40_u32, 50_u32)\n let delay: Duration = std.time.add(std.time.ms(250), std.time.seconds(1))\n expect first == 1025555898_u32 && bit && bounded.has && ranged.has && std.time.asMsFloor(delay) == 1250\n}\n```\n\nUse `std.testing` inside `expect` when the comparison shape matters to readers\nor agents:\n\n```zero\ntest \"output shape\" {\n expect std.testing.equalBytes(\"zero\", \"zero\")\n expect std.testing.containsBytes(\"zerolang\", \"lang\")\n expect std.testing.jsonPathEquals(\"{\\\"service\\\":{\\\"ok\\\":true}}\", \"service.ok\", \"true\")\n}\n```\n\nUse `std.log` as a caller-buffer formatter, then write the resulting span\nthrough an explicit output capability:\n\n```zero\npub fn main(world: World) -> Void raises {\n var storage: [128]u8 = [0_u8; 128]\n var field_storage: [64]u8 = [0_u8; 64]\n let field: Maybe<Span<u8>> = std.log.stringField(field_storage, \"event\", \"startup\")\n if field.has {\n let entry: Maybe<Span<u8>> = std.log.messageField(storage, std.log.levelInfo(), \"started\", field.value)\n if entry.has {\n check world.out.write(entry.value)\n }\n }\n}\n```\n\n## Function Signatures\n\nThis catalog is generated from the compiler's standard-library signature table. Use these names exactly; helpers with `T` are generic over the concrete span or item type inferred from the call.\n\nFetch one module's section instead of this whole catalog with `zero skills get stdlib --topic <prefix>`, for example `zero skills get stdlib --topic std.time`.\n\n### std.args\n\n```text\nlen() -> usize\nget(arg0: usize) -> Maybe<String>\nhas(arg0: usize) -> Bool\ngetOr(arg0: usize, arg1: String) -> String\nfind(arg0: String) -> Maybe<usize>\nvalueAfter(arg0: String) -> Maybe<String>\nparseU32(arg0: usize) -> Maybe<u32>\n```\n\n### std.ascii\n\n```text\ndigitValue(arg0: u8) -> Maybe<u8>\nhexValue(arg0: u8) -> Maybe<u8>\nisAlnum(arg0: u8) -> Bool\nisAlpha(arg0: u8) -> Bool\nisDigit(arg0: u8) -> Bool\nisHexDigit(arg0: u8) -> Bool\nisLower(arg0: u8) -> Bool\nisUpper(arg0: u8) -> Bool\nisWhitespace(arg0: u8) -> Bool\ntoLower(arg0: u8) -> u8\ntoUpper(arg0: u8) -> u8\n```\n\n### std.cli\n\n```text\nargEquals(arg0: usize, arg1: String) -> Bool\ncommand() -> Maybe<String>\ncommandOr(arg0: String) -> String\ncommandEquals(arg0: String) -> Bool\nargOr(arg0: usize, arg1: String) -> String\nargU32Or(arg0: usize, arg1: u32) -> u32\nhasFlag(arg0: String) -> Bool\noptionValue(arg0: String) -> Maybe<String>\noptionValueOr(arg0: String, arg1: String) -> String\noptionU32(arg0: String) -> Maybe<u32>\nsuccessExitCode() -> i32\nusageExitCode() -> i32\nisHelp(arg0: Span<u8>) -> Bool\nneedsHelp() -> Bool\ncommandIn2(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Bool\ncommandIn3(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Bool\nformatUsage(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\n",
|
||||
"formatCommand(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nformatOption(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nformatSection(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nformatHelpRow(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nformatHelpRowCustom(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: usize, arg4: usize) -> Maybe<Span<u8>>\nformatHelpRowWithWidth(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: usize) -> Maybe<Span<u8>>\nformatHelp(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nformatError(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nformatUnknownCommand(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nformatMissingOperand(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nformatInvalidOption(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\n```\n\n### std.codec\n\n```text\ncrc32(arg0: String) -> u32\ncrc32Bytes(arg0: Span<u8>) -> u32\nencodedVarintLen(arg0: u32) -> usize\nencodedVarintLen64(arg0: u64) -> usize\nencodedSignedVarintLen(arg0: i32) -> usize\nencodedSignedVarintLen64(arg0: i64) -> usize\nhexDecodedLen(arg0: Span<u8>) -> Maybe<usize>\nhexDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase32EncodedLen(arg0: usize) -> usize\nbase32Encode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase32DecodedLen(arg0: Span<u8>) -> Maybe<usize>\nbase32Decode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase32RawEncodedLen(arg0: usize) -> usize\nbase32RawEncode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase32RawDecodedLen(arg0: Span<u8>) -> Maybe<usize>\nbase32RawDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase64DecodedLen(arg0: Span<u8>) -> Maybe<usize>\nbase64Decode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase64RawEncodedLen(arg0: usize) -> usize\nbase64RawEncode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase64RawDecodedLen(arg0: Span<u8>) -> Maybe<usize>\nbase64RawDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase64UrlEncodedLen(arg0: usize) -> usize\nbase64UrlEncode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nbase64UrlDecodedLen(arg0: Span<u8>) -> Maybe<usize>\nbase64UrlDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nreadU16Le(arg0: Span<u8>) -> Maybe<u16>\nreadU16Be(arg0: Span<u8>) -> Maybe<u16>\nreadU32Le(arg0: Span<u8>) -> Maybe<u32>\nreadU32Be(arg0: Span<u8>) -> Maybe<u32>\nreadU64Le(arg0: Span<u8>) -> Maybe<u64>\nreadU64Be(arg0: Span<u8>) -> Maybe<u64>\nwriteU16Le(arg0: MutSpan<u8>, arg1: u16) -> Maybe<Span<u8>>\nwriteU16Be(arg0: MutSpan<u8>, arg1: u16) -> Maybe<Span<u8>>\nwriteU32Le(arg0: MutSpan<u8>, arg1: u32) -> Maybe<Span<u8>>\nwriteU32Be(arg0: MutSpan<u8>, arg1: u32) -> Maybe<Span<u8>>\nwriteU64Le(arg0: MutSpan<u8>, arg1: u64) -> Maybe<Span<u8>>\n",
|
||||
"writeU64Be(arg0: MutSpan<u8>, arg1: u64) -> Maybe<Span<u8>>\nvarintEncode(arg0: MutSpan<u8>, arg1: u32) -> Maybe<Span<u8>>\nvarintDecode(arg0: Span<u8>) -> Maybe<u32>\nvarintEncode64(arg0: MutSpan<u8>, arg1: u64) -> Maybe<Span<u8>>\nvarintDecode64(arg0: Span<u8>) -> Maybe<u64>\nsignedVarintEncode(arg0: MutSpan<u8>, arg1: i32) -> Maybe<Span<u8>>\nsignedVarintDecode(arg0: Span<u8>) -> Maybe<i32>\nsignedVarintEncode64(arg0: MutSpan<u8>, arg1: i64) -> Maybe<Span<u8>>\nsignedVarintDecode64(arg0: Span<u8>) -> Maybe<i64>\nbase64EncodedLen(arg0: usize) -> usize\nbase64Encode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<String>\nhexEncode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<String>\nutf8Valid(arg0: Span<u8>) -> Bool\nurlEncode(arg0: MutSpan<u8>, arg1: String) -> Maybe<String>\n```\n\n### std.collections\n\n```text\nappend(storage: MutSpan<T>, len: usize, values: Span<T>) -> usize\nclear(storage: Span<T>, len: usize) -> usize\ncontains(storage: Span<T>, len: usize, value: T) -> Bool\ncount(storage: Span<T>, len: usize, value: T) -> usize\ndequeBack(storage: Span<T>, len: usize) -> Maybe<T>\ndequeFront(storage: Span<T>, len: usize) -> Maybe<T>\ndequePopBack(storage: Span<T>, len: usize) -> usize\ndequePopFront(storage: MutSpan<T>, len: usize) -> usize\ndequePushBack(storage: MutSpan<T>, len: usize, value: T) -> usize\ndequePushFront(storage: MutSpan<T>, len: usize, value: T) -> usize\nfill(storage: MutSpan<T>, len: usize, value: T) -> Bool\nfirst(storage: Span<T>, len: usize) -> Maybe<T>\nfixedDeque(storage: MutSpan<T>, len: usize) -> FixedDeque<T>\nfixedDequeBack(deque: ref<FixedDeque<T>>) -> Maybe<T>\nfixedDequeClear(deque: mutref<FixedDeque<T>>) -> usize\nfixedDequeFront(deque: ref<FixedDeque<T>>) -> Maybe<T>\nfixedDequeIsFull(deque: ref<FixedDeque<T>>) -> Bool\nfixedDequeLen(deque: ref<FixedDeque<T>>) -> usize\nfixedDequePopBack(deque: mutref<FixedDeque<T>>) -> Maybe<T>\nfixedDequePopFront(deque: mutref<FixedDeque<T>>) -> Maybe<T>\nfixedDequePushBack(deque: mutref<FixedDeque<T>>, value: T) -> Bool\nfixedDequePushFront(deque: mutref<FixedDeque<T>>, value: T) -> Bool\nfixedDequeRemaining(deque: ref<FixedDeque<T>>) -> usize\nfixedDequeTruncate(deque: mutref<FixedDeque<T>>, newLen: usize) -> usize\nfixedDequeView(deque: ref<FixedDeque<T>>) -> Span<T>\nfixedRingBuffer(storage: MutSpan<T>, head: usize, len: usize) -> FixedRingBuffer<T>\nfixedRingBufferBack(ring: ref<FixedRingBuffer<T>>) -> Maybe<T>\nfixedRingBufferCapacity(ring: ref<FixedRingBuffer<T>>) -> usize\nfixedRingBufferClear(ring: mutref<FixedRingBuffer<T>>) -> usize\nfixedRingBufferFront(ring: ref<FixedRingBuffer<T>>) -> Maybe<T>\nfixedRingBufferGet(ring: ref<FixedRingBuffer<T>>, index: usize) -> Maybe<T>\nfixedRingBufferIsFull(ring: ref<FixedRingBuffer<T>>) -> Bool\nfixedRingBufferLen(ring: ref<FixedRingBuffer<T>>) -> usize\nfixedRingBufferPopBack(ring: mutref<FixedRingBuffer<T>>) -> Maybe<T>\nfixedRingBufferPopFront(ring: mutref<FixedRingBuffer<T>>) -> Maybe<T>\n",
|
||||
"fixedRingBufferPushBack(ring: mutref<FixedRingBuffer<T>>, value: T) -> Bool\nfixedRingBufferPushFront(ring: mutref<FixedRingBuffer<T>>, value: T) -> Bool\nfixedRingBufferRemaining(ring: ref<FixedRingBuffer<T>>) -> usize\nfixedRingBufferTruncate(ring: mutref<FixedRingBuffer<T>>, newLen: usize) -> usize\nfixedMap(keys: MutSpan<K>, values: MutSpan<V>, len: usize) -> FixedMap<K,V>\nfixedMapClear(map: mutref<FixedMap<K,V>>) -> usize\nfixedMapContains(map: ref<FixedMap<K,V>>, key: K) -> Bool\nfixedMapGet(map: ref<FixedMap<K,V>>, key: K) -> Maybe<V>\nfixedMapIndex(map: ref<FixedMap<K,V>>, key: K) -> usize\nfixedMapIsFull(map: ref<FixedMap<K,V>>) -> Bool\nfixedMapKeys(map: ref<FixedMap<K,V>>) -> Span<K>\nfixedMapLen(map: ref<FixedMap<K,V>>) -> usize\nfixedMapPut(map: mutref<FixedMap<K,V>>, key: K, value: V) -> Bool\nfixedMapRemaining(map: ref<FixedMap<K,V>>) -> usize\nfixedMapRemove(map: mutref<FixedMap<K,V>>, key: K) -> Bool\nfixedMapTruncate(map: mutref<FixedMap<K,V>>, newLen: usize) -> usize\nfixedMapValues(map: ref<FixedMap<K,V>>) -> Span<V>\nfixedSet(storage: MutSpan<T>, len: usize) -> FixedSet<T>\nfixedSetClear(set: mutref<FixedSet<T>>) -> usize\nfixedSetContains(set: ref<FixedSet<T>>, value: T) -> Bool\nfixedSetInsert(set: mutref<FixedSet<T>>, value: T) -> Bool\nfixedSetIsFull(set: ref<FixedSet<T>>) -> Bool\nfixedSetLen(set: ref<FixedSet<T>>) -> usize\nfixedSetRemaining(set: ref<FixedSet<T>>) -> usize\nfixedSetRemove(set: mutref<FixedSet<T>>, value: T) -> Bool\nfixedSetTruncate(set: mutref<FixedSet<T>>, newLen: usize) -> usize\nfixedSetView(set: ref<FixedSet<T>>) -> Span<T>\ninsertAt(storage: MutSpan<T>, len: usize, index: usize, value: T) -> usize\ninsertUnique(storage: MutSpan<T>, len: usize, value: T) -> usize\nisFull(storage: Span<T>, len: usize) -> Bool\nlast(storage: Span<T>, len: usize) -> Maybe<T>\nmapClear(keys: Span<K>, values: Span<V>, len: usize) -> usize\nmapContains(keys: Span<K>, len: usize, key: K) -> Bool\nmapGet(keys: Span<K>, values: Span<V>, len: usize, key: K) -> Maybe<V>\nmapIndex(keys: Span<K>, len: usize, key: K) -> usize\nmapIsFull(keys: Span<K>, values: Span<V>, len: usize) -> Bool\nmapKeys(keys: Span<K>, len: usize) -> Span<K>\nmapPut(keys: MutSpan<K>, values: MutSpan<V>, len: usize, key: K, value: V) -> usize\nmapRemaining(keys: Span<K>, values: Span<V>, len: usize) -> usize\nmapRemove(keys: MutSpan<K>, values: MutSpan<V>, len: usize, key: K) -> usize\nmapTruncate(keys: Span<K>, values: Span<V>, len: usize, newLen: usize) -> usize\nmapValues(keys: Span<K>, values: Span<V>, len: usize) -> Span<V>\nmoveToFront(storage: MutSpan<T>, len: usize, index: usize) -> usize\npop(storage: Span<T>, len: usize) -> usize\npush(storage: MutSpan<T>, len: usize, value: T) -> usize\nremaining(storage: Span<T>, len: usize) -> usize\nreplaceAt(storage: MutSpan<T>, len: usize, index: usize, value: T) -> Bool\nremoveAt(storage: MutSpan<T>, len: usize, index: usize) -> usize\nremoveValue(storage: MutSpan<T>, len: usize, value: T) -> usize\n",
|
||||
"removeSwap(storage: MutSpan<T>, len: usize, index: usize) -> usize\nreverse(storage: MutSpan<T>, len: usize) -> Bool\nrotateLeft(storage: MutSpan<T>, len: usize, count: usize) -> Bool\nrotateRight(storage: MutSpan<T>, len: usize, count: usize) -> Bool\nsetClear(storage: Span<T>, len: usize) -> usize\nsetContains(storage: Span<T>, len: usize, value: T) -> Bool\nsetInsert(storage: MutSpan<T>, len: usize, value: T) -> usize\nsetIsFull(storage: Span<T>, len: usize) -> Bool\nsetRemaining(storage: Span<T>, len: usize) -> usize\nsetRemove(storage: MutSpan<T>, len: usize, value: T) -> usize\nsetTruncate(storage: Span<T>, len: usize, newLen: usize) -> usize\nsetView(storage: Span<T>, len: usize) -> Span<T>\nswapAt(storage: MutSpan<T>, len: usize, left: usize, right: usize) -> Bool\ntruncate(storage: Span<T>, len: usize, newLen: usize) -> usize\nview(storage: Span<T>, len: usize) -> Span<T>\n```\n\n### std.crypto\n\n```text\nhash32(arg0: Span<u8>) -> u32\nhmac32(arg0: Span<u8>, arg1: Span<u8>) -> u32\nconstantTimeEql(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nsecureRandomU32() -> u32\nfixedHex32(arg0: MutSpan<u8>, arg1: u32) -> Maybe<Span<u8>>\nhashHex32(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nhmacHex32(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nstableId32(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nrandomId32(arg0: MutSpan<u8>) -> Maybe<Span<u8>>\nsha256(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nsha256Hex(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nhmacSha256(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nhmacSha256Hex(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\n```\n\n### std.csv\n\n```text\nvalid(arg0: Span<u8>) -> Bool\nrecordCount(arg0: Span<u8>) -> Maybe<usize>\nrecord(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nfieldCount(arg0: Span<u8>) -> Maybe<usize>\nfield(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\nencodedFieldLen(arg0: Span<u8>) -> usize\nwriteField(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteRecord2(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteRecord3(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\n```\n\n### std.diag\n\n```text\ncolumn(arg0: Span<u8>, arg1: usize) -> usize\nformatLocation(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize, arg3: usize) -> Maybe<Span<u8>>\nformatOffsetLocation(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: usize) -> Maybe<Span<u8>>\nline(arg0: Span<u8>, arg1: usize) -> usize\nlineEnd(arg0: Span<u8>, arg1: usize) -> usize\nlineStart(arg0: Span<u8>, arg1: usize) -> usize\nlineText(arg0: Span<u8>, arg1: usize) -> Span<u8>\nrangeLen(arg0: Span<u8>, arg1: usize, arg2: usize) -> usize\nrangeText(arg0: Span<u8>, arg1: usize, arg2: usize) -> Span<u8>\n```\n\n### std.env\n\n```text\nget(arg0: String) -> Maybe<String>\nhas(arg0: String) -> Bool\n",
|
||||
"getOr(arg0: String, arg1: String) -> String\nequals(arg0: String, arg1: String) -> Bool\nparseBool(arg0: String) -> Maybe<Bool>\nparseBoolOr(arg0: String, arg1: Bool) -> Bool\nparseI32(arg0: String) -> Maybe<i32>\nparseI32Or(arg0: String, arg1: i32) -> i32\nparseU32(arg0: String) -> Maybe<u32>\nparseU32Or(arg0: String, arg1: u32) -> u32\nparseUsize(arg0: String) -> Maybe<usize>\nparseUsizeOr(arg0: String, arg1: usize) -> usize\n```\n\n### std.fmt\n\n```text\nbool(arg0: MutSpan<u8>, arg1: Bool) -> Maybe<Span<u8>>\nhexLowerU32(arg0: MutSpan<u8>, arg1: u32) -> Maybe<Span<u8>>\ni32(arg0: MutSpan<u8>, arg1: i32) -> Maybe<Span<u8>>\ni32Base(arg0: MutSpan<u8>, arg1: i32, arg2: u32) -> Maybe<Span<u8>>\ni32Sign(arg0: MutSpan<u8>, arg1: i32) -> Maybe<Span<u8>>\ni64(arg0: MutSpan<u8>, arg1: i64) -> Maybe<Span<u8>>\ni64Base(arg0: MutSpan<u8>, arg1: i64, arg2: u32) -> Maybe<Span<u8>>\ni64Sign(arg0: MutSpan<u8>, arg1: i64) -> Maybe<Span<u8>>\npadLeft(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize, arg3: u8) -> Maybe<Span<u8>>\npadRight(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize, arg3: u8) -> Maybe<Span<u8>>\nu32(arg0: MutSpan<u8>, arg1: u32) -> Maybe<Span<u8>>\nu32Base(arg0: MutSpan<u8>, arg1: u32, arg2: u32) -> Maybe<Span<u8>>\nu64(arg0: MutSpan<u8>, arg1: u64) -> Maybe<Span<u8>>\nu64Base(arg0: MutSpan<u8>, arg1: u64, arg2: u32) -> Maybe<Span<u8>>\nusize(arg0: MutSpan<u8>, arg1: usize) -> Maybe<Span<u8>>\nusizeBase(arg0: MutSpan<u8>, arg1: usize, arg2: u32) -> Maybe<Span<u8>>\nwriteBool(arg0: mutref<FixedWriter>, arg1: Bool) -> Bool\nwriteI32(arg0: mutref<FixedWriter>, arg1: i32) -> Bool\nwriteI32Sign(arg0: mutref<FixedWriter>, arg1: i32) -> Bool\nwriteI64(arg0: mutref<FixedWriter>, arg1: i64) -> Bool\nwriteI64Sign(arg0: mutref<FixedWriter>, arg1: i64) -> Bool\nwriteSpan(arg0: mutref<FixedWriter>, arg1: Span<u8>) -> Bool\nwriteU32(arg0: mutref<FixedWriter>, arg1: u32) -> Bool\nwriteU64(arg0: mutref<FixedWriter>, arg1: u64) -> Bool\nwriteUsize(arg0: mutref<FixedWriter>, arg1: usize) -> Bool\n```\n\n### std.fs\n\n```text\nhost() -> Fs\nopen(arg0: Fs, arg1: String) -> Maybe<owned<File>>\nopenOrRaise(arg0: Fs, arg1: String) -> owned<File> raises [NotFound, TooLarge, Io]\ncreate(arg0: Fs, arg1: String) -> Maybe<owned<File>>\ncreateOrRaise(arg0: Fs, arg1: String) -> owned<File> raises [NotFound, TooLarge, Io]\nread(arg0: String, arg1: MutSpan<u8>) -> usize\nreadOrRaise(arg0: mutref<File>, arg1: MutSpan<u8>) -> usize raises [NotFound, TooLarge, Io]\nwrite(arg0: String, arg1: String) -> usize\nwriteAll(arg0: mutref<File>, arg1: Span<u8>) -> Bool\nwriteAllOrRaise(arg0: mutref<File>, arg1: Span<u8>) -> Void raises [NotFound, TooLarge, Io]\nfileLenOrRaise(arg0: mutref<File>) -> usize raises [NotFound, TooLarge, Io]\nreadAll(allocator: Alloc, fs: Fs, path: String, max: usize) -> Maybe<owned<ByteBuf>>\nreadAllOrRaise(allocator: Alloc, fs: Fs, path: String, max: usize) -> owned<ByteBuf> raises [NotFound, TooLarge, Io]\nexists(arg0: String) -> Bool\n",
|
||||
"readBytes(arg0: String, arg1: MutSpan<u8>) -> Maybe<usize>\nreadBytesAt(arg0: String, arg1: usize, arg2: MutSpan<u8>) -> Maybe<usize>\nwriteBytes(arg0: String, arg1: Span<u8>) -> Maybe<usize>\nappendBytes(arg0: String, arg1: Span<u8>) -> Maybe<usize>\nisDir(arg0: String) -> Bool\nmakeDir(arg0: String) -> Bool\nremoveDir(arg0: String) -> Bool\nremove(arg0: String) -> Bool\nrename(arg0: String, arg1: String) -> Bool\ndirEntryCount(arg0: String) -> Maybe<usize>\ndirEntryName(arg0: MutSpan<u8>, arg1: String, arg2: usize) -> Maybe<Span<u8>>\ntempName(arg0: MutSpan<u8>, arg1: String) -> Maybe<String>\natomicWrite(arg0: String, arg1: String, arg2: Span<u8>) -> Bool\nfileLen(arg0: mutref<File>) -> Maybe<usize>\nclose(arg0: mutref<File>) -> Void\nreadFile(arg0: Fs, arg1: String, arg2: MutSpan<u8>) -> Maybe<usize>\nwriteFile(arg0: Fs, arg1: String, arg2: Span<u8>) -> Bool\nappendFile(arg0: Fs, arg1: String, arg2: Span<u8>) -> Bool\nreadFileBytes(arg0: Fs, arg1: String, arg2: MutSpan<u8>) -> Maybe<Span<u8>>\nreadFileEquals(arg0: Fs, arg1: String, arg2: MutSpan<u8>, arg3: Span<u8>) -> Bool\ncopyFile(arg0: String, arg1: String, arg2: MutSpan<u8>) -> Bool\nfileSize(arg0: Fs, arg1: String) -> Maybe<usize>\nisFile(arg0: String) -> Bool\nensureDir(arg0: String) -> Bool\n```\n\n`readBytes` and `readFile` fill the caller buffer and return the TOTAL file size\n(snprintf convention): a value above `len(buffer)` means the buffer holds only the\nfirst `len(buffer)` bytes, so compare the result against the buffer length instead\nof assuming the whole file arrived. `readFileBytes` returns `null` when the file\nexceeds the buffer. Process files larger than your buffer with `readBytesAt`:\nloop `offset += len(buffer)` until `offset` reaches the returned total, taking\n`min(len(buffer), total - offset)` valid bytes per chunk.\n\n### std.http\n\n```text\nparseMethod(arg0: String) -> HttpMethod\nclient(arg0: Net) -> HttpClient\nserver(arg0: Net, arg1: Address) -> HttpServer\nlisten(arg0: World, arg1: u16) -> Void raises [Io]\nfetch(arg0: HttpClient, arg1: Span<u8>, arg2: MutSpan<u8>, arg3: Duration) -> HttpResult\nresultOk(arg0: HttpResult) -> Bool\nresultStatus(arg0: HttpResult) -> u16\nresultBodyLen(arg0: HttpResult) -> usize\nresultError(arg0: HttpResult) -> HttpError\nerrorNone() -> HttpError\nerrorInvalidUrl() -> HttpError\nerrorUnsupportedProtocol() -> HttpError\nerrorDns() -> HttpError\nerrorConnect() -> HttpError\nerrorTls() -> HttpError\nerrorTimeout() -> HttpError\nerrorTooLarge() -> HttpError\nerrorProviderUnavailable() -> HttpError\nerrorIo() -> HttpError\nerrorInvalidRequest() -> HttpError\nerrorName(arg0: HttpError) -> String\nresponseLen(arg0: Span<u8>) -> usize\nresponseHeadersLen(arg0: Span<u8>) -> usize\nresponseBodyOffset(arg0: Span<u8>) -> usize\nheaderValue(arg0: Span<u8>, arg1: Span<u8>) -> HttpHeaderValue\nheaderFound(arg0: HttpHeaderValue) -> Bool\nheaderOffset(arg0: HttpHeaderValue) -> usize\nheaderLen(arg0: HttpHeaderValue) -> usize\n",
|
||||
"tlsBoundary() -> String\nstatusReason(arg0: u16) -> String\nstatusIsInformational(arg0: u16) -> Bool\nstatusIsSuccess(arg0: u16) -> Bool\nstatusIsRedirect(arg0: u16) -> Bool\nstatusIsClientError(arg0: u16) -> Bool\nstatusIsServerError(arg0: u16) -> Bool\nwriteRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteMethodRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteGetRequest(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteHeadRequest(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteDeleteRequest(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonMethodRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwritePostJsonRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwritePutJsonRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwritePatchJsonRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteResponse(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonResponse(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteResponseWithHeader(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteResponseWithHeaders(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonResponseWithHeader(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonResponseWithHeaders(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonResponseWithCookie(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonError(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteCorsPreflight(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteCorsJsonResponse(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteTextResponse(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteTextOk(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteHtmlResponse(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteHtmlOk(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteRedirect(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteFound(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteSeeOther(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteMovedPermanently(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwritePermanentRedirect(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\ncontentTypeForPath(arg0: Span<u8>) -> String\nwriteStaticResponse(arg0: MutSpan<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\n",
|
||||
"writeJsonOk(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonCreated(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonBadRequest(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonUnauthorized(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonForbidden(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonNotFound(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonMethodNotAllowed(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonConflict(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonUnprocessable(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonTooManyRequests(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonInternalServerError(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nwriteNoContent(arg0: MutSpan<u8>) -> Maybe<Span<u8>>\nwriteRequestWithHeader(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteRequestWithHeaders(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonRequestWithHeader(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteJsonRequestWithHeaders(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nrequestMethodName(arg0: Span<u8>) -> Maybe<Span<u8>>\nrequestTarget(arg0: Span<u8>) -> Maybe<Span<u8>>\nrequestPath(arg0: Span<u8>) -> Maybe<Span<u8>>\npathSegmentCount(arg0: Span<u8>) -> usize\npathSegment(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\npathMatchesPattern(arg0: Span<u8>, arg1: Span<u8>) -> Bool\npathParam(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nrequestPathSegmentCount(arg0: Span<u8>) -> usize\nrequestPathSegment(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nrequestQuery(arg0: Span<u8>) -> Maybe<Span<u8>>\nrequestQueryValue(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nrequestHeader(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nrequestBearerToken(arg0: Span<u8>) -> Maybe<Span<u8>>\nrequestCookie(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nrequestContentLength(arg0: Span<u8>) -> Maybe<usize>\nrequestContentType(arg0: Span<u8>) -> Maybe<Span<u8>>\nrequestAccepts(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestAcceptsJson(arg0: Span<u8>) -> Bool\nrequestBody(arg0: Span<u8>) -> Maybe<Span<u8>>\nrequestBodyWithin(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nrequestHasJsonContentType(arg0: Span<u8>) -> Bool\nrequestJsonBodyWithin(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nrequestJsonField(arg0: Span<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\nrequestMatches(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Bool\nmethodAllowed(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestMethodAllowed(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestMethodIs(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestIsGet(arg0: Span<u8>, arg1: Span<u8>) -> Bool\n",
|
||||
"requestIsHead(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestIsOptions(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestIsPost(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestIsPut(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestIsPatch(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestIsDelete(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestPathStartsWith(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nrequestPathTailAfter(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nrequestRouteMatches(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Bool\nrequestRouteMethodAllowed(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Bool\nrequestPathParam(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nheaderBlockSafe(arg0: Span<u8>) -> Bool\nheaderBytes(arg0: Span<u8>, arg1: HttpHeaderValue) -> Maybe<Span<u8>>\nresponseBody(arg0: Span<u8>, arg1: HttpResult) -> Maybe<Span<u8>>\nresponseBodyBytes(arg0: Span<u8>) -> Maybe<Span<u8>>\nresponseBodyEquals(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nresponseStatus(arg0: Span<u8>) -> Maybe<u16>\nresponseStatusIs(arg0: Span<u8>, arg1: u16) -> Bool\nresponseHeader(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nresponseContentType(arg0: Span<u8>) -> Maybe<Span<u8>>\nresponseRedirectLocation(arg0: Span<u8>) -> Maybe<Span<u8>>\nresponseMatches(arg0: Span<u8>, arg1: u16, arg2: Span<u8>, arg3: Span<u8>) -> Bool\ntestRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\ntestJsonRequest(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\n```\n\n### std.io\n\n```text\nbufferedReader(arg0: MutSpan<u8>) -> BufferedReader\nbufferedWriter(arg0: MutSpan<u8>) -> BufferedWriter\nreaderCapacity(arg0: ref<BufferedReader>) -> usize\nwriterCapacity(arg0: ref<BufferedWriter>) -> usize\ncopy(arg0: MutSpan<u8>, arg1: Span<u8>) -> usize\ncopyN(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<usize>\ncopyBuffer(arg0: mutref<FixedReader>, arg1: mutref<FixedWriter>, arg2: MutSpan<u8>) -> usize\ncopyReaderN(arg0: mutref<FixedReader>, arg1: mutref<FixedWriter>, arg2: usize, arg3: MutSpan<u8>) -> Maybe<usize>\ndiscard(arg0: mutref<FixedReader>, arg1: MutSpan<u8>) -> usize\nerrorCapacity() -> u32\nerrorEof() -> u32\nerrorIo() -> u32\nerrorName(arg0: u32) -> String\nerrorNone() -> u32\nerrorPermission() -> u32\nerrorShortRead() -> u32\nerrorShortWrite() -> u32\nerrorTimeout() -> u32\nfixedReader(arg0: Span<u8>, arg1: usize) -> FixedReader\nfixedReaderCursor(arg0: ref<FixedReader>) -> usize\nfixedReaderDone(arg0: ref<FixedReader>) -> Bool\nfixedReaderLen(arg0: ref<FixedReader>) -> usize\nfixedReaderLimit(arg0: ref<FixedReader>, arg1: usize) -> FixedReader\nfixedReaderRead(arg0: mutref<FixedReader>, arg1: MutSpan<u8>) -> usize\nfixedReaderReadAll(arg0: mutref<FixedReader>, arg1: MutSpan<u8>) -> Maybe<Span<u8>>\nfixedReaderReadAt(arg0: ref<FixedReader>, arg1: usize, arg2: MutSpan<u8>) -> Maybe<usize>\n",
|
||||
"fixedReaderReadByte(arg0: mutref<FixedReader>) -> Maybe<u8>\nfixedReaderReadExact(arg0: mutref<FixedReader>, arg1: MutSpan<u8>) -> Bool\nfixedReaderReadLine(arg0: mutref<FixedReader>) -> Maybe<Span<u8>>\nfixedReaderReadUntilDelimiter(arg0: mutref<FixedReader>, arg1: u8) -> Maybe<Span<u8>>\nfixedReaderRemaining(arg0: ref<FixedReader>) -> usize\nfixedReaderSeek(arg0: mutref<FixedReader>, arg1: usize) -> Bool\nfixedWriter(arg0: MutSpan<u8>, arg1: usize) -> FixedWriter\nfixedWriterCapacity(arg0: ref<FixedWriter>) -> usize\nfixedWriterClear(arg0: mutref<FixedWriter>) -> usize\nfixedWriterCursor(arg0: ref<FixedWriter>) -> usize\nfixedWriterRemaining(arg0: ref<FixedWriter>) -> usize\nfixedWriterSeek(arg0: mutref<FixedWriter>, arg1: usize) -> Bool\nfixedWriterTruncate(arg0: mutref<FixedWriter>, arg1: usize) -> usize\nfixedWriterView(arg0: ref<FixedWriter>) -> Span<u8>\nfixedWriterWrite(arg0: mutref<FixedWriter>, arg1: Span<u8>) -> Bool\nfixedWriterWriteAll(arg0: mutref<FixedWriter>, arg1: Span<u8>) -> Bool\nfixedWriterWriteAt(arg0: mutref<FixedWriter>, arg1: usize, arg2: Span<u8>) -> Bool\nfixedWriterWriteByte(arg0: mutref<FixedWriter>, arg1: u8) -> Bool\nmultiRead(arg0: mutref<FixedReader>, arg1: mutref<FixedReader>, arg2: MutSpan<u8>) -> usize\nread(arg0: Span<u8>, arg1: usize, arg2: MutSpan<u8>) -> Maybe<usize>\nreadAll(arg0: Span<u8>, arg1: MutSpan<u8>) -> Maybe<Span<u8>>\nreadAt(arg0: Span<u8>, arg1: usize, arg2: MutSpan<u8>) -> Maybe<usize>\nreadByte(arg0: Span<u8>, arg1: usize) -> Maybe<u8>\nreadExact(arg0: Span<u8>, arg1: usize, arg2: MutSpan<u8>) -> Maybe<usize>\nreadLine(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nreadLineStart(arg0: Span<u8>, arg1: usize) -> usize\nreadUntilDelimiter(arg0: Span<u8>, arg1: usize, arg2: u8) -> Maybe<Span<u8>>\nreadUntilDelimiterStart(arg0: Span<u8>, arg1: usize, arg2: u8) -> usize\nteeRead(arg0: mutref<FixedReader>, arg1: mutref<FixedWriter>, arg2: MutSpan<u8>) -> Maybe<usize>\nwriteByte(arg0: MutSpan<u8>, arg1: usize, arg2: u8) -> Maybe<usize>\nwriteAll(arg0: MutSpan<u8>, arg1: usize, arg2: Span<u8>) -> Maybe<usize>\nwriteAt(arg0: MutSpan<u8>, arg1: usize, arg2: Span<u8>) -> Maybe<usize>\nwriteSpan(arg0: MutSpan<u8>, arg1: usize, arg2: Span<u8>) -> Maybe<usize>\nwritten(arg0: Span<u8>, arg1: usize) -> Span<u8>\nremaining(arg0: Span<u8>, arg1: usize) -> usize\nnextLine(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nnextLineStart(arg0: Span<u8>, arg1: usize) -> usize\ncountLines(arg0: Span<u8>) -> usize\n```\n\n### std.inet\n\n```text\nisIpv4(text: Span<u8>) -> Bool\nparseIpv4(text: Span<u8>) -> Maybe<u32>\nwriteIpv4(buffer: MutSpan<u8>, value: u32) -> Maybe<Span<u8>>\nisIpv4Unspecified(value: u32) -> Bool\nisIpv4Loopback(value: u32) -> Bool\nisIpv4Private(value: u32) -> Bool\nisIpv4LinkLocal(value: u32) -> Bool\nisIpv4Multicast(value: u32) -> Bool\nisIpv6(text: Span<u8>) -> Bool\nparseIpv6(buffer: MutSpan<u8>, text: Span<u8>) -> Maybe<Span<u8>>\nisIp(text: Span<u8>) -> Bool\n",
|
||||
"parseIp(buffer: MutSpan<u8>, text: Span<u8>) -> Maybe<Span<u8>>\nisIpv6Unspecified(bytes: Span<u8>) -> Bool\nisIpv6Loopback(bytes: Span<u8>) -> Bool\nisIpv6Multicast(bytes: Span<u8>) -> Bool\nisIpv6LinkLocal(bytes: Span<u8>) -> Bool\nisIpv6Private(bytes: Span<u8>) -> Bool\nisIpv6UniqueLocal(bytes: Span<u8>) -> Bool\nisIpv6MappedIpv4(bytes: Span<u8>) -> Bool\nipv6MappedIpv4(bytes: Span<u8>) -> Maybe<u32>\nisHostname(text: Span<u8>) -> Bool\n```\n\nInternet address literal helpers, kept separate from `std.net` so they stay\nusable on targets without the Net capability. `isIpv4`/`parseIpv4` accept\nstrict dotted quads (four 0-255 octets, no leading zeros; the parse packs\nbig-endian), and `writeIpv4` writes a packed value back to dotted-quad text.\nIPv4 classification helpers cover unspecified, loopback, RFC 1918 private,\nlink-local, and multicast ranges. `isIpv6`/`parseIpv6` accept RFC 4291 forms\nincluding `::` compression and embedded IPv4, writing 16 network-order bytes\ninto the caller buffer. IPv6 helpers classify unspecified, loopback,\nmulticast, link-local, unique-local/private, and IPv4-mapped addresses;\n`parseIp` accepts either family and writes 4 or 16 network-order bytes.\n`isHostname` enforces RFC 1123: dot-separated labels of 1-63 alphanumeric/hyphen\nbytes, no leading/trailing hyphens, 253 bytes total.\n\n### std.json\n\n```text\nvalidate(arg0: String) -> Bool\nvalidateBytes(arg0: Span<u8>) -> Bool\nparse(allocator: Alloc, text: String) -> Maybe<JsonDoc>\nparseBytes(allocator: Alloc, bytes: Span<u8>) -> Maybe<JsonDoc>\nstreamTokens(arg0: String) -> usize\nstreamTokensBytes(arg0: Span<u8>) -> usize\nwriteString(arg0: MutSpan<u8>, arg1: String) -> Maybe<String>\ndecodeBoundary() -> String\nerrorNone() -> u32\nerrorInvalid() -> u32\nerrorTrailing() -> u32\nerrorName(arg0: u32) -> String\nerrorExpected(arg0: u32) -> String\nvalidateError(arg0: Span<u8>) -> u32\nerrorOffset(arg0: Span<u8>) -> usize\nerrorLine(arg0: Span<u8>) -> usize\nerrorColumn(arg0: Span<u8>) -> usize\nfield(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nobjectFieldCount(arg0: Span<u8>) -> Maybe<usize>\nobjectKey(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\nobjectValue(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\narrayCount(arg0: Span<u8>) -> Maybe<usize>\narrayValue(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\npath(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\npathString(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\npathU32(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<u32>\npathBool(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Bool>\nstringDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nstring(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nu32(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<u32>\nbool(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Bool>\nwriteStringBytes(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\n",
|
||||
"writeObject1String(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteObject1U32(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: u32) -> Maybe<Span<u8>>\nwriteObject1Bool(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Bool) -> Maybe<Span<u8>>\nwriteFieldRaw(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteFieldString(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteFieldU32(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: u32) -> Maybe<Span<u8>>\nwriteFieldBool(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Bool) -> Maybe<Span<u8>>\nwriteObject2Fields(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteObject2StringField(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteObject2U32Field(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: u32, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteObject2BoolField(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Bool, arg3: Span<u8>) -> Maybe<Span<u8>>\nwriteArray2Strings(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteArray2U32(arg0: MutSpan<u8>, arg1: u32, arg2: u32) -> Maybe<Span<u8>>\nwriteArray2Bools(arg0: MutSpan<u8>, arg1: Bool, arg2: Bool) -> Maybe<Span<u8>>\n```\n\n### std.toml\n\n```text\nvalidate(arg0: String) -> Bool\nvalidateBytes(arg0: Span<u8>) -> Bool\nfield(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nstringDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nstring(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nu32(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<u32>\ni32(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<i32>\nbool(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Bool>\narrayCount(arg0: Span<u8>) -> Maybe<usize>\narrayValue(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\narrayString(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\narrayU32(arg0: Span<u8>, arg1: usize) -> Maybe<u32>\narrayI32(arg0: Span<u8>, arg1: usize) -> Maybe<i32>\narrayBool(arg0: Span<u8>, arg1: usize) -> Maybe<Bool>\nwriteKeyValueString(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteKeyValueU32(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: u32) -> Maybe<Span<u8>>\nwriteKeyValueBool(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Bool) -> Maybe<Span<u8>>\nwriteTableHeader(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\n```\n\n### std.log\n\n```text\nmessage(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nkeyValue(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nlevelDebug() -> String\nlevelInfo() -> String\nlevelWarn() -> String\nlevelError() -> String\nstringField(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nmessageField(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nredacted(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\n```\n\n### std.term\n\n```text\nreset() -> String\n",
|
||||
"bold() -> String\ndim() -> String\nunderline() -> String\ninverse() -> String\nfgDefault() -> String\nfgBlack() -> String\nfgRed() -> String\nfgGreen() -> String\nfgYellow() -> String\nfgBlue() -> String\nfgMagenta() -> String\nfgCyan() -> String\nfgWhite() -> String\nbgDefault() -> String\nbgBlack() -> String\nbgRed() -> String\nbgGreen() -> String\nbgYellow() -> String\nbgBlue() -> String\nbgMagenta() -> String\nbgCyan() -> String\nbgWhite() -> String\nclearScreen() -> String\nclearScreenDown() -> String\nclearScreenUp() -> String\nclearLine() -> String\nclearLineRight() -> String\nclearLineLeft() -> String\ncursorHome() -> String\ncursorTo(arg0: MutSpan<u8>, arg1: usize, arg2: usize) -> Maybe<Span<u8>>\ncursorUp(arg0: MutSpan<u8>, arg1: usize) -> Maybe<Span<u8>>\ncursorDown(arg0: MutSpan<u8>, arg1: usize) -> Maybe<Span<u8>>\ncursorRight(arg0: MutSpan<u8>, arg1: usize) -> Maybe<Span<u8>>\ncursorLeft(arg0: MutSpan<u8>, arg1: usize) -> Maybe<Span<u8>>\nsaveCursor() -> String\nrestoreCursor() -> String\nhideCursor() -> String\nshowCursor() -> String\nenterAltScreen() -> String\nleaveAltScreen() -> String\nenterBracketedPaste() -> String\nleaveBracketedPaste() -> String\nenterMouseCapture() -> String\nleaveMouseCapture() -> String\nkeyNone() -> u32\nkeyEscape() -> u32\nkeyEnter() -> u32\nkeyTab() -> u32\nkeyBackspace() -> u32\nkeyCtrlA() -> u32\nkeyCtrlC() -> u32\nkeyCtrlD() -> u32\nkeyCtrlE() -> u32\nkeyCtrlK() -> u32\nkeyCtrlL() -> u32\nkeyCtrlN() -> u32\nkeyCtrlP() -> u32\nkeyCtrlR() -> u32\nkeyCtrlU() -> u32\nkeyCtrlW() -> u32\nkeyArrowUp() -> u32\nkeyArrowDown() -> u32\nkeyArrowRight() -> u32\nkeyArrowLeft() -> u32\nkeyDelete() -> u32\nkeyHome() -> u32\nkeyEnd() -> u32\nkeyPageUp() -> u32\nkeyPageDown() -> u32\nkeyInsert() -> u32\nkeyShiftTab() -> u32\nkeyF1() -> u32\nkeyF2() -> u32\nkeyF3() -> u32\nkeyF4() -> u32\nkeyF5() -> u32\nkeyF6() -> u32\nkeyF7() -> u32\nkeyF8() -> u32\nkeyF9() -> u32\nkeyF10() -> u32\nkeyF11() -> u32\nkeyF12() -> u32\nkeyPasteStart() -> u32\nkeyPasteEnd() -> u32\nkeyCode(arg0: Span<u8>) -> u32\nkeyByteLen(arg0: Span<u8>) -> usize\nstdinIsTty() -> Bool\nstdoutIsTty() -> Bool\nwidthOr(arg0: usize) -> usize\nheightOr(arg0: usize) -> usize\nenterRawMode() -> Bool\nleaveRawMode() -> Bool\nreadInput(arg0: MutSpan<u8>) -> Maybe<usize>\n```\n\n`std.term.readInput(buffer)` fills the caller buffer with currently available\nstdin bytes without blocking. It returns `null` when no bytes are available, the\nbuffer is empty, or the hosted input source cannot be read.\n\n### std.math\n\n```text\nabsI32(arg0: i32) -> u32\nabsI64(arg0: i64) -> u64\nbinomialU32(arg0: u32, arg1: u32) -> Maybe<u32>\ncheckedAddI32(arg0: i32, arg1: i32) -> Maybe<i32>\ncheckedAddU32(arg0: u32, arg1: u32) -> Maybe<u32>\ncheckedAddUsize(arg0: usize, arg1: usize) -> Maybe<usize>\ncheckedLcmU32(arg0: u32, arg1: u32) -> Maybe<u32>\ncheckedMulI32(arg0: i32, arg1: i32) -> Maybe<i32>\ncheckedMulU32(arg0: u32, arg1: u32) -> Maybe<u32>\n",
|
||||
"checkedMulUsize(arg0: usize, arg1: usize) -> Maybe<usize>\ncheckedPowU32(arg0: u32, arg1: u32) -> Maybe<u32>\ncheckedSubI32(arg0: i32, arg1: i32) -> Maybe<i32>\ncheckedSubU32(arg0: u32, arg1: u32) -> Maybe<u32>\ncheckedSubUsize(arg0: usize, arg1: usize) -> Maybe<usize>\nclampI32(arg0: i32, arg1: i32, arg2: i32) -> i32\nclampI64(arg0: i64, arg1: i64, arg2: i64) -> i64\nclampU32(arg0: u32, arg1: u32, arg2: u32) -> u32\nclampU64(arg0: u64, arg1: u64, arg2: u64) -> u64\nclampUsize(arg0: usize, arg1: usize, arg2: usize) -> usize\ndivisorCountU32(arg0: u32) -> u32\nfactorialU32(arg0: u32) -> Maybe<u32>\ngcdU32(arg0: u32, arg1: u32) -> u32\nisEvenU32(arg0: u32) -> Bool\nisOddU32(arg0: u32) -> Bool\nisPrimeU32(arg0: u32) -> Bool\nlcmU32(arg0: u32, arg1: u32) -> u32\nmaxI32(arg0: i32, arg1: i32) -> i32\nmaxI64(arg0: i64, arg1: i64) -> i64\nmaxU32(arg0: u32, arg1: u32) -> u32\nmaxU64(arg0: u64, arg1: u64) -> u64\nmaxUsize(arg0: usize, arg1: usize) -> usize\nminI32(arg0: i32, arg1: i32) -> i32\nminI64(arg0: i64, arg1: i64) -> i64\nminU32(arg0: u32, arg1: u32) -> u32\nminU64(arg0: u64, arg1: u64) -> u64\nminUsize(arg0: usize, arg1: usize) -> usize\nmodPowU32(arg0: u32, arg1: u32, arg2: u32) -> u32\npowU32(arg0: u32, arg1: u32) -> u32\nproperDivisorSumU32(arg0: u32) -> u32\nsaturatingAddI32(arg0: i32, arg1: i32) -> i32\nsaturatingAddU32(arg0: u32, arg1: u32) -> u32\nsaturatingAddUsize(arg0: usize, arg1: usize) -> usize\nsaturatingMulI32(arg0: i32, arg1: i32) -> i32\nsaturatingMulU32(arg0: u32, arg1: u32) -> u32\nsaturatingMulUsize(arg0: usize, arg1: usize) -> usize\nsaturatingSubI32(arg0: i32, arg1: i32) -> i32\nsaturatingSubU32(arg0: u32, arg1: u32) -> u32\nsaturatingSubUsize(arg0: usize, arg1: usize) -> usize\nsqrtFloorU32(arg0: u32) -> u32\n```\n\n### std.mem\n\n```text\ncopy(arg0: MutSpan<u8>, arg1: Span<u8>) -> usize\ncopyItems(dst: MutSpan<T>, src: Span<T>) -> usize\nfill(arg0: MutSpan<u8>, arg1: u8) -> usize\nfillItems(dst: MutSpan<T>, value: T) -> usize\neql(arg0: String, arg1: String) -> Bool\nspan(arg0: String) -> Span<u8>\ncontains(items: Span<T>, value: T) -> Bool\ncompareI32(arg0: Span<i32>, arg1: Span<i32>) -> i32\ncompareU8(arg0: Span<u8>, arg1: Span<u8>) -> i32\ncompareBytes(arg0: Span<u8>, arg1: Span<u8>) -> i32\ncompareU32(arg0: Span<u32>, arg1: Span<u32>) -> i32\ncompareUsize(arg0: Span<usize>, arg1: Span<usize>) -> i32\nstartsWith(items: Span<T>, prefix: Span<T>) -> Bool\nendsWith(items: Span<T>, suffix: Span<T>) -> Bool\nsplitBefore(items: Span<T>, delimiter: T) -> Span<T>\nsplitAfter(items: Span<T>, delimiter: T) -> Span<T>\nisEmpty(items: Span<T>) -> Bool\nchunkCount(items: Span<T>, chunkSize: usize) -> usize\nchunk(items: Span<T>, chunkIndex: usize, chunkSize: usize) -> Span<T>\nwindowCount(items: Span<T>, windowSize: usize) -> usize\nwindow(items: Span<T>, windowIndex: usize, windowSize: usize) -> Span<T>\nadvance(items: Span<T>, cursor: usize, count: usize) -> usize\ncursorDone(items: Span<T>, cursor: usize) -> Bool\n",
|
||||
"remaining(items: Span<T>, cursor: usize) -> Span<T>\ncursorChunk(items: Span<T>, cursor: usize, count: usize) -> Span<T>\nprefix(items: Span<T>, len: usize) -> Span<T>\ndropPrefix(items: Span<T>, len: usize) -> Span<T>\nsuffix(items: Span<T>, len: usize) -> Span<T>\ndropSuffix(items: Span<T>, len: usize) -> Span<T>\nslice(items: Span<T>, start: usize, len: usize) -> Span<T>\nlen(items: Span<T>) -> usize\nget(items: Span<T>, index: usize) -> Maybe<T>\neqlBytes(left: Span<T>, right: Span<T>) -> Bool\nnullAlloc() -> NullAlloc\nfixedBufAlloc(arg0: MutSpan<u8>) -> FixedBufAlloc\narena(arg0: MutSpan<u8>) -> FixedBufAlloc\npageAlloc() -> PageAlloc\ngeneralAlloc() -> GeneralAlloc\nallocBytes(allocator: Alloc, len: usize) -> Maybe<MutSpan<u8>>\nbyteBuf(allocator: Alloc, capacity: usize) -> Maybe<owned<ByteBuf>>\nvec(arg0: MutSpan<u8>) -> Vec\nvecPush(arg0: mutref<Vec>, arg1: u8) -> Bool\nvecBytes(arg0: ref<Vec>) -> Span<u8>\nvecGet(arg0: ref<Vec>, arg1: usize) -> Maybe<u8>\nvecSet(arg0: mutref<Vec>, arg1: usize, arg2: u8) -> Bool\nvecClear(arg0: mutref<Vec>) -> usize\nvecPop(arg0: mutref<Vec>) -> Bool\nvecTruncate(arg0: mutref<Vec>, arg1: usize) -> usize\nvecRemoveSwap(arg0: mutref<Vec>, arg1: usize) -> Bool\nvecIndex(arg0: ref<Vec>, arg1: u8) -> usize\nvecContains(arg0: ref<Vec>, arg1: u8) -> Bool\nvecInsertUnique(arg0: mutref<Vec>, arg1: u8) -> Bool\nvecRemoveValue(arg0: mutref<Vec>, arg1: u8) -> Bool\nvecLen(arg0: ref<Vec>) -> usize\nvecCapacity(arg0: ref<Vec>) -> usize\nvecRemaining(arg0: ref<Vec>) -> usize\nvecIsEmpty(arg0: ref<Vec>) -> Bool\nvecIsFull(arg0: ref<Vec>) -> Bool\nbufBytes(arg0: ref<ByteBuf>) -> MutSpan<u8>\nbufLen(arg0: ref<ByteBuf>) -> usize\nreset(arg0: mutref<FixedBufAlloc>) -> Void\ncapacity(arg0: FixedBufAlloc) -> usize\n```\n\n### std.net\n\n```text\nhost() -> Net\naddress(arg0: String, arg1: u16) -> Address\ndnsName(arg0: Address) -> String\nconnect(arg0: Net, arg1: Address) -> Maybe<Conn>\nlisten(arg0: Net, arg1: Address) -> Maybe<Listener>\nwithTimeout(arg0: Address, arg1: Duration) -> Address\nlocalhost(arg0: u16) -> Address\nloopback(arg0: u16) -> Address\n```\n\n### std.parse\n\n```text\nisAsciiDigit(arg0: Span<u8>) -> Bool\nisAsciiAlpha(arg0: Span<u8>) -> Bool\nisIdentifierStart(arg0: Span<u8>) -> Bool\nisWhitespace(arg0: Span<u8>) -> Bool\nscanDigits(arg0: Span<u8>) -> usize\nscanIdentifier(arg0: Span<u8>) -> usize\nscanUntilByte(arg0: Span<u8>, arg1: u8) -> usize\nscanWhitespace(arg0: Span<u8>) -> usize\ntokenAscii(arg0: Span<u8>) -> Span<u8>\nparseBool(arg0: Span<u8>) -> Maybe<Bool>\nparseByteSize(arg0: Span<u8>) -> Maybe<usize>\nparseDuration(arg0: Span<u8>) -> Maybe<Duration>\nparseI32(arg0: Span<u8>) -> Maybe<i32>\nparseI32Base(arg0: Span<u8>, arg1: u32) -> Maybe<i32>\nparseI32Prefix(arg0: Span<u8>) -> Maybe<i32>\nparseI64(arg0: Span<u8>) -> Maybe<i64>\nparseI64Base(arg0: Span<u8>, arg1: u32) -> Maybe<i64>\nparseI64Prefix(arg0: Span<u8>) -> Maybe<i64>\nparseU8(arg0: Span<u8>) -> Maybe<u8>\n",
|
||||
"parseU16(arg0: Span<u8>) -> Maybe<u16>\nparseU32(arg0: Span<u8>) -> Maybe<u32>\nparseU32Base(arg0: Span<u8>, arg1: u32) -> Maybe<u32>\nparseU32Prefix(arg0: Span<u8>) -> Maybe<u32>\nparseU64(arg0: Span<u8>) -> Maybe<u64>\nparseU64Base(arg0: Span<u8>, arg1: u32) -> Maybe<u64>\nparseU64Prefix(arg0: Span<u8>) -> Maybe<u64>\nparseUsize(arg0: Span<u8>) -> Maybe<usize>\nparseUsizeBase(arg0: Span<u8>, arg1: u32) -> Maybe<usize>\nparseUsizePrefix(arg0: Span<u8>) -> Maybe<usize>\n```\n\n### std.path\n\n```text\nbasename(arg0: String) -> String\ndirname(arg0: String) -> String\nextension(arg0: String) -> String\nstem(arg0: String) -> String\nsplitDir(arg0: String) -> String\nsplitBase(arg0: String) -> String\ncomponentCount(arg0: String) -> usize\ncomponent(arg0: String, arg1: usize) -> Maybe<String>\nisAbs(arg0: String) -> Bool\nabs(arg0: MutSpan<u8>, arg1: String, arg2: String) -> Maybe<String>\njoin(arg0: MutSpan<u8>, arg1: String, arg2: String) -> Maybe<String>\nnormalize(arg0: MutSpan<u8>, arg1: String) -> Maybe<String>\nrelative(arg0: MutSpan<u8>, arg1: String, arg2: String) -> Maybe<String>\n```\n\n### std.proc\n\n```text\nspawn(arg0: String) -> ProcStatus\nspawnInherit(arg0: String) -> ProcStatus\nspawnInheritArgs(arg0: String, arg1: Span<u8>, arg2: String, arg3: Span<u8>) -> ProcStatus\nexitCode(arg0: ProcStatus) -> i32\nsucceeded(arg0: ProcStatus) -> Bool\nfailed(arg0: ProcStatus) -> Bool\nrunOk(arg0: String) -> Bool\nrunCode(arg0: String) -> i32\ncapture(arg0: String, arg1: MutSpan<u8>) -> Maybe<usize>\ncaptureArgs(arg0: String, arg1: Span<u8>, arg2: MutSpan<u8>) -> Maybe<usize>\ncaptureFiles(arg0: String, arg1: String, arg2: String) -> ProcStatus\ncaptureFilesArgs(arg0: String, arg1: Span<u8>, arg2: String, arg3: String) -> ProcStatus\nspawnChild(arg0: String) -> ProcChild\nspawnChildIn(arg0: String, arg1: String) -> ProcChild\nspawnChildInEnv(arg0: String, arg1: String, arg2: Span<u8>) -> ProcChild\nspawnChildArgs(arg0: String, arg1: Span<u8>, arg2: String, arg3: Span<u8>) -> ProcChild\nchildValid(arg0: ProcChild) -> Bool\nrunning(arg0: ProcChild) -> Bool\nwait(arg0: ProcChild) -> ProcStatus\nkill(arg0: ProcChild) -> Bool\ninterrupt(arg0: ProcChild) -> Bool\nclose(arg0: ProcChild) -> Bool\ncloseStdin(arg0: ProcChild) -> Bool\npid(arg0: ProcChild) -> i32\npidRunning(arg0: i32) -> Bool\nkillPid(arg0: i32) -> Bool\ninterruptPid(arg0: i32) -> Bool\nkillGroupPid(arg0: i32) -> Bool\ninterruptGroupPid(arg0: i32) -> Bool\nreadStdout(arg0: ProcChild, arg1: MutSpan<u8>) -> Maybe<usize>\nreadStderr(arg0: ProcChild, arg1: MutSpan<u8>) -> Maybe<usize>\nwriteStdin(arg0: ProcChild, arg1: Span<u8>) -> Maybe<usize>\n```\n\n### std.pty\n\n```text\nspawn(arg0: String) -> ProcChild\nspawnIn(arg0: String, arg1: String) -> ProcChild\nspawnInEnv(arg0: String, arg1: String, arg2: Span<u8>) -> ProcChild\nspawnArgs(arg0: String, arg1: Span<u8>, arg2: String, arg3: Span<u8>) -> ProcChild\nvalid(arg0: ProcChild) -> Bool\nchildValid(arg0: ProcChild) -> Bool\n",
|
||||
"running(arg0: ProcChild) -> Bool\nwait(arg0: ProcChild) -> ProcStatus\nkill(arg0: ProcChild) -> Bool\ninterrupt(arg0: ProcChild) -> Bool\nclose(arg0: ProcChild) -> Bool\npid(arg0: ProcChild) -> i32\nread(arg0: ProcChild, arg1: MutSpan<u8>) -> Maybe<usize>\nwrite(arg0: ProcChild, arg1: Span<u8>) -> Maybe<usize>\nresize(arg0: ProcChild, arg1: usize, arg2: usize) -> Bool\n```\n\n`std.pty` starts hosted children attached to a pseudoterminal instead of\nseparate stdin/stdout/stderr pipes. It returns the same `ProcChild` handle shape\nas `std.proc`, so `running`, `wait`, `interrupt`, `kill`, `close`, and `pid`\nhave the same lifecycle meaning. `read` and `write` are nonblocking; `read`\nreturns `null` when no bytes are currently available or the terminal has closed.\nFor short-lived PTY children, drain output with `read` before `wait`; once the\nchild exits, host PTYs may report the terminal as closed.\nUse PTY children for interactive programs that need terminal behavior such as\nline editing, prompts, color, cursor control, or terminal-size awareness.\n\n### std.rand\n\n```text\nseed(arg0: u32) -> RandSource\nnextU32(arg0: mutref<RandSource>) -> u32\nnextBool(arg0: mutref<RandSource>) -> Bool\nnextBelow(arg0: mutref<RandSource>, arg1: u32) -> Maybe<u32>\nrangeU32(arg0: mutref<RandSource>, arg1: u32, arg2: u32) -> Maybe<u32>\nentropyU32() -> u32\nentropySeed() -> RandSource\nentropyHex32(arg0: MutSpan<u8>) -> Maybe<Span<u8>>\n```\n\n### std.regex\n\n```text\ncompile(buffer: MutSpan<u8>, pattern: Span<u8>) -> Maybe<Span<u8>>\ncompileErrorOffset(buffer: MutSpan<u8>, pattern: Span<u8>) -> Maybe<usize>\ncompileStatus(buffer: MutSpan<u8>, pattern: Span<u8>) -> u32\ncontains(pattern: Span<u8>, text: Span<u8>) -> Maybe<Bool>\nfind(pattern: Span<u8>, text: Span<u8>) -> Maybe<Span<u8>>\nfindCount(pattern: Span<u8>, text: Span<u8>) -> Maybe<usize>\nfindIndex(pattern: Span<u8>, text: Span<u8>) -> Maybe<usize>\nfindNth(pattern: Span<u8>, text: Span<u8>, index: usize) -> Maybe<Span<u8>>\nfindNthIndex(pattern: Span<u8>, text: Span<u8>, index: usize) -> Maybe<usize>\nreplace(buffer: MutSpan<u8>, pattern: Span<u8>, text: Span<u8>, replacement: Span<u8>) -> Maybe<Span<u8>>\nsplit(pattern: Span<u8>, text: Span<u8>, index: usize) -> Maybe<Span<u8>>\nsplitCount(pattern: Span<u8>, text: Span<u8>) -> Maybe<usize>\nstatusName(status: u32) -> String\nisMatch(program: Span<u8>, text: Span<u8>) -> Bool\nmatches(pattern: Span<u8>, text: Span<u8>) -> Maybe<Bool>\n```\n\nSupported pattern subset (ECMA-262-leaning syntax, matching by codepoint,\nunanchored search like `RegExp.prototype.test`): literals, `.`, classes with negation,\nranges, and `\\d \\D \\w \\W \\s \\S`, anchors `^` `$`, word boundaries `\\b` `\\B`,\ngreedy quantifiers `* + ? {m} {m,} {m,n}`, alternation `|`, capturing and\n`(?:...)` groups (matching only). Compile once into a caller buffer, then call\n`isMatch` repeatedly. Unsupported constructs are compile errors with status\n",
|
||||
"codes: 1 backreference, 2 lookahead, 3 lookbehind, 4 named group, 5 lazy\nquantifier, 6 group modifier, 7 unicode property escape, 8 syntax, 9 quantifier\nrange, 10 over buffer/2048-byte program limit, 11 pattern not UTF-8, 12 nesting\ndepth over 32. `statusName` names a code for diagnostics. Search, split, and\nreplace helpers use the leftmost start and longest end for each match; `split`\nand `splitCount` use non-empty matches as separators and ignore zero-length\nmatches.\n\n### std.search\n\n```text\nbinaryI32(arg0: Span<i32>, arg1: i32) -> usize\nbinaryDescI32(arg0: Span<i32>, arg1: i32) -> usize\nbinaryU32(arg0: Span<u32>, arg1: u32) -> usize\nbinaryDescU32(arg0: Span<u32>, arg1: u32) -> usize\nbinaryUsize(arg0: Span<usize>, arg1: usize) -> usize\nbinaryDescUsize(arg0: Span<usize>, arg1: usize) -> usize\ncontainsSortedI32(arg0: Span<i32>, arg1: i32) -> Bool\ncontainsSortedDescI32(arg0: Span<i32>, arg1: i32) -> Bool\ncontainsSortedU32(arg0: Span<u32>, arg1: u32) -> Bool\ncontainsSortedDescU32(arg0: Span<u32>, arg1: u32) -> Bool\ncontainsSortedUsize(arg0: Span<usize>, arg1: usize) -> Bool\ncontainsSortedDescUsize(arg0: Span<usize>, arg1: usize) -> Bool\ncountSortedI32(arg0: Span<i32>, arg1: i32) -> usize\ncountSortedDescI32(arg0: Span<i32>, arg1: i32) -> usize\ncountSortedU32(arg0: Span<u32>, arg1: u32) -> usize\ncountSortedDescU32(arg0: Span<u32>, arg1: u32) -> usize\ncountSortedUsize(arg0: Span<usize>, arg1: usize) -> usize\ncountSortedDescUsize(arg0: Span<usize>, arg1: usize) -> usize\nequalRangeI32(arg0: Span<i32>, arg1: i32) -> Span<i32>\nequalRangeDescI32(arg0: Span<i32>, arg1: i32) -> Span<i32>\nequalRangeU32(arg0: Span<u32>, arg1: u32) -> Span<u32>\nequalRangeDescU32(arg0: Span<u32>, arg1: u32) -> Span<u32>\nequalRangeUsize(arg0: Span<usize>, arg1: usize) -> Span<usize>\nequalRangeDescUsize(arg0: Span<usize>, arg1: usize) -> Span<usize>\npartitionPointI32(arg0: Span<i32>, arg1: i32) -> usize\npartitionPointDescI32(arg0: Span<i32>, arg1: i32) -> usize\npartitionPointU32(arg0: Span<u32>, arg1: u32) -> usize\npartitionPointDescU32(arg0: Span<u32>, arg1: u32) -> usize\npartitionPointUsize(arg0: Span<usize>, arg1: usize) -> usize\npartitionPointDescUsize(arg0: Span<usize>, arg1: usize) -> usize\nindexOf(items: Span<T>, value: T) -> usize\nlastIndexOf(items: Span<T>, value: T) -> usize\nlowerBoundI32(arg0: Span<i32>, arg1: i32) -> usize\nlowerBoundDescI32(arg0: Span<i32>, arg1: i32) -> usize\nlowerBoundU32(arg0: Span<u32>, arg1: u32) -> usize\nlowerBoundDescU32(arg0: Span<u32>, arg1: u32) -> usize\nlowerBoundUsize(arg0: Span<usize>, arg1: usize) -> usize\nlowerBoundDescUsize(arg0: Span<usize>, arg1: usize) -> usize\nminI32(arg0: Span<i32>) -> Maybe<i32>\nminU32(arg0: Span<u32>) -> Maybe<u32>\nminUsize(arg0: Span<usize>) -> Maybe<usize>\nminIndexI32(arg0: Span<i32>) -> usize\nminIndexU32(arg0: Span<u32>) -> usize\nminIndexUsize(arg0: Span<usize>) -> usize\nmaxI32(arg0: Span<i32>) -> Maybe<i32>\nmaxU32(arg0: Span<u32>) -> Maybe<u32>\n",
|
||||
"maxUsize(arg0: Span<usize>) -> Maybe<usize>\nmaxIndexI32(arg0: Span<i32>) -> usize\nmaxIndexU32(arg0: Span<u32>) -> usize\nmaxIndexUsize(arg0: Span<usize>) -> usize\nupperBoundI32(arg0: Span<i32>, arg1: i32) -> usize\nupperBoundDescI32(arg0: Span<i32>, arg1: i32) -> usize\nupperBoundU32(arg0: Span<u32>, arg1: u32) -> usize\nupperBoundDescU32(arg0: Span<u32>, arg1: u32) -> usize\nupperBoundUsize(arg0: Span<usize>, arg1: usize) -> usize\nupperBoundDescUsize(arg0: Span<usize>, arg1: usize) -> usize\n```\n\n### std.sort\n\n```text\ninsertionI32(arg0: MutSpan<i32>) -> Void\ninsertionDescI32(arg0: MutSpan<i32>) -> Void\ninsertionU32(arg0: MutSpan<u32>) -> Void\ninsertionDescU32(arg0: MutSpan<u32>) -> Void\ninsertionUsize(arg0: MutSpan<usize>) -> Void\ninsertionDescUsize(arg0: MutSpan<usize>) -> Void\nstableI32(arg0: MutSpan<i32>) -> Void\nstableU32(arg0: MutSpan<u32>) -> Void\nstableUsize(arg0: MutSpan<usize>) -> Void\nstableDescI32(arg0: MutSpan<i32>) -> Void\nstableDescU32(arg0: MutSpan<u32>) -> Void\nstableDescUsize(arg0: MutSpan<usize>) -> Void\nunstableI32(arg0: MutSpan<i32>) -> Void\nunstableU32(arg0: MutSpan<u32>) -> Void\nunstableUsize(arg0: MutSpan<usize>) -> Void\nunstableDescI32(arg0: MutSpan<i32>) -> Void\nunstableDescU32(arg0: MutSpan<u32>) -> Void\nunstableDescUsize(arg0: MutSpan<usize>) -> Void\nreverseI32(arg0: MutSpan<i32>) -> Void\nswapI32(arg0: MutSpan<i32>, arg1: usize, arg2: usize) -> Bool\nrotateLeftI32(arg0: MutSpan<i32>, arg1: usize) -> Void\nrotateRightI32(arg0: MutSpan<i32>, arg1: usize) -> Void\nreverseU32(arg0: MutSpan<u32>) -> Void\nswapU32(arg0: MutSpan<u32>, arg1: usize, arg2: usize) -> Bool\nrotateLeftU32(arg0: MutSpan<u32>, arg1: usize) -> Void\nrotateRightU32(arg0: MutSpan<u32>, arg1: usize) -> Void\nreverseUsize(arg0: MutSpan<usize>) -> Void\nswapUsize(arg0: MutSpan<usize>, arg1: usize, arg2: usize) -> Bool\nrotateLeftUsize(arg0: MutSpan<usize>, arg1: usize) -> Void\nrotateRightUsize(arg0: MutSpan<usize>, arg1: usize) -> Void\nisSortedI32(arg0: Span<i32>) -> Bool\nisSortedDescI32(arg0: Span<i32>) -> Bool\nisSortedU32(arg0: Span<u32>) -> Bool\nisSortedDescU32(arg0: Span<u32>) -> Bool\nisSortedUsize(arg0: Span<usize>) -> Bool\nisSortedDescUsize(arg0: Span<usize>) -> Bool\npartitionI32(arg0: MutSpan<i32>, arg1: i32) -> usize\npartitionDescI32(arg0: MutSpan<i32>, arg1: i32) -> usize\npartitionU32(arg0: MutSpan<u32>, arg1: u32) -> usize\npartitionDescU32(arg0: MutSpan<u32>, arg1: u32) -> usize\npartitionUsize(arg0: MutSpan<usize>, arg1: usize) -> usize\npartitionDescUsize(arg0: MutSpan<usize>, arg1: usize) -> usize\nisPartitionedI32(arg0: Span<i32>, arg1: i32) -> Bool\nisPartitionedDescI32(arg0: Span<i32>, arg1: i32) -> Bool\nisPartitionedU32(arg0: Span<u32>, arg1: u32) -> Bool\nisPartitionedDescU32(arg0: Span<u32>, arg1: u32) -> Bool\nisPartitionedUsize(arg0: Span<usize>, arg1: usize) -> Bool\nisPartitionedDescUsize(arg0: Span<usize>, arg1: usize) -> Bool\nselectNthI32(arg0: MutSpan<i32>, arg1: usize) -> Bool\n",
|
||||
"selectNthDescI32(arg0: MutSpan<i32>, arg1: usize) -> Bool\nselectNthU32(arg0: MutSpan<u32>, arg1: usize) -> Bool\nselectNthDescU32(arg0: MutSpan<u32>, arg1: usize) -> Bool\nselectNthUsize(arg0: MutSpan<usize>, arg1: usize) -> Bool\nselectNthDescUsize(arg0: MutSpan<usize>, arg1: usize) -> Bool\nmergeSortedI32(arg0: MutSpan<i32>, arg1: Span<i32>, arg2: Span<i32>) -> usize\nmergeSortedDescI32(arg0: MutSpan<i32>, arg1: Span<i32>, arg2: Span<i32>) -> usize\nmergeSortedU32(arg0: MutSpan<u32>, arg1: Span<u32>, arg2: Span<u32>) -> usize\nmergeSortedDescU32(arg0: MutSpan<u32>, arg1: Span<u32>, arg2: Span<u32>) -> usize\nmergeSortedUsize(arg0: MutSpan<usize>, arg1: Span<usize>, arg2: Span<usize>) -> usize\nmergeSortedDescUsize(arg0: MutSpan<usize>, arg1: Span<usize>, arg2: Span<usize>) -> usize\ndedupeSortedI32(arg0: MutSpan<i32>) -> usize\ndedupeSortedU32(arg0: MutSpan<u32>) -> usize\ndedupeSortedUsize(arg0: MutSpan<usize>) -> usize\n```\n\n### std.str\n\n```text\ncontains(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nconcat(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\ncopy(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\ncount(arg0: Span<u8>, arg1: Span<u8>) -> usize\ncountByte(arg0: Span<u8>, arg1: u8) -> usize\neqlIgnoreAsciiCase(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nendsWith(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nfieldAscii(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nfieldCountAscii(arg0: Span<u8>) -> usize\nindexOf(arg0: Span<u8>, arg1: Span<u8>) -> usize\nlastIndexOf(arg0: Span<u8>, arg1: Span<u8>) -> usize\nline(arg0: Span<u8>, arg1: usize) -> Maybe<Span<u8>>\nlineCount(arg0: Span<u8>) -> usize\nrepeat(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\nreplace(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nreverse(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nsplit(arg0: Span<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\nsplitCount(arg0: Span<u8>, arg1: Span<u8>) -> usize\nstartsWith(arg0: Span<u8>, arg1: Span<u8>) -> Bool\ntoLowerAscii(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\ntoUpperAscii(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\ntrimAscii(arg0: Span<u8>) -> Span<u8>\ntrimEndAscii(arg0: Span<u8>) -> Span<u8>\ntrimStartAscii(arg0: Span<u8>) -> Span<u8>\nwordCountAscii(arg0: Span<u8>) -> usize\n```\n\n### std.testing\n\n```text\nisTrue(arg0: Bool) -> Bool\nisFalse(arg0: Bool) -> Bool\nequalBool(arg0: Bool, arg1: Bool) -> Bool\nequalUsize(arg0: usize, arg1: usize) -> Bool\nequalU32(arg0: u32, arg1: u32) -> Bool\nequalI32(arg0: i32, arg1: i32) -> Bool\nequalBytes(arg0: Span<u8>, arg1: Span<u8>) -> Bool\ncontainsBytes(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nstartsWith(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nendsWith(arg0: Span<u8>, arg1: Span<u8>) -> Bool\nnotEqualBytes(arg0: Span<u8>, arg1: Span<u8>) -> Bool\ndiffIndexBytes(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<usize>\n",
|
||||
"jsonFieldEquals(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Bool\njsonPathEquals(arg0: Span<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Bool\ncaseName(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: usize) -> Maybe<Span<u8>>\n```\n\n### std.text\n\n```text\nisAscii(arg0: Span<u8>) -> Bool\nutf8Len(arg0: Span<u8>) -> Maybe<usize>\nutf8Valid(arg0: Span<u8>) -> Bool\n```\n\n### std.time\n\n```text\nns(arg0: i64) -> Duration\nus(arg0: i64) -> Duration\nms(arg0: i32) -> Duration\nseconds(arg0: i32) -> Duration\nminutes(arg0: i32) -> Duration\nhours(arg0: i32) -> Duration\nzero() -> Duration\nadd(arg0: Duration, arg1: Duration) -> Duration\nmonotonic() -> Duration\nwallSeconds() -> i64\nsub(arg0: Duration, arg1: Duration) -> Duration\nasNs(arg0: Duration) -> i64\nasUsFloor(arg0: Duration) -> i64\nasMsFloor(arg0: Duration) -> i32\nasSecondsFloor(arg0: Duration) -> i64\nmin(arg0: Duration, arg1: Duration) -> Duration\nmax(arg0: Duration, arg1: Duration) -> Duration\nclamp(arg0: Duration, arg1: Duration, arg2: Duration) -> Duration\nlessThan(arg0: Duration, arg1: Duration) -> Bool\nisZero(arg0: Duration) -> Bool\nabs(arg0: Duration) -> Duration\nbetween(arg0: Duration, arg1: Duration) -> Duration\nhasElapsed(arg0: Duration, arg1: Duration, arg2: Duration) -> Bool\ndeadlineAfter(arg0: Duration, arg1: Duration) -> Duration\nremainingUntil(arg0: Duration, arg1: Duration) -> Duration\ndeadlineExpired(arg0: Duration, arg1: Duration) -> Bool\nsleep(arg0: Duration) -> Bool\nisRfc3339Date(text: Span<u8>) -> Bool\nisRfc3339Time(text: Span<u8>) -> Bool\nisRfc3339DateTime(text: Span<u8>) -> Bool\nparseRfc3339DateTimeOr(text: Span<u8>, fallback: i64) -> i64\nisLeapYear(year: u32) -> Bool\ndaysInMonth(year: u32, month: u32) -> u32\nwriteDurationNs(arg0: MutSpan<u8>, arg1: Duration) -> Maybe<Span<u8>>\nwriteDurationMs(arg0: MutSpan<u8>, arg1: Duration) -> Maybe<Span<u8>>\nwriteDurationSeconds(arg0: MutSpan<u8>, arg1: Duration) -> Maybe<Span<u8>>\n```\n\nThe RFC 3339 helpers are target-neutral and validate calendar dates (leap\nyears, days-in-month), times with fractional seconds and numeric offsets, and\ndate-times joined by `T` or `t`. The leap-second rule is exact: seconds `60`\nis valid only when the time normalized by its offset equals `23:59:60` UTC,\nwrapping modulo 24 hours (`00:29:60+00:30` is valid; `23:59:60-01:00` is not).\n`parseRfc3339DateTimeOr` returns UTC epoch seconds, truncating fractions and\nmapping a valid leap second to the same epoch second as `:59`; it returns the\nfallback for invalid text.\nUse `writeDurationNs`, `writeDurationMs`, or `writeDurationSeconds` when a\ntyped `Duration` needs a compact textual value in caller-owned storage.\nUse `deadlineAfter`, `remainingUntil`, and `deadlineExpired` to model request\nbudgets against monotonic `Duration` instants without observing the clock inside\nthe helper.\n`std.time.sleep` is hosted and returns `false` on host failure. Timer and\nfake-clock handles are not exposed in the current surface.\n\n",
|
||||
"### std.unicode\n\n```text\ndecodeAt(text: Span<u8>, index: usize) -> Maybe<u32>\ndecodeStatusAt(text: Span<u8>, index: usize) -> u32\nwidthAt(text: Span<u8>, index: usize) -> Maybe<usize>\nencode(buffer: MutSpan<u8>, cp: u32) -> Maybe<Span<u8>>\nencodedWidth(cp: u32) -> Maybe<usize>\ninvalidIndex(text: Span<u8>) -> usize\nisDigit(cp: u32) -> Bool\nisWord(cp: u32) -> Bool\nisSpace(cp: u32) -> Bool\nnextIndex(text: Span<u8>, index: usize) -> Maybe<usize>\nstatusName(status: u32) -> String\n```\n\nDecoding is strict UTF-8 (overlong encodings, surrogates, values above\nU+10FFFF, and truncated sequences return `null`). Iterate codepoints by\nadvancing a byte index with `nextIndex` or `widthAt`. `invalidIndex` returns\nthe first invalid byte index or the input length, and `decodeStatusAt` plus\n`statusName` provide allocation-free decode diagnostics. The class helpers use\nECMA-262 regex semantics by codepoint (`\\d` `\\w` `\\s`).\n\n### std.url\n\n```text\npercentEncode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\npercentDecode(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nqueryEscape(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nqueryUnescape(arg0: MutSpan<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nscheme(arg0: Span<u8>) -> Maybe<Span<u8>>\nauthority(arg0: Span<u8>) -> Maybe<Span<u8>>\nhost(arg0: Span<u8>) -> Maybe<Span<u8>>\npath(arg0: Span<u8>) -> Span<u8>\nquery(arg0: Span<u8>) -> Maybe<Span<u8>>\nfragment(arg0: Span<u8>) -> Maybe<Span<u8>>\nqueryValue(arg0: Span<u8>, arg1: Span<u8>) -> Maybe<Span<u8>>\nqueryValueDecoded(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteQueryParam(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteFormField(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nappendFormField(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nformValue(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nappendQuery(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\nwriteUrl(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>, arg3: Span<u8>) -> Maybe<Span<u8>>\nappendFragment(arg0: MutSpan<u8>, arg1: Span<u8>, arg2: Span<u8>) -> Maybe<Span<u8>>\n```\n\n## Maybe Pattern\n\n```zero\npub fn main(world: World) -> Void raises {\n let first: Maybe<String> = std.args.get(1)\n if first.has {\n check world.out.write(first.value)\n }\n}\n```\n\nUse the CLI helpers for command, fallback, exact flag, and option conventions\nbefore writing a custom argument loop:\n\n```zero\npub fn main(world: World) -> Void raises {\n let command: String = std.cli.commandOr(\"help\")\n let name: String = std.cli.argOr(2, \"world\")\n if std.mem.eql(command, \"hello\") {\n check world.out.write(\"hello \")\n check world.out.write(name)\n check world.out.write(\"\\n\")\n }\n}\n```\n\n",
|
||||
"Use `check maybeValue` only when absence should propagate as a failure in a fallible function.\nRead `maybeValue.value` only inside a visible `if maybeValue.has { ... }` guard.\n\n## HTTP Pattern\n\nUse the request/response envelope helpers instead of hand-building byte\nheaders when possible. `std.http.writeRequest` and\n`std.http.writeJsonRequest` take a start line such as `\"GET /health\"` or\n`\"POST https://example.com/api\"` and write into caller-owned storage. Use\n`std.http.writeMethodRequest`, `std.http.writeGetRequest`, and the POST/PUT/PATCH\nJSON request helpers when method and target are already separate values.\n\n```zero\npub fn main() -> Void {\n var request_buf: [128]u8 = [0_u8; 128]\n let request: Maybe<Span<u8>> = std.http.writeJsonRequest(request_buf, \"POST /users\", \"{\\\"id\\\":7}\")\n expect request.has\n}\n```\n\nFor API-style handlers, parse the request envelope with route helpers such as\n`std.http.requestIsGet`, `std.http.requestIsHead`,\n`std.http.requestIsOptions`, `std.http.requestIsPost`,\n`std.http.requestPathStartsWith`, `std.http.requestPathTailAfter`,\n`std.http.requestRouteMatches`, `std.http.requestPathParam`,\n`std.http.pathMatchesPattern`, `std.http.pathParam`,\n`std.http.pathSegmentCount`, `std.http.pathSegment`,\n`std.http.requestPathSegmentCount`, `std.http.requestPathSegment`,\n`std.http.requestQueryValue`, `std.http.requestHeader`,\n`std.http.requestContentLength`, `std.http.requestContentType`,\n`std.http.requestAccepts`, `std.http.requestAcceptsJson`,\n`std.http.requestBearerToken`, `std.http.requestCookie`,\n`std.http.requestHasJsonContentType`, `std.http.requestJsonBodyWithin`, and\n`std.http.requestJsonField`. Use `std.http.methodAllowed`,\n`std.http.requestMethodAllowed`, and `std.http.requestRouteMethodAllowed` for\nexplicit handler dispatch across a comma-separated method allow list. Use path\nsegment helpers for resource routes such as `/users/7`; they borrow zero-based,\nnon-empty segments and ignore leading, trailing, or repeated `/`. Pattern routes\nmatch literal segments, `:name` parameters, and a trailing `*` wildcard.\nPrefer the status-specific JSON writers for common success responses and\n`std.http.writeJsonError(response, status, code)` for conventional\n`{\"error\":\"code\"}` failures. `writeJsonError` validates the code before writing\nJSON, so agents do not need to hand-build simple error bodies. The full custom\nbody writers remain available:\n`std.http.writeJsonOk`, `std.http.writeJsonCreated`,\n`std.http.writeJsonBadRequest`, `std.http.writeJsonUnauthorized`,\n`std.http.writeJsonForbidden`, `std.http.writeJsonNotFound`,\n`std.http.writeJsonMethodNotAllowed`, `std.http.writeJsonConflict`,\n`std.http.writeJsonUnprocessable`, `std.http.writeJsonTooManyRequests`, and\n`std.http.writeJsonInternalServerError`. Use `std.http.writeNoContent` for\n204 responses, `std.http.writeCorsPreflight` for `OPTIONS` preflight responses,\n",
|
||||
"and `std.http.writeCorsJsonResponse` when a JSON response also needs\n`access-control-allow-origin`. `writeCorsJsonResponse` takes a status-line\nfragment such as `\"200 OK\"` or `\"422 Unprocessable Entity\"`. Use\n`std.http.writeResponseWithHeader`, `std.http.writeJsonResponseWithHeader`, or\n`std.http.writeJsonResponseWithCookie` when a response needs one explicit safe\nheader line or cookie value. Use the `WithHeaders` variants with\n`std.http.headerBlockSafe` for newline-separated header blocks. Use\n`std.http.writeRequestWithHeader`, `std.http.writeRequestWithHeaders`,\n`std.http.writeJsonRequestWithHeader`, or\n`std.http.writeJsonRequestWithHeaders` for outbound request envelopes with\nexplicit safe headers. These writers reject headers they manage themselves:\nrequest writers reject `content-length` and `transfer-encoding`; JSON request\nwriters also reject `content-type`; response writers reject `content-length`,\n`transfer-encoding`, and `connection`; JSON response writers also reject\n`content-type`. Use `std.http.writeTextOk` or\n`std.http.writeHtmlOk` for simple non-JSON responses such as health text,\n`robots.txt`, or a small HTML page. Use `std.http.contentTypeForPath` and\n`std.http.writeStaticResponse` for small static responses whose media type can\ncome from a path suffix. Use redirect helpers such as\n`std.http.writeFound`, `std.http.writeSeeOther`,\n`std.http.writeMovedPermanently`, or `std.http.writePermanentRedirect` instead\nof hand-writing `Location` headers; they reject empty or control-character\nlocation values before writing. Use\n`std.http.responseStatus`, `std.http.responseStatusIs`,\n`std.http.responseHeader`, `std.http.responseContentType`,\n`std.http.responseRedirectLocation`, `std.http.responseBodyBytes`,\n`std.http.responseBodyEquals`, and `std.http.responseMatches` to inspect a\nlocal response envelope produced by the response writers. Use\n`std.http.testRequest` and `std.http.testJsonRequest` to build synthetic request\nenvelopes for handler checks without opening a socket.\n\nFor a runnable local API server, define a same-module handler and call\n`std.http.listen(world)` from `main`. The handler signature is\n`fn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>>`.\nWhen no port is passed, `std.http.listen(world)` starts at development port\n`3000` and increments by one until it finds a free loopback port. It prints the\nactual URL, such as `listening on http://127.0.0.1:3001`; use that printed port\nfor local HTTP requests. Do not assume `3000`, because another local server may\nalready be using it. When a port is explicit,\n`std.http.listen(world, 3000_u16)` tries exactly that port and fails with a bind\ndiagnostic if it is occupied.\n\n```zero\npub fn main(world: World) -> Void raises {\n check std.http.listen(world)\n}\n\nfn handle(request: Span<u8>, response: MutSpan<u8>) -> Maybe<Span<u8>> {\n if std.http.requestIsGet(request, \"/ping\") {\n",
|
||||
" return std.http.writeJsonOk(response, \"{\\\"message\\\":\\\"pong\\\"}\")\n }\n return std.http.writeJsonError(response, 404, \"not_found\")\n}\n```\n\n```zero\npub fn main() -> Void {\n let request: Span<u8> = \"POST /users?tenant=demo\\ncontent-type: application/json\\n\\n{\\\"id\\\":7}\"\n var response_buf: [192]u8 = [0_u8; 192]\n let body: Maybe<Span<u8>> = std.http.requestJsonBodyWithin(request, 64)\n let tenant: Maybe<Span<u8>> = std.http.requestQueryValue(request, \"tenant\")\n let resource: Maybe<Span<u8>> = std.http.requestPathSegment(request, 0)\n if std.http.requestIsPost(request, \"/users\") && resource.has && std.mem.eql(resource.value, \"users\") && tenant.has && body.has {\n let response: Maybe<Span<u8>> = std.http.writeJsonCreated(response_buf, \"{\\\"created\\\":true}\")\n expect response.has\n }\n}\n```\n\nFor browser-facing APIs, handle preflight and CORS in the response writer\ninstead of hand-assembling headers:\n\n```zero\npub fn main() -> Void {\n let request: Span<u8> = \"OPTIONS /users\\naccess-control-request-method: POST\\n\\n\"\n var response_buf: [256]u8 = [0_u8; 256]\n if std.http.requestIsOptions(request, \"/users\") {\n let response: Maybe<Span<u8>> = std.http.writeCorsPreflight(response_buf, \"*\", \"GET, POST, OPTIONS\", \"content-type, authorization\")\n expect response.has\n }\n}\n```\n\nFor authenticated APIs, use header-specific helpers rather than parsing\n`authorization` or `cookie` by hand:\n\n```zero\npub fn main() -> Void {\n let request: Span<u8> = \"GET /me\\nauthorization: Bearer token-123\\ncookie: sid=abc; theme=dark\\n\\n\"\n let token: Maybe<Span<u8>> = std.http.requestBearerToken(request)\n let session: Maybe<Span<u8>> = std.http.requestCookie(request, \"sid\")\n expect token.has && session.has\n}\n```\n\nFor hosted client calls, keep the network capability explicit and read response\nbytes through `std.http.responseBody`. Use `std.http.headerBytes` when a\nheader value from `std.http.headerValue` must be borrowed as a span.\n\n## Resource Pattern\n\nHosted file APIs can use explicit handles:\n\n```zero\npub fn main(world: World) -> Void raises {\n let fs: Fs = std.fs.host()\n var read_buf: [32]u8 = [0_u8; 32]\n if std.fs.writeFile(fs, \".zero/out/log.txt\", \"hello\\n\") && std.fs.appendFile(fs, \".zero/out/log.txt\", \"again\\n\") && std.fs.readFileEquals(fs, \".zero/out/log.txt\", read_buf, \"hello\\nagain\\n\") {\n check world.out.write(\"wrote\\n\")\n }\n}\n```\n\nOwned resources are deterministic. Do not invent hidden heap, global logger, or ambient filesystem APIs.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_testing_chunks[] = {
|
||||
"---\nname: testing\ndescription: Write and run Zero test blocks.\n---\n\n# Zero Testing\n\nUse this when adding tests, debugging failing tests, or wiring Zero checks into CI and editor workflows.\n\n## Test Blocks\n\nZero test blocks live beside source:\n\n```zero\nfn add(left: i32, right: i32) -> i32 {\n return left + right\n}\n\ntest \"addition works\" {\n expect add(2, 3) == 5\n}\n```\n\n`expect` requires a `Bool`. A false expectation fails the test.\n\nUse `std.testing` helpers when a predicate should be explicit in the source:\n\n```zero\ntest \"output shape\" {\n expect std.testing.equalBytes(\"zero\", \"zero\")\n expect std.testing.containsBytes(\"zerolang\", \"lang\")\n}\n```\n\n`std.testing` does not register tests or print output. It only returns `Bool`\nvalues for ordinary `expect` statements.\n\n## Run\n\n```sh\nzero test conformance/native/pass/test-blocks.graph\nzero test --filter addition conformance/native/pass/test-blocks.graph\nzero test conformance/packages/test-app\n```\n\nUse `--filter` for a narrow loop. The filter matches test names by substring.\n\nFor packages, normal `zero test [package]` compiles from `zero.graph` and can\nrun before `.0` projections exist:\n\n```sh\nzero patch --op 'addTest name=\"addition works\" call=\"add\" arg0=\"2\" arg1=\"3\" expect=\"5\" type=\"i32\"'\nzero test\nzero test --filter addition\n```\n\nPrefer `addTest` for one pure function call with literal arguments. Use\n`addTestBody name=\"...\" ... end` only when the test needs custom body rows.\nTest labels are display names, not callable function names; do not rename them\nto `__zero_test_*`.\nIf `zero test` reports an unknown function for a display label, do not rename\nthe label to chase runner internals. Delete the malformed custom test and\nrecreate simple pure coverage with `addTest`, or use behavior smoke checks for\neffectful paths.\n\nIf another tool hands you a derived ProgramGraph artifact, `zero test` can\nvalidate it. Do not create a standalone graph artifact for the ordinary package\ntest loop; test the package path so the compiler reads `zero.graph` directly.\n\n## JSON Fields\n\nUse `zero test --json` when a tool or CI job needs exact fields. Useful fields:\n\n- `testDiscovery`: how files and tests were found\n- `fixtures`: fixture inputs and snapshot metadata\n- `snapshotKey`: stable test snapshot contract\n- `discoveredTests`, `selectedTests`, `passedTests`, `failedTests`\n- `expectedFailures`, `unexpectedPasses`\n- `targetFacts`: selected target and capability facts\n- `results`: per-test name, status, duration, source location, and failure span\n- `stdout`, `stderr`, `durationMs`\n\nUse JSON for machines and CI contracts. Normal test output is the default agent loop.\n\n## Expected Failures\n\nExpected-fail tests use one of these name markers:\n\n- `xfail:`\n- `expected fail:`\n- `[xfail]`\n\nExample:\n\n```zero\ntest \"xfail: pending parser edge case\" {\n expect false\n}\n```\n\n",
|
||||
"An expected-fail test passes the command only when it fails as expected. If it starts passing, the command fails with `unexpectedPasses`.\n\n## Agent Workflow\n\n1. Add the smallest test that owns the behavior.\n2. Run a filtered test while editing.\n3. Run the containing package or fixture before finishing.\n4. Do not leave an expected-fail marker on a fixed bug.\n5. Use `zero check` first when the failure is a compile error; rerun with `--json` only if you need exact diagnostic fields.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const zero_embedded_skill_zero_chunks[] = {
|
||||
"---\nname: zero\ndescription: Install Zero and load version-matched workflows with zero skills.\n---\n\n# Zero\n\nZero is an agent-first programming language. zero.graph is the program; `.0` files are projections of it. Edit through `zero patch`.\n\nInstall this skill once in an agent's skill manager. Keep it thin; Zero's own CLI serves the version-matched workflow for each installed compiler.\n\nUse the zero already on PATH or at `~/.zero/bin/zero` when one exists; environments often pin a specific compiler, and replacing it silently breaks version-matched stores and workflows. Install only when zero is missing:\n\n```sh\ncommand -v zero >/dev/null 2>&1 || { curl -fsSL https://zerolang.ai/install.sh | bash; }\nexport PATH=\"$PATH:$HOME/.zero/bin\"\nzero --version\n```\n\n## Version-Matched Skills\n\nThis file is only a discovery stub. Do not treat it as the full Zero workflow\nor as command reference.\n\nBefore editing, checking, testing, or repairing Zero code, ask the installed compiler for the skill content that matches that exact binary:\n\n```sh\nzero skills\nzero skills get zero\nzero skills get zero --full\nzero skills get stdlib --topic std.time # one section instead of the whole topic\n```\n\nIf the user has multiple Zero binaries, use the same binary that will run the project:\n\n```sh\n/path/to/zero skills\n/path/to/zero skills get zero --full\n```\n\nUse `zero skills get <name>` to load only what the task needs, and fetch each\ntopic at most once per session: the content is fixed for a given compiler\nbinary, so refetching a loaded topic returns the same text. Topics and\napproximate served sizes:\n\n- `zero` (~2 KB): this discovery stub\n- `agent` (~4 KB): read-edit-verify loop, zero query usage, edit surfaces, verification\n- `language` (~6 KB): syntax, types, effects, control flow, generics\n- `graph` (~9 KB): zero.graph store, query/view, patch operations, import/export/merge\n- `diagnostics` (~4 KB): reading diagnostics, zero explain, typed fix plans\n- `packages` (~5 KB): manifests, package layout, creation and repair\n- `builds` (~5 KB): build/run, targets, profiles, emitted artifacts\n- `testing` (~3 KB): test blocks, filters, runtime checks\n- `stdlib` (~39 KB): full signature reference, including ready-made validators: `std.time` (RFC 3339 incl. the exact leap-second rule), `std.inet` (IPv4/IPv6/hostname), `std.regex` (ECMA subset), `std.unicode` (strict UTF-8). Check here before hand-writing any parsing or validation logic. Fetch one module's section (~1 KB) with `zero skills get stdlib --topic <prefix>`, e.g. `--topic std.time`.\n\nBefore hand-writing any parsing or validation, check the stdlib catalog: `zero skills get stdlib --topic <module>` serves one section (`std.time` covers RFC 3339 incl. leap seconds).\n\n",
|
||||
"Edit through the graph: `zero patch` covers everything from surgical in-function text edits (`--replace-in-fn <fn> --old <text> --new <text>`, Edit semantics) to complete helper creation (`upsertFunction ... end`) and whole function bodies (`--replace-fn <fn> --body-file -` with a heredoc). Direct `.0` text edits are a last resort for changes no patch op expresses; package commands refresh `zero.graph` from edited source, but patch keeps the loop faster and preserves node identity. Read one function with `zero view --fn <name>` instead of whole files. Prefer concise text output during interactive agent work; use `--json` only for automation, exact spans, contracts, or machine-readable diagnostics.\n\n## Common Entry Points\n\n```sh\nzero query [graph-or-package]\nzero patch [graph-or-package] --op '<operation>'\nzero check [graph-or-package]\nzero test [graph-or-package]\nzero run [graph-or-package] -- <args>\nzero diff [graph-or-package]\nzero explain <diagnostic-code>\nzero fix --plan [graph-or-package]\n```\n\nIn a Zero repository checkout, prefer `bin/zero` when the task is about that checkout rather than the globally installed compiler.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const ZeroEmbeddedSkill zero_embedded_skills[] = {
|
||||
{"agent", "Graph-first agent workflow for making focused Zero changes with CLI feedback.", false, zero_embedded_skill_agent_chunks},
|
||||
{"builds", "Build, run, target, and profile Zero programs.", false, zero_embedded_skill_builds_chunks},
|
||||
{"diagnostics", "Read Zero diagnostics, explanations, and typed fix plans.", false, zero_embedded_skill_diagnostics_chunks},
|
||||
{"graph", "Use ProgramGraph commands as the primary agent authoring and inspection surface.", false, zero_embedded_skill_graph_chunks},
|
||||
{"language", "Compact zerolang syntax and semantics guide for agents.", false, zero_embedded_skill_language_chunks},
|
||||
{"packages", "Create, inspect, and repair Zero packages and manifests.", false, zero_embedded_skill_packages_chunks},
|
||||
{"stdlib", "Use Zero standard library modules and target-gated capabilities.", false, zero_embedded_skill_stdlib_chunks},
|
||||
{"testing", "Write and run Zero test blocks.", false, zero_embedded_skill_testing_chunks},
|
||||
{"zero", "Install Zero and load version-matched workflows with zero skills.", false, zero_embedded_skill_zero_chunks},
|
||||
};
|
||||
static const size_t zero_embedded_skill_count = sizeof(zero_embedded_skills) / sizeof(zero_embedded_skills[0]);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,542 @@
|
||||
#include "zero.h"
|
||||
#include "aarch64_direct.h"
|
||||
#include "aarch64_emit.h"
|
||||
#include "coff_format.h"
|
||||
#include "direct_emit.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
ZCoffImageDataPatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} CoffA64DataPatches;
|
||||
|
||||
typedef struct {
|
||||
size_t *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} CoffA64BranchPatches;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned callee_index;
|
||||
unsigned external_index;
|
||||
bool external_call;
|
||||
} CoffA64CallPatch;
|
||||
|
||||
typedef struct {
|
||||
CoffA64CallPatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} CoffA64CallPatches;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
} CoffA64RuntimePatch;
|
||||
|
||||
typedef struct {
|
||||
CoffA64RuntimePatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} CoffA64RuntimePatches;
|
||||
|
||||
typedef struct {
|
||||
CoffA64DataPatches data;
|
||||
CoffA64CallPatches calls;
|
||||
CoffA64RuntimePatches runtime[A64_DIRECT_RUNTIME_HELPER_COUNT];
|
||||
CoffA64BranchPatches world_write;
|
||||
bool allow_runtime_patches;
|
||||
} CoffA64EmitState;
|
||||
|
||||
static bool coff_a64_diag(ZDiag *diag, const char *message, int line, int column, const char *actual) {
|
||||
if (diag) {
|
||||
diag->code = 4004;
|
||||
diag->line = line > 0 ? line : 1;
|
||||
diag->column = column > 0 ? column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message);
|
||||
snprintf(diag->expected, sizeof(diag->expected), "direct COFF AArch64 object subset");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "unsupported construct");
|
||||
snprintf(diag->help, sizeof(diag->help), "choose a supported direct target or restrict this program to AArch64 COFF supported direct-backend constructs");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool coff_a64_record_data_patch(void *user, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag) {
|
||||
CoffA64EmitState *state = user;
|
||||
CoffA64DataPatches *patches = state ? &state->data : NULL;
|
||||
if (!patches) return coff_a64_diag(diag, "direct COFF AArch64 readonly data patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (patches->len == patches->cap) {
|
||||
patches->cap = z_grow_capacity(patches->cap, patches->len + 1, 8);
|
||||
patches->items = z_checked_reallocarray(patches->items, patches->cap, sizeof(ZCoffImageDataPatch));
|
||||
}
|
||||
if (!patches->items) return coff_a64_diag(diag, "direct COFF AArch64 backend ran out of memory", value ? value->line : 1, value ? value->column : 1, "allocation failed");
|
||||
patches->items[patches->len++] = (ZCoffImageDataPatch){.patch_offset = patch_offset, .data_offset = data_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
static void coff_a64_data_patches_free(CoffA64DataPatches *patches) {
|
||||
if (!patches) return;
|
||||
free(patches->items);
|
||||
}
|
||||
|
||||
static bool coff_a64_record_branch_patch(CoffA64BranchPatches *patches, size_t patch_offset, const IrInstr *instr, ZDiag *diag) {
|
||||
if (!patches) return coff_a64_diag(diag, "direct COFF AArch64 branch patch requires an emit context", instr ? instr->line : 1, instr ? instr->column : 1, "missing context");
|
||||
if (patches->len == patches->cap) {
|
||||
patches->cap = z_grow_capacity(patches->cap, patches->len + 1, 8);
|
||||
patches->items = z_checked_reallocarray(patches->items, patches->cap, sizeof(size_t));
|
||||
}
|
||||
if (!patches->items) return coff_a64_diag(diag, "direct COFF AArch64 backend ran out of memory", instr ? instr->line : 1, instr ? instr->column : 1, "allocation failed");
|
||||
patches->items[patches->len++] = patch_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool coff_a64_record_call_patch(void *user, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag) {
|
||||
CoffA64EmitState *state = user;
|
||||
if (!state) return coff_a64_diag(diag, "direct COFF AArch64 call patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (state->calls.len == state->calls.cap) {
|
||||
state->calls.cap = z_grow_capacity(state->calls.cap, state->calls.len + 1, 8);
|
||||
state->calls.items = z_checked_reallocarray(state->calls.items, state->calls.cap, sizeof(CoffA64CallPatch));
|
||||
}
|
||||
if (!state->calls.items) return coff_a64_diag(diag, "direct COFF AArch64 backend ran out of memory", value ? value->line : 1, value ? value->column : 1, "allocation failed");
|
||||
state->calls.items[state->calls.len++] = (CoffA64CallPatch){
|
||||
.patch_offset = patch_offset,
|
||||
.callee_index = callee_index,
|
||||
.external_index = value ? value->external_index : 0,
|
||||
.external_call = value && value->external_call
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool coff_a64_runtime_helper_valid(ZAArch64DirectRuntimeHelper helper) {
|
||||
return helper >= 0 && helper < A64_DIRECT_RUNTIME_HELPER_COUNT;
|
||||
}
|
||||
|
||||
static const char *coff_a64_runtime_helper_symbol(ZAArch64DirectRuntimeHelper helper) {
|
||||
switch (helper) {
|
||||
case A64_DIRECT_RUNTIME_JSON_PARSE_BYTES: return "zero_json_parse_bytes";
|
||||
case A64_DIRECT_RUNTIME_JSON_DIAGNOSTIC: return "zero_json_diagnostic";
|
||||
case A64_DIRECT_RUNTIME_JSON_FIELD: return "zero_json_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_LOOKUP_SCALAR: return "zero_json_lookup_scalar";
|
||||
case A64_DIRECT_RUNTIME_JSON_STRING_DECODE: return "zero_json_string_decode";
|
||||
case A64_DIRECT_RUNTIME_JSON_STRING_FIELD: return "zero_json_string_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_STRING: return "zero_json_write_string";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_RAW: return "zero_json_write_field_raw";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_STRING: return "zero_json_write_field_string";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_U32: return "zero_json_write_field_u32";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_BOOL: return "zero_json_write_field_bool";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_STRING: return "zero_json_write_object1_string";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_U32: return "zero_json_write_object1_u32";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_BOOL: return "zero_json_write_object1_bool";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_FIELDS: return "zero_json_write_object2_fields";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_STRING_FIELD: return "zero_json_write_object2_string_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_U32_FIELD: return "zero_json_write_object2_u32_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_BOOL_FIELD: return "zero_json_write_object2_bool_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_STRINGS: return "zero_json_write_array2_strings";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_U32: return "zero_json_write_array2_u32";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_BOOLS: return "zero_json_write_array2_bools";
|
||||
case A64_DIRECT_RUNTIME_STR_BUFFER_OP: return "zero_str_buffer_op";
|
||||
case A64_DIRECT_RUNTIME_STR_CONCAT: return "zero_str_concat";
|
||||
case A64_DIRECT_RUNTIME_STR_REPEAT: return "zero_str_repeat";
|
||||
case A64_DIRECT_RUNTIME_STR_TRIM_OP: return "zero_str_trim_op";
|
||||
case A64_DIRECT_RUNTIME_STR_PAIR_OP: return "zero_str_pair_op";
|
||||
case A64_DIRECT_RUNTIME_STR_COUNT_BYTE: return "zero_str_count_byte";
|
||||
case A64_DIRECT_RUNTIME_STR_WORD_COUNT_ASCII: return "zero_str_word_count_ascii";
|
||||
case A64_DIRECT_RUNTIME_CRYPTO_DIGEST: return "zero_crypto_digest";
|
||||
case A64_DIRECT_RUNTIME_CRYPTO_HMAC_SHA256: return "zero_crypto_hmac_sha256";
|
||||
case A64_DIRECT_RUNTIME_CRYPTO_HMAC_SHA256_HEX: return "zero_crypto_hmac_sha256_hex";
|
||||
case A64_DIRECT_RUNTIME_ASCII_OP: return "zero_ascii_op";
|
||||
case A64_DIRECT_RUNTIME_TEXT_OP: return "zero_text_op";
|
||||
case A64_DIRECT_RUNTIME_PARSE_OP: return "zero_parse_op";
|
||||
case A64_DIRECT_RUNTIME_PARSE_USIZE: return "zero_parse_usize";
|
||||
case A64_DIRECT_RUNTIME_PARSE_I32: return "zero_parse_i32";
|
||||
case A64_DIRECT_RUNTIME_PARSE_U32: return "zero_parse_u32";
|
||||
case A64_DIRECT_RUNTIME_FMT_BOOL: return "zero_fmt_bool";
|
||||
case A64_DIRECT_RUNTIME_FMT_HEX_U32: return "zero_fmt_hex_lower_u32";
|
||||
case A64_DIRECT_RUNTIME_FMT_I32: return "zero_fmt_i32";
|
||||
case A64_DIRECT_RUNTIME_FMT_U32: return "zero_fmt_u32";
|
||||
case A64_DIRECT_RUNTIME_FMT_USIZE: return "zero_fmt_usize";
|
||||
case A64_DIRECT_RUNTIME_TIME_OP: return "zero_time_op";
|
||||
case A64_DIRECT_RUNTIME_TERM_OP: return "zero_term_op";
|
||||
case A64_DIRECT_RUNTIME_TERM_READ_INPUT: return "zero_term_read_input";
|
||||
case A64_DIRECT_RUNTIME_MATH_OP: return "zero_math_op";
|
||||
case A64_DIRECT_RUNTIME_MATH_USIZE_OP: return "zero_math_usize_op";
|
||||
case A64_DIRECT_RUNTIME_SEARCH_OP: return "zero_search_op";
|
||||
case A64_DIRECT_RUNTIME_SORT_OP: return "zero_sort_op";
|
||||
case A64_DIRECT_RUNTIME_SORT_IS_SORTED_OP: return "zero_sort_is_sorted_op";
|
||||
case A64_DIRECT_RUNTIME_HTTP_REQUEST_METHOD_NAME: return "zero_http_request_method_name";
|
||||
case A64_DIRECT_RUNTIME_HTTP_REQUEST_PATH: return "zero_http_request_path";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_INHERIT: return "zero_proc_spawn_inherit";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_INHERIT_ARGS: return "zero_proc_spawn_inherit_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE: return "zero_proc_capture";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE_ARGS: return "zero_proc_capture_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE_FILES: return "zero_proc_capture_files";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE_FILES_ARGS: return "zero_proc_capture_files_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD: return "zero_proc_spawn_child";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_IN: return "zero_proc_spawn_child_in";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_IN_ENV: return "zero_proc_spawn_child_in_env";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_ARGS: return "zero_proc_spawn_child_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_CHILD_OP: return "zero_proc_child_op";
|
||||
case A64_DIRECT_RUNTIME_PROC_CHILD_IO: return "zero_proc_child_io";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN: return "zero_pty_spawn";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN_IN: return "zero_pty_spawn_in";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN_IN_ENV: return "zero_pty_spawn_in_env";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN_ARGS: return "zero_pty_spawn_args";
|
||||
case A64_DIRECT_RUNTIME_PTY_RESIZE: return "zero_pty_resize";
|
||||
case A64_DIRECT_RUNTIME_HELPER_COUNT: break;
|
||||
}
|
||||
return "zero_runtime_helper";
|
||||
}
|
||||
|
||||
static bool coff_a64_record_runtime_patch(void *user, size_t patch_offset, ZAArch64DirectRuntimeHelper helper, const IrValue *value, ZDiag *diag) {
|
||||
CoffA64EmitState *state = user;
|
||||
if (!state || !coff_a64_runtime_helper_valid(helper)) return coff_a64_diag(diag, "direct COFF AArch64 runtime patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (!state->allow_runtime_patches) return coff_a64_diag(diag, "direct COFF AArch64 executable runtime helpers require object emission and an explicit runtime link step", value ? value->line : 1, value ? value->column : 1, "use --emit obj and link zero_runtime.c");
|
||||
CoffA64RuntimePatches *patches = &state->runtime[helper];
|
||||
if (patches->len == patches->cap) {
|
||||
patches->cap = z_grow_capacity(patches->cap, patches->len + 1, 4);
|
||||
patches->items = z_checked_reallocarray(patches->items, patches->cap, sizeof(CoffA64RuntimePatch));
|
||||
}
|
||||
if (!patches->items) return coff_a64_diag(diag, "direct COFF AArch64 backend ran out of memory", value ? value->line : 1, value ? value->column : 1, "allocation failed");
|
||||
patches->items[patches->len++] = (CoffA64RuntimePatch){.patch_offset = patch_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
static void coff_a64_emit_state_free(CoffA64EmitState *state) {
|
||||
if (!state) return;
|
||||
coff_a64_data_patches_free(&state->data);
|
||||
free(state->calls.items);
|
||||
for (unsigned i = 0; i < A64_DIRECT_RUNTIME_HELPER_COUNT; i++) free(state->runtime[i].items);
|
||||
free(state->world_write.items);
|
||||
}
|
||||
|
||||
static void coff_a64_patch_call_branches(ZBuf *text, const CoffA64CallPatches *patches, const size_t *offsets, size_t function_count) {
|
||||
for (size_t i = 0; patches && i < patches->len; i++) {
|
||||
const CoffA64CallPatch *patch = &patches->items[i];
|
||||
if (patch->external_call) continue;
|
||||
if (patch->callee_index < function_count) z_aarch64_patch_branch26(text, patch->patch_offset, offsets[patch->callee_index]);
|
||||
}
|
||||
}
|
||||
|
||||
static void coff_a64_append_external_call_relocations(ZBuf *relocs, const CoffA64CallPatches *patches, uint32_t external_symbol_base) {
|
||||
for (size_t i = 0; patches && i < patches->len; i++) {
|
||||
const CoffA64CallPatch *patch = &patches->items[i];
|
||||
if (!patch->external_call) continue;
|
||||
z_coff_append_reloc(relocs, (uint32_t)patch->patch_offset, external_symbol_base + patch->external_index, Z_COFF_RELOC_ARM64_BRANCH26);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t coff_a64_external_call_relocation_count(const CoffA64CallPatches *patches) {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; patches && i < patches->len; i++) {
|
||||
if (patches->items[i].external_call) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void coff_a64_append_runtime_relocations(ZBuf *relocs, const CoffA64EmitState *state, uint32_t runtime_symbol_base) {
|
||||
for (unsigned helper = 0; state && helper < A64_DIRECT_RUNTIME_HELPER_COUNT; helper++) {
|
||||
const CoffA64RuntimePatches *patches = &state->runtime[helper];
|
||||
for (size_t i = 0; i < patches->len; i++) {
|
||||
z_coff_append_reloc(relocs, (uint32_t)patches->items[i].patch_offset, runtime_symbol_base + helper, Z_COFF_RELOC_ARM64_BRANCH26);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t coff_a64_runtime_relocation_count(const CoffA64EmitState *state) {
|
||||
size_t count = 0;
|
||||
for (unsigned helper = 0; state && helper < A64_DIRECT_RUNTIME_HELPER_COUNT; helper++) count += state->runtime[helper].len;
|
||||
return count;
|
||||
}
|
||||
|
||||
static void coff_a64_append_object_rodata_relocations(ZBuf *text, ZBuf *relocs, const CoffA64DataPatches *patches, unsigned rodata_base_offset) {
|
||||
for (size_t i = 0; patches && i < patches->len; i++) {
|
||||
const ZCoffImageDataPatch *patch = &patches->items[i];
|
||||
z_coff_patch_u64(text, patch->patch_offset, patch->data_offset - rodata_base_offset);
|
||||
z_coff_append_reloc(relocs, (uint32_t)patch->patch_offset, 1u, Z_COFF_RELOC_ARM64_ADDR64);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t coff_a64_emit_import_call(ZBuf *text, ZCoffImportPatch *patches, size_t *patch_len, unsigned import_index) {
|
||||
while (((text->len + 8) % 8) != 0) z_aarch64_emit_nop(text);
|
||||
z_aarch64_emit_ldr_x_literal8(text, 16);
|
||||
z_aarch64_emit_b_offset_words(text, 3);
|
||||
size_t patch = text->len;
|
||||
z_aarch64_append_u64(text, 0);
|
||||
z_aarch64_emit_load_x_imm(text, 16, 16, 0);
|
||||
z_aarch64_emit_blr_x(text, 16);
|
||||
if (patches && patch_len && *patch_len < 8) {
|
||||
patches[*patch_len] = (ZCoffImportPatch){.patch_offset = patch, .import_index = import_index};
|
||||
*patch_len += 1;
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
static size_t coff_a64_emit_exe_start_stub(ZBuf *text, ZCoffImportPatch *import_patches, size_t *import_patch_len) {
|
||||
z_aarch64_emit_sub_sp_imm(text, 16);
|
||||
size_t main_patch = z_aarch64_emit_bl_placeholder(text);
|
||||
coff_a64_emit_import_call(text, import_patches, import_patch_len, Z_COFF_IMPORT_EXIT_PROCESS);
|
||||
z_aarch64_emit_brk(text);
|
||||
return main_patch;
|
||||
}
|
||||
|
||||
static bool coff_a64_emit_world_write_call(ZBuf *text, const IrInstr *instr, ZAArch64DirectContext *ctx, ZDiag *diag) {
|
||||
CoffA64EmitState *state = ctx ? ctx->patch_user : NULL;
|
||||
size_t patch = z_aarch64_emit_bl_placeholder(text);
|
||||
if (!coff_a64_record_branch_patch(state ? &state->world_write : NULL, patch, instr, diag)) return false;
|
||||
size_t ok_patch = z_aarch64_emit_cbz_w_placeholder(text, 0);
|
||||
z_aarch64_emit_brk(text);
|
||||
z_aarch64_patch_cond19(text, ok_patch, text->len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t coff_a64_emit_exe_world_write(ZBuf *text, ZCoffImportPatch *import_patches, size_t *import_patch_len) {
|
||||
size_t offset = text->len;
|
||||
z_aarch64_emit_sub_sp_imm(text, 48);
|
||||
z_aarch64_emit_store_x_sp(text, 1, 0);
|
||||
z_aarch64_emit_store_x_sp(text, 2, 8);
|
||||
z_aarch64_emit_store_w_sp(text, 31, 16);
|
||||
z_aarch64_emit_store_x_sp(text, 30, 40);
|
||||
z_aarch64_emit_movz_w(text, 8, 2);
|
||||
z_aarch64_emit_cmp_w(text, 0, 8);
|
||||
z_aarch64_emit_movz_w(text, 0, 0xfffffff5u);
|
||||
size_t stdout_patch = z_aarch64_emit_b_cond_placeholder(text, 1);
|
||||
z_aarch64_emit_movz_w(text, 0, 0xfffffff4u);
|
||||
z_aarch64_patch_cond19(text, stdout_patch, text->len);
|
||||
coff_a64_emit_import_call(text, import_patches, import_patch_len, Z_COFF_IMPORT_GET_STD_HANDLE);
|
||||
z_aarch64_emit_load_x_sp(text, 1, 0);
|
||||
z_aarch64_emit_load_w_sp(text, 2, 8);
|
||||
z_aarch64_emit_add_x_sp_imm(text, 3, 16);
|
||||
z_aarch64_emit_movz_x(text, 4, 0);
|
||||
coff_a64_emit_import_call(text, import_patches, import_patch_len, Z_COFF_IMPORT_WRITE_FILE);
|
||||
z_aarch64_emit_cmp_w(text, 0, 31);
|
||||
size_t ok_patch = z_aarch64_emit_b_cond_placeholder(text, 1);
|
||||
z_aarch64_emit_brk(text);
|
||||
z_aarch64_patch_cond19(text, ok_patch, text->len);
|
||||
z_aarch64_emit_movz_w(text, 0, 0);
|
||||
z_aarch64_emit_load_x_sp(text, 30, 40);
|
||||
z_aarch64_emit_add_sp_imm(text, 48);
|
||||
z_aarch64_emit_ret(text);
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool z_emit_coff_aarch64_object_from_ir(const IrProgram *program, ZBuf *out, ZDiag *diag) {
|
||||
if (!program || !out) return coff_a64_diag(diag, "direct COFF AArch64 backend received no program", 1, 1, "missing MIR");
|
||||
if (!program->mir_valid) {
|
||||
bool ok = coff_a64_diag(diag, program->mir_message[0] ? program->mir_message : "direct backend lowering failed", program->mir_line, program->mir_column, program->mir_actual);
|
||||
z_diag_set_backend_blocker(diag, &program->backend_blocker);
|
||||
return ok;
|
||||
}
|
||||
if (program->function_len == 0) return coff_a64_diag(diag, "direct COFF AArch64 object backend requires at least one exported function", 1, 1, "empty program");
|
||||
|
||||
ZBuf text;
|
||||
ZBuf rodata;
|
||||
ZBuf relocs;
|
||||
zbuf_init(&text);
|
||||
zbuf_init(&rodata);
|
||||
zbuf_init(&relocs);
|
||||
|
||||
bool has_rodata = program->readonly_data_bytes > 0 || program->data_segment_len > 0;
|
||||
unsigned rodata_base_offset = z_aarch64_direct_rodata_base_offset(program);
|
||||
if (has_rodata) z_aarch64_direct_append_rodata(&rodata, program, rodata_base_offset);
|
||||
|
||||
size_t *offsets = z_checked_calloc(program->function_len, sizeof(size_t));
|
||||
ZCoffSymbol *symbols = z_checked_calloc(program->function_len + program->external_function_len + A64_DIRECT_RUNTIME_HELPER_COUNT, sizeof(ZCoffSymbol));
|
||||
if (!offsets || !symbols) {
|
||||
free(offsets);
|
||||
free(symbols);
|
||||
zbuf_free(&relocs);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&text);
|
||||
return coff_a64_diag(diag, "out of memory while emitting COFF AArch64 object", 1, 1, "allocation failed");
|
||||
}
|
||||
|
||||
CoffA64EmitState state = {.allow_runtime_patches = true};
|
||||
ZAArch64DirectContext ctx = {
|
||||
.program = program,
|
||||
.function_offsets = offsets,
|
||||
.function_count = program->function_len,
|
||||
.rodata_base_offset = rodata_base_offset,
|
||||
.patch_user = &state,
|
||||
.record_data_patch = coff_a64_record_data_patch,
|
||||
.record_call_patch = coff_a64_record_call_patch,
|
||||
.record_runtime_patch = coff_a64_record_runtime_patch
|
||||
};
|
||||
|
||||
bool has_export = false;
|
||||
size_t symbol_len = 0;
|
||||
for (size_t i = 0; i < program->function_len; i++) {
|
||||
if (program->functions[i].is_exported) has_export = true;
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
offsets[i] = text.len;
|
||||
if (!z_aarch64_direct_emit_function_text(&text, &program->functions[i], &ctx, diag)) {
|
||||
coff_a64_emit_state_free(&state);
|
||||
free(offsets);
|
||||
free(symbols);
|
||||
zbuf_free(&relocs);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&text);
|
||||
return false;
|
||||
}
|
||||
if (program->functions[i].is_exported) {
|
||||
symbols[symbol_len++] = (ZCoffSymbol){
|
||||
.name = program->functions[i].name ? program->functions[i].name : "zero_fn",
|
||||
.value = (uint32_t)offsets[i],
|
||||
.section_number = 1,
|
||||
.type = 0x20,
|
||||
.storage_class = Z_COFF_SYMBOL_EXTERNAL
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!has_export) {
|
||||
coff_a64_emit_state_free(&state);
|
||||
free(offsets);
|
||||
free(symbols);
|
||||
zbuf_free(&relocs);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&text);
|
||||
return coff_a64_diag(diag, "direct COFF AArch64 object backend requires at least one exported function", 1, 1, "no exported function");
|
||||
}
|
||||
coff_a64_patch_call_branches(&text, &state.calls, offsets, program->function_len);
|
||||
if (has_rodata) coff_a64_append_object_rodata_relocations(&text, &relocs, &state.data, rodata_base_offset);
|
||||
uint32_t runtime_symbol_base = (has_rodata ? 2u : 1u) + (uint32_t)symbol_len;
|
||||
for (unsigned helper = 0; helper < A64_DIRECT_RUNTIME_HELPER_COUNT; helper++) {
|
||||
symbols[symbol_len++] = (ZCoffSymbol){
|
||||
.name = coff_a64_runtime_helper_symbol((ZAArch64DirectRuntimeHelper)helper),
|
||||
.section_number = 0,
|
||||
.type = 0x20,
|
||||
.storage_class = Z_COFF_SYMBOL_EXTERNAL
|
||||
};
|
||||
}
|
||||
coff_a64_append_runtime_relocations(&relocs, &state, runtime_symbol_base);
|
||||
uint32_t external_symbol_base = runtime_symbol_base + A64_DIRECT_RUNTIME_HELPER_COUNT;
|
||||
for (size_t i = 0; i < program->external_function_len; i++) {
|
||||
symbols[symbol_len++] = (ZCoffSymbol){
|
||||
.name = program->external_functions[i].symbol ? program->external_functions[i].symbol : "zero_external",
|
||||
.section_number = 0,
|
||||
.type = 0x20,
|
||||
.storage_class = Z_COFF_SYMBOL_EXTERNAL
|
||||
};
|
||||
}
|
||||
coff_a64_append_external_call_relocations(&relocs, &state.calls, external_symbol_base);
|
||||
|
||||
ZCoffObjectImage image = {
|
||||
.machine = Z_COFF_MACHINE_ARM64,
|
||||
.text = &text,
|
||||
.rodata = has_rodata ? &rodata : NULL,
|
||||
.text_relocs = &relocs,
|
||||
.text_reloc_count = (uint16_t)(state.data.len + coff_a64_runtime_relocation_count(&state) + coff_a64_external_call_relocation_count(&state.calls)),
|
||||
.symbols = symbols,
|
||||
.symbol_len = symbol_len
|
||||
};
|
||||
z_coff_write_object(out, &image);
|
||||
|
||||
coff_a64_emit_state_free(&state);
|
||||
free(offsets);
|
||||
free(symbols);
|
||||
zbuf_free(&relocs);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&text);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_emit_coff_aarch64_exe_from_ir(const IrProgram *program, ZBuf *out, ZDiag *diag) {
|
||||
if (!program || !out) return coff_a64_diag(diag, "direct COFF AArch64 executable backend received no program", 1, 1, "missing MIR");
|
||||
if (!program->mir_valid) {
|
||||
bool ok = coff_a64_diag(diag, program->mir_message[0] ? program->mir_message : "direct backend lowering failed", program->mir_line, program->mir_column, program->mir_actual);
|
||||
z_diag_set_backend_blocker(diag, &program->backend_blocker);
|
||||
return ok;
|
||||
}
|
||||
if (!z_direct_exe_reject_c_import_calls(program, diag, "COFF AArch64")) return false;
|
||||
|
||||
unsigned main_index = 0;
|
||||
if (!z_aarch64_direct_find_main(program, &main_index, diag)) return false;
|
||||
|
||||
ZBuf text;
|
||||
ZBuf rdata;
|
||||
zbuf_init(&text);
|
||||
zbuf_init(&rdata);
|
||||
|
||||
unsigned rodata_base_offset = z_aarch64_direct_rodata_base_offset(program);
|
||||
ZDirectTrapMessages trap_messages = {0};
|
||||
z_aarch64_direct_append_rodata(&rdata, program, rodata_base_offset);
|
||||
z_aarch64_direct_append_trap_messages(&rdata, rodata_base_offset, &trap_messages);
|
||||
|
||||
size_t *offsets = z_checked_calloc(program->function_len, sizeof(size_t));
|
||||
if (!offsets) {
|
||||
zbuf_free(&rdata);
|
||||
zbuf_free(&text);
|
||||
return coff_a64_diag(diag, "out of memory while emitting COFF AArch64 executable", 1, 1, "allocation failed");
|
||||
}
|
||||
|
||||
CoffA64EmitState state = {0};
|
||||
ZAArch64DirectContext ctx = {
|
||||
.program = program,
|
||||
.function_offsets = offsets,
|
||||
.function_count = program->function_len,
|
||||
.rodata_base_offset = rodata_base_offset,
|
||||
.patch_user = &state,
|
||||
.record_data_patch = coff_a64_record_data_patch,
|
||||
.record_call_patch = coff_a64_record_call_patch,
|
||||
.record_runtime_patch = coff_a64_record_runtime_patch,
|
||||
.emit_world_write = coff_a64_emit_world_write_call,
|
||||
.trap_messages = trap_messages
|
||||
};
|
||||
|
||||
ZCoffImportPatch import_patches[8];
|
||||
size_t import_patch_len = 0;
|
||||
size_t start_main_patch = coff_a64_emit_exe_start_stub(&text, import_patches, &import_patch_len);
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
for (size_t i = 0; i < program->function_len; i++) {
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
offsets[i] = text.len;
|
||||
if (!z_aarch64_direct_emit_function_text(&text, &program->functions[i], &ctx, diag)) {
|
||||
z_direct_trap_branches_free(ctx.trap_branches, Z_DIRECT_TRAP_KIND_COUNT);
|
||||
coff_a64_emit_state_free(&state);
|
||||
free(offsets);
|
||||
zbuf_free(&rdata);
|
||||
zbuf_free(&text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!z_aarch64_direct_emit_trap_stubs(&text, &ctx, diag)) {
|
||||
coff_a64_emit_state_free(&state);
|
||||
free(offsets);
|
||||
zbuf_free(&rdata);
|
||||
zbuf_free(&text);
|
||||
return false;
|
||||
}
|
||||
z_aarch64_patch_branch26(&text, start_main_patch, offsets[main_index]);
|
||||
coff_a64_patch_call_branches(&text, &state.calls, offsets, program->function_len);
|
||||
size_t world_write_offset = 0;
|
||||
if (state.world_write.len > 0) {
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
world_write_offset = coff_a64_emit_exe_world_write(&text, import_patches, &import_patch_len);
|
||||
for (size_t i = 0; i < state.world_write.len; i++) {
|
||||
z_aarch64_patch_branch26(&text, state.world_write.items[i], world_write_offset);
|
||||
}
|
||||
}
|
||||
|
||||
ZCoffExecutableImage image = {
|
||||
.machine = Z_COFF_MACHINE_ARM64,
|
||||
.image_base = 0x140000000ull,
|
||||
.section_alignment = 0x1000,
|
||||
.file_alignment = 0x200,
|
||||
.text = &text,
|
||||
.rdata = &rdata,
|
||||
.rodata_base_offset = rodata_base_offset,
|
||||
.rodata_patches = state.data.items,
|
||||
.rodata_patch_len = state.data.len,
|
||||
.import_patches = import_patches,
|
||||
.import_patch_len = import_patch_len
|
||||
};
|
||||
z_coff_write_pe64_executable(out, &image);
|
||||
|
||||
coff_a64_emit_state_free(&state);
|
||||
free(offsets);
|
||||
zbuf_free(&rdata);
|
||||
zbuf_free(&text);
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,511 @@
|
||||
#include "zero.h"
|
||||
#include "aarch64_direct.h"
|
||||
#include "aarch64_emit.h"
|
||||
#include "direct_emit.h"
|
||||
#include "elf_format.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum {
|
||||
R_AARCH64_ABS64 = 257u,
|
||||
R_AARCH64_CALL26 = 283u
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned data_offset;
|
||||
} A64ElfDataPatch;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned callee_index;
|
||||
unsigned external_index;
|
||||
bool external_call;
|
||||
} A64ElfCallPatch;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
} A64ElfRuntimePatch;
|
||||
|
||||
typedef struct {
|
||||
A64ElfRuntimePatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} A64ElfRuntimePatchList;
|
||||
|
||||
typedef struct {
|
||||
A64ElfDataPatch *data_patches;
|
||||
size_t data_patch_len;
|
||||
size_t data_patch_cap;
|
||||
A64ElfCallPatch *call_patches;
|
||||
size_t call_patch_len;
|
||||
size_t call_patch_cap;
|
||||
A64ElfRuntimePatchList runtime_patches[A64_DIRECT_RUNTIME_HELPER_COUNT];
|
||||
bool allow_runtime_patches;
|
||||
} A64ElfPatchContext;
|
||||
|
||||
static bool a64_diag(ZDiag *diag, const char *message, int line, int column, const char *actual) {
|
||||
if (diag) {
|
||||
diag->code = 4004;
|
||||
diag->line = line > 0 ? line : 1;
|
||||
diag->column = column > 0 ? column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message);
|
||||
snprintf(diag->expected, sizeof(diag->expected), "direct AArch64 ELF backend subset");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "unsupported construct");
|
||||
snprintf(diag->help, sizeof(diag->help), "choose a supported direct target or restrict this program to AArch64 ELF supported direct-backend constructs");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool a64_elf_record_data_patch(void *user, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag) {
|
||||
A64ElfPatchContext *ctx = user;
|
||||
if (!ctx) return a64_diag(diag, "direct AArch64 ELF readonly data patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (ctx->data_patch_len + 1 > ctx->data_patch_cap) {
|
||||
ctx->data_patch_cap = z_grow_capacity(ctx->data_patch_cap, ctx->data_patch_len + 1, 8);
|
||||
ctx->data_patches = z_checked_reallocarray(ctx->data_patches, ctx->data_patch_cap, sizeof(A64ElfDataPatch));
|
||||
}
|
||||
if (!ctx->data_patches) return a64_diag(diag, "direct AArch64 ELF backend ran out of memory", value ? value->line : 1, value ? value->column : 1, "allocation failed");
|
||||
ctx->data_patches[ctx->data_patch_len++] = (A64ElfDataPatch){.patch_offset = patch_offset, .data_offset = data_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool a64_elf_record_call_patch(void *user, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag) {
|
||||
A64ElfPatchContext *ctx = user;
|
||||
if (!ctx) return a64_diag(diag, "direct AArch64 ELF call patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (ctx->call_patch_len + 1 > ctx->call_patch_cap) {
|
||||
ctx->call_patch_cap = z_grow_capacity(ctx->call_patch_cap, ctx->call_patch_len + 1, 8);
|
||||
ctx->call_patches = z_checked_reallocarray(ctx->call_patches, ctx->call_patch_cap, sizeof(A64ElfCallPatch));
|
||||
}
|
||||
if (!ctx->call_patches) return a64_diag(diag, "direct AArch64 ELF backend ran out of memory", value ? value->line : 1, value ? value->column : 1, "allocation failed");
|
||||
ctx->call_patches[ctx->call_patch_len++] = (A64ElfCallPatch){
|
||||
.patch_offset = patch_offset,
|
||||
.callee_index = callee_index,
|
||||
.external_index = value ? value->external_index : 0,
|
||||
.external_call = value && value->external_call
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *a64_runtime_helper_symbol(ZAArch64DirectRuntimeHelper helper) {
|
||||
switch (helper) {
|
||||
case A64_DIRECT_RUNTIME_JSON_PARSE_BYTES: return "zero_json_parse_bytes";
|
||||
case A64_DIRECT_RUNTIME_JSON_DIAGNOSTIC: return "zero_json_diagnostic";
|
||||
case A64_DIRECT_RUNTIME_JSON_FIELD: return "zero_json_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_LOOKUP_SCALAR: return "zero_json_lookup_scalar";
|
||||
case A64_DIRECT_RUNTIME_JSON_STRING_DECODE: return "zero_json_string_decode";
|
||||
case A64_DIRECT_RUNTIME_JSON_STRING_FIELD: return "zero_json_string_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_STRING: return "zero_json_write_string";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_RAW: return "zero_json_write_field_raw";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_STRING: return "zero_json_write_field_string";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_U32: return "zero_json_write_field_u32";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_FIELD_BOOL: return "zero_json_write_field_bool";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_STRING: return "zero_json_write_object1_string";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_U32: return "zero_json_write_object1_u32";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT1_BOOL: return "zero_json_write_object1_bool";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_FIELDS: return "zero_json_write_object2_fields";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_STRING_FIELD: return "zero_json_write_object2_string_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_U32_FIELD: return "zero_json_write_object2_u32_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_OBJECT2_BOOL_FIELD: return "zero_json_write_object2_bool_field";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_STRINGS: return "zero_json_write_array2_strings";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_U32: return "zero_json_write_array2_u32";
|
||||
case A64_DIRECT_RUNTIME_JSON_WRITE_ARRAY2_BOOLS: return "zero_json_write_array2_bools";
|
||||
case A64_DIRECT_RUNTIME_STR_BUFFER_OP: return "zero_str_buffer_op";
|
||||
case A64_DIRECT_RUNTIME_STR_CONCAT: return "zero_str_concat";
|
||||
case A64_DIRECT_RUNTIME_STR_REPEAT: return "zero_str_repeat";
|
||||
case A64_DIRECT_RUNTIME_STR_TRIM_OP: return "zero_str_trim_op";
|
||||
case A64_DIRECT_RUNTIME_STR_PAIR_OP: return "zero_str_pair_op";
|
||||
case A64_DIRECT_RUNTIME_STR_COUNT_BYTE: return "zero_str_count_byte";
|
||||
case A64_DIRECT_RUNTIME_STR_WORD_COUNT_ASCII: return "zero_str_word_count_ascii";
|
||||
case A64_DIRECT_RUNTIME_CRYPTO_DIGEST: return "zero_crypto_digest";
|
||||
case A64_DIRECT_RUNTIME_CRYPTO_HMAC_SHA256: return "zero_crypto_hmac_sha256";
|
||||
case A64_DIRECT_RUNTIME_CRYPTO_HMAC_SHA256_HEX: return "zero_crypto_hmac_sha256_hex";
|
||||
case A64_DIRECT_RUNTIME_ASCII_OP: return "zero_ascii_op";
|
||||
case A64_DIRECT_RUNTIME_TEXT_OP: return "zero_text_op";
|
||||
case A64_DIRECT_RUNTIME_PARSE_OP: return "zero_parse_op";
|
||||
case A64_DIRECT_RUNTIME_PARSE_USIZE: return "zero_parse_usize";
|
||||
case A64_DIRECT_RUNTIME_PARSE_I32: return "zero_parse_i32";
|
||||
case A64_DIRECT_RUNTIME_PARSE_U32: return "zero_parse_u32";
|
||||
case A64_DIRECT_RUNTIME_FMT_BOOL: return "zero_fmt_bool";
|
||||
case A64_DIRECT_RUNTIME_FMT_HEX_U32: return "zero_fmt_hex_lower_u32";
|
||||
case A64_DIRECT_RUNTIME_FMT_I32: return "zero_fmt_i32";
|
||||
case A64_DIRECT_RUNTIME_FMT_U32: return "zero_fmt_u32";
|
||||
case A64_DIRECT_RUNTIME_FMT_USIZE: return "zero_fmt_usize";
|
||||
case A64_DIRECT_RUNTIME_TIME_OP: return "zero_time_op";
|
||||
case A64_DIRECT_RUNTIME_TERM_OP: return "zero_term_op";
|
||||
case A64_DIRECT_RUNTIME_TERM_READ_INPUT: return "zero_term_read_input";
|
||||
case A64_DIRECT_RUNTIME_MATH_OP: return "zero_math_op";
|
||||
case A64_DIRECT_RUNTIME_MATH_USIZE_OP: return "zero_math_usize_op";
|
||||
case A64_DIRECT_RUNTIME_SEARCH_OP: return "zero_search_op";
|
||||
case A64_DIRECT_RUNTIME_SORT_OP: return "zero_sort_op";
|
||||
case A64_DIRECT_RUNTIME_SORT_IS_SORTED_OP: return "zero_sort_is_sorted_op";
|
||||
case A64_DIRECT_RUNTIME_HTTP_REQUEST_METHOD_NAME: return "zero_http_request_method_name";
|
||||
case A64_DIRECT_RUNTIME_HTTP_REQUEST_PATH: return "zero_http_request_path";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_INHERIT: return "zero_proc_spawn_inherit";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_INHERIT_ARGS: return "zero_proc_spawn_inherit_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE: return "zero_proc_capture";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE_ARGS: return "zero_proc_capture_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE_FILES: return "zero_proc_capture_files";
|
||||
case A64_DIRECT_RUNTIME_PROC_CAPTURE_FILES_ARGS: return "zero_proc_capture_files_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD: return "zero_proc_spawn_child";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_IN: return "zero_proc_spawn_child_in";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_IN_ENV: return "zero_proc_spawn_child_in_env";
|
||||
case A64_DIRECT_RUNTIME_PROC_SPAWN_CHILD_ARGS: return "zero_proc_spawn_child_args";
|
||||
case A64_DIRECT_RUNTIME_PROC_CHILD_OP: return "zero_proc_child_op";
|
||||
case A64_DIRECT_RUNTIME_PROC_CHILD_IO: return "zero_proc_child_io";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN: return "zero_pty_spawn";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN_IN: return "zero_pty_spawn_in";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN_IN_ENV: return "zero_pty_spawn_in_env";
|
||||
case A64_DIRECT_RUNTIME_PTY_SPAWN_ARGS: return "zero_pty_spawn_args";
|
||||
case A64_DIRECT_RUNTIME_PTY_RESIZE: return "zero_pty_resize";
|
||||
case A64_DIRECT_RUNTIME_HELPER_COUNT: break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool a64_runtime_helper_valid(ZAArch64DirectRuntimeHelper helper) {
|
||||
return helper >= 0 && helper < A64_DIRECT_RUNTIME_HELPER_COUNT;
|
||||
}
|
||||
|
||||
static bool a64_elf_record_runtime_patch(void *user, size_t patch_offset, ZAArch64DirectRuntimeHelper helper, const IrValue *value, ZDiag *diag) {
|
||||
A64ElfPatchContext *ctx = user;
|
||||
if (!ctx || !a64_runtime_helper_valid(helper)) return a64_diag(diag, "direct AArch64 ELF runtime patch requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (!ctx->allow_runtime_patches) return a64_diag(diag, "direct AArch64 ELF executable runtime helpers require object emission and an explicit runtime link step", value ? value->line : 1, value ? value->column : 1, "use --emit obj and link zero_runtime.c");
|
||||
A64ElfRuntimePatchList *list = &ctx->runtime_patches[helper];
|
||||
if (list->len + 1 > list->cap) {
|
||||
list->cap = z_grow_capacity(list->cap, list->len + 1, 4);
|
||||
list->items = z_checked_reallocarray(list->items, list->cap, sizeof(A64ElfRuntimePatch));
|
||||
}
|
||||
if (!list->items) return a64_diag(diag, "direct AArch64 ELF backend ran out of memory", value ? value->line : 1, value ? value->column : 1, "allocation failed");
|
||||
list->items[list->len++] = (A64ElfRuntimePatch){.patch_offset = patch_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
static void a64_elf_patch_context_free(A64ElfPatchContext *ctx) {
|
||||
if (!ctx) return;
|
||||
free(ctx->data_patches);
|
||||
free(ctx->call_patches);
|
||||
for (unsigned i = 0; i < A64_DIRECT_RUNTIME_HELPER_COUNT; i++) free(ctx->runtime_patches[i].items);
|
||||
}
|
||||
|
||||
static bool a64_elf_emit_world_write(ZBuf *text, const IrInstr *instr, ZAArch64DirectContext *ctx, ZDiag *diag) {
|
||||
(void)ctx;
|
||||
z_aarch64_emit_movz_x(text, 8, 64); // Linux SYS_write(fd=x0, buf=x1, len=x2)
|
||||
z_aarch64_emit_svc(text, 0);
|
||||
z_aarch64_emit_cmp_x(text, 0, 31);
|
||||
size_t ok_patch = z_aarch64_emit_b_cond_placeholder(text, 10);
|
||||
z_aarch64_emit_brk(text);
|
||||
z_aarch64_patch_cond19(text, ok_patch, text->len);
|
||||
(void)instr;
|
||||
(void)diag;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t z_elf_aarch64_stack_bytes_from_ir(const IrProgram *program) {
|
||||
return z_aarch64_direct_stack_bytes_from_ir(program);
|
||||
}
|
||||
|
||||
size_t z_elf_aarch64_max_frame_bytes_from_ir(const IrProgram *program) {
|
||||
return z_aarch64_direct_max_frame_bytes_from_ir(program);
|
||||
}
|
||||
|
||||
static void a64_patch_data_patches(ZBuf *text, const A64ElfPatchContext *patches, uint64_t rodata_addr, unsigned rodata_base_offset) {
|
||||
for (size_t i = 0; patches && i < patches->data_patch_len; i++) {
|
||||
const A64ElfDataPatch *patch = &patches->data_patches[i];
|
||||
uint64_t addr = rodata_addr + (patch->data_offset - rodata_base_offset);
|
||||
z_aarch64_patch_u64(text, patch->patch_offset, addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void a64_patch_call_patches(ZBuf *text, const A64ElfPatchContext *patches, const size_t *function_offsets, size_t function_count) {
|
||||
for (size_t i = 0; patches && i < patches->call_patch_len; i++) {
|
||||
const A64ElfCallPatch *patch = &patches->call_patches[i];
|
||||
if (patch->external_call) continue;
|
||||
if (patch->callee_index < function_count) z_aarch64_patch_branch26(text, patch->patch_offset, function_offsets[patch->callee_index]);
|
||||
}
|
||||
}
|
||||
|
||||
static void a64_append_data_relocations(ZBuf *rela_text, const A64ElfPatchContext *patches, unsigned rodata_base_offset, uint32_t rodata_symbol) {
|
||||
for (size_t i = 0; patches && i < patches->data_patch_len; i++) {
|
||||
const A64ElfDataPatch *patch = &patches->data_patches[i];
|
||||
z_elf_append_rela(rela_text, patch->patch_offset, rodata_symbol, R_AARCH64_ABS64, patch->data_offset - rodata_base_offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void a64_append_external_call_relocations(ZBuf *rela_text, const A64ElfPatchContext *patches, uint32_t external_symbol_base) {
|
||||
for (size_t i = 0; patches && i < patches->call_patch_len; i++) {
|
||||
const A64ElfCallPatch *patch = &patches->call_patches[i];
|
||||
if (!patch->external_call) continue;
|
||||
z_elf_append_rela(rela_text, patch->patch_offset, external_symbol_base + patch->external_index, R_AARCH64_CALL26, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void a64_append_runtime_relocations(ZBuf *rela_text, const A64ElfPatchContext *patches, ZAArch64DirectRuntimeHelper helper, uint32_t runtime_symbol) {
|
||||
if (!patches || !a64_runtime_helper_valid(helper)) return;
|
||||
const A64ElfRuntimePatchList *list = &patches->runtime_patches[helper];
|
||||
for (size_t i = 0; i < list->len; i++) {
|
||||
z_elf_append_rela(rela_text, list->items[i].patch_offset, runtime_symbol, R_AARCH64_CALL26, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t a64_append_exported_function_symbols(ZBuf *symtab, const IrProgram *ir, const size_t *function_offsets, const size_t *function_sizes, const uint32_t *symbol_names, bool has_rodata) {
|
||||
uint32_t external_symbol_base = has_rodata ? 2u : 1u;
|
||||
for (size_t i = 0; i < ir->function_len; i++) {
|
||||
if (function_sizes[i] > 0 && ir->functions[i].is_exported) {
|
||||
z_elf_append_symbol(symtab, symbol_names[i], 0x12, 1, function_offsets[i], function_sizes[i]);
|
||||
external_symbol_base++;
|
||||
}
|
||||
}
|
||||
return external_symbol_base;
|
||||
}
|
||||
|
||||
static uint32_t a64_append_runtime_symbols(ZBuf *strtab, ZBuf *symtab, ZBuf *rela_text, const A64ElfPatchContext *patches, uint32_t next_symbol) {
|
||||
for (unsigned helper = 0; helper < A64_DIRECT_RUNTIME_HELPER_COUNT; helper++) {
|
||||
ZAArch64DirectRuntimeHelper runtime_helper = (ZAArch64DirectRuntimeHelper)helper;
|
||||
const A64ElfRuntimePatchList *list = patches ? &patches->runtime_patches[helper] : NULL;
|
||||
if (!list || list->len == 0) continue;
|
||||
uint32_t name = (uint32_t)strtab->len;
|
||||
zbuf_append(strtab, a64_runtime_helper_symbol(runtime_helper));
|
||||
z_elf_append_u8(strtab, 0);
|
||||
z_elf_append_symbol(symtab, name, 0x12, 0, 0, 0);
|
||||
a64_append_runtime_relocations(rela_text, patches, runtime_helper, next_symbol);
|
||||
next_symbol++;
|
||||
}
|
||||
return next_symbol;
|
||||
}
|
||||
|
||||
static void a64_append_external_symbols(ZBuf *strtab, ZBuf *symtab, const IrProgram *ir, uint32_t *external_names) {
|
||||
for (size_t i = 0; i < ir->external_function_len; i++) {
|
||||
external_names[i] = (uint32_t)strtab->len;
|
||||
zbuf_append(strtab, ir->external_functions[i].symbol ? ir->external_functions[i].symbol : "zero_external");
|
||||
z_elf_append_u8(strtab, 0);
|
||||
z_elf_append_symbol(symtab, external_names[i], 0x12, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void a64_write_object_image(ZBuf *out, ZBuf *text, ZBuf *rodata, ZBuf *rela_text, ZBuf *symtab, ZBuf *strtab, bool has_rodata) {
|
||||
ZElfObjectImage image = {
|
||||
.machine = Z_ELF_MACHINE_AARCH64,
|
||||
.text = text,
|
||||
.text_align = 16,
|
||||
.rodata = has_rodata ? rodata : NULL,
|
||||
.rodata_align = 8,
|
||||
.rela_text = rela_text->len > 0 ? rela_text : NULL,
|
||||
.symtab = symtab,
|
||||
.strtab = strtab,
|
||||
.local_symbol_count = has_rodata ? 2 : 1
|
||||
};
|
||||
z_elf_write_object64(out, &image);
|
||||
}
|
||||
|
||||
bool z_emit_elf_aarch64_object_from_ir(const IrProgram *ir, ZBuf *out, ZDiag *diag) {
|
||||
if (!ir) return a64_diag(diag, "direct AArch64 ELF object backend requires MIR", 1, 1, "missing MIR");
|
||||
if (!ir->mir_valid) {
|
||||
bool ok = a64_diag(diag, ir->mir_message[0] ? ir->mir_message : "direct backend lowering failed", ir->mir_line, ir->mir_column, ir->mir_actual);
|
||||
z_diag_set_backend_blocker(diag, &ir->backend_blocker);
|
||||
return ok;
|
||||
}
|
||||
|
||||
ZBuf text;
|
||||
ZBuf rodata;
|
||||
ZBuf rela_text;
|
||||
ZBuf strtab;
|
||||
ZBuf symtab;
|
||||
zbuf_init(&text);
|
||||
zbuf_init(&rodata);
|
||||
zbuf_init(&rela_text);
|
||||
zbuf_init(&strtab);
|
||||
zbuf_init(&symtab);
|
||||
z_elf_append_u8(&strtab, 0);
|
||||
z_elf_append_zeros(&symtab, 24);
|
||||
|
||||
bool has_rodata = ir->readonly_data_bytes > 0 || ir->data_segment_len > 0;
|
||||
unsigned rodata_base_offset = z_aarch64_direct_rodata_base_offset(ir);
|
||||
if (has_rodata) {
|
||||
z_aarch64_direct_append_rodata(&rodata, ir, rodata_base_offset);
|
||||
z_elf_append_symbol(&symtab, 0, 0x03, 2, 0, 0);
|
||||
}
|
||||
|
||||
size_t *function_offsets = z_checked_calloc(ir->function_len, sizeof(size_t));
|
||||
size_t *function_sizes = z_checked_calloc(ir->function_len, sizeof(size_t));
|
||||
uint32_t *symbol_names = z_checked_calloc(ir->function_len, sizeof(uint32_t));
|
||||
uint32_t *external_names = ir->external_function_len > 0 ? z_checked_calloc(ir->external_function_len, sizeof(uint32_t)) : NULL;
|
||||
if (!function_offsets || !function_sizes || !symbol_names || (ir->external_function_len > 0 && !external_names)) {
|
||||
free(function_offsets);
|
||||
free(function_sizes);
|
||||
free(symbol_names);
|
||||
free(external_names);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&rela_text);
|
||||
zbuf_free(&strtab);
|
||||
zbuf_free(&symtab);
|
||||
return a64_diag(diag, "direct AArch64 ELF object backend ran out of memory", 1, 1, "allocation failed");
|
||||
}
|
||||
A64ElfPatchContext patch_ctx = {.allow_runtime_patches = true};
|
||||
ZAArch64DirectContext ctx = {
|
||||
.program = ir,
|
||||
.function_offsets = function_offsets,
|
||||
.function_count = ir->function_len,
|
||||
.rodata_base_offset = rodata_base_offset,
|
||||
.patch_user = &patch_ctx,
|
||||
.record_data_patch = a64_elf_record_data_patch,
|
||||
.record_call_patch = a64_elf_record_call_patch,
|
||||
.record_runtime_patch = a64_elf_record_runtime_patch
|
||||
};
|
||||
|
||||
bool has_export = false;
|
||||
for (size_t i = 0; i < ir->function_len; i++) {
|
||||
if (ir->functions[i].is_exported) has_export = true;
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
function_offsets[i] = text.len;
|
||||
if (!z_aarch64_direct_emit_function_text(&text, &ir->functions[i], &ctx, diag)) {
|
||||
a64_elf_patch_context_free(&patch_ctx);
|
||||
free(function_offsets);
|
||||
free(function_sizes);
|
||||
free(symbol_names);
|
||||
free(external_names);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&rela_text);
|
||||
zbuf_free(&strtab);
|
||||
zbuf_free(&symtab);
|
||||
return false;
|
||||
}
|
||||
function_sizes[i] = text.len - function_offsets[i];
|
||||
if (ir->functions[i].is_exported) {
|
||||
symbol_names[i] = (uint32_t)strtab.len;
|
||||
zbuf_append(&strtab, ir->functions[i].name ? ir->functions[i].name : "zero_fn");
|
||||
z_elf_append_u8(&strtab, 0);
|
||||
}
|
||||
}
|
||||
if (!has_export) {
|
||||
a64_elf_patch_context_free(&patch_ctx);
|
||||
free(function_offsets);
|
||||
free(function_sizes);
|
||||
free(symbol_names);
|
||||
free(external_names);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&rela_text);
|
||||
zbuf_free(&strtab);
|
||||
zbuf_free(&symtab);
|
||||
return a64_diag(diag, "direct AArch64 ELF object backend requires at least one exported function", 1, 1, "no exported function");
|
||||
}
|
||||
|
||||
a64_patch_call_patches(&text, &patch_ctx, function_offsets, ir->function_len);
|
||||
if (has_rodata) a64_append_data_relocations(&rela_text, &patch_ctx, rodata_base_offset, 1);
|
||||
uint32_t external_symbol_base = a64_append_exported_function_symbols(&symtab, ir, function_offsets, function_sizes, symbol_names, has_rodata);
|
||||
external_symbol_base = a64_append_runtime_symbols(&strtab, &symtab, &rela_text, &patch_ctx, external_symbol_base);
|
||||
a64_append_external_symbols(&strtab, &symtab, ir, external_names);
|
||||
a64_append_external_call_relocations(&rela_text, &patch_ctx, external_symbol_base);
|
||||
|
||||
a64_write_object_image(out, &text, &rodata, &rela_text, &symtab, &strtab, has_rodata);
|
||||
|
||||
a64_elf_patch_context_free(&patch_ctx);
|
||||
free(function_offsets);
|
||||
free(function_sizes);
|
||||
free(symbol_names);
|
||||
free(external_names);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
zbuf_free(&rela_text);
|
||||
zbuf_free(&strtab);
|
||||
zbuf_free(&symtab);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_emit_elf_aarch64_exe_from_ir(const IrProgram *ir, ZBuf *out, ZDiag *diag) {
|
||||
if (!ir) return a64_diag(diag, "direct AArch64 ELF executable backend requires MIR", 1, 1, "missing MIR");
|
||||
if (!ir->mir_valid) {
|
||||
bool ok = a64_diag(diag, ir->mir_message[0] ? ir->mir_message : "direct backend lowering failed", ir->mir_line, ir->mir_column, ir->mir_actual);
|
||||
z_diag_set_backend_blocker(diag, &ir->backend_blocker);
|
||||
return ok;
|
||||
}
|
||||
if (!z_direct_exe_reject_c_import_calls(ir, diag, "AArch64 ELF")) return false;
|
||||
unsigned main_index = 0;
|
||||
if (!z_aarch64_direct_find_main(ir, &main_index, diag)) return false;
|
||||
|
||||
ZBuf text;
|
||||
ZBuf rodata;
|
||||
zbuf_init(&text);
|
||||
zbuf_init(&rodata);
|
||||
bool has_rodata = true;
|
||||
unsigned rodata_base_offset = z_aarch64_direct_rodata_base_offset(ir);
|
||||
ZDirectTrapMessages trap_messages = {0};
|
||||
z_aarch64_direct_append_rodata(&rodata, ir, rodata_base_offset);
|
||||
z_aarch64_direct_append_trap_messages(&rodata, rodata_base_offset, &trap_messages);
|
||||
|
||||
size_t start_call_patch = z_aarch64_emit_bl_placeholder(&text);
|
||||
z_aarch64_emit_movz_x(&text, 8, 93);
|
||||
z_aarch64_emit_svc(&text, 0);
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
|
||||
size_t *function_offsets = z_checked_calloc(ir->function_len, sizeof(size_t));
|
||||
if (!function_offsets) {
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
return a64_diag(diag, "direct AArch64 ELF executable backend ran out of memory", 1, 1, "allocation failed");
|
||||
}
|
||||
A64ElfPatchContext patch_ctx = {0};
|
||||
ZAArch64DirectContext ctx = {
|
||||
.program = ir,
|
||||
.function_offsets = function_offsets,
|
||||
.function_count = ir->function_len,
|
||||
.rodata_base_offset = rodata_base_offset,
|
||||
.patch_user = &patch_ctx,
|
||||
.record_data_patch = a64_elf_record_data_patch,
|
||||
.record_call_patch = a64_elf_record_call_patch,
|
||||
.record_runtime_patch = a64_elf_record_runtime_patch,
|
||||
.emit_world_write = a64_elf_emit_world_write,
|
||||
.trap_messages = trap_messages
|
||||
};
|
||||
for (size_t i = 0; i < ir->function_len; i++) {
|
||||
z_aarch64_pad_to(&text, z_aarch64_align(text.len, 16));
|
||||
function_offsets[i] = text.len;
|
||||
if (!z_aarch64_direct_emit_function_text(&text, &ir->functions[i], &ctx, diag)) {
|
||||
z_direct_trap_branches_free(ctx.trap_branches, Z_DIRECT_TRAP_KIND_COUNT);
|
||||
a64_elf_patch_context_free(&patch_ctx);
|
||||
free(function_offsets);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!z_aarch64_direct_emit_trap_stubs(&text, &ctx, diag)) {
|
||||
a64_elf_patch_context_free(&patch_ctx);
|
||||
free(function_offsets);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
return false;
|
||||
}
|
||||
z_aarch64_patch_branch26(&text, start_call_patch, function_offsets[main_index]);
|
||||
a64_patch_call_patches(&text, &patch_ctx, function_offsets, ir->function_len);
|
||||
|
||||
const uint64_t base_addr = 0x400000;
|
||||
const size_t ehdr_size = 64;
|
||||
const size_t phdr_size = 56;
|
||||
const size_t text_offset = ehdr_size + phdr_size;
|
||||
const uint64_t entry_addr = base_addr + text_offset;
|
||||
size_t rodata_offset = has_rodata ? z_elf_align(text_offset + text.len, 8) : 0;
|
||||
uint64_t rodata_addr = has_rodata ? base_addr + rodata_offset : 0;
|
||||
a64_patch_data_patches(&text, &patch_ctx, rodata_addr, rodata_base_offset);
|
||||
ZElfExecutableImage image = {
|
||||
.machine = Z_ELF_MACHINE_AARCH64,
|
||||
.base_addr = base_addr,
|
||||
.entry_addr = entry_addr,
|
||||
.text_offset = text_offset,
|
||||
.text = &text,
|
||||
.rodata = has_rodata ? &rodata : NULL,
|
||||
.rodata_offset = rodata_offset,
|
||||
.segment_align = 0x1000
|
||||
};
|
||||
z_elf_write_executable64(out, &image);
|
||||
a64_elf_patch_context_free(&patch_ctx);
|
||||
free(function_offsets);
|
||||
zbuf_free(&text);
|
||||
zbuf_free(&rodata);
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
#include "zero.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
void z_diag_set_path_copy(ZDiag *diag, const char *path) {
|
||||
if (!diag) return;
|
||||
diag->path = path ? z_strdup(path) : NULL;
|
||||
}
|
||||
|
||||
static void read_diag_io(ZDiag *diag, const char *path, const char *action) {
|
||||
if (!diag) return;
|
||||
diag->code = 1;
|
||||
z_diag_set_path_copy(diag, path);
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "failed to %s '%s': %s", action, path ? path : "", strerror(errno));
|
||||
}
|
||||
|
||||
static bool read_path_is_existing_directory(const char *path) {
|
||||
struct stat st;
|
||||
return path && stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
static bool reject_invalid_read_path(const char *path, ZDiag *diag) {
|
||||
if (!path || !path[0]) {
|
||||
errno = EINVAL;
|
||||
read_diag_io(diag, path, "read");
|
||||
return false;
|
||||
}
|
||||
if (read_path_is_existing_directory(path)) {
|
||||
errno = EACCES;
|
||||
read_diag_io(diag, path, "read");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_read_binary_file(const char *path, unsigned char **out, size_t *out_len, ZDiag *diag) {
|
||||
if (out) *out = NULL;
|
||||
if (out_len) *out_len = 0;
|
||||
if (!out || !out_len) {
|
||||
errno = EINVAL;
|
||||
read_diag_io(diag, path, "read");
|
||||
return false;
|
||||
}
|
||||
if (!reject_invalid_read_path(path, diag)) return false;
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file) { read_diag_io(diag, path, "read"); return false; }
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
if (errno == 0) errno = EIO;
|
||||
read_diag_io(diag, path, "read");
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
long size = ftell(file);
|
||||
if (size < 0 || (size_t)size > SIZE_MAX - 1) {
|
||||
if (errno == 0) errno = EIO;
|
||||
read_diag_io(diag, path, "read");
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
if (errno == 0) errno = EIO;
|
||||
read_diag_io(diag, path, "read");
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
unsigned char *data = z_checked_malloc((size_t)size + 1);
|
||||
if (size > 0 && fread(data, 1, (size_t)size, file) != (size_t)size) {
|
||||
if (errno == 0) errno = EIO;
|
||||
read_diag_io(diag, path, "read");
|
||||
free(data);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
data[(size_t)size] = 0;
|
||||
if (fclose(file) != 0) {
|
||||
read_diag_io(diag, path, "read");
|
||||
free(data);
|
||||
return false;
|
||||
}
|
||||
*out = data;
|
||||
*out_len = (size_t)size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_read_file_prefix(const char *path, void *bytes, size_t len, size_t *out_read, ZDiag *diag) {
|
||||
if (out_read) *out_read = 0;
|
||||
if (!bytes || len == 0) {
|
||||
errno = EINVAL;
|
||||
read_diag_io(diag, path, "read");
|
||||
return false;
|
||||
}
|
||||
if (!reject_invalid_read_path(path, diag)) return false;
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file) { read_diag_io(diag, path, "read"); return false; }
|
||||
size_t read = fread(bytes, 1, len, file);
|
||||
if (out_read) *out_read = read;
|
||||
if (ferror(file)) {
|
||||
if (errno == 0) errno = EIO;
|
||||
read_diag_io(diag, path, "read");
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
read_diag_io(diag, path, "read");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
char *z_read_file(const char *path, ZDiag *diag) {
|
||||
unsigned char *data = NULL;
|
||||
size_t len = 0;
|
||||
if (!z_read_binary_file(path, &data, &len, diag)) return NULL;
|
||||
(void)len;
|
||||
return (char *)data;
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
#ifndef _DARWIN_C_SOURCE
|
||||
#define _DARWIN_C_SOURCE
|
||||
#endif
|
||||
|
||||
#include "http_listen_runner.h"
|
||||
#include "http_listen_temp.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
int z_http_listen_run(const ZHttpListenRunConfig *config, ZDiag *diag) {
|
||||
(void)config;
|
||||
if (diag) {
|
||||
diag->code = 2002;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "std.http.listen is not available on this host yet");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "POSIX host runtime listener");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "Windows host");
|
||||
snprintf(diag->help, sizeof(diag->help), "use a Darwin or Linux host for zero run on std.http.listen programs");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 4096
|
||||
#endif
|
||||
|
||||
#define Z_HTTP_LISTEN_REQUEST_CAP 4096
|
||||
#define Z_HTTP_LISTEN_RESPONSE_CAP 262144
|
||||
#define Z_HTTP_LISTEN_HANDLER_RESPONSE_CAP 4096
|
||||
#define Z_HTTP_LISTEN_CLIENT_TIMEOUT_SECONDS 2
|
||||
|
||||
static volatile sig_atomic_t listen_stop_requested = 0;
|
||||
static volatile sig_atomic_t listen_server_fd_for_signal = -1;
|
||||
|
||||
static void listen_diag(ZDiag *diag, const char *message, const char *expected, const char *actual, const char *help);
|
||||
|
||||
static void listen_handle_stop_signal(int signum) {
|
||||
(void)signum;
|
||||
listen_stop_requested = 1;
|
||||
sig_atomic_t fd = listen_server_fd_for_signal;
|
||||
if (fd >= 0) {
|
||||
listen_server_fd_for_signal = -1;
|
||||
close((int)fd);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
struct sigaction old_term_action;
|
||||
struct sigaction old_int_action;
|
||||
} ListenSignalState;
|
||||
|
||||
static void listen_install_stop_handlers(int server_fd, ListenSignalState *state) {
|
||||
if (!state) return;
|
||||
struct sigaction stop_action;
|
||||
memset(&stop_action, 0, sizeof(stop_action));
|
||||
memset(state, 0, sizeof(*state));
|
||||
sigemptyset(&stop_action.sa_mask);
|
||||
stop_action.sa_handler = listen_handle_stop_signal;
|
||||
listen_stop_requested = 0;
|
||||
listen_server_fd_for_signal = server_fd;
|
||||
(void)sigaction(SIGTERM, &stop_action, &state->old_term_action);
|
||||
(void)sigaction(SIGINT, &stop_action, &state->old_int_action);
|
||||
}
|
||||
|
||||
static void listen_restore_stop_handlers(const ListenSignalState *state) {
|
||||
listen_server_fd_for_signal = -1;
|
||||
if (!state) return;
|
||||
(void)sigaction(SIGTERM, &state->old_term_action, NULL);
|
||||
(void)sigaction(SIGINT, &state->old_int_action, NULL);
|
||||
}
|
||||
|
||||
static void listen_diag(ZDiag *diag, const char *message, const char *expected, const char *actual, const char *help) {
|
||||
if (!diag) return;
|
||||
diag->code = 2002;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message ? message : "std.http.listen failed");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", expected ? expected : "valid std.http.listen run");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "listener setup failed");
|
||||
snprintf(diag->help, sizeof(diag->help), "%s", help ? help : "inspect the program graph and host runtime diagnostics");
|
||||
}
|
||||
|
||||
static int run_argv(const char *const *argv, bool suppress_stdout) {
|
||||
fflush(NULL);
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
if (suppress_stdout) {
|
||||
int dev_null = open("/dev/null", O_WRONLY);
|
||||
if (dev_null >= 0) {
|
||||
dup2(dev_null, STDOUT_FILENO);
|
||||
close(dev_null);
|
||||
}
|
||||
}
|
||||
execv(argv[0], (char *const *)argv);
|
||||
perror("zero listen");
|
||||
_exit(127);
|
||||
}
|
||||
if (pid < 0) {
|
||||
perror("zero listen");
|
||||
return 1;
|
||||
}
|
||||
int status = 0;
|
||||
while (waitpid(pid, &status, 0) < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("zero listen");
|
||||
return 1;
|
||||
}
|
||||
if (WIFEXITED(status)) return WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void argv_push(const char **argv, size_t cap, size_t *len, const char *value) {
|
||||
if (!argv || !len || *len + 1 >= cap) return;
|
||||
argv[(*len)++] = value;
|
||||
argv[*len] = NULL;
|
||||
}
|
||||
|
||||
static bool build_handler_graph(const ZHttpListenRunConfig *config, const char *temp_dir, char *handler_exe, size_t handler_exe_cap, ZDiag *diag) {
|
||||
if (!config || !config->zero_exe || !config->input || !config->handler || !temp_dir || !handler_exe || handler_exe_cap == 0) {
|
||||
listen_diag(diag, "std.http.listen runner is missing required configuration", "zero executable, input graph, and handler", "missing configuration", NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
char base_graph[PATH_MAX];
|
||||
char patch_path[PATH_MAX];
|
||||
char handler_graph[PATH_MAX];
|
||||
if (!z_http_listen_temp_path(temp_dir, "base.graph", base_graph, sizeof(base_graph), diag) ||
|
||||
!z_http_listen_temp_path(temp_dir, "handler.patch", patch_path, sizeof(patch_path), diag) ||
|
||||
!z_http_listen_temp_path(temp_dir, "handler.graph", handler_graph, sizeof(handler_graph), diag) ||
|
||||
!z_http_listen_temp_path(temp_dir, "handler", handler_exe, handler_exe_cap, diag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ZBuf patch;
|
||||
zbuf_init(&patch);
|
||||
zbuf_append(&patch, "zero-program-graph-patch v1\n");
|
||||
zbuf_append(&patch, "replaceFunctionBody main\n");
|
||||
zbuf_append(&patch, " let maybe_request_path Maybe<String> = std.args.get 1\n");
|
||||
zbuf_append(&patch, " if !maybe_request_path.has\n");
|
||||
zbuf_append(&patch, " check world.err.write \"usage: pass one HTTP request path\\n\"\n");
|
||||
zbuf_append(&patch, " return\n");
|
||||
zbuf_appendf(&patch, " var request [%" PRIu64 "]u8 = [0_u8; %" PRIu64 "]\n", (uint64_t)Z_HTTP_LISTEN_REQUEST_CAP, (uint64_t)Z_HTTP_LISTEN_REQUEST_CAP);
|
||||
zbuf_append(&patch, " let maybe_request_len Maybe<usize> = std.fs.readBytes(maybe_request_path.value, request)\n");
|
||||
zbuf_append(&patch, " if !maybe_request_len.has\n");
|
||||
zbuf_append(&patch, " check world.err.write \"http request read failed\\n\"\n");
|
||||
zbuf_append(&patch, " return\n");
|
||||
zbuf_append(&patch, " let request_span Span<u8> = request[..maybe_request_len.value]\n");
|
||||
zbuf_appendf(&patch, " var response [%" PRIu64 "]u8 = [0_u8; %" PRIu64 "]\n", (uint64_t)Z_HTTP_LISTEN_HANDLER_RESPONSE_CAP, (uint64_t)Z_HTTP_LISTEN_HANDLER_RESPONSE_CAP);
|
||||
zbuf_appendf(&patch, " let output Maybe<Span<u8>> = %s(request_span, response)\n", config->handler);
|
||||
zbuf_append(&patch, " if output.has\n");
|
||||
zbuf_append(&patch, " check world.out.write output.value\n");
|
||||
zbuf_append(&patch, " return\n");
|
||||
zbuf_append(&patch, " check world.err.write \"http handler failed\\n\"\n");
|
||||
zbuf_append(&patch, "end\n");
|
||||
|
||||
bool wrote_patch = z_write_file(patch_path, patch.data ? patch.data : "", diag);
|
||||
zbuf_free(&patch);
|
||||
if (!wrote_patch) return false;
|
||||
|
||||
const char *dump_argv[] = {config->zero_exe, "dump", "--format", "binary", "--out", base_graph, config->input, NULL};
|
||||
if (run_argv(dump_argv, true) != 0) {
|
||||
listen_diag(diag, "std.http.listen could not prepare a handler graph", "zero dump writes a temporary graph artifact", "zero dump failed", "run zero dump on the package to inspect graph input readiness");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *patch_argv[] = {config->zero_exe, "patch", "--format", "binary", "--out", handler_graph, base_graph, patch_path, NULL};
|
||||
if (run_argv(patch_argv, true) != 0) {
|
||||
listen_diag(diag, "std.http.listen could not build the handler entry graph", "handler wrapper graph patch applies", "zero patch failed", "inspect the package handler signature and graph patch support");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *build_argv[20] = {0};
|
||||
size_t build_len = 0;
|
||||
argv_push(build_argv, 20, &build_len, config->zero_exe);
|
||||
argv_push(build_argv, 20, &build_len, "build");
|
||||
if (config->target && config->target[0]) {
|
||||
argv_push(build_argv, 20, &build_len, "--target");
|
||||
argv_push(build_argv, 20, &build_len, config->target);
|
||||
}
|
||||
if (config->profile && config->profile[0]) {
|
||||
argv_push(build_argv, 20, &build_len, "--profile");
|
||||
argv_push(build_argv, 20, &build_len, config->profile);
|
||||
}
|
||||
if (config->backend && config->backend[0]) {
|
||||
argv_push(build_argv, 20, &build_len, "--backend");
|
||||
argv_push(build_argv, 20, &build_len, config->backend);
|
||||
}
|
||||
if (config->cc && config->cc[0]) {
|
||||
argv_push(build_argv, 20, &build_len, "--cc");
|
||||
argv_push(build_argv, 20, &build_len, config->cc);
|
||||
}
|
||||
argv_push(build_argv, 20, &build_len, "--out");
|
||||
argv_push(build_argv, 20, &build_len, handler_exe);
|
||||
argv_push(build_argv, 20, &build_len, handler_graph);
|
||||
if (run_argv(build_argv, true) != 0) {
|
||||
listen_diag(diag, "std.http.listen could not build the handler executable", "temporary handler graph builds as a host executable", "zero build failed", "run zero check on the package and inspect the handler body");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send_all(int fd, const char *data, size_t len) {
|
||||
if (!data && len > 0) return false;
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = send(fd, data + sent, len - sent, 0);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return false;
|
||||
}
|
||||
if (n == 0) return false;
|
||||
sent += (size_t)n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void configure_client_socket(int fd) {
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = Z_HTTP_LISTEN_CLIENT_TIMEOUT_SECONDS;
|
||||
timeout.tv_usec = 0;
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||
}
|
||||
|
||||
static size_t header_end_offset(const char *buf, size_t len) {
|
||||
for (size_t i = 0; i + 3 < len; i++) {
|
||||
if (buf[i] == '\r' && buf[i + 1] == '\n' && buf[i + 2] == '\r' && buf[i + 3] == '\n') return i + 4;
|
||||
}
|
||||
for (size_t i = 0; i + 1 < len; i++) {
|
||||
if (buf[i] == '\n' && buf[i + 1] == '\n') return i + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ascii_ieq_n(const char *left, const char *right, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char a = (unsigned char)left[i];
|
||||
unsigned char b = (unsigned char)right[i];
|
||||
if (a >= 'A' && a <= 'Z') a = (unsigned char)(a + ('a' - 'A'));
|
||||
if (b >= 'A' && b <= 'Z') b = (unsigned char)(b + ('a' - 'A'));
|
||||
if (a != b) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool present;
|
||||
bool valid;
|
||||
size_t value;
|
||||
} ParsedContentLength;
|
||||
|
||||
static ParsedContentLength parse_content_length(const char *buf, size_t header_len) {
|
||||
ParsedContentLength result = {.present = false, .valid = true, .value = 0};
|
||||
const char key[] = "content-length:";
|
||||
size_t key_len = sizeof(key) - 1;
|
||||
size_t line_start = 0;
|
||||
while (line_start < header_len) {
|
||||
size_t line_end = line_start;
|
||||
while (line_end < header_len && buf[line_end] != '\n') line_end++;
|
||||
size_t line_len = line_end - line_start;
|
||||
if (line_len > 0 && buf[line_start + line_len - 1] == '\r') line_len--;
|
||||
if (line_len >= key_len && ascii_ieq_n(buf + line_start, key, key_len)) {
|
||||
result.present = true;
|
||||
size_t i = line_start + key_len;
|
||||
while (i < line_start + line_len && (buf[i] == ' ' || buf[i] == '\t')) i++;
|
||||
size_t value = 0;
|
||||
size_t digits = 0;
|
||||
while (i < line_start + line_len && buf[i] >= '0' && buf[i] <= '9') {
|
||||
if (value > (SIZE_MAX - (size_t)(buf[i] - '0')) / 10u) {
|
||||
result.valid = false;
|
||||
return result;
|
||||
}
|
||||
value = value * 10 + (size_t)(buf[i] - '0');
|
||||
i++;
|
||||
digits++;
|
||||
}
|
||||
while (i < line_start + line_len && (buf[i] == ' ' || buf[i] == '\t')) i++;
|
||||
if (digits == 0 || i != line_start + line_len) {
|
||||
result.valid = false;
|
||||
return result;
|
||||
}
|
||||
result.value = value;
|
||||
return result;
|
||||
}
|
||||
line_start = line_end + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool read_http_request(int fd, char *buf, size_t cap, size_t *out_len, unsigned *out_status) {
|
||||
if (out_len) *out_len = 0;
|
||||
if (out_status) *out_status = 400;
|
||||
if (!buf || cap < 2) return false;
|
||||
size_t total = 0;
|
||||
size_t header_end = 0;
|
||||
size_t content_len = 0;
|
||||
while (total + 1 < cap) {
|
||||
ssize_t n = recv(fd, buf + total, cap - total - 1, 0);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
if (out_status) *out_status = 408;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (n == 0) break;
|
||||
total += (size_t)n;
|
||||
buf[total] = '\0';
|
||||
if (header_end == 0) {
|
||||
header_end = header_end_offset(buf, total);
|
||||
if (header_end > 0) {
|
||||
ParsedContentLength parsed = parse_content_length(buf, header_end);
|
||||
if (!parsed.valid) return false;
|
||||
content_len = parsed.present ? parsed.value : 0;
|
||||
if (content_len > cap - header_end - 1) {
|
||||
if (out_status) *out_status = 413;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (header_end > 0 && total >= header_end + content_len) break;
|
||||
}
|
||||
if (out_len) *out_len = total;
|
||||
bool complete = total > 0 && header_end_offset(buf, total) > 0 && total >= header_end + content_len;
|
||||
if (!complete && out_status && total + 1 >= cap) *out_status = 413;
|
||||
return complete;
|
||||
}
|
||||
|
||||
static bool write_request_file(const char *path, const char *request, size_t request_len) {
|
||||
if (!path || !request) return false;
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||
if (fd < 0) return false;
|
||||
size_t written = 0;
|
||||
while (written < request_len) {
|
||||
ssize_t n = write(fd, request + written, request_len - written);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
if (n == 0) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
written += (size_t)n;
|
||||
}
|
||||
if (close(fd) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool run_handler_capture(const char *handler_exe, const char *request_path, char **out_data, size_t *out_len) {
|
||||
if (out_data) *out_data = NULL;
|
||||
if (out_len) *out_len = 0;
|
||||
if (!handler_exe || !request_path) return false;
|
||||
int fds[2];
|
||||
if (pipe(fds) != 0) return false;
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
if (close(fds[0]) != 0) _exit(127);
|
||||
if (dup2(fds[1], STDOUT_FILENO) < 0) _exit(127);
|
||||
if (close(fds[1]) != 0) _exit(127);
|
||||
char *const argv[] = {(char *)handler_exe, (char *)request_path, NULL};
|
||||
execv(handler_exe, argv);
|
||||
perror("zero listen handler");
|
||||
_exit(127);
|
||||
}
|
||||
close(fds[1]);
|
||||
if (pid < 0) {
|
||||
close(fds[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
char *response = z_checked_malloc(Z_HTTP_LISTEN_RESPONSE_CAP + 1);
|
||||
size_t len = 0;
|
||||
bool overflow = false;
|
||||
bool read_ok = true;
|
||||
char chunk[4096];
|
||||
while (true) {
|
||||
ssize_t n = read(fds[0], chunk, sizeof(chunk));
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
read_ok = false;
|
||||
break;
|
||||
}
|
||||
if (n == 0) break;
|
||||
size_t count = (size_t)n;
|
||||
if (count > Z_HTTP_LISTEN_RESPONSE_CAP - len) {
|
||||
size_t remaining = Z_HTTP_LISTEN_RESPONSE_CAP - len;
|
||||
if (remaining > 0) memcpy(response + len, chunk, remaining);
|
||||
len = Z_HTTP_LISTEN_RESPONSE_CAP;
|
||||
overflow = true;
|
||||
} else {
|
||||
memcpy(response + len, chunk, count);
|
||||
len += count;
|
||||
}
|
||||
}
|
||||
if (close(fds[0]) != 0) read_ok = false;
|
||||
response[len] = '\0';
|
||||
|
||||
int status = 0;
|
||||
while (waitpid(pid, &status, 0) < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
free(response);
|
||||
return false;
|
||||
}
|
||||
bool handler_ok = WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
bool response_ok = len >= 5 && memcmp(response, "HTTP/", 5) == 0;
|
||||
bool ok = read_ok && !overflow && handler_ok && response_ok;
|
||||
if (!ok) {
|
||||
free(response);
|
||||
return false;
|
||||
}
|
||||
if (out_data) *out_data = response;
|
||||
else free(response);
|
||||
if (out_len) *out_len = len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send_json_error(int fd, unsigned status, const char *reason, const char *body) {
|
||||
char response[512];
|
||||
size_t body_len = strlen(body ? body : "");
|
||||
int len = snprintf(response, sizeof(response),
|
||||
"HTTP/1.1 %u %s\r\ncontent-type: application/json\r\nconnection: close\r\ncontent-length: %zu\r\n\r\n%s",
|
||||
status, reason ? reason : "Error", body_len, body ? body : "");
|
||||
if (len < 0 || (size_t)len >= sizeof(response)) return false;
|
||||
return send_all(fd, response, (size_t)len);
|
||||
}
|
||||
|
||||
static int listen_open_server_socket(const ZHttpListenRunConfig *config, uint16_t *out_bound_port, ZDiag *diag) {
|
||||
if (out_bound_port) *out_bound_port = 0;
|
||||
int server_fd = -1;
|
||||
unsigned attempts = config->auto_increment_port ? (65535u - (unsigned)config->port + 1u) : 1u;
|
||||
for (unsigned attempt = 0; attempt < attempts; attempt++) {
|
||||
uint16_t candidate = (uint16_t)((unsigned)config->port + attempt);
|
||||
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) {
|
||||
listen_diag(diag, "std.http.listen could not open a TCP socket", "host socket", strerror(errno), NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1) {
|
||||
listen_diag(diag, "std.http.listen could not configure loopback address", "127.0.0.1 address", "inet_pton failed", NULL);
|
||||
close(server_fd);
|
||||
return -1;
|
||||
}
|
||||
addr.sin_port = htons(candidate);
|
||||
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
|
||||
if (listen(server_fd, 32) == 0) {
|
||||
if (out_bound_port) *out_bound_port = candidate;
|
||||
return server_fd;
|
||||
}
|
||||
listen_diag(diag, "std.http.listen could not start listening", "listening TCP socket", strerror(errno), NULL);
|
||||
close(server_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int bind_errno = errno;
|
||||
close(server_fd);
|
||||
server_fd = -1;
|
||||
if (!config->auto_increment_port || bind_errno != EADDRINUSE) {
|
||||
listen_diag(diag, "std.http.listen could not bind the requested port", "available loopback TCP port", strerror(bind_errno), config->auto_increment_port ? "choose another start port or stop the process already using it" : "omit the port to auto-select the next free dev port, or stop the process already using it");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
listen_diag(diag, "std.http.listen could not find a free loopback port", "available loopback TCP port at or above the requested port", "all candidate ports were busy", "pass an explicit free port");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int z_http_listen_run(const ZHttpListenRunConfig *config, ZDiag *diag) {
|
||||
if (!config || config->port == 0 || !config->handler || !config->handler[0]) {
|
||||
listen_diag(diag, "std.http.listen requires a port and handler", "literal port and same-module handle function", "missing listener configuration", NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char handler_exe[PATH_MAX];
|
||||
char temp_dir[PATH_MAX];
|
||||
if (!z_http_listen_create_temp_dir(temp_dir, sizeof(temp_dir), diag)) return 1;
|
||||
if (!build_handler_graph(config, temp_dir, handler_exe, sizeof(handler_exe), diag)) {
|
||||
z_http_listen_cleanup_temp_dir(temp_dir);
|
||||
return 1;
|
||||
}
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
uint16_t bound_port = 0;
|
||||
int server_fd = listen_open_server_socket(config, &bound_port, diag);
|
||||
if (server_fd < 0) {
|
||||
z_http_listen_cleanup_temp_dir(temp_dir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ListenSignalState signal_state;
|
||||
listen_install_stop_handlers(server_fd, &signal_state);
|
||||
fprintf(stderr, "listening on http://127.0.0.1:%u\n", (unsigned)bound_port);
|
||||
uint64_t request_id = 0;
|
||||
for (;;) {
|
||||
int client_fd = accept(server_fd, NULL, NULL);
|
||||
if (client_fd < 0) {
|
||||
if (listen_stop_requested) {
|
||||
server_fd = -1;
|
||||
break;
|
||||
}
|
||||
if (errno == EINTR) continue;
|
||||
perror("zero listen accept");
|
||||
break;
|
||||
}
|
||||
configure_client_socket(client_fd);
|
||||
char request[Z_HTTP_LISTEN_REQUEST_CAP];
|
||||
size_t request_len = 0;
|
||||
unsigned request_status = 400;
|
||||
if (!read_http_request(client_fd, request, sizeof(request), &request_len, &request_status)) {
|
||||
const char *reason = request_status == 413 ? "Payload Too Large" : request_status == 408 ? "Request Timeout" : "Bad Request";
|
||||
const char *body = request_status == 413 ? "{\"error\":\"payload_too_large\"}" : request_status == 408 ? "{\"error\":\"request_timeout\"}" : "{\"error\":\"bad_request\"}";
|
||||
send_json_error(client_fd, request_status, reason, body);
|
||||
close(client_fd);
|
||||
continue;
|
||||
}
|
||||
request[request_len] = '\0';
|
||||
char request_path[PATH_MAX];
|
||||
request_id++;
|
||||
char request_leaf[64];
|
||||
snprintf(request_leaf, sizeof(request_leaf), "request-%" PRIu64 ".http", request_id);
|
||||
if (!z_http_listen_temp_path(temp_dir, request_leaf, request_path, sizeof(request_path), NULL)) {
|
||||
send_json_error(client_fd, 500, "Internal Server Error", "{\"error\":\"request_spool_failed\"}");
|
||||
close(client_fd);
|
||||
continue;
|
||||
}
|
||||
if (!write_request_file(request_path, request, request_len)) {
|
||||
unlink(request_path);
|
||||
send_json_error(client_fd, 500, "Internal Server Error", "{\"error\":\"request_spool_failed\"}");
|
||||
close(client_fd);
|
||||
continue;
|
||||
}
|
||||
char *response = NULL;
|
||||
size_t response_len = 0;
|
||||
if (run_handler_capture(handler_exe, request_path, &response, &response_len)) {
|
||||
(void)send_all(client_fd, response, response_len);
|
||||
free(response);
|
||||
} else {
|
||||
send_json_error(client_fd, 500, "Internal Server Error", "{\"error\":\"handler_failed\"}");
|
||||
}
|
||||
unlink(request_path);
|
||||
close(client_fd);
|
||||
}
|
||||
|
||||
listen_restore_stop_handlers(&signal_state);
|
||||
if (server_fd >= 0) close(server_fd);
|
||||
z_http_listen_cleanup_temp_dir(temp_dir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef ZERO_C_HTTP_LISTEN_RUNNER_H
|
||||
#define ZERO_C_HTTP_LISTEN_RUNNER_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
const char *zero_exe;
|
||||
const char *input;
|
||||
const char *target;
|
||||
const char *profile;
|
||||
const char *backend;
|
||||
const char *cc;
|
||||
const char *handler;
|
||||
uint16_t port;
|
||||
bool auto_increment_port;
|
||||
} ZHttpListenRunConfig;
|
||||
|
||||
int z_http_listen_run(const ZHttpListenRunConfig *config, ZDiag *diag);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,191 @@
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
#ifndef _DARWIN_C_SOURCE
|
||||
#define _DARWIN_C_SOURCE
|
||||
#endif
|
||||
|
||||
#include "http_listen_temp.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#if defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 4096
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define ZERO_LISTEN_PATH_SEP '\\'
|
||||
#else
|
||||
#define ZERO_LISTEN_PATH_SEP '/'
|
||||
#endif
|
||||
|
||||
static void listen_temp_diag(ZDiag *diag, const char *message, const char *expected, const char *actual, const char *help) {
|
||||
if (!diag) return;
|
||||
diag->code = 2002;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message ? message : "std.http.listen temporary file setup failed");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", expected ? expected : "safe temporary listener artifact");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "temporary file setup failed");
|
||||
snprintf(diag->help, sizeof(diag->help), "%s", help ? help : "inspect /tmp permissions and available disk space");
|
||||
}
|
||||
|
||||
bool z_http_listen_temp_path(const char *temp_dir, const char *leaf, char *out, size_t out_cap, ZDiag *diag) {
|
||||
if (!temp_dir || !leaf || !out || out_cap == 0) {
|
||||
listen_temp_diag(diag, "std.http.listen runner is missing temporary path storage", "temporary directory and file name", "missing path storage", NULL);
|
||||
return false;
|
||||
}
|
||||
int n = snprintf(out, out_cap, "%s%c%s", temp_dir, ZERO_LISTEN_PATH_SEP, leaf);
|
||||
if (n < 0 || (size_t)n >= out_cap) {
|
||||
listen_temp_diag(diag, "std.http.listen temporary path is too long", "temporary path within host PATH_MAX", "path truncated", "use a shorter TMPDIR or package path");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static const char *listen_temp_root(void) {
|
||||
const char *root = getenv("TEMP");
|
||||
if (root && root[0]) return root;
|
||||
root = getenv("TMP");
|
||||
if (root && root[0]) return root;
|
||||
return ".";
|
||||
}
|
||||
|
||||
static bool listen_create_unique_temp_dir(char *out, size_t out_cap, ZDiag *diag) {
|
||||
const char *root = listen_temp_root();
|
||||
unsigned long pid = (unsigned long)_getpid();
|
||||
for (unsigned long attempt = 0; attempt < 1000; attempt++) {
|
||||
int n = snprintf(out, out_cap, "%s%czero-listen-%lu-%lu", root, ZERO_LISTEN_PATH_SEP, pid, attempt);
|
||||
if (n < 0 || (size_t)n >= out_cap) {
|
||||
listen_temp_diag(diag, "std.http.listen temporary directory path is too long", "temporary path within host PATH_MAX", "path truncated", "use a shorter TEMP directory");
|
||||
return false;
|
||||
}
|
||||
if (_mkdir(out) == 0) return true;
|
||||
if (errno == EEXIST) continue;
|
||||
listen_temp_diag(diag, "std.http.listen could not create a private temporary directory", "private zero-listen-* directory", strerror(errno), "check temporary directory permissions and available disk space");
|
||||
return false;
|
||||
}
|
||||
listen_temp_diag(diag, "std.http.listen could not create a unique temporary directory", "unused zero-listen-* directory", "temporary directory attempts exhausted", "clean stale temporary directories and retry");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool z_http_listen_create_temp_dir(char *out, size_t out_cap, ZDiag *diag) {
|
||||
#if defined(_WIN32)
|
||||
if (!out || out_cap == 0) {
|
||||
listen_temp_diag(diag, "std.http.listen temporary directory buffer is too small", "temporary path storage", "path buffer too small", NULL);
|
||||
return false;
|
||||
}
|
||||
return listen_create_unique_temp_dir(out, out_cap, diag);
|
||||
#else
|
||||
static const char template_path[] = "/tmp/zero-listen-XXXXXX";
|
||||
if (!out || out_cap < sizeof(template_path)) {
|
||||
listen_temp_diag(diag, "std.http.listen temporary directory buffer is too small", "temporary path storage", "path buffer too small", NULL);
|
||||
return false;
|
||||
}
|
||||
memcpy(out, template_path, sizeof(template_path));
|
||||
if (!mkdtemp(out)) {
|
||||
listen_temp_diag(diag, "std.http.listen could not create a private temporary directory", "private /tmp/zero-listen-* directory", strerror(errno), "check /tmp permissions and available disk space");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool listen_temp_dir_is_owned(const char *temp_dir) {
|
||||
#if defined(_WIN32)
|
||||
static const char marker[] = "zero-listen-";
|
||||
if (!temp_dir || !temp_dir[0]) return false;
|
||||
const char *base = strrchr(temp_dir, '/');
|
||||
const char *backslash = strrchr(temp_dir, '\\');
|
||||
if (!base || (backslash && backslash > base)) base = backslash;
|
||||
const char *name = base ? base + 1 : temp_dir;
|
||||
size_t marker_len = sizeof(marker) - 1;
|
||||
return strncmp(name, marker, marker_len) == 0 && name[marker_len] != '\0';
|
||||
#else
|
||||
static const char prefix[] = "/tmp/zero-listen-";
|
||||
size_t prefix_len = sizeof(prefix) - 1;
|
||||
if (!temp_dir || strncmp(temp_dir, prefix, prefix_len) != 0) return false;
|
||||
const char *suffix = temp_dir + prefix_len;
|
||||
return suffix[0] != '\0' && strchr(suffix, '/') == NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int listen_stat_path(const char *path, struct stat *st) {
|
||||
#if defined(_WIN32)
|
||||
return stat(path, st);
|
||||
#else
|
||||
return lstat(path, st);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool listen_stat_is_dir(const struct stat *st) {
|
||||
#if defined(_WIN32)
|
||||
return st && (st->st_mode & _S_IFDIR) != 0;
|
||||
#else
|
||||
return st && S_ISDIR(st->st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int listen_remove_file(const char *path) {
|
||||
#if defined(_WIN32)
|
||||
return remove(path);
|
||||
#else
|
||||
return unlink(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int listen_remove_dir(const char *path) {
|
||||
#if defined(_WIN32)
|
||||
return _rmdir(path);
|
||||
#else
|
||||
return rmdir(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void listen_remove_tree(const char *path, unsigned depth) {
|
||||
if (!path || depth > 8) return;
|
||||
DIR *dir = opendir(path);
|
||||
if (!dir) {
|
||||
listen_remove_file(path);
|
||||
return;
|
||||
}
|
||||
struct dirent *entry = NULL;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) continue;
|
||||
char child[PATH_MAX];
|
||||
int n = snprintf(child, sizeof(child), "%s%c%s", path, ZERO_LISTEN_PATH_SEP, entry->d_name);
|
||||
if (n < 0 || (size_t)n >= sizeof(child)) continue;
|
||||
struct stat st;
|
||||
if (listen_stat_path(child, &st) != 0) {
|
||||
listen_remove_file(child);
|
||||
continue;
|
||||
}
|
||||
if (listen_stat_is_dir(&st)) listen_remove_tree(child, depth + 1);
|
||||
else listen_remove_file(child);
|
||||
}
|
||||
closedir(dir);
|
||||
listen_remove_dir(path);
|
||||
}
|
||||
|
||||
void z_http_listen_cleanup_temp_dir(const char *temp_dir) {
|
||||
if (!temp_dir || !temp_dir[0]) return;
|
||||
if (!listen_temp_dir_is_owned(temp_dir)) return;
|
||||
listen_remove_tree(temp_dir, 0);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef ZERO_C_HTTP_LISTEN_TEMP_H
|
||||
#define ZERO_C_HTTP_LISTEN_TEMP_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
bool z_http_listen_temp_path(const char *temp_dir, const char *leaf, char *out, size_t out_cap, ZDiag *diag);
|
||||
bool z_http_listen_create_temp_dir(char *out, size_t out_cap, ZDiag *diag);
|
||||
void z_http_listen_cleanup_temp_dir(const char *temp_dir);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,303 @@
|
||||
#include "init_template.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static void init_append_json_string(ZBuf *buf, const char *text) {
|
||||
zbuf_append_char(buf, '"');
|
||||
for (const unsigned char *cursor = (const unsigned char *)(text ? text : ""); *cursor; cursor++) {
|
||||
switch (*cursor) {
|
||||
case '"': zbuf_append(buf, "\\\""); break;
|
||||
case '\\': zbuf_append(buf, "\\\\"); break;
|
||||
case '\n': zbuf_append(buf, "\\n"); break;
|
||||
case '\r': zbuf_append(buf, "\\r"); break;
|
||||
case '\t': zbuf_append(buf, "\\t"); break;
|
||||
default:
|
||||
if (*cursor < 0x20) zbuf_appendf(buf, "\\u%04x", *cursor);
|
||||
else zbuf_append_char(buf, (char)*cursor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
zbuf_append_char(buf, '"');
|
||||
}
|
||||
|
||||
static char *init_join_path(const char *left, const char *right) {
|
||||
ZBuf buf;
|
||||
zbuf_init(&buf);
|
||||
zbuf_append(&buf, left ? left : "");
|
||||
if (buf.len > 0 && buf.data[buf.len - 1] != '/') zbuf_append_char(&buf, '/');
|
||||
zbuf_append(&buf, right ? right : "");
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
static bool init_path_exists(const char *path) {
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
static bool init_write_project_file(const char *root, const char *relative, const char *text, ZDiag *diag) {
|
||||
char *path = init_join_path(root, relative);
|
||||
bool ok = z_write_file(path, text, diag);
|
||||
free(path);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool init_write_project_buf(const char *root, const char *relative, ZBuf *buf, ZDiag *diag) {
|
||||
bool ok = init_write_project_file(root, relative, buf->data ? buf->data : "", diag);
|
||||
zbuf_free(buf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void append_template_manifest_json(ZBuf *buf, const char *name, const char *main_path) {
|
||||
zbuf_append(buf, "{\n");
|
||||
zbuf_append(buf, " \"package\": {\n");
|
||||
zbuf_append(buf, " \"name\": ");
|
||||
init_append_json_string(buf, name);
|
||||
zbuf_append(buf, ",\n \"version\": \"0.1.0\",\n \"license\": \"MIT\"\n");
|
||||
zbuf_append(buf, " },\n");
|
||||
zbuf_append(buf, " \"targets\": {\n");
|
||||
zbuf_append(buf, " \"cli\": {\n");
|
||||
zbuf_append(buf, " \"kind\": \"exe\",\n");
|
||||
zbuf_append(buf, " \"main\": ");
|
||||
init_append_json_string(buf, main_path);
|
||||
zbuf_append(buf, ",\n \"defaultTarget\": \"linux-musl-x64\",\n \"devTarget\": \"host\",\n \"releaseProfile\": \"release-small\"\n }\n");
|
||||
zbuf_append(buf, " },\n");
|
||||
zbuf_append(buf, " \"deps\": {},\n");
|
||||
zbuf_append(buf, " \"profiles\": {\n");
|
||||
zbuf_append(buf, " \"dev\": { \"inherits\": \"dev\" },\n");
|
||||
zbuf_append(buf, " \"release-small\": { \"inherits\": \"release-small\" }\n");
|
||||
zbuf_append(buf, " },\n");
|
||||
zbuf_append(buf, " \"docs\": {\n");
|
||||
zbuf_append(buf, " \"readme\": \"README.md\",\n");
|
||||
zbuf_append(buf, " \"examples\": [");
|
||||
init_append_json_string(buf, main_path);
|
||||
zbuf_append(buf, "]\n");
|
||||
zbuf_append(buf, " }\n");
|
||||
zbuf_append(buf, "}\n");
|
||||
}
|
||||
|
||||
static void append_graph_first_manifest_json(ZBuf *buf, const char *name, const char *main_path) {
|
||||
zbuf_append(buf, "{\n");
|
||||
zbuf_append(buf, " \"package\": {\n");
|
||||
zbuf_append(buf, " \"name\": ");
|
||||
init_append_json_string(buf, name);
|
||||
zbuf_append(buf, ",\n \"version\": \"0.1.0\",\n \"license\": \"MIT\"\n");
|
||||
zbuf_append(buf, " },\n");
|
||||
zbuf_append(buf, " \"targets\": {\n");
|
||||
zbuf_append(buf, " \"cli\": {\n");
|
||||
zbuf_append(buf, " \"kind\": \"exe\",\n");
|
||||
zbuf_append(buf, " \"main\": ");
|
||||
init_append_json_string(buf, main_path);
|
||||
zbuf_append(buf, ",\n \"defaultTarget\": \"linux-musl-x64\",\n \"devTarget\": \"host\",\n \"releaseProfile\": \"release-small\"\n }\n");
|
||||
zbuf_append(buf, " },\n");
|
||||
zbuf_append(buf, " \"deps\": {},\n");
|
||||
zbuf_append(buf, " \"profiles\": {\n");
|
||||
zbuf_append(buf, " \"dev\": { \"inherits\": \"dev\" },\n");
|
||||
zbuf_append(buf, " \"release-small\": { \"inherits\": \"release-small\" }\n");
|
||||
zbuf_append(buf, " }\n");
|
||||
zbuf_append(buf, "}\n");
|
||||
}
|
||||
|
||||
static void append_graph_first_manifest_toml(ZBuf *buf, const char *name, const char *main_path) {
|
||||
zbuf_append(buf, "[package]\n");
|
||||
zbuf_append(buf, "name = ");
|
||||
init_append_json_string(buf, name);
|
||||
zbuf_append(buf, "\nversion = \"0.1.0\"\nlicense = \"MIT\"\n\n");
|
||||
zbuf_append(buf, "[targets.cli]\n");
|
||||
zbuf_append(buf, "kind = \"exe\"\nmain = ");
|
||||
init_append_json_string(buf, main_path);
|
||||
zbuf_append(buf, "\ndefaultTarget = \"linux-musl-x64\"\ndevTarget = \"host\"\nreleaseProfile = \"release-small\"\n\n");
|
||||
zbuf_append(buf, "[deps]\n\n");
|
||||
zbuf_append(buf, "[profiles.dev]\n");
|
||||
zbuf_append(buf, "inherits = \"dev\"\n\n");
|
||||
zbuf_append(buf, "[profiles.release-small]\n");
|
||||
zbuf_append(buf, "inherits = \"release-small\"\n");
|
||||
}
|
||||
|
||||
static const char *init_manifest_file_name(const char *manifest_format, const char *root, ZDiag *diag) {
|
||||
const char *format = manifest_format && manifest_format[0] ? manifest_format : "toml";
|
||||
if (strcmp(format, "json") == 0) return "zero.json";
|
||||
if (strcmp(format, "toml") == 0) return "zero.toml";
|
||||
if (diag) {
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 2002;
|
||||
diag->path = root ? root : ".";
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "manifest format is not supported");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "--manifest toml|json");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", format ? format : "");
|
||||
snprintf(diag->help, sizeof(diag->help), "omit --manifest for zero.toml, or pass --manifest json for compatibility metadata");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool z_init_write_manifest(const char *root, const char *name, const char *main_path, const char *manifest_format, bool template_manifest, const char **manifest_file_name_out, ZDiag *diag) {
|
||||
const char *manifest_file_name = init_manifest_file_name(manifest_format, root, diag);
|
||||
if (!manifest_file_name) return false;
|
||||
ZBuf manifest;
|
||||
zbuf_init(&manifest);
|
||||
if (strcmp(manifest_file_name, "zero.toml") == 0) append_graph_first_manifest_toml(&manifest, name, main_path);
|
||||
else if (template_manifest) append_template_manifest_json(&manifest, name, main_path);
|
||||
else append_graph_first_manifest_json(&manifest, name, main_path);
|
||||
if (!init_write_project_buf(root, manifest_file_name, &manifest, diag)) return false;
|
||||
if (manifest_file_name_out) *manifest_file_name_out = manifest_file_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_init_template_kind_is_known(const char *kind) {
|
||||
return kind && (strcmp(kind, "cli") == 0 || strcmp(kind, "lib") == 0 || strcmp(kind, "package") == 0);
|
||||
}
|
||||
|
||||
const char *z_init_template_projection_path(const char *kind) {
|
||||
return kind && strcmp(kind, "lib") == 0 ? "src/lib.0" : "src/main.0";
|
||||
}
|
||||
|
||||
static bool init_template_reject_existing_path(const char *root, const char *relative, ZDiag *diag) {
|
||||
char *path = init_join_path(root, relative);
|
||||
if (init_path_exists(path)) {
|
||||
diag->code = 2002;
|
||||
diag->path = path;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "template output already exists");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "project path without generated template files");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", path);
|
||||
snprintf(diag->help, sizeof(diag->help), "choose a new project path or remove the conflicting file");
|
||||
return false;
|
||||
}
|
||||
free(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_init_template_reject_existing_outputs(const char *root, const char *kind, ZDiag *diag) {
|
||||
if (!init_template_reject_existing_path(root, "README.md", diag)) return false;
|
||||
if (!init_template_reject_existing_path(root, ".gitignore", diag)) return false;
|
||||
if (strcmp(kind, "cli") == 0) {
|
||||
if (!init_template_reject_existing_path(root, "src/main.0", diag)) return false;
|
||||
if (!init_template_reject_existing_path(root, "src/lib.0", diag)) return false;
|
||||
} else if (strcmp(kind, "lib") == 0) {
|
||||
if (!init_template_reject_existing_path(root, "src/lib.0", diag)) return false;
|
||||
} else if (strcmp(kind, "package") == 0) {
|
||||
if (!init_template_reject_existing_path(root, "src/main.0", diag)) return false;
|
||||
if (!init_template_reject_existing_path(root, "src/model.0", diag)) return false;
|
||||
if (!init_template_reject_existing_path(root, "src/math.0", diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool create_cli_template(const char *root, const char *name, const char *manifest_format, const char **manifest_file_name, ZDiag *diag) {
|
||||
if (!z_init_write_manifest(root, name, "src/main.0", manifest_format, true, manifest_file_name, diag)) return false;
|
||||
if (!init_write_project_file(root, "src/lib.0",
|
||||
"pub fn greeting_code() -> i32 {\n"
|
||||
" return 42\n"
|
||||
"}\n",
|
||||
diag)) return false;
|
||||
if (!init_write_project_file(root, "src/main.0",
|
||||
"use lib\n\n"
|
||||
"pub fn main(world: World) -> Void raises {\n"
|
||||
" if greeting_code() == 42 {\n"
|
||||
" check world.out.write(\"hello from zero\\n\")\n"
|
||||
" }\n"
|
||||
"}\n\n"
|
||||
"test \"greeting is stable\" {\n"
|
||||
" expect greeting_code() == 42\n"
|
||||
"}\n",
|
||||
diag)) return false;
|
||||
if (!init_write_project_file(root, "README.md",
|
||||
"# Zero CLI\n\n"
|
||||
"This project was created with `zero init --template cli`.\n\n"
|
||||
"Try:\n\n"
|
||||
"```sh\n"
|
||||
"zero check\n"
|
||||
"zero test\n"
|
||||
"zero run\n"
|
||||
"zero dev --json\n"
|
||||
"zero build --target linux-musl-x64 --out .zero/out/app\n"
|
||||
"```\n\n"
|
||||
"The entry point receives `World` explicitly, so I/O is visible in the function signature. The generated output is deterministic and the manifest records the default release target.\n",
|
||||
diag)) return false;
|
||||
return init_write_project_file(root, ".gitignore", ".zero/\n", diag);
|
||||
}
|
||||
|
||||
static bool create_lib_template(const char *root, const char *name, const char *manifest_format, const char **manifest_file_name, ZDiag *diag) {
|
||||
if (!z_init_write_manifest(root, name, "src/lib.0", manifest_format, true, manifest_file_name, diag)) return false;
|
||||
if (!init_write_project_file(root, "src/lib.0",
|
||||
"pub fn add_one(value: i32) -> i32 {\n"
|
||||
" return value + 1\n"
|
||||
"}\n\n"
|
||||
"test \"public api works\" {\n"
|
||||
" expect add_one(41) == 42\n"
|
||||
"}\n",
|
||||
diag)) return false;
|
||||
if (!init_write_project_file(root, "README.md",
|
||||
"# Zero Library\n\n"
|
||||
"This small package exposes one public function, package metadata, and an inline test.\n\n"
|
||||
"Try:\n\n"
|
||||
"```sh\n"
|
||||
"zero check\n"
|
||||
"zero test\n"
|
||||
"zero dev --json\n"
|
||||
"zero inspect --json\n"
|
||||
"zero doc --json\n"
|
||||
"```\n",
|
||||
diag)) return false;
|
||||
return init_write_project_file(root, ".gitignore", ".zero/\n", diag);
|
||||
}
|
||||
|
||||
static bool create_package_template(const char *root, const char *name, const char *manifest_format, const char **manifest_file_name, ZDiag *diag) {
|
||||
if (!z_init_write_manifest(root, name, "src/main.0", manifest_format, true, manifest_file_name, diag)) return false;
|
||||
if (!init_write_project_file(root, "src/model.0",
|
||||
"pub type Point {\n"
|
||||
" value: i32,\n"
|
||||
"}\n",
|
||||
diag)) return false;
|
||||
if (!init_write_project_file(root, "src/math.0",
|
||||
"fn base(value: i32) -> i32 {\n"
|
||||
" return value\n"
|
||||
"}\n\n"
|
||||
"pub fn add_one(value: i32) -> i32 {\n"
|
||||
" return base(value) + 1\n"
|
||||
"}\n",
|
||||
diag)) return false;
|
||||
if (!init_write_project_file(root, "src/main.0",
|
||||
"use math\n"
|
||||
"\n"
|
||||
"use model\n\n"
|
||||
"pub fn main(world: World) -> Void raises {\n"
|
||||
" let point: Point = Point { value: add_one(41) }\n"
|
||||
" if point.value == 42 {\n"
|
||||
" check world.out.write(\"package ok\\n\")\n"
|
||||
" }\n"
|
||||
"}\n\n"
|
||||
"test \"package import works\" {\n"
|
||||
" expect add_one(41) == 42\n"
|
||||
"}\n",
|
||||
diag)) return false;
|
||||
if (!init_write_project_file(root, "README.md",
|
||||
"# Zero Package\n\n"
|
||||
"This template shows package-local imports, one public symbol, and one private helper.\n\n"
|
||||
"Try:\n\n"
|
||||
"```sh\n"
|
||||
"zero check\n"
|
||||
"zero test\n"
|
||||
"zero run\n"
|
||||
"zero dev --json\n"
|
||||
"zero build --target linux-musl-x64 --out .zero/out/app\n"
|
||||
"zero inspect --json\n"
|
||||
"```\n",
|
||||
diag)) return false;
|
||||
return init_write_project_file(root, ".gitignore", ".zero/\n", diag);
|
||||
}
|
||||
|
||||
bool z_init_template_write_files(const char *root, const char *kind, const char *name, const char *manifest_format, const char **manifest_file_name, ZDiag *diag) {
|
||||
if (strcmp(kind, "cli") == 0) return create_cli_template(root, name, manifest_format, manifest_file_name, diag);
|
||||
if (strcmp(kind, "lib") == 0) return create_lib_template(root, name, manifest_format, manifest_file_name, diag);
|
||||
if (strcmp(kind, "package") == 0) return create_package_template(root, name, manifest_format, manifest_file_name, diag);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef ZERO_C_INIT_TEMPLATE_H
|
||||
#define ZERO_C_INIT_TEMPLATE_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool z_init_write_manifest(const char *root, const char *name, const char *main_path, const char *manifest_format, bool template_manifest, const char **manifest_file_name_out, ZDiag *diag);
|
||||
bool z_init_template_kind_is_known(const char *kind);
|
||||
const char *z_init_template_projection_path(const char *kind);
|
||||
bool z_init_template_reject_existing_outputs(const char *root, const char *kind, ZDiag *diag);
|
||||
bool z_init_template_write_files(const char *root, const char *kind, const char *name, const char *manifest_format, const char **manifest_file_name, ZDiag *diag);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
#include "zero.h"
|
||||
|
||||
static void metadata_append_json_string(ZBuf *buf, const char *value) {
|
||||
zbuf_append_char(buf, '"');
|
||||
for (const char *cursor = value ? value : ""; *cursor; cursor++) {
|
||||
unsigned char ch = (unsigned char)*cursor;
|
||||
if (ch == '"') zbuf_append(buf, "\\\"");
|
||||
else if (ch == '\\') zbuf_append(buf, "\\\\");
|
||||
else if (ch == '\n') zbuf_append(buf, "\\n");
|
||||
else if (ch == '\r') zbuf_append(buf, "\\r");
|
||||
else if (ch == '\t') zbuf_append(buf, "\\t");
|
||||
else if (ch < 0x20) zbuf_appendf(buf, "\\u%04x", (unsigned)ch);
|
||||
else zbuf_append_char(buf, (char)ch);
|
||||
}
|
||||
zbuf_append_char(buf, '"');
|
||||
}
|
||||
|
||||
const char *z_llvm_backend_lifecycle_json_text(void) {
|
||||
return "{\"stability\":\"experimental\",\"defaultEligible\":false,\"releaseEligible\":false,\"fallbackEligible\":false,\"supportedReleaseFamily\":\"direct\",\"reason\":\"LLVM is explicit experimental build/run/IR only; direct emitters remain the supported release path\"}";
|
||||
}
|
||||
|
||||
void z_append_llvm_backend_lifecycle_json(ZBuf *buf) {
|
||||
zbuf_append(buf, z_llvm_backend_lifecycle_json_text());
|
||||
}
|
||||
|
||||
void z_append_llvm_backend_lifecycle_field_json(ZBuf *buf) {
|
||||
zbuf_append(buf, ",\"backendLifecycle\":");
|
||||
z_append_llvm_backend_lifecycle_json(buf);
|
||||
}
|
||||
|
||||
void z_append_doctor_llvm_toolchain_json(ZBuf *buf, const ZTargetInfo *host_target, const ZLlvmToolchainPlan *plan) {
|
||||
zbuf_append(buf, ",\n \"llvmToolchain\": {\"status\":");
|
||||
metadata_append_json_string(buf, plan ? plan->status : "unsupported-target");
|
||||
zbuf_append(buf, ",\"target\":");
|
||||
metadata_append_json_string(buf, host_target && host_target->name ? host_target->name : z_host_target());
|
||||
zbuf_append(buf, ",\"driverKind\":\"clang\",\"selectionSource\":");
|
||||
metadata_append_json_string(buf, plan ? plan->selection_source : "path");
|
||||
zbuf_append(buf, ",\"compiler\":");
|
||||
metadata_append_json_string(buf, plan ? plan->compiler : "clang");
|
||||
zbuf_append(buf, ",\"targetTriple\":");
|
||||
metadata_append_json_string(buf, plan ? plan->target_triple : "unknown-unknown-unknown");
|
||||
zbuf_append(buf, ",\"nativeExecutable\":");
|
||||
zbuf_append(buf, plan && plan->native_executable ? "true" : "false");
|
||||
z_append_llvm_backend_lifecycle_field_json(buf);
|
||||
zbuf_append(buf, ",\"reason\":");
|
||||
metadata_append_json_string(buf, plan ? plan->reason : "host target is not supported by native LLVM executable builds");
|
||||
zbuf_append(buf, "}");
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static void llvm_append_json_string(ZBuf *buf, const char *value) {
|
||||
zbuf_append_char(buf, '"');
|
||||
for (const char *cursor = value ? value : ""; *cursor; cursor++) {
|
||||
unsigned char ch = (unsigned char)*cursor;
|
||||
if (ch == '"') zbuf_append(buf, "\\\"");
|
||||
else if (ch == '\\') zbuf_append(buf, "\\\\");
|
||||
else if (ch == '\n') zbuf_append(buf, "\\n");
|
||||
else if (ch == '\r') zbuf_append(buf, "\\r");
|
||||
else if (ch == '\t') zbuf_append(buf, "\\t");
|
||||
else if (ch < 0x20) zbuf_appendf(buf, "\\u%04x", (unsigned)ch);
|
||||
else zbuf_append_char(buf, (char)ch);
|
||||
}
|
||||
zbuf_append_char(buf, '"');
|
||||
}
|
||||
|
||||
static void llvm_append_capabilities_json(ZBuf *buf, const ZTargetInfo *target) {
|
||||
zbuf_append_char(buf, '[');
|
||||
const char *caps[] = {"memory", "stdio", "args", "env", "fs", "net", "proc", "time", "rand", "web", NULL};
|
||||
bool first = true;
|
||||
for (int i = 0; caps[i]; i++) {
|
||||
if (!z_target_has_capability(target, caps[i])) continue;
|
||||
if (!first) zbuf_append_char(buf, ',');
|
||||
first = false;
|
||||
llvm_append_json_string(buf, caps[i]);
|
||||
}
|
||||
zbuf_append_char(buf, ']');
|
||||
}
|
||||
|
||||
static bool llvm_path_is_executable_file(const char *path) {
|
||||
struct stat st;
|
||||
if (!path || !path[0] || stat(path, &st) != 0 || !S_ISREG(st.st_mode)) return false;
|
||||
#if defined(_WIN32)
|
||||
return _access(path, 4) == 0;
|
||||
#else
|
||||
return access(path, X_OK) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool llvm_command_available(const char *name) {
|
||||
if (!name || !name[0]) return false;
|
||||
if (strchr(name, '/') || strchr(name, '\\')) return llvm_path_is_executable_file(name);
|
||||
const char *path = getenv("PATH");
|
||||
if (!path || !path[0]) return false;
|
||||
#if defined(_WIN32)
|
||||
const char separator = ';';
|
||||
#else
|
||||
const char separator = ':';
|
||||
#endif
|
||||
const char *cursor = path;
|
||||
while (*cursor) {
|
||||
const char *end = strchr(cursor, separator);
|
||||
size_t len = end ? (size_t)(end - cursor) : strlen(cursor);
|
||||
if (len > 0) {
|
||||
ZBuf candidate;
|
||||
zbuf_init(&candidate);
|
||||
for (size_t i = 0; i < len; i++) zbuf_append_char(&candidate, cursor[i]);
|
||||
zbuf_append_char(&candidate, '/');
|
||||
zbuf_append(&candidate, name);
|
||||
bool found = llvm_path_is_executable_file(candidate.data);
|
||||
zbuf_free(&candidate);
|
||||
if (found) return true;
|
||||
}
|
||||
if (!end) break;
|
||||
cursor = end + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *z_llvm_target_triple(const ZTargetInfo *target) {
|
||||
if (!target || !target->name) return "unknown-unknown-unknown";
|
||||
if (strcmp(target->name, "darwin-arm64") == 0) return "arm64-apple-darwin";
|
||||
if (strcmp(target->name, "darwin-x64") == 0) return "x86_64-apple-darwin";
|
||||
if (strcmp(target->name, "linux-arm64") == 0) return "aarch64-unknown-linux-gnu";
|
||||
if (strcmp(target->name, "linux-x64") == 0) return "x86_64-unknown-linux-gnu";
|
||||
if (strcmp(target->name, "linux-musl-arm64") == 0) return "aarch64-unknown-linux-musl";
|
||||
if (strcmp(target->name, "linux-musl-x64") == 0) return "x86_64-unknown-linux-musl";
|
||||
if (strcmp(target->name, "win32-arm64.exe") == 0) return "aarch64-pc-windows-msvc";
|
||||
if (strcmp(target->name, "win32-x64.exe") == 0) return "x86_64-pc-windows-msvc";
|
||||
return "unknown-unknown-unknown";
|
||||
}
|
||||
|
||||
static bool llvm_native_target_supported(const ZTargetInfo *target) {
|
||||
if (!target || !z_target_is_host(target)) return false;
|
||||
return target->os && (strcmp(target->os, "macos") == 0 || strcmp(target->os, "linux") == 0);
|
||||
}
|
||||
|
||||
const char *z_llvm_optimization_level(const char *profile) {
|
||||
const char *value = profile && profile[0] ? profile : "release";
|
||||
if (strcmp(value, "debug") == 0 || strcmp(value, "dev") == 0) return "-O0";
|
||||
if (strcmp(value, "fast") == 0 || strcmp(value, "release-fast") == 0) return "-O2";
|
||||
if (strcmp(value, "audit") == 0) return "-O1";
|
||||
if (strcmp(value, "tiny") == 0 || strcmp(value, "small") == 0 || strcmp(value, "release-small") == 0 || strcmp(value, "release") == 0) return "-Oz";
|
||||
return "-Oz";
|
||||
}
|
||||
|
||||
ZLlvmToolchainPlan z_llvm_toolchain_plan(const ZTargetInfo *target) {
|
||||
const char *env = getenv("ZERO_LLVM_CLANG");
|
||||
bool env_selected = env && env[0];
|
||||
const char *compiler = env_selected ? env : "clang";
|
||||
bool target_supported = llvm_native_target_supported(target);
|
||||
bool tool_available = llvm_command_available(compiler);
|
||||
const char *status = "ready";
|
||||
const char *reason = "clang can compile textual LLVM IR for this host target";
|
||||
if (!target_supported) {
|
||||
status = "unsupported-target";
|
||||
reason = "native LLVM executable builds are currently limited to macOS/Linux host targets";
|
||||
} else if (!tool_available) {
|
||||
status = "tool-missing";
|
||||
reason = "clang is required for native LLVM executable builds";
|
||||
}
|
||||
ZLlvmToolchainPlan plan = {
|
||||
.driver_kind = "clang",
|
||||
.selection_source = env_selected ? "env" : "path",
|
||||
.compiler = compiler,
|
||||
.target_triple = z_llvm_target_triple(target),
|
||||
.status = status,
|
||||
.reason = reason,
|
||||
.target_supported = target_supported,
|
||||
.tool_available = tool_available,
|
||||
.native_executable = target_supported && tool_available
|
||||
};
|
||||
return plan;
|
||||
}
|
||||
|
||||
ZToolchainPlan z_llvm_c_toolchain_plan(const ZTargetInfo *target) {
|
||||
ZLlvmToolchainPlan llvm = z_llvm_toolchain_plan(target);
|
||||
ZToolchainPlan plan = {
|
||||
.driver_kind = "override-cc",
|
||||
.selection_source = llvm.selection_source,
|
||||
.compiler = llvm.compiler,
|
||||
.target_triple = llvm.target_triple,
|
||||
.linker_flavor = "clang",
|
||||
.libc_mode = target ? z_target_libc_mode(target) : "host-default",
|
||||
.sysroot_env = "",
|
||||
.sysroot_path = "",
|
||||
.sysroot_status = "not-required",
|
||||
.requires_sysroot = false,
|
||||
.uses_target_flag = false,
|
||||
.uses_zig_cache = false,
|
||||
.strip_artifact = false
|
||||
};
|
||||
return plan;
|
||||
}
|
||||
|
||||
bool z_llvm_native_executable_ready(const ZTargetInfo *target, const char *path, ZDiag *diag) {
|
||||
ZLlvmToolchainPlan plan = z_llvm_toolchain_plan(target);
|
||||
if (plan.native_executable) return true;
|
||||
if (diag) {
|
||||
memset(diag, 0, sizeof(*diag));
|
||||
diag->code = 2004;
|
||||
diag->path = path;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
if (!plan.target_supported) {
|
||||
snprintf(diag->message, sizeof(diag->message), "LLVM native executable backend does not support target '%s' yet", target && target->name ? target->name : "unknown");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "host macOS/Linux target for native LLVM executable builds");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "target=%s host=%s", target && target->name ? target->name : "unknown", z_host_target());
|
||||
snprintf(diag->help, sizeof(diag->help), "use --emit llvm-ir to inspect LLVM IR or choose the current host target for native LLVM execution");
|
||||
z_backend_blocker_set(&diag->backend_blocker,
|
||||
target && target->name ? target->name : "unknown",
|
||||
target && target->object_format ? target->object_format : "unknown",
|
||||
"llvm",
|
||||
"target-selection",
|
||||
"llvm host executable target");
|
||||
} else {
|
||||
snprintf(diag->message, sizeof(diag->message), "LLVM native executable backend requires clang");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "clang on PATH or ZERO_LLVM_CLANG");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s not executable or not found", plan.compiler ? plan.compiler : "clang");
|
||||
snprintf(diag->help, sizeof(diag->help), "install clang or set ZERO_LLVM_CLANG to a clang-compatible driver");
|
||||
z_backend_blocker_set(&diag->backend_blocker,
|
||||
target && target->name ? target->name : "unknown",
|
||||
target && target->object_format ? target->object_format : "unknown",
|
||||
"llvm",
|
||||
"toolchain",
|
||||
"clang");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_llvm_link_executable(const char *llvm_file, const char *runtime_object_file, const char *exe_file, const ZToolchainPlan *plan, const ZTargetInfo *target, const char *profile, bool links_zero_runtime, ZDiag *diag) {
|
||||
ZBuf pre_flags;
|
||||
zbuf_init(&pre_flags);
|
||||
zbuf_append(&pre_flags, "-Wno-override-module ");
|
||||
zbuf_append(&pre_flags, z_llvm_optimization_level(profile));
|
||||
#if defined(__linux__)
|
||||
zbuf_append(&pre_flags, " -no-pie");
|
||||
#endif
|
||||
const char *object_files[2] = {0};
|
||||
size_t object_count = 0;
|
||||
if (llvm_file && llvm_file[0]) object_files[object_count++] = llvm_file;
|
||||
if (runtime_object_file && runtime_object_file[0]) object_files[object_count++] = runtime_object_file;
|
||||
bool ok = z_toolchain_link_objects(plan, target, object_files, object_count, exe_file, pre_flags.data ? pre_flags.data : "-Wno-override-module", "2>/dev/null");
|
||||
zbuf_free(&pre_flags);
|
||||
if (!ok && diag) {
|
||||
diag->code = 2004;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "LLVM executable link failed");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", links_zero_runtime ? "LLVM IR plus zero runtime object link successfully with clang" : "LLVM IR links successfully with clang");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "clang LLVM link command failed");
|
||||
snprintf(diag->help, sizeof(diag->help), "inspect the emitted LLVM IR, runtime object, and clang installation; use --emit llvm-ir to write the IR only");
|
||||
z_backend_blocker_set(&diag->backend_blocker, target && target->name ? target->name : "unknown", target && target->object_format ? target->object_format : "unknown", "llvm", "toolchain", "clang");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void z_append_llvm_toolchain_plan_json(ZBuf *buf, const ZTargetInfo *target) {
|
||||
ZLlvmToolchainPlan plan = z_llvm_toolchain_plan(target);
|
||||
zbuf_append(buf, "{\"driverKind\":\"clang\",\"selectionSource\":");
|
||||
llvm_append_json_string(buf, plan.selection_source);
|
||||
zbuf_append(buf, ",\"compiler\":");
|
||||
llvm_append_json_string(buf, plan.compiler);
|
||||
zbuf_append(buf, ",\"targetTriple\":");
|
||||
llvm_append_json_string(buf, plan.target_triple);
|
||||
zbuf_append(buf, ",\"linkerFlavor\":\"clang\",\"libcMode\":");
|
||||
llvm_append_json_string(buf, target ? z_target_libc_mode(target) : "host-default");
|
||||
zbuf_append(buf, ",\"requiresSysroot\":false,\"sysrootEnv\":\"\",\"sysrootStatus\":\"not-required\",\"usesTargetFlag\":false,\"usesToolchainCache\":false,\"stripArtifact\":false,\"available\":");
|
||||
zbuf_append(buf, plan.tool_available ? "true" : "false");
|
||||
zbuf_append(buf, ",\"status\":");
|
||||
llvm_append_json_string(buf, plan.status);
|
||||
zbuf_append(buf, ",\"reason\":");
|
||||
llvm_append_json_string(buf, plan.reason);
|
||||
z_append_llvm_backend_lifecycle_field_json(buf);
|
||||
zbuf_append(buf, "}");
|
||||
}
|
||||
|
||||
void z_append_llvm_target_backend_json(ZBuf *buf, const ZTargetInfo *target) {
|
||||
ZLlvmToolchainPlan llvm = z_llvm_toolchain_plan(target);
|
||||
zbuf_append(buf, "{\"status\":");
|
||||
llvm_append_json_string(buf, llvm.status);
|
||||
zbuf_append(buf, ",\"buildable\":");
|
||||
zbuf_append(buf, llvm.native_executable ? "true" : "false");
|
||||
zbuf_append(buf, ",\"emit\":[\"llvm-ir\"");
|
||||
if (llvm.native_executable) zbuf_append(buf, ",\"exe\"");
|
||||
zbuf_append(buf, "],\"targetTriple\":");
|
||||
llvm_append_json_string(buf, llvm.target_triple);
|
||||
zbuf_append(buf, ",\"toolchain\":{\"driverKind\":\"clang\",\"selectionSource\":");
|
||||
llvm_append_json_string(buf, llvm.selection_source);
|
||||
zbuf_append(buf, ",\"available\":");
|
||||
zbuf_append(buf, llvm.tool_available ? "true" : "false");
|
||||
zbuf_append(buf, "},\"reason\":");
|
||||
llvm_append_json_string(buf, llvm.reason);
|
||||
z_append_llvm_backend_lifecycle_field_json(buf);
|
||||
zbuf_append(buf, "}");
|
||||
}
|
||||
|
||||
void z_append_llvm_ir_backend_json(ZBuf *buf, const SourceInput *input, const ZTargetInfo *target, const char *emit_kind) {
|
||||
bool links_zero_runtime = input && input->direct_runtime_helper_count > 0;
|
||||
zbuf_append(buf, "{\"internalIr\":{\"typeRepresentation\":\"Zero MIR scalar values\",\"controlFlowRepresentation\":\"LLVM textual IR blocks\",\"callRepresentation\":\"direct MIR calls\",\"debugRepresentation\":\"source spans retained on diagnostics\"}");
|
||||
zbuf_append(buf, ",\"objectEmission\":{\"path\":\"llvm-ir\",\"functions\":true,\"dataSections\":");
|
||||
zbuf_append(buf, input && input->direct_readonly_data_bytes > 0 ? "true" : "false");
|
||||
zbuf_appendf(buf, ",\"symbols\":true,\"relocations\":\"none\",\"symbolCount\":%zu,\"internalHelperCount\":0}", input ? input->direct_function_count + input->direct_runtime_helper_count : 0);
|
||||
zbuf_append(buf, ",\"linking\":{\"linkerFlavor\":\"none\",\"objectFormat\":");
|
||||
llvm_append_json_string(buf, target && target->object_format ? target->object_format : "unknown");
|
||||
zbuf_appendf(buf, ",\"targetLibraries\":\"%s\",\"symbolMap\":\"llvm-module\",\"externalToolchain\":\"none\",\"toolchainSource\":\"%s\",\"stripArtifacts\":false}", links_zero_runtime ? "zero-runtime" : "none", links_zero_runtime ? "textual-llvm-ir-runtime-link-plan" : "textual-llvm-ir");
|
||||
zbuf_appendf(buf, ",\"linkerPlan\":{\"format\":\"llvm-ir\",\"flavor\":\"none\",\"archives\":[],\"staticLibraries\":%s,\"importLibraries\":[],\"systemLibraries\":[],\"rpaths\":[],\"loadPaths\":[],\"visibility\":\"exported-c-and-main-only\",\"crossLinking\":false,\"externalToolchain\":\"none\",\"reproducible\":true,\"libcMode\":\"none\",\"requiresSysroot\":false,\"sysrootStatus\":\"not-required\"}", links_zero_runtime ? "[\"zero_runtime.o\"]" : "[]");
|
||||
zbuf_append(buf, ",\"targetFacts\":{\"directAvailable\":false,\"llvmAvailable\":true,\"status\":\"ir-only\",\"selectedEmitter\":\"llvm-ir\",\"objectFormat\":");
|
||||
llvm_append_json_string(buf, target && target->object_format ? target->object_format : "unknown");
|
||||
zbuf_append(buf, ",\"arch\":");
|
||||
llvm_append_json_string(buf, target && target->arch ? target->arch : "unknown");
|
||||
zbuf_append(buf, ",\"abi\":");
|
||||
llvm_append_json_string(buf, target && target->abi ? target->abi : "");
|
||||
zbuf_append(buf, ",\"libcMode\":");
|
||||
llvm_append_json_string(buf, z_target_libc_mode(target));
|
||||
zbuf_append(buf, ",\"requiresSysroot\":false,\"capabilities\":");
|
||||
llvm_append_capabilities_json(buf, target);
|
||||
zbuf_append(buf, ",\"fallbackPolicy\":\"none\",\"reason\":\"LLVM textual IR emission is available; native host executable output requires --backend llvm --emit exe on a supported host with clang\"}");
|
||||
zbuf_appendf(buf, ",\"moduleCount\":%zu,\"emitKind\":", input ? input->module_count : 0);
|
||||
llvm_append_json_string(buf, emit_kind ? emit_kind : "llvm-ir");
|
||||
z_append_llvm_backend_lifecycle_field_json(buf);
|
||||
zbuf_append(buf, ",\"backendFamily\":\"llvm\"}");
|
||||
}
|
||||
|
||||
void z_append_llvm_native_backend_json(ZBuf *buf, const SourceInput *input, const ZTargetInfo *target, const char *emit_kind) {
|
||||
bool links_zero_runtime = input && input->direct_runtime_helper_count > 0;
|
||||
ZLlvmToolchainPlan plan = z_llvm_toolchain_plan(target);
|
||||
zbuf_append(buf, "{\"internalIr\":{\"typeRepresentation\":\"Zero MIR scalar values\",\"controlFlowRepresentation\":\"LLVM textual IR lowered by external clang\",\"callRepresentation\":\"LLVM direct calls\",\"functionIdentity\":\"module-qualified-stable-sorted\",\"debugRepresentation\":\"source spans retained on MIR nodes\"}");
|
||||
zbuf_append(buf, ",\"objectEmission\":{\"path\":\"llvm-clang-exe\",\"functions\":true,\"dataSections\":");
|
||||
zbuf_append(buf, input && input->direct_readonly_data_bytes > 0 ? "true" : "false");
|
||||
zbuf_appendf(buf, ",\"symbols\":true,\"relocations\":\"llvm-toolchain\",\"symbolCount\":%zu,\"internalHelperCount\":0}", input ? input->direct_function_count + input->direct_runtime_helper_count : 0);
|
||||
zbuf_append(buf, ",\"linking\":{\"linkerFlavor\":\"clang\",\"objectFormat\":");
|
||||
llvm_append_json_string(buf, target && target->object_format ? target->object_format : "unknown");
|
||||
zbuf_append(buf, ",\"targetLibraries\":");
|
||||
llvm_append_json_string(buf, links_zero_runtime ? "zero-runtime" : "none");
|
||||
zbuf_append(buf, ",\"symbolMap\":\"llvm-module\",\"externalToolchain\":");
|
||||
llvm_append_json_string(buf, plan.compiler);
|
||||
zbuf_append(buf, ",\"toolchainSource\":\"llvm-ir-clang-link-plan\",\"stripArtifacts\":false}");
|
||||
zbuf_append(buf, ",\"linkerPlan\":{\"format\":\"llvm-ir-to-native\",\"flavor\":\"clang\",\"archives\":[],\"staticLibraries\":");
|
||||
zbuf_append(buf, links_zero_runtime ? "[\"zero_runtime.o\"]" : "[]");
|
||||
zbuf_append(buf, ",\"importLibraries\":[],\"systemLibraries\":[],\"rpaths\":[],\"loadPaths\":[],\"visibility\":\"exported-c-and-main-only\",\"crossLinking\":false,\"externalToolchain\":");
|
||||
llvm_append_json_string(buf, plan.compiler);
|
||||
zbuf_append(buf, ",\"reproducible\":false,\"libcMode\":");
|
||||
llvm_append_json_string(buf, target ? z_target_libc_mode(target) : "host-default");
|
||||
zbuf_append(buf, ",\"requiresSysroot\":false,\"sysrootStatus\":\"not-required\",\"targetTriple\":");
|
||||
llvm_append_json_string(buf, plan.target_triple);
|
||||
zbuf_append(buf, "}");
|
||||
zbuf_append(buf, ",\"targetFacts\":{\"directAvailable\":false,\"llvmAvailable\":");
|
||||
zbuf_append(buf, plan.native_executable ? "true" : "false");
|
||||
zbuf_append(buf, ",\"status\":");
|
||||
llvm_append_json_string(buf, plan.status);
|
||||
zbuf_append(buf, ",\"selectedEmitter\":\"llvm-clang-exe\",\"objectFormat\":");
|
||||
llvm_append_json_string(buf, target && target->object_format ? target->object_format : "unknown");
|
||||
zbuf_append(buf, ",\"arch\":");
|
||||
llvm_append_json_string(buf, target && target->arch ? target->arch : "unknown");
|
||||
zbuf_append(buf, ",\"abi\":");
|
||||
llvm_append_json_string(buf, target && target->abi ? target->abi : "");
|
||||
zbuf_append(buf, ",\"libcMode\":");
|
||||
llvm_append_json_string(buf, z_target_libc_mode(target));
|
||||
zbuf_append(buf, ",\"requiresSysroot\":false,\"capabilities\":");
|
||||
llvm_append_capabilities_json(buf, target);
|
||||
zbuf_append(buf, ",\"fallbackPolicy\":\"none\",\"reason\":");
|
||||
llvm_append_json_string(buf, plan.reason);
|
||||
zbuf_append(buf, ",\"toolchain\":");
|
||||
z_append_llvm_toolchain_plan_json(buf, target);
|
||||
zbuf_append(buf, "}");
|
||||
zbuf_appendf(buf, ",\"moduleCount\":%zu,\"emitKind\":", input ? input->module_count : 0);
|
||||
llvm_append_json_string(buf, emit_kind ? emit_kind : "exe");
|
||||
z_append_llvm_backend_lifecycle_field_json(buf);
|
||||
zbuf_append(buf, ",\"backendFamily\":\"llvm\"}");
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
#include "macho_emit_state.h"
|
||||
#include "macho_format.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *const runtime_helper_symbols[MACHO_RUNTIME_HELPER_COUNT] = {
|
||||
"_zero_world_write",
|
||||
"_zero_json_parse_bytes",
|
||||
"_zero_json_diagnostic",
|
||||
"_zero_json_field",
|
||||
"_zero_json_lookup_scalar",
|
||||
"_zero_json_string_decode",
|
||||
"_zero_json_string_field",
|
||||
"_zero_json_write_string",
|
||||
"_zero_json_write_field_raw",
|
||||
"_zero_json_write_field_string",
|
||||
"_zero_json_write_field_u32",
|
||||
"_zero_json_write_field_bool",
|
||||
"_zero_json_write_object1_string",
|
||||
"_zero_json_write_object1_u32",
|
||||
"_zero_json_write_object1_bool",
|
||||
"_zero_json_write_object2_fields",
|
||||
"_zero_json_write_object2_string_field",
|
||||
"_zero_json_write_object2_u32_field",
|
||||
"_zero_json_write_object2_bool_field",
|
||||
"_zero_json_write_array2_strings",
|
||||
"_zero_json_write_array2_u32",
|
||||
"_zero_json_write_array2_bools",
|
||||
"_zero_http_fetch_result",
|
||||
"_zero_http_result_ok",
|
||||
"_zero_http_result_status",
|
||||
"_zero_http_result_body_len",
|
||||
"_zero_http_result_error",
|
||||
"_zero_http_response_len",
|
||||
"_zero_http_response_headers_len",
|
||||
"_zero_http_response_body_offset",
|
||||
"_zero_http_header_value",
|
||||
"_zero_http_header_found",
|
||||
"_zero_http_header_offset",
|
||||
"_zero_http_header_len",
|
||||
"_zero_http_write_json_response",
|
||||
"_zero_http_request_method_name",
|
||||
"_zero_http_request_path",
|
||||
"_zero_http_request_matches",
|
||||
"_zero_http_request_body_within",
|
||||
"_zero_ascii_op",
|
||||
"_zero_text_op",
|
||||
"_zero_parse_op",
|
||||
"_zero_parse_usize",
|
||||
"_zero_time_op",
|
||||
"_zero_term_op",
|
||||
"_zero_term_read_input",
|
||||
"_zero_math_op",
|
||||
"_zero_math_usize_op",
|
||||
"_zero_search_op",
|
||||
"_zero_sort_op",
|
||||
"_zero_sort_is_sorted_op",
|
||||
"_zero_str_contains",
|
||||
"_zero_str_buffer_op",
|
||||
"_zero_str_concat",
|
||||
"_zero_str_repeat",
|
||||
"_zero_str_trim_op",
|
||||
"_zero_str_pair_op",
|
||||
"_zero_str_count_byte",
|
||||
"_zero_str_word_count_ascii",
|
||||
"_zero_crypto_digest",
|
||||
"_zero_crypto_hmac_sha256",
|
||||
"_zero_crypto_hmac_sha256_hex",
|
||||
"_zero_fs_read_bytes",
|
||||
"_zero_fs_read_bytes_at",
|
||||
"_zero_fs_write_bytes",
|
||||
"_zero_fs_append_bytes",
|
||||
"_zero_fs_dir_entry_count",
|
||||
"_zero_fs_path_op",
|
||||
"_zero_fs_rename",
|
||||
"_zero_fs_atomic_write",
|
||||
"_zero_fs_dir_entry_name",
|
||||
"_zero_args_find",
|
||||
"_zero_parse_i32",
|
||||
"_zero_parse_u32",
|
||||
"_zero_fmt_bool",
|
||||
"_zero_fmt_hex_lower_u32",
|
||||
"_zero_fmt_i32",
|
||||
"_zero_fmt_u32",
|
||||
"_zero_fmt_usize",
|
||||
"_zero_rand_entropy_u32",
|
||||
"_zero_proc_spawn_inherit",
|
||||
"_zero_proc_spawn_inherit_args",
|
||||
"_zero_proc_capture",
|
||||
"_zero_proc_capture_args",
|
||||
"_zero_proc_capture_files",
|
||||
"_zero_proc_capture_files_args",
|
||||
"_zero_proc_spawn_child",
|
||||
"_zero_proc_spawn_child_in",
|
||||
"_zero_proc_spawn_child_in_env",
|
||||
"_zero_proc_spawn_child_args",
|
||||
"_zero_proc_child_op",
|
||||
"_zero_proc_child_io",
|
||||
"_zero_pty_spawn",
|
||||
"_zero_pty_spawn_in",
|
||||
"_zero_pty_spawn_in_env",
|
||||
"_zero_pty_spawn_args",
|
||||
"_zero_pty_resize",
|
||||
};
|
||||
|
||||
static bool macho_emit_state_diag_at(ZDiag *diag, const char *message, int line, int column, const char *actual) {
|
||||
if (diag) {
|
||||
diag->code = 4004;
|
||||
diag->line = line > 0 ? line : 1;
|
||||
diag->column = column > 0 ? column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message);
|
||||
snprintf(diag->expected, sizeof(diag->expected), "direct AArch64 Mach-O object MVP subset");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "unsupported construct");
|
||||
snprintf(diag->help, sizeof(diag->help), "choose a supported direct target or restrict this program to exported no-parameter functions returning small integer literals");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool macho_runtime_helper_valid(MachORuntimeHelper helper) {
|
||||
return helper >= 0 && helper < MACHO_RUNTIME_HELPER_COUNT;
|
||||
}
|
||||
|
||||
const char *z_macho_runtime_helper_symbol(MachORuntimeHelper helper) {
|
||||
if (!macho_runtime_helper_valid(helper)) return "";
|
||||
return runtime_helper_symbols[helper];
|
||||
}
|
||||
|
||||
void z_macho_emit_context_free(MachOEmitContext *ctx) {
|
||||
if (!ctx) return;
|
||||
for (unsigned i = 0; i < MACHO_RUNTIME_HELPER_COUNT; i++) {
|
||||
free(ctx->runtime_patches[i].items);
|
||||
}
|
||||
free(ctx->data_patches);
|
||||
free(ctx->call_patches);
|
||||
z_direct_trap_branches_free(ctx->trap_branches, Z_DIRECT_TRAP_KIND_COUNT);
|
||||
}
|
||||
|
||||
bool z_macho_record_call_patch(MachOEmitContext *ctx, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag) {
|
||||
if (!ctx || (((!value) || !value->external_call) && callee_index >= ctx->function_count)) {
|
||||
return macho_emit_state_diag_at(diag, "direct AArch64 Mach-O call target is out of range", value ? value->line : 1, value ? value->column : 1, "invalid callee");
|
||||
}
|
||||
if (ctx->call_patch_len == ctx->call_patch_cap) {
|
||||
ctx->call_patch_cap = z_grow_capacity(ctx->call_patch_cap, ctx->call_patch_len + 1, 8);
|
||||
ctx->call_patches = z_checked_reallocarray(ctx->call_patches, ctx->call_patch_cap, sizeof(MachOCallPatch));
|
||||
}
|
||||
ctx->call_patches[ctx->call_patch_len++] = (MachOCallPatch){
|
||||
.patch_offset = patch_offset,
|
||||
.callee_index = callee_index,
|
||||
.external_index = value ? value->external_index : 0,
|
||||
.line = value ? value->line : 1,
|
||||
.column = value ? value->column : 1,
|
||||
.external_call = value && value->external_call
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_macho_record_data_patch(MachOEmitContext *ctx, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag) {
|
||||
if (!ctx) return macho_emit_state_diag_at(diag, "direct AArch64 Mach-O data relocation requires an emit context", value ? value->line : 1, value ? value->column : 1, "missing context");
|
||||
if (ctx->data_patch_len == ctx->data_patch_cap) {
|
||||
ctx->data_patch_cap = z_grow_capacity(ctx->data_patch_cap, ctx->data_patch_len + 1, 8);
|
||||
ctx->data_patches = z_checked_reallocarray(ctx->data_patches, ctx->data_patch_cap, sizeof(MachODataPatch));
|
||||
}
|
||||
ctx->data_patches[ctx->data_patch_len++] = (MachODataPatch){.patch_offset = patch_offset, .data_offset = data_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool macho_record_runtime_patch_at(MachOEmitContext *ctx, MachORuntimeHelper helper, size_t patch_offset, int line, int column, ZDiag *diag) {
|
||||
if (!ctx || !macho_runtime_helper_valid(helper)) {
|
||||
return macho_emit_state_diag_at(diag, "direct AArch64 Mach-O runtime relocation requires an emit context", line, column, "missing context");
|
||||
}
|
||||
MachOPatchList *list = &ctx->runtime_patches[helper];
|
||||
if (list->len == list->cap) {
|
||||
list->cap = z_grow_capacity(list->cap, list->len + 1, 4);
|
||||
list->items = z_checked_reallocarray(list->items, list->cap, sizeof(MachOPatch));
|
||||
}
|
||||
list->items[list->len++] = (MachOPatch){.patch_offset = patch_offset};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_macho_record_value_runtime_patch(MachOEmitContext *ctx, MachORuntimeHelper helper, size_t patch_offset, const IrValue *value, ZDiag *diag) {
|
||||
return macho_record_runtime_patch_at(ctx, helper, patch_offset, value ? value->line : 1, value ? value->column : 1, diag);
|
||||
}
|
||||
|
||||
bool z_macho_record_instr_runtime_patch(MachOEmitContext *ctx, MachORuntimeHelper helper, size_t patch_offset, const IrInstr *instr, ZDiag *diag) {
|
||||
return macho_record_runtime_patch_at(ctx, helper, patch_offset, instr ? instr->line : 1, instr ? instr->column : 1, diag);
|
||||
}
|
||||
|
||||
size_t z_macho_runtime_patch_count(const MachOEmitContext *ctx, MachORuntimeHelper helper) {
|
||||
if (!ctx || !macho_runtime_helper_valid(helper)) return 0;
|
||||
return ctx->runtime_patches[helper].len;
|
||||
}
|
||||
|
||||
const MachOPatchList *z_macho_runtime_patch_list(const MachOEmitContext *ctx, MachORuntimeHelper helper) {
|
||||
if (!ctx || !macho_runtime_helper_valid(helper)) return NULL;
|
||||
return &ctx->runtime_patches[helper];
|
||||
}
|
||||
|
||||
bool z_macho_has_unsupported_exe_runtime_patches(const MachOEmitContext *ctx) {
|
||||
if (!ctx) return false;
|
||||
for (unsigned i = 0; i < MACHO_RUNTIME_HELPER_COUNT; i++) {
|
||||
if (i == MACHO_RUNTIME_WORLD_WRITE) continue;
|
||||
if (ctx->runtime_patches[i].len > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void macho_append_branch_relocations(ZBuf *relocs, const MachOPatchList *patches, unsigned symbol_index) {
|
||||
for (size_t i = 0; patches && i < patches->len; i++) {
|
||||
uint32_t reloc_info = (symbol_index & 0x00ffffffu) |
|
||||
(1u << 24) |
|
||||
(2u << 25) |
|
||||
(1u << 27) |
|
||||
(2u << 28);
|
||||
z_macho_append_u32(relocs, (uint32_t)patches->items[i].patch_offset);
|
||||
z_macho_append_u32(relocs, reloc_info);
|
||||
}
|
||||
}
|
||||
|
||||
void z_macho_append_call_relocations(ZBuf *relocs, const MachOEmitContext *ctx, uint32_t external_symbol_base) {
|
||||
for (size_t i = 0; ctx && i < ctx->call_patch_len; i++) {
|
||||
const MachOCallPatch *patch = &ctx->call_patches[i];
|
||||
uint32_t symbol_index = patch->external_call ? external_symbol_base + patch->external_index : patch->callee_index;
|
||||
uint32_t reloc_info = (symbol_index & 0x00ffffffu) |
|
||||
(1u << 24) |
|
||||
(2u << 25) |
|
||||
(1u << 27) |
|
||||
(2u << 28);
|
||||
z_macho_append_u32(relocs, (uint32_t)patch->patch_offset);
|
||||
z_macho_append_u32(relocs, reloc_info);
|
||||
}
|
||||
}
|
||||
|
||||
void z_macho_append_runtime_relocations(ZBuf *relocs, const MachOEmitContext *ctx, MachORuntimeHelper helper, unsigned symbol_index) {
|
||||
macho_append_branch_relocations(relocs, z_macho_runtime_patch_list(ctx, helper), symbol_index);
|
||||
}
|
||||
|
||||
size_t z_macho_data_relocation_count(const MachOEmitContext *ctx) {
|
||||
if (!ctx) return 0;
|
||||
if (!ctx->pie_relative_data) return ctx->data_patch_len;
|
||||
size_t count = ctx->data_patch_len * 2;
|
||||
for (size_t i = 0; i < ctx->data_patch_len; i++) {
|
||||
const MachODataPatch *patch = &ctx->data_patches[i];
|
||||
if (patch->data_offset != ctx->rodata_base_offset) count += 2;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t z_macho_text_relocation_count(const MachOEmitContext *ctx) {
|
||||
if (!ctx) return 0;
|
||||
size_t count = ctx->call_patch_len + z_macho_data_relocation_count(ctx);
|
||||
for (unsigned i = 0; i < MACHO_RUNTIME_HELPER_COUNT; i++) {
|
||||
count += ctx->runtime_patches[i].len;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void macho_append_reloc(ZBuf *relocs, uint32_t address, uint32_t symbol_or_addend, bool pcrel, unsigned length, bool external, unsigned type) {
|
||||
uint32_t reloc_info = (symbol_or_addend & 0x00ffffffu) |
|
||||
((pcrel ? 1u : 0u) << 24) |
|
||||
((length & 3u) << 25) |
|
||||
((external ? 1u : 0u) << 27) |
|
||||
((type & 15u) << 28);
|
||||
z_macho_append_u32(relocs, address);
|
||||
z_macho_append_u32(relocs, reloc_info);
|
||||
}
|
||||
|
||||
void z_macho_append_data_relocations(ZBuf *relocs, const MachOEmitContext *ctx, unsigned data_symbol_index) {
|
||||
for (size_t i = 0; ctx && i < ctx->data_patch_len; i++) {
|
||||
const MachODataPatch *patch = &ctx->data_patches[i];
|
||||
if (ctx->pie_relative_data) {
|
||||
uint32_t addend = patch->data_offset - ctx->rodata_base_offset;
|
||||
if (addend != 0) macho_append_reloc(relocs, (uint32_t)patch->patch_offset + 4u, addend, false, 2, false, 10);
|
||||
macho_append_reloc(relocs, (uint32_t)patch->patch_offset + 4u, data_symbol_index, false, 2, true, 4);
|
||||
if (addend != 0) macho_append_reloc(relocs, (uint32_t)patch->patch_offset, addend, false, 2, false, 10);
|
||||
macho_append_reloc(relocs, (uint32_t)patch->patch_offset, data_symbol_index, true, 2, true, 3);
|
||||
} else {
|
||||
macho_append_reloc(relocs, (uint32_t)patch->patch_offset, data_symbol_index, false, 3, true, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
#ifndef ZERO_C_MACHO_EMIT_STATE_H
|
||||
#define ZERO_C_MACHO_EMIT_STATE_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
MACHO_RUNTIME_WORLD_WRITE,
|
||||
MACHO_RUNTIME_JSON_PARSE_BYTES,
|
||||
MACHO_RUNTIME_JSON_DIAGNOSTIC,
|
||||
MACHO_RUNTIME_JSON_FIELD,
|
||||
MACHO_RUNTIME_JSON_LOOKUP_SCALAR,
|
||||
MACHO_RUNTIME_JSON_STRING_DECODE,
|
||||
MACHO_RUNTIME_JSON_STRING_FIELD,
|
||||
MACHO_RUNTIME_JSON_WRITE_STRING,
|
||||
MACHO_RUNTIME_JSON_WRITE_FIELD_RAW,
|
||||
MACHO_RUNTIME_JSON_WRITE_FIELD_STRING,
|
||||
MACHO_RUNTIME_JSON_WRITE_FIELD_U32,
|
||||
MACHO_RUNTIME_JSON_WRITE_FIELD_BOOL,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT1_STRING,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT1_U32,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT1_BOOL,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT2_FIELDS,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT2_STRING_FIELD,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT2_U32_FIELD,
|
||||
MACHO_RUNTIME_JSON_WRITE_OBJECT2_BOOL_FIELD,
|
||||
MACHO_RUNTIME_JSON_WRITE_ARRAY2_STRINGS,
|
||||
MACHO_RUNTIME_JSON_WRITE_ARRAY2_U32,
|
||||
MACHO_RUNTIME_JSON_WRITE_ARRAY2_BOOLS,
|
||||
MACHO_RUNTIME_HTTP_FETCH,
|
||||
MACHO_RUNTIME_HTTP_RESULT_OK,
|
||||
MACHO_RUNTIME_HTTP_RESULT_STATUS,
|
||||
MACHO_RUNTIME_HTTP_RESULT_BODY_LEN,
|
||||
MACHO_RUNTIME_HTTP_RESULT_ERROR,
|
||||
MACHO_RUNTIME_HTTP_RESPONSE_LEN,
|
||||
MACHO_RUNTIME_HTTP_RESPONSE_HEADERS_LEN,
|
||||
MACHO_RUNTIME_HTTP_RESPONSE_BODY_OFFSET,
|
||||
MACHO_RUNTIME_HTTP_HEADER_VALUE,
|
||||
MACHO_RUNTIME_HTTP_HEADER_FOUND,
|
||||
MACHO_RUNTIME_HTTP_HEADER_OFFSET,
|
||||
MACHO_RUNTIME_HTTP_HEADER_LEN,
|
||||
MACHO_RUNTIME_HTTP_WRITE_JSON_RESPONSE,
|
||||
MACHO_RUNTIME_HTTP_REQUEST_METHOD_NAME,
|
||||
MACHO_RUNTIME_HTTP_REQUEST_PATH,
|
||||
MACHO_RUNTIME_HTTP_REQUEST_MATCHES,
|
||||
MACHO_RUNTIME_HTTP_REQUEST_BODY_WITHIN,
|
||||
MACHO_RUNTIME_ASCII_OP,
|
||||
MACHO_RUNTIME_TEXT_OP,
|
||||
MACHO_RUNTIME_PARSE_OP,
|
||||
MACHO_RUNTIME_PARSE_USIZE,
|
||||
MACHO_RUNTIME_TIME_OP,
|
||||
MACHO_RUNTIME_TERM_OP,
|
||||
MACHO_RUNTIME_TERM_READ_INPUT,
|
||||
MACHO_RUNTIME_MATH_OP,
|
||||
MACHO_RUNTIME_MATH_USIZE_OP,
|
||||
MACHO_RUNTIME_SEARCH_OP,
|
||||
MACHO_RUNTIME_SORT_OP,
|
||||
MACHO_RUNTIME_SORT_IS_SORTED_OP,
|
||||
MACHO_RUNTIME_STR_CONTAINS,
|
||||
MACHO_RUNTIME_STR_BUFFER_OP,
|
||||
MACHO_RUNTIME_STR_CONCAT,
|
||||
MACHO_RUNTIME_STR_REPEAT,
|
||||
MACHO_RUNTIME_STR_TRIM_OP,
|
||||
MACHO_RUNTIME_STR_PAIR_OP,
|
||||
MACHO_RUNTIME_STR_COUNT_BYTE,
|
||||
MACHO_RUNTIME_STR_WORD_COUNT_ASCII,
|
||||
MACHO_RUNTIME_CRYPTO_DIGEST,
|
||||
MACHO_RUNTIME_CRYPTO_HMAC_SHA256,
|
||||
MACHO_RUNTIME_CRYPTO_HMAC_SHA256_HEX,
|
||||
MACHO_RUNTIME_FS_READ_BYTES,
|
||||
MACHO_RUNTIME_FS_READ_BYTES_AT,
|
||||
MACHO_RUNTIME_FS_WRITE_BYTES,
|
||||
MACHO_RUNTIME_FS_APPEND_BYTES,
|
||||
MACHO_RUNTIME_FS_DIR_ENTRY_COUNT,
|
||||
MACHO_RUNTIME_FS_PATH_OP,
|
||||
MACHO_RUNTIME_FS_RENAME,
|
||||
MACHO_RUNTIME_FS_ATOMIC_WRITE,
|
||||
MACHO_RUNTIME_FS_DIR_ENTRY_NAME,
|
||||
MACHO_RUNTIME_ARGS_FIND,
|
||||
MACHO_RUNTIME_PARSE_I32,
|
||||
MACHO_RUNTIME_PARSE_U32,
|
||||
MACHO_RUNTIME_FMT_BOOL,
|
||||
MACHO_RUNTIME_FMT_HEX_LOWER_U32,
|
||||
MACHO_RUNTIME_FMT_I32,
|
||||
MACHO_RUNTIME_FMT_U32,
|
||||
MACHO_RUNTIME_FMT_USIZE,
|
||||
MACHO_RUNTIME_RAND_ENTROPY_U32,
|
||||
MACHO_RUNTIME_PROC_SPAWN_INHERIT,
|
||||
MACHO_RUNTIME_PROC_SPAWN_INHERIT_ARGS,
|
||||
MACHO_RUNTIME_PROC_CAPTURE,
|
||||
MACHO_RUNTIME_PROC_CAPTURE_ARGS,
|
||||
MACHO_RUNTIME_PROC_CAPTURE_FILES,
|
||||
MACHO_RUNTIME_PROC_CAPTURE_FILES_ARGS,
|
||||
MACHO_RUNTIME_PROC_SPAWN_CHILD,
|
||||
MACHO_RUNTIME_PROC_SPAWN_CHILD_IN,
|
||||
MACHO_RUNTIME_PROC_SPAWN_CHILD_IN_ENV,
|
||||
MACHO_RUNTIME_PROC_SPAWN_CHILD_ARGS,
|
||||
MACHO_RUNTIME_PROC_CHILD_OP,
|
||||
MACHO_RUNTIME_PROC_CHILD_IO,
|
||||
MACHO_RUNTIME_PTY_SPAWN,
|
||||
MACHO_RUNTIME_PTY_SPAWN_IN,
|
||||
MACHO_RUNTIME_PTY_SPAWN_IN_ENV,
|
||||
MACHO_RUNTIME_PTY_SPAWN_ARGS,
|
||||
MACHO_RUNTIME_PTY_RESIZE,
|
||||
MACHO_RUNTIME_HELPER_COUNT
|
||||
} MachORuntimeHelper;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
} MachOPatch;
|
||||
|
||||
typedef struct {
|
||||
MachOPatch *items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} MachOPatchList;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned callee_index;
|
||||
unsigned external_index;
|
||||
int line;
|
||||
int column;
|
||||
bool external_call;
|
||||
} MachOCallPatch;
|
||||
|
||||
typedef struct {
|
||||
size_t patch_offset;
|
||||
unsigned data_offset;
|
||||
} MachODataPatch;
|
||||
|
||||
typedef struct {
|
||||
const IrProgram *program;
|
||||
size_t *function_offsets;
|
||||
size_t function_count;
|
||||
MachOCallPatch *call_patches;
|
||||
size_t call_patch_len;
|
||||
size_t call_patch_cap;
|
||||
MachODataPatch *data_patches;
|
||||
size_t data_patch_len;
|
||||
size_t data_patch_cap;
|
||||
MachOPatchList runtime_patches[MACHO_RUNTIME_HELPER_COUNT];
|
||||
unsigned rodata_base_offset;
|
||||
bool pie_relative_data;
|
||||
bool seed_main_process_args;
|
||||
ZDirectTrapMessages trap_messages;
|
||||
ZDirectTrapBranchList trap_branches[Z_DIRECT_TRAP_KIND_COUNT];
|
||||
ZDirectLoopFrame *loop;
|
||||
} MachOEmitContext;
|
||||
|
||||
const char *z_macho_runtime_helper_symbol(MachORuntimeHelper helper);
|
||||
void z_macho_emit_context_free(MachOEmitContext *ctx);
|
||||
bool z_macho_record_call_patch(MachOEmitContext *ctx, size_t patch_offset, unsigned callee_index, const IrValue *value, ZDiag *diag);
|
||||
bool z_macho_record_data_patch(MachOEmitContext *ctx, size_t patch_offset, unsigned data_offset, const IrValue *value, ZDiag *diag);
|
||||
bool z_macho_record_value_runtime_patch(MachOEmitContext *ctx, MachORuntimeHelper helper, size_t patch_offset, const IrValue *value, ZDiag *diag);
|
||||
bool z_macho_record_instr_runtime_patch(MachOEmitContext *ctx, MachORuntimeHelper helper, size_t patch_offset, const IrInstr *instr, ZDiag *diag);
|
||||
size_t z_macho_runtime_patch_count(const MachOEmitContext *ctx, MachORuntimeHelper helper);
|
||||
const MachOPatchList *z_macho_runtime_patch_list(const MachOEmitContext *ctx, MachORuntimeHelper helper);
|
||||
bool z_macho_has_unsupported_exe_runtime_patches(const MachOEmitContext *ctx);
|
||||
void z_macho_append_call_relocations(ZBuf *relocs, const MachOEmitContext *ctx, uint32_t external_symbol_base);
|
||||
void z_macho_append_runtime_relocations(ZBuf *relocs, const MachOEmitContext *ctx, MachORuntimeHelper helper, unsigned symbol_index);
|
||||
size_t z_macho_data_relocation_count(const MachOEmitContext *ctx);
|
||||
size_t z_macho_text_relocation_count(const MachOEmitContext *ctx);
|
||||
void z_macho_append_data_relocations(ZBuf *relocs, const MachOEmitContext *ctx, unsigned data_symbol_index);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,462 @@
|
||||
#include "macho_format.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static const uint32_t MACHO_CPU_TYPE_ARM64 = 0x0100000c;
|
||||
static const uint32_t MACHO_CPU_SUBTYPE_ARM64_ALL = 0;
|
||||
static const uint32_t MACHO_CPU_TYPE_X86_64 = 0x01000007;
|
||||
static const uint32_t MACHO_CPU_SUBTYPE_X86_64_ALL = 3;
|
||||
static const uint32_t MACHO_MAGIC_64 = 0xfeedfacf;
|
||||
static const uint32_t MACHO_LC_SEGMENT_64 = 0x19;
|
||||
static const uint32_t MACHO_LC_SYMTAB = 0x2;
|
||||
static const uint32_t MACHO_LC_DYLD_INFO_ONLY = 0x80000022;
|
||||
static const uint32_t MACHO_LC_UUID = 0x1b;
|
||||
static const uint32_t MACHO_LC_LOAD_DYLINKER = 0xe;
|
||||
static const uint32_t MACHO_LC_LOAD_DYLIB = 0xc;
|
||||
static const uint32_t MACHO_LC_MAIN = 0x80000028;
|
||||
static const uint32_t MACHO_LC_BUILD_VERSION = 0x32;
|
||||
static const uint32_t MACHO_LC_CODE_SIGNATURE = 0x1d;
|
||||
|
||||
typedef struct {
|
||||
uint32_t state[8];
|
||||
uint64_t bitlen;
|
||||
unsigned char data[64];
|
||||
size_t datalen;
|
||||
} MachOSha256;
|
||||
|
||||
size_t z_macho_align(size_t value, size_t alignment) {
|
||||
size_t remainder = alignment ? value % alignment : 0;
|
||||
return remainder == 0 ? value : value + (alignment - remainder);
|
||||
}
|
||||
|
||||
void z_macho_append_u8(ZBuf *buf, unsigned value) {
|
||||
zbuf_append_char(buf, (char)(value & 0xffu));
|
||||
}
|
||||
|
||||
void z_macho_append_u16(ZBuf *buf, uint16_t value) {
|
||||
z_macho_append_u8(buf, value);
|
||||
z_macho_append_u8(buf, value >> 8);
|
||||
}
|
||||
|
||||
void z_macho_append_u32(ZBuf *buf, uint32_t value) {
|
||||
z_macho_append_u8(buf, value);
|
||||
z_macho_append_u8(buf, value >> 8);
|
||||
z_macho_append_u8(buf, value >> 16);
|
||||
z_macho_append_u8(buf, value >> 24);
|
||||
}
|
||||
|
||||
void z_macho_append_u64(ZBuf *buf, uint64_t value) {
|
||||
z_macho_append_u32(buf, (uint32_t)(value & 0xffffffffu));
|
||||
z_macho_append_u32(buf, (uint32_t)(value >> 32));
|
||||
}
|
||||
|
||||
void z_macho_patch_u64(ZBuf *buf, size_t offset, uint64_t value) {
|
||||
for (unsigned i = 0; i < 8; i++) buf->data[offset + i] = (char)((value >> (i * 8)) & 0xffu);
|
||||
}
|
||||
|
||||
void z_macho_append_bytes(ZBuf *buf, const char *bytes, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) z_macho_append_u8(buf, (unsigned char)bytes[i]);
|
||||
}
|
||||
|
||||
void z_macho_pad_to(ZBuf *buf, size_t offset) {
|
||||
while (buf->len < offset) z_macho_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
void z_macho_append_uleb128(ZBuf *buf, uint64_t value) {
|
||||
do {
|
||||
unsigned byte = (unsigned)(value & 0x7fu);
|
||||
value >>= 7;
|
||||
if (value) byte |= 0x80u;
|
||||
z_macho_append_u8(buf, byte);
|
||||
} while (value);
|
||||
}
|
||||
|
||||
static void macho_append_fixed(ZBuf *buf, const char *text, size_t width) {
|
||||
size_t len = text ? strlen(text) : 0;
|
||||
if (len > width) len = width;
|
||||
for (size_t i = 0; i < len; i++) z_macho_append_u8(buf, (unsigned char)text[i]);
|
||||
for (size_t i = len; i < width; i++) z_macho_append_u8(buf, 0);
|
||||
}
|
||||
|
||||
static void macho_append_section64(ZBuf *out, const char *sectname, const char *segname, uint64_t addr, uint64_t size, uint32_t offset, uint32_t align, uint32_t reloff, uint32_t nreloc, uint32_t flags) {
|
||||
macho_append_fixed(out, sectname, 16);
|
||||
macho_append_fixed(out, segname, 16);
|
||||
z_macho_append_u64(out, addr);
|
||||
z_macho_append_u64(out, size);
|
||||
z_macho_append_u32(out, offset);
|
||||
z_macho_append_u32(out, align);
|
||||
z_macho_append_u32(out, reloff);
|
||||
z_macho_append_u32(out, nreloc);
|
||||
z_macho_append_u32(out, flags);
|
||||
z_macho_append_u32(out, 0);
|
||||
z_macho_append_u32(out, 0);
|
||||
z_macho_append_u32(out, 0);
|
||||
}
|
||||
|
||||
static void macho_append_segment64(ZBuf *out, const char *segname, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize, uint32_t maxprot, uint32_t initprot, uint32_t nsects, uint32_t flags) {
|
||||
z_macho_append_u32(out, MACHO_LC_SEGMENT_64);
|
||||
z_macho_append_u32(out, 72 + nsects * 80);
|
||||
macho_append_fixed(out, segname, 16);
|
||||
z_macho_append_u64(out, vmaddr);
|
||||
z_macho_append_u64(out, vmsize);
|
||||
z_macho_append_u64(out, fileoff);
|
||||
z_macho_append_u64(out, filesize);
|
||||
z_macho_append_u32(out, maxprot);
|
||||
z_macho_append_u32(out, initprot);
|
||||
z_macho_append_u32(out, nsects);
|
||||
z_macho_append_u32(out, flags);
|
||||
}
|
||||
|
||||
static uint32_t macho_cpu_type(ZMachOCpu cpu) {
|
||||
return cpu == Z_MACHO_CPU_X86_64 ? MACHO_CPU_TYPE_X86_64 : MACHO_CPU_TYPE_ARM64;
|
||||
}
|
||||
|
||||
static uint32_t macho_cpu_subtype(ZMachOCpu cpu) {
|
||||
return cpu == Z_MACHO_CPU_X86_64 ? MACHO_CPU_SUBTYPE_X86_64_ALL : MACHO_CPU_SUBTYPE_ARM64_ALL;
|
||||
}
|
||||
|
||||
static void macho_append_header64(ZBuf *out, ZMachOCpu cpu, uint32_t filetype, uint32_t ncmds, uint32_t sizeofcmds, uint32_t flags) {
|
||||
z_macho_append_u32(out, MACHO_MAGIC_64);
|
||||
z_macho_append_u32(out, macho_cpu_type(cpu));
|
||||
z_macho_append_u32(out, macho_cpu_subtype(cpu));
|
||||
z_macho_append_u32(out, filetype);
|
||||
z_macho_append_u32(out, ncmds);
|
||||
z_macho_append_u32(out, sizeofcmds);
|
||||
z_macho_append_u32(out, flags);
|
||||
z_macho_append_u32(out, 0);
|
||||
}
|
||||
|
||||
static void macho_append_nlist64(ZBuf *out, const ZMachOSymbol *symbol) {
|
||||
z_macho_append_u32(out, symbol ? symbol->string_offset : 0);
|
||||
z_macho_append_u8(out, symbol ? symbol->type : 0);
|
||||
z_macho_append_u8(out, symbol ? symbol->section : 0);
|
||||
z_macho_append_u16(out, symbol ? symbol->desc : 0);
|
||||
z_macho_append_u64(out, symbol ? symbol->value : 0);
|
||||
}
|
||||
|
||||
void z_macho_write_object64(ZBuf *out, const ZMachOObjectImage *image) {
|
||||
const ZBuf empty = {0};
|
||||
const ZBuf *text = image && image->text ? image->text : ∅
|
||||
const ZBuf *rodata = image && image->rodata ? image->rodata : ∅
|
||||
const ZBuf *relocs = image && image->relocs ? image->relocs : ∅
|
||||
const ZBuf *strings = image && image->strings ? image->strings : ∅
|
||||
bool has_rodata = rodata->len > 0;
|
||||
uint32_t header_size = 32;
|
||||
uint32_t section_count = has_rodata ? 2u : 1u;
|
||||
uint32_t segment_cmd_size = 72 + section_count * 80;
|
||||
uint32_t symtab_cmd_size = 24;
|
||||
uint32_t sizeofcmds = segment_cmd_size + symtab_cmd_size;
|
||||
uint32_t text_offset = header_size + sizeofcmds;
|
||||
uint32_t const_addr = has_rodata ? (uint32_t)z_macho_align(text->len, 8) : 0;
|
||||
uint32_t segment_file_size = has_rodata ? const_addr + (uint32_t)rodata->len : (uint32_t)text->len;
|
||||
uint32_t reloff = relocs->len > 0 ? text_offset + segment_file_size : 0;
|
||||
uint32_t symoff = text_offset + segment_file_size + (uint32_t)relocs->len;
|
||||
uint32_t nsyms = image ? (uint32_t)image->symbol_len : 0;
|
||||
uint32_t stroff = symoff + nsyms * 16;
|
||||
|
||||
zbuf_init(out);
|
||||
ZMachOCpu cpu = image ? image->cpu : Z_MACHO_CPU_ARM64;
|
||||
macho_append_header64(out, cpu, 1, 2, sizeofcmds, 0);
|
||||
macho_append_segment64(out, "", 0, segment_file_size, text_offset, segment_file_size, 7, 5, section_count, 0);
|
||||
macho_append_section64(out, "__text", "__TEXT", 0, text->len, text_offset, 2, reloff, image ? image->text_reloc_count : 0, 0x80000400u);
|
||||
if (has_rodata) macho_append_section64(out, "__const", "__DATA", const_addr, rodata->len, text_offset + const_addr, 3, 0, 0, 0);
|
||||
z_macho_append_u32(out, MACHO_LC_SYMTAB);
|
||||
z_macho_append_u32(out, symtab_cmd_size);
|
||||
z_macho_append_u32(out, symoff);
|
||||
z_macho_append_u32(out, nsyms);
|
||||
z_macho_append_u32(out, stroff);
|
||||
z_macho_append_u32(out, (uint32_t)strings->len);
|
||||
if (text->data) z_macho_append_bytes(out, text->data, text->len);
|
||||
if (has_rodata) {
|
||||
z_macho_pad_to(out, text_offset + const_addr);
|
||||
if (rodata->data) z_macho_append_bytes(out, rodata->data, rodata->len);
|
||||
}
|
||||
if (relocs->data) z_macho_append_bytes(out, relocs->data, relocs->len);
|
||||
for (size_t i = 0; image && i < image->symbol_len; i++) macho_append_nlist64(out, &image->symbols[i]);
|
||||
if (strings->data) z_macho_append_bytes(out, strings->data, strings->len);
|
||||
}
|
||||
|
||||
static uint32_t macho_sha_rotr(uint32_t value, unsigned bits) {
|
||||
return (value >> bits) | (value << (32 - bits));
|
||||
}
|
||||
|
||||
static void macho_sha256_transform(MachOSha256 *ctx, const unsigned char data[64]) {
|
||||
static const uint32_t k[64] = {
|
||||
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u,
|
||||
0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u,
|
||||
0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
|
||||
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u,
|
||||
0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u,
|
||||
0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
|
||||
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u,
|
||||
0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u
|
||||
};
|
||||
uint32_t m[64];
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
m[i] = ((uint32_t)data[i * 4] << 24) | ((uint32_t)data[i * 4 + 1] << 16) | ((uint32_t)data[i * 4 + 2] << 8) | ((uint32_t)data[i * 4 + 3]);
|
||||
}
|
||||
for (unsigned i = 16; i < 64; i++) {
|
||||
uint32_t s0 = macho_sha_rotr(m[i - 15], 7) ^ macho_sha_rotr(m[i - 15], 18) ^ (m[i - 15] >> 3);
|
||||
uint32_t s1 = macho_sha_rotr(m[i - 2], 17) ^ macho_sha_rotr(m[i - 2], 19) ^ (m[i - 2] >> 10);
|
||||
m[i] = m[i - 16] + s0 + m[i - 7] + s1;
|
||||
}
|
||||
uint32_t a = ctx->state[0], b = ctx->state[1], c = ctx->state[2], d = ctx->state[3];
|
||||
uint32_t e = ctx->state[4], f = ctx->state[5], g = ctx->state[6], h = ctx->state[7];
|
||||
for (unsigned i = 0; i < 64; i++) {
|
||||
uint32_t s1 = macho_sha_rotr(e, 6) ^ macho_sha_rotr(e, 11) ^ macho_sha_rotr(e, 25);
|
||||
uint32_t ch = (e & f) ^ ((~e) & g);
|
||||
uint32_t temp1 = h + s1 + ch + k[i] + m[i];
|
||||
uint32_t s0 = macho_sha_rotr(a, 2) ^ macho_sha_rotr(a, 13) ^ macho_sha_rotr(a, 22);
|
||||
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
uint32_t temp2 = s0 + maj;
|
||||
h = g; g = f; f = e; e = d + temp1; d = c; c = b; b = a; a = temp1 + temp2;
|
||||
}
|
||||
ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
|
||||
ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
|
||||
}
|
||||
|
||||
static void macho_sha256_init(MachOSha256 *ctx) {
|
||||
ctx->datalen = 0;
|
||||
ctx->bitlen = 0;
|
||||
ctx->state[0] = 0x6a09e667u; ctx->state[1] = 0xbb67ae85u; ctx->state[2] = 0x3c6ef372u; ctx->state[3] = 0xa54ff53au;
|
||||
ctx->state[4] = 0x510e527fu; ctx->state[5] = 0x9b05688cu; ctx->state[6] = 0x1f83d9abu; ctx->state[7] = 0x5be0cd19u;
|
||||
}
|
||||
|
||||
static void macho_sha256_update(MachOSha256 *ctx, const unsigned char *data, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ctx->data[ctx->datalen++] = data[i];
|
||||
if (ctx->datalen == 64) {
|
||||
macho_sha256_transform(ctx, ctx->data);
|
||||
ctx->bitlen += 512;
|
||||
ctx->datalen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void macho_sha256_final(MachOSha256 *ctx, unsigned char hash[32]) {
|
||||
size_t i = ctx->datalen;
|
||||
ctx->data[i++] = 0x80;
|
||||
if (i > 56) {
|
||||
while (i < 64) ctx->data[i++] = 0;
|
||||
macho_sha256_transform(ctx, ctx->data);
|
||||
i = 0;
|
||||
}
|
||||
while (i < 56) ctx->data[i++] = 0;
|
||||
ctx->bitlen += ctx->datalen * 8;
|
||||
for (unsigned j = 0; j < 8; j++) ctx->data[63 - j] = (unsigned char)(ctx->bitlen >> (j * 8));
|
||||
macho_sha256_transform(ctx, ctx->data);
|
||||
for (unsigned j = 0; j < 8; j++) {
|
||||
hash[j * 4] = (unsigned char)(ctx->state[j] >> 24);
|
||||
hash[j * 4 + 1] = (unsigned char)(ctx->state[j] >> 16);
|
||||
hash[j * 4 + 2] = (unsigned char)(ctx->state[j] >> 8);
|
||||
hash[j * 4 + 3] = (unsigned char)ctx->state[j];
|
||||
}
|
||||
}
|
||||
|
||||
static void macho_sha256_hash(const unsigned char *data, size_t len, unsigned char hash[32]) {
|
||||
MachOSha256 ctx;
|
||||
macho_sha256_init(&ctx);
|
||||
macho_sha256_update(&ctx, data, len);
|
||||
macho_sha256_final(&ctx, hash);
|
||||
}
|
||||
|
||||
static void macho_append_u32be(ZBuf *buf, uint32_t value) {
|
||||
z_macho_append_u8(buf, value >> 24);
|
||||
z_macho_append_u8(buf, value >> 16);
|
||||
z_macho_append_u8(buf, value >> 8);
|
||||
z_macho_append_u8(buf, value);
|
||||
}
|
||||
|
||||
static void macho_append_u64be(ZBuf *buf, uint64_t value) {
|
||||
macho_append_u32be(buf, (uint32_t)(value >> 32));
|
||||
macho_append_u32be(buf, (uint32_t)(value & 0xffffffffu));
|
||||
}
|
||||
|
||||
static void macho_append_code_signature(ZBuf *sig, const unsigned char *code, size_t code_len, const char *identifier) {
|
||||
const uint32_t page_log = 12;
|
||||
const size_t page_size = 1u << page_log;
|
||||
const uint32_t hash_size = 32;
|
||||
const uint32_t nslots = (uint32_t)((code_len + page_size - 1) / page_size);
|
||||
const uint32_t cd_header_size = 88;
|
||||
const uint32_t ident_offset = cd_header_size;
|
||||
const uint32_t ident_len = (uint32_t)strlen(identifier) + 1;
|
||||
const uint32_t hash_offset = (uint32_t)z_macho_align(ident_offset + ident_len, 4);
|
||||
const uint32_t cd_length = hash_offset + nslots * hash_size;
|
||||
const uint32_t cd_offset = 20;
|
||||
const uint32_t super_length = cd_offset + cd_length;
|
||||
|
||||
zbuf_init(sig);
|
||||
macho_append_u32be(sig, 0xfade0cc0u);
|
||||
macho_append_u32be(sig, super_length);
|
||||
macho_append_u32be(sig, 1);
|
||||
macho_append_u32be(sig, 0);
|
||||
macho_append_u32be(sig, cd_offset);
|
||||
macho_append_u32be(sig, 0xfade0c02u);
|
||||
macho_append_u32be(sig, cd_length);
|
||||
macho_append_u32be(sig, 0x00020400u);
|
||||
macho_append_u32be(sig, 0x00000002u);
|
||||
macho_append_u32be(sig, hash_offset);
|
||||
macho_append_u32be(sig, ident_offset);
|
||||
macho_append_u32be(sig, 0);
|
||||
macho_append_u32be(sig, nslots);
|
||||
macho_append_u32be(sig, (uint32_t)code_len);
|
||||
z_macho_append_u8(sig, hash_size);
|
||||
z_macho_append_u8(sig, 2);
|
||||
z_macho_append_u8(sig, 0);
|
||||
z_macho_append_u8(sig, page_log);
|
||||
macho_append_u32be(sig, 0);
|
||||
macho_append_u32be(sig, 0);
|
||||
macho_append_u32be(sig, 0);
|
||||
macho_append_u32be(sig, 0);
|
||||
macho_append_u64be(sig, code_len);
|
||||
macho_append_u64be(sig, 0);
|
||||
macho_append_u64be(sig, code_len);
|
||||
macho_append_u64be(sig, 0);
|
||||
z_macho_append_bytes(sig, identifier, ident_len);
|
||||
z_macho_pad_to(sig, cd_offset + hash_offset);
|
||||
for (uint32_t slot = 0; slot < nslots; slot++) {
|
||||
size_t offset = slot * page_size;
|
||||
size_t len = code_len - offset;
|
||||
if (len > page_size) len = page_size;
|
||||
unsigned char hash[32];
|
||||
macho_sha256_hash(code + offset, len, hash);
|
||||
z_macho_append_bytes(sig, (const char *)hash, sizeof(hash));
|
||||
}
|
||||
}
|
||||
|
||||
void z_macho_compute_executable64_layout(ZMachOExecutableLayout *layout, const ZBuf *text, const ZBuf *rodata, const ZBuf *rebase, const char *code_signature_id) {
|
||||
if (!layout) return;
|
||||
const ZBuf empty = {0};
|
||||
text = text ? text : ∅
|
||||
rodata = rodata ? rodata : ∅
|
||||
rebase = rebase ? rebase : ∅
|
||||
const char *identifier = code_signature_id ? code_signature_id : "zero-direct";
|
||||
bool has_rodata = rodata->len > 0;
|
||||
layout->base_addr = 0x100000000ull;
|
||||
layout->page_size = 0x4000;
|
||||
layout->header_size = 32;
|
||||
layout->pagezero_cmd_size = 72;
|
||||
layout->text_segment_cmd_size = 72 + (has_rodata ? 2u : 1u) * 80;
|
||||
layout->linkedit_cmd_size = 72;
|
||||
layout->dyld_info_cmd_size = 48;
|
||||
layout->uuid_cmd_size = 24;
|
||||
layout->dylinker_cmd_size = 32;
|
||||
layout->libsystem_cmd_size = 56;
|
||||
layout->main_cmd_size = 24;
|
||||
layout->build_version_cmd_size = 24;
|
||||
layout->code_signature_cmd_size = 16;
|
||||
layout->sizeofcmds = layout->pagezero_cmd_size + layout->text_segment_cmd_size + layout->linkedit_cmd_size + layout->dyld_info_cmd_size + layout->uuid_cmd_size + layout->dylinker_cmd_size + layout->libsystem_cmd_size + layout->main_cmd_size + layout->build_version_cmd_size + layout->code_signature_cmd_size;
|
||||
layout->text_offset = (uint32_t)z_macho_align(layout->header_size + layout->sizeofcmds, 16);
|
||||
layout->rodata_offset = has_rodata ? (uint32_t)z_macho_align(layout->text_offset + text->len, 8) : 0;
|
||||
uint64_t segment_content_size = has_rodata ? layout->rodata_offset + rodata->len : layout->text_offset + text->len;
|
||||
layout->segment_file_size = z_macho_align((size_t)segment_content_size, layout->page_size);
|
||||
layout->segment_vm_size = layout->segment_file_size;
|
||||
layout->rebase_offset = rebase->len > 0 ? (uint32_t)layout->segment_file_size : 0;
|
||||
layout->rebase_size = (uint32_t)rebase->len;
|
||||
layout->code_signature_offset = (uint32_t)z_macho_align((size_t)layout->segment_file_size + rebase->len, 16);
|
||||
uint32_t hash_offset = (uint32_t)z_macho_align(88 + strlen(identifier) + 1, 4);
|
||||
uint32_t slots = (layout->code_signature_offset + 4095u) / 4096u;
|
||||
layout->code_signature_size = 20 + hash_offset + slots * 32;
|
||||
layout->linkedit_vmaddr = layout->base_addr + layout->segment_file_size;
|
||||
layout->linkedit_vmsize = z_macho_align(layout->code_signature_size, layout->page_size);
|
||||
}
|
||||
|
||||
static void macho_patch_bytes(ZBuf *buf, size_t offset, const unsigned char *bytes, size_t len) {
|
||||
if (!buf || !bytes || offset + len > buf->len) return;
|
||||
for (size_t i = 0; i < len; i++) buf->data[offset + i] = (char)bytes[i];
|
||||
}
|
||||
|
||||
static void macho_append_dylinker_command(ZBuf *out, const ZMachOExecutableLayout *layout) {
|
||||
z_macho_append_u32(out, MACHO_LC_LOAD_DYLINKER);
|
||||
z_macho_append_u32(out, layout->dylinker_cmd_size);
|
||||
z_macho_append_u32(out, 12);
|
||||
z_macho_append_bytes(out, "/usr/lib/dyld", strlen("/usr/lib/dyld") + 1);
|
||||
z_macho_pad_to(out, layout->header_size + layout->pagezero_cmd_size + layout->text_segment_cmd_size + layout->linkedit_cmd_size + layout->dyld_info_cmd_size + layout->uuid_cmd_size + layout->dylinker_cmd_size);
|
||||
}
|
||||
|
||||
static void macho_append_libsystem_command(ZBuf *out, const ZMachOExecutableLayout *layout) {
|
||||
z_macho_append_u32(out, MACHO_LC_LOAD_DYLIB);
|
||||
z_macho_append_u32(out, layout->libsystem_cmd_size);
|
||||
z_macho_append_u32(out, 24);
|
||||
z_macho_append_u32(out, 2);
|
||||
z_macho_append_u32(out, 0x054c0000);
|
||||
z_macho_append_u32(out, 0x00010000);
|
||||
z_macho_append_bytes(out, "/usr/lib/libSystem.B.dylib", strlen("/usr/lib/libSystem.B.dylib") + 1);
|
||||
z_macho_pad_to(out, layout->header_size + layout->pagezero_cmd_size + layout->text_segment_cmd_size + layout->linkedit_cmd_size + layout->dyld_info_cmd_size + layout->uuid_cmd_size + layout->dylinker_cmd_size + layout->libsystem_cmd_size);
|
||||
}
|
||||
|
||||
static void macho_append_executable_load_commands(ZBuf *out, const ZMachOExecutableImage *image, size_t *uuid_offset) {
|
||||
const ZMachOExecutableLayout *layout = &image->layout;
|
||||
const ZBuf empty = {0};
|
||||
const ZBuf *text = image->text ? image->text : ∅
|
||||
const ZBuf *rodata = image->rodata ? image->rodata : ∅
|
||||
uint32_t section_count = rodata->len > 0 ? 2u : 1u;
|
||||
|
||||
macho_append_segment64(out, "__PAGEZERO", 0, layout->base_addr, 0, 0, 0, 0, 0, 0);
|
||||
macho_append_segment64(out, "__TEXT", layout->base_addr, layout->segment_vm_size, 0, layout->segment_file_size, 5, 5, section_count, 0);
|
||||
macho_append_section64(out, "__text", "__TEXT", layout->base_addr + layout->text_offset, text->len, layout->text_offset, 2, 0, 0, 0x80000400u);
|
||||
if (rodata->len > 0) macho_append_section64(out, "__const", "__TEXT", layout->base_addr + layout->rodata_offset, rodata->len, layout->rodata_offset, 3, 0, 0, 0);
|
||||
macho_append_segment64(out, "__LINKEDIT", layout->linkedit_vmaddr, layout->linkedit_vmsize, layout->code_signature_offset, layout->code_signature_size, 1, 1, 0, 0);
|
||||
z_macho_append_u32(out, MACHO_LC_DYLD_INFO_ONLY);
|
||||
z_macho_append_u32(out, layout->dyld_info_cmd_size);
|
||||
z_macho_append_u32(out, layout->rebase_offset);
|
||||
z_macho_append_u32(out, layout->rebase_size);
|
||||
for (unsigned i = 0; i < 8; i++) z_macho_append_u32(out, 0);
|
||||
z_macho_append_u32(out, MACHO_LC_UUID);
|
||||
z_macho_append_u32(out, layout->uuid_cmd_size);
|
||||
if (uuid_offset) *uuid_offset = out->len;
|
||||
for (unsigned i = 0; i < 16; i++) z_macho_append_u8(out, 0);
|
||||
macho_append_dylinker_command(out, layout);
|
||||
macho_append_libsystem_command(out, layout);
|
||||
z_macho_append_u32(out, MACHO_LC_MAIN);
|
||||
z_macho_append_u32(out, layout->main_cmd_size);
|
||||
z_macho_append_u64(out, layout->text_offset);
|
||||
z_macho_append_u64(out, 0);
|
||||
z_macho_append_u32(out, MACHO_LC_BUILD_VERSION);
|
||||
z_macho_append_u32(out, layout->build_version_cmd_size);
|
||||
z_macho_append_u32(out, 1);
|
||||
z_macho_append_u32(out, 0x000b0000);
|
||||
z_macho_append_u32(out, 0);
|
||||
z_macho_append_u32(out, 0);
|
||||
z_macho_append_u32(out, MACHO_LC_CODE_SIGNATURE);
|
||||
z_macho_append_u32(out, layout->code_signature_cmd_size);
|
||||
z_macho_append_u32(out, layout->code_signature_offset);
|
||||
z_macho_append_u32(out, layout->code_signature_size);
|
||||
}
|
||||
|
||||
void z_macho_write_executable64(ZBuf *out, const ZMachOExecutableImage *image) {
|
||||
const ZBuf empty = {0};
|
||||
const char *identifier = image && image->code_signature_id ? image->code_signature_id : "zero-direct";
|
||||
const ZBuf *text = image && image->text ? image->text : ∅
|
||||
const ZBuf *rodata = image && image->rodata ? image->rodata : ∅
|
||||
const ZBuf *rebase = image && image->rebase ? image->rebase : ∅
|
||||
ZMachOExecutableImage local = image ? *image : (ZMachOExecutableImage){0};
|
||||
if (local.layout.text_offset == 0) z_macho_compute_executable64_layout(&local.layout, text, rodata, rebase, identifier);
|
||||
local.text = text;
|
||||
local.rodata = rodata;
|
||||
local.rebase = rebase;
|
||||
local.code_signature_id = identifier;
|
||||
|
||||
zbuf_init(out);
|
||||
macho_append_header64(out, local.cpu, 2, 10, local.layout.sizeofcmds, 0x200085);
|
||||
size_t uuid_offset = 0;
|
||||
macho_append_executable_load_commands(out, &local, &uuid_offset);
|
||||
z_macho_pad_to(out, local.layout.text_offset);
|
||||
if (text->data) z_macho_append_bytes(out, text->data, text->len);
|
||||
if (rodata->len > 0) {
|
||||
z_macho_pad_to(out, local.layout.rodata_offset);
|
||||
if (rodata->data) z_macho_append_bytes(out, rodata->data, rodata->len);
|
||||
}
|
||||
z_macho_pad_to(out, (size_t)local.layout.segment_file_size);
|
||||
if (rebase->data) z_macho_append_bytes(out, rebase->data, rebase->len);
|
||||
z_macho_pad_to(out, local.layout.code_signature_offset);
|
||||
unsigned char uuid_hash[32];
|
||||
macho_sha256_hash((const unsigned char *)out->data, out->len, uuid_hash);
|
||||
uuid_hash[6] = (unsigned char)((uuid_hash[6] & 0x0fu) | 0x50u);
|
||||
uuid_hash[8] = (unsigned char)((uuid_hash[8] & 0x3fu) | 0x80u);
|
||||
macho_patch_bytes(out, uuid_offset, uuid_hash, 16);
|
||||
ZBuf signature;
|
||||
macho_append_code_signature(&signature, (const unsigned char *)out->data, out->len, identifier);
|
||||
if (signature.data) z_macho_append_bytes(out, signature.data, signature.len);
|
||||
zbuf_free(&signature);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef ZERO_C_MACHO_FORMAT_H
|
||||
#define ZERO_C_MACHO_FORMAT_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t string_offset;
|
||||
unsigned char type;
|
||||
unsigned char section;
|
||||
uint16_t desc;
|
||||
uint64_t value;
|
||||
} ZMachOSymbol;
|
||||
|
||||
typedef enum {
|
||||
Z_MACHO_CPU_ARM64,
|
||||
Z_MACHO_CPU_X86_64
|
||||
} ZMachOCpu;
|
||||
|
||||
typedef struct {
|
||||
ZMachOCpu cpu;
|
||||
const ZBuf *text;
|
||||
const ZBuf *rodata;
|
||||
const ZBuf *relocs;
|
||||
const ZBuf *strings;
|
||||
const ZMachOSymbol *symbols;
|
||||
size_t symbol_len;
|
||||
uint32_t text_reloc_count;
|
||||
} ZMachOObjectImage;
|
||||
|
||||
typedef struct {
|
||||
uint64_t base_addr;
|
||||
uint32_t page_size;
|
||||
uint32_t header_size;
|
||||
uint32_t pagezero_cmd_size;
|
||||
uint32_t text_segment_cmd_size;
|
||||
uint32_t linkedit_cmd_size;
|
||||
uint32_t dyld_info_cmd_size;
|
||||
uint32_t uuid_cmd_size;
|
||||
uint32_t dylinker_cmd_size;
|
||||
uint32_t libsystem_cmd_size;
|
||||
uint32_t main_cmd_size;
|
||||
uint32_t build_version_cmd_size;
|
||||
uint32_t code_signature_cmd_size;
|
||||
uint32_t sizeofcmds;
|
||||
uint32_t text_offset;
|
||||
uint32_t rodata_offset;
|
||||
uint64_t segment_file_size;
|
||||
uint64_t segment_vm_size;
|
||||
uint32_t rebase_offset;
|
||||
uint32_t rebase_size;
|
||||
uint32_t code_signature_offset;
|
||||
uint32_t code_signature_size;
|
||||
uint64_t linkedit_vmaddr;
|
||||
uint64_t linkedit_vmsize;
|
||||
} ZMachOExecutableLayout;
|
||||
|
||||
typedef struct {
|
||||
ZMachOCpu cpu;
|
||||
const ZBuf *text;
|
||||
const ZBuf *rodata;
|
||||
const ZBuf *rebase;
|
||||
ZMachOExecutableLayout layout;
|
||||
const char *code_signature_id;
|
||||
} ZMachOExecutableImage;
|
||||
|
||||
size_t z_macho_align(size_t value, size_t alignment);
|
||||
void z_macho_append_u8(ZBuf *buf, unsigned value);
|
||||
void z_macho_append_u16(ZBuf *buf, uint16_t value);
|
||||
void z_macho_append_u32(ZBuf *buf, uint32_t value);
|
||||
void z_macho_append_u64(ZBuf *buf, uint64_t value);
|
||||
void z_macho_patch_u64(ZBuf *buf, size_t offset, uint64_t value);
|
||||
void z_macho_append_bytes(ZBuf *buf, const char *bytes, size_t len);
|
||||
void z_macho_pad_to(ZBuf *buf, size_t offset);
|
||||
void z_macho_append_uleb128(ZBuf *buf, uint64_t value);
|
||||
void z_macho_write_object64(ZBuf *out, const ZMachOObjectImage *image);
|
||||
void z_macho_compute_executable64_layout(ZMachOExecutableLayout *layout, const ZBuf *text, const ZBuf *rodata, const ZBuf *rebase, const char *code_signature_id);
|
||||
void z_macho_write_executable64(ZBuf *out, const ZMachOExecutableImage *image);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,429 @@
|
||||
#include "manifest_toml.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *toml_skip_ws(const char *cursor) {
|
||||
while (*cursor && isspace((unsigned char)*cursor)) cursor++;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
static const char *toml_skip_string(const char *cursor) {
|
||||
if (*cursor != '"') return NULL;
|
||||
cursor++;
|
||||
while (*cursor) {
|
||||
if (*cursor == '\\' && cursor[1]) {
|
||||
cursor += 2;
|
||||
continue;
|
||||
}
|
||||
if (*cursor == '"') return cursor + 1;
|
||||
cursor++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void toml_manifest_push_c_lib(ZManifest *manifest, ZManifestCLib lib) {
|
||||
manifest->c_libs = z_checked_reallocarray(manifest->c_libs, manifest->c_lib_count + 1, sizeof(ZManifestCLib));
|
||||
manifest->c_libs[manifest->c_lib_count++] = lib;
|
||||
}
|
||||
|
||||
static void toml_manifest_push_dependency(ZManifest *manifest, ZManifestDependency dep) {
|
||||
manifest->dependencies = z_checked_reallocarray(manifest->dependencies, manifest->dependency_count + 1, sizeof(ZManifestDependency));
|
||||
manifest->dependencies[manifest->dependency_count++] = dep;
|
||||
}
|
||||
|
||||
static ZManifestDependency *toml_manifest_dependency_named(ZManifest *manifest, const char *name) {
|
||||
if (!manifest || !name || !name[0]) return NULL;
|
||||
for (size_t i = 0; i < manifest->dependency_count; i++) {
|
||||
if (manifest->dependencies[i].name && strcmp(manifest->dependencies[i].name, name) == 0) return &manifest->dependencies[i];
|
||||
}
|
||||
ZManifestDependency dep = {0};
|
||||
dep.name = z_strdup(name ? name : "");
|
||||
dep.version = z_strdup("");
|
||||
dep.path = z_strdup("");
|
||||
dep.targets_json = z_strdup("[]");
|
||||
toml_manifest_push_dependency(manifest, dep);
|
||||
return &manifest->dependencies[manifest->dependency_count - 1];
|
||||
}
|
||||
|
||||
static ZManifestCLib *toml_manifest_c_lib_named(ZManifest *manifest, const char *name) {
|
||||
if (!manifest || !name || !name[0]) return NULL;
|
||||
for (size_t i = 0; i < manifest->c_lib_count; i++) {
|
||||
if (manifest->c_libs[i].name && strcmp(manifest->c_libs[i].name, name) == 0) return &manifest->c_libs[i];
|
||||
}
|
||||
ZManifestCLib lib = {0};
|
||||
lib.name = z_strdup(name ? name : "");
|
||||
lib.headers_json = z_strdup("[]");
|
||||
lib.include_json = z_strdup("[]");
|
||||
lib.lib_json = z_strdup("[]");
|
||||
lib.link_json = z_strdup("[]");
|
||||
lib.mode = z_strdup("static");
|
||||
lib.pkg_config = z_strdup("");
|
||||
toml_manifest_push_c_lib(manifest, lib);
|
||||
return &manifest->c_libs[manifest->c_lib_count - 1];
|
||||
}
|
||||
|
||||
static void toml_manifest_replace_string(char **slot, char *value) {
|
||||
if (!slot) {
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
free(*slot);
|
||||
*slot = value ? value : z_strdup("");
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TOML_MANIFEST_STRING,
|
||||
TOML_MANIFEST_STRING_ARRAY,
|
||||
TOML_MANIFEST_STRING_LIST,
|
||||
} TomlManifestValueKind;
|
||||
|
||||
typedef struct {
|
||||
const char *field;
|
||||
char **slot;
|
||||
TomlManifestValueKind kind;
|
||||
} TomlManifestFieldBinding;
|
||||
|
||||
typedef struct {
|
||||
char *trimmed;
|
||||
char *key;
|
||||
char *value;
|
||||
char *full;
|
||||
} TomlParsedLine;
|
||||
|
||||
static void toml_parsed_line_free(TomlParsedLine *parsed) {
|
||||
if (!parsed) return;
|
||||
free(parsed->full);
|
||||
free(parsed->value);
|
||||
free(parsed->key);
|
||||
free(parsed->trimmed);
|
||||
memset(parsed, 0, sizeof(*parsed));
|
||||
}
|
||||
|
||||
static void toml_set_diag(ZDiag *diag, int line, const char *message, const char *expected, const char *actual, const char *help) {
|
||||
if (!diag) return;
|
||||
diag->code = 2002;
|
||||
diag->line = line;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message ? message : "zero.toml parse error");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", expected ? expected : "valid zero.toml");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : "");
|
||||
snprintf(diag->help, sizeof(diag->help), "%s", help ? help : "fix zero.toml before using the package");
|
||||
}
|
||||
|
||||
static char *toml_trim_copy(const char *start, size_t len) {
|
||||
while (len > 0 && isspace((unsigned char)*start)) {
|
||||
start++;
|
||||
len--;
|
||||
}
|
||||
while (len > 0 && isspace((unsigned char)start[len - 1])) len--;
|
||||
return z_strndup(start, len);
|
||||
}
|
||||
|
||||
static void toml_strip_comment(char *line) {
|
||||
bool in_string = false;
|
||||
bool escaped = false;
|
||||
for (char *cursor = line; cursor && *cursor; cursor++) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (in_string && *cursor == '\\') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (*cursor == '"') {
|
||||
in_string = !in_string;
|
||||
continue;
|
||||
}
|
||||
if (!in_string && *cursor == '#') {
|
||||
*cursor = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char *toml_parse_string_copy(const char *value) {
|
||||
const char *cursor = toml_skip_ws(value ? value : "");
|
||||
if (*cursor != '"') return NULL;
|
||||
cursor++;
|
||||
ZBuf out;
|
||||
zbuf_init(&out);
|
||||
while (*cursor) {
|
||||
if (*cursor == '"') {
|
||||
if (!out.data) zbuf_append(&out, "");
|
||||
return out.data;
|
||||
}
|
||||
if (*cursor == '\\' && cursor[1]) {
|
||||
cursor++;
|
||||
switch (*cursor) {
|
||||
case '"': zbuf_append_char(&out, '"'); break;
|
||||
case '\\': zbuf_append_char(&out, '\\'); break;
|
||||
case 'n': zbuf_append_char(&out, '\n'); break;
|
||||
case 'r': zbuf_append_char(&out, '\r'); break;
|
||||
case 't': zbuf_append_char(&out, '\t'); break;
|
||||
default: zbuf_append_char(&out, *cursor); break;
|
||||
}
|
||||
cursor++;
|
||||
continue;
|
||||
}
|
||||
zbuf_append_char(&out, *cursor++);
|
||||
}
|
||||
zbuf_free(&out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool toml_parse_bool(const char *value, bool *out) {
|
||||
const char *start = toml_skip_ws(value ? value : "");
|
||||
const char *end = start + strlen(start);
|
||||
while (end > start && isspace((unsigned char)end[-1])) end--;
|
||||
if ((size_t)(end - start) == 4 && strncmp(start, "true", 4) == 0) {
|
||||
if (out) *out = true;
|
||||
return true;
|
||||
}
|
||||
if ((size_t)(end - start) == 5 && strncmp(start, "false", 5) == 0) {
|
||||
if (out) *out = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void toml_append_json_string(ZBuf *buf, const char *text) {
|
||||
zbuf_append_char(buf, '"');
|
||||
for (const unsigned char *cursor = (const unsigned char *)(text ? text : ""); *cursor; cursor++) {
|
||||
switch (*cursor) {
|
||||
case '\\': zbuf_append(buf, "\\\\"); break;
|
||||
case '"': zbuf_append(buf, "\\\""); break;
|
||||
case '\n': zbuf_append(buf, "\\n"); break;
|
||||
case '\r': zbuf_append(buf, "\\r"); break;
|
||||
case '\t': zbuf_append(buf, "\\t"); break;
|
||||
default:
|
||||
if (*cursor < 0x20) zbuf_appendf(buf, "\\u%04x", *cursor);
|
||||
else zbuf_append_char(buf, (char)*cursor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
zbuf_append_char(buf, '"');
|
||||
}
|
||||
|
||||
static char *toml_array_to_json(const char *value) {
|
||||
const char *cursor = toml_skip_ws(value ? value : "");
|
||||
if (*cursor != '[') return z_strdup("[]");
|
||||
cursor++;
|
||||
ZBuf out;
|
||||
zbuf_init(&out);
|
||||
zbuf_append_char(&out, '[');
|
||||
bool first = true;
|
||||
while (*cursor) {
|
||||
cursor = toml_skip_ws(cursor);
|
||||
if (*cursor == ']') {
|
||||
zbuf_append_char(&out, ']');
|
||||
return out.data ? out.data : z_strdup("[]");
|
||||
}
|
||||
char *item = toml_parse_string_copy(cursor);
|
||||
if (!item) break;
|
||||
if (!first) zbuf_append_char(&out, ',');
|
||||
toml_append_json_string(&out, item);
|
||||
first = false;
|
||||
free(item);
|
||||
const char *after_item = toml_skip_string(cursor);
|
||||
if (!after_item) break;
|
||||
cursor = toml_skip_ws(after_item);
|
||||
if (*cursor == ',') {
|
||||
cursor++;
|
||||
continue;
|
||||
}
|
||||
if (*cursor == ']') {
|
||||
zbuf_append_char(&out, ']');
|
||||
return out.data ? out.data : z_strdup("[]");
|
||||
}
|
||||
break;
|
||||
}
|
||||
zbuf_free(&out);
|
||||
return z_strdup("[]");
|
||||
}
|
||||
|
||||
static char *toml_string_to_json_array(const char *value) {
|
||||
char *item = toml_parse_string_copy(value);
|
||||
if (!item) return toml_array_to_json(value);
|
||||
ZBuf out;
|
||||
zbuf_init(&out);
|
||||
zbuf_append_char(&out, '[');
|
||||
toml_append_json_string(&out, item);
|
||||
zbuf_append_char(&out, ']');
|
||||
free(item);
|
||||
return out.data ? out.data : z_strdup("[]");
|
||||
}
|
||||
|
||||
static char *toml_full_key(const char *table, const char *key) {
|
||||
if (!table || !table[0]) return z_strdup(key ? key : "");
|
||||
ZBuf out;
|
||||
zbuf_init(&out);
|
||||
zbuf_append(&out, table);
|
||||
zbuf_append_char(&out, '.');
|
||||
zbuf_append(&out, key ? key : "");
|
||||
return out.data ? out.data : z_strdup("");
|
||||
}
|
||||
|
||||
static bool toml_split_name_field(const char *suffix, char *name, size_t name_len, const char **field) {
|
||||
const char *dot = suffix ? strchr(suffix, '.') : NULL;
|
||||
if (!dot || dot == suffix) return false;
|
||||
size_t len = (size_t)(dot - suffix);
|
||||
if (len >= name_len) len = name_len - 1;
|
||||
memcpy(name, suffix, len);
|
||||
name[len] = 0;
|
||||
if (field) *field = dot + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *toml_value_for_kind(TomlManifestValueKind kind, const char *value) {
|
||||
switch (kind) {
|
||||
case TOML_MANIFEST_STRING: return toml_parse_string_copy(value);
|
||||
case TOML_MANIFEST_STRING_ARRAY: return toml_string_to_json_array(value);
|
||||
case TOML_MANIFEST_STRING_LIST: return toml_array_to_json(value);
|
||||
}
|
||||
return z_strdup("");
|
||||
}
|
||||
|
||||
static bool toml_assign_bound_field(const char *field, const char *value, const TomlManifestFieldBinding *bindings, size_t binding_count) {
|
||||
if (!field || !bindings) return false;
|
||||
for (size_t i = 0; i < binding_count; i++) {
|
||||
if (bindings[i].field && strcmp(field, bindings[i].field) == 0) {
|
||||
toml_manifest_replace_string(bindings[i].slot, toml_value_for_kind(bindings[i].kind, value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool toml_set_dependency_field(ZManifest *out, const char *suffix, const char *value) {
|
||||
char name[128];
|
||||
const char *field = NULL;
|
||||
if (!toml_split_name_field(suffix, name, sizeof(name), &field)) {
|
||||
ZManifestDependency *dep = toml_manifest_dependency_named(out, suffix);
|
||||
char *version = dep ? toml_parse_string_copy(value) : NULL;
|
||||
if (version) toml_manifest_replace_string(&dep->version, version);
|
||||
return dep != NULL;
|
||||
}
|
||||
ZManifestDependency *dep = toml_manifest_dependency_named(out, name);
|
||||
if (!dep) return false;
|
||||
TomlManifestFieldBinding bindings[] = {
|
||||
{"path", &dep->path, TOML_MANIFEST_STRING},
|
||||
{"version", &dep->version, TOML_MANIFEST_STRING},
|
||||
{"targets", &dep->targets_json, TOML_MANIFEST_STRING_LIST},
|
||||
{"target", &dep->targets_json, TOML_MANIFEST_STRING_ARRAY},
|
||||
};
|
||||
toml_assign_bound_field(field, value, bindings, sizeof(bindings) / sizeof(bindings[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool toml_set_c_lib_field(ZManifest *out, const char *suffix, const char *value) {
|
||||
char name[128];
|
||||
const char *field = NULL;
|
||||
if (!toml_split_name_field(suffix, name, sizeof(name), &field)) return true;
|
||||
ZManifestCLib *lib = toml_manifest_c_lib_named(out, name);
|
||||
if (!lib) return false;
|
||||
TomlManifestFieldBinding bindings[] = {
|
||||
{"headers", &lib->headers_json, TOML_MANIFEST_STRING_LIST},
|
||||
{"include", &lib->include_json, TOML_MANIFEST_STRING_LIST},
|
||||
{"lib", &lib->lib_json, TOML_MANIFEST_STRING_LIST},
|
||||
{"link", &lib->link_json, TOML_MANIFEST_STRING_LIST},
|
||||
{"mode", &lib->mode, TOML_MANIFEST_STRING},
|
||||
{"pkg_config", &lib->pkg_config, TOML_MANIFEST_STRING},
|
||||
{"pkgConfig", &lib->pkg_config, TOML_MANIFEST_STRING},
|
||||
};
|
||||
toml_assign_bound_field(field, value, bindings, sizeof(bindings) / sizeof(bindings[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool toml_key_has_prefix(const char *key, const char *prefix, const char **suffix) {
|
||||
if (!key || !prefix) return false;
|
||||
size_t len = strlen(prefix);
|
||||
if (strncmp(key, prefix, len) != 0) return false;
|
||||
if (suffix) *suffix = key + len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_parse_manifest_toml(const char *manifest, ZManifest *out, ZDiag *diag) {
|
||||
if (!out) return false;
|
||||
memset(out, 0, sizeof(*out));
|
||||
char *copy = z_strdup(manifest ? manifest : "");
|
||||
char table[256] = {0};
|
||||
int line_number = 0;
|
||||
char *line = copy;
|
||||
while (line) {
|
||||
line_number++;
|
||||
char *next = strchr(line, '\n');
|
||||
if (next) *next++ = 0;
|
||||
toml_strip_comment(line);
|
||||
char *trimmed = toml_trim_copy(line, strlen(line));
|
||||
if (!trimmed[0]) {
|
||||
free(trimmed);
|
||||
line = next;
|
||||
continue;
|
||||
}
|
||||
if (trimmed[0] == '[') {
|
||||
char *end = strchr(trimmed, ']');
|
||||
if (!end) {
|
||||
toml_set_diag(diag, line_number, "zero.toml table header is not closed", "[table]", trimmed, "close the TOML table header before adding fields");
|
||||
free(trimmed);
|
||||
free(copy);
|
||||
return false;
|
||||
}
|
||||
char *name = toml_trim_copy(trimmed + 1, (size_t)(end - trimmed - 1));
|
||||
snprintf(table, sizeof(table), "%s", name);
|
||||
free(name);
|
||||
free(trimmed);
|
||||
line = next;
|
||||
continue;
|
||||
}
|
||||
char *equals = strchr(trimmed, '=');
|
||||
if (!equals) {
|
||||
free(trimmed);
|
||||
line = next;
|
||||
continue;
|
||||
}
|
||||
TomlParsedLine parsed = {0};
|
||||
parsed.trimmed = trimmed;
|
||||
parsed.key = toml_trim_copy(trimmed, (size_t)(equals - trimmed));
|
||||
parsed.value = toml_trim_copy(equals + 1, strlen(equals + 1));
|
||||
parsed.full = toml_full_key(table, parsed.key);
|
||||
|
||||
TomlManifestFieldBinding fields[] = {
|
||||
{"package.name", &out->package_name, TOML_MANIFEST_STRING},
|
||||
{"package.version", &out->package_version, TOML_MANIFEST_STRING},
|
||||
{"targets.cli.main", &out->main_path, TOML_MANIFEST_STRING},
|
||||
{"targets.cli.graph", &out->graph_path, TOML_MANIFEST_STRING},
|
||||
{"targets.cli.kind", &out->kind, TOML_MANIFEST_STRING},
|
||||
};
|
||||
const char *suffix = NULL;
|
||||
bool handled = toml_assign_bound_field(parsed.full, parsed.value, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (!handled && strcmp(parsed.full, "repositoryGraph.compilerInput") == 0) {
|
||||
bool bool_value = false;
|
||||
if (!toml_parse_bool(parsed.value, &bool_value)) {
|
||||
toml_set_diag(diag, line_number, "repositoryGraph.compilerInput must be a boolean", "true or false", parsed.value, "remove repositoryGraph.compilerInput or set it to a boolean compatibility value");
|
||||
toml_parsed_line_free(&parsed);
|
||||
free(copy);
|
||||
return false;
|
||||
}
|
||||
out->repository_graph_compiler_input_present = true;
|
||||
out->repository_graph_compiler_input = bool_value;
|
||||
handled = true;
|
||||
}
|
||||
if (!handled && toml_key_has_prefix(parsed.full, "deps.", &suffix)) {
|
||||
toml_set_dependency_field(out, suffix, parsed.value);
|
||||
} else if (!handled && toml_key_has_prefix(parsed.full, "dependencies.", &suffix)) {
|
||||
toml_set_dependency_field(out, suffix, parsed.value);
|
||||
} else if (!handled && toml_key_has_prefix(parsed.full, "c.libs.", &suffix)) {
|
||||
toml_set_c_lib_field(out, suffix, parsed.value);
|
||||
}
|
||||
|
||||
toml_parsed_line_free(&parsed);
|
||||
line = next;
|
||||
}
|
||||
free(copy);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef ZERO_MANIFEST_TOML_H
|
||||
#define ZERO_MANIFEST_TOML_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
bool z_parse_manifest_toml(const char *manifest, ZManifest *out, ZDiag *diag);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
#ifndef ZERO_C_MIR_BINARY_H
|
||||
#define ZERO_C_MIR_BINARY_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef struct {
|
||||
bool hit;
|
||||
bool mapped;
|
||||
bool borrowed_storage;
|
||||
size_t byte_len;
|
||||
char path[512];
|
||||
} ZMirBinaryCacheFacts;
|
||||
|
||||
Z_RET_OWNED char *z_mir_binary_cache_path_for_graph_store(Z_IN const char *store_path,
|
||||
Z_IN const char *graph_hash,
|
||||
Z_IN const ZTargetInfo *target,
|
||||
Z_IN const char *emit_kind,
|
||||
Z_IN const char *backend);
|
||||
|
||||
bool z_mir_binary_write_path(Z_IN const char *path,
|
||||
Z_IN const IrProgram *program,
|
||||
Z_IN const char *graph_hash,
|
||||
Z_IN const ZTargetInfo *target,
|
||||
Z_IN const char *emit_kind,
|
||||
Z_IN const char *backend,
|
||||
Z_OUT ZDiag *diag);
|
||||
|
||||
bool z_mir_binary_load_path(Z_IN const char *path,
|
||||
Z_IN const char *expected_graph_hash,
|
||||
Z_IN const ZTargetInfo *target,
|
||||
Z_IN const char *emit_kind,
|
||||
Z_IN const char *backend,
|
||||
Z_OUT IrProgram *out,
|
||||
Z_OUT ZMirBinaryCacheFacts *facts,
|
||||
Z_OUT ZDiag *diag);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
#ifndef ZERO_C_MIR_VERIFY_H
|
||||
#define ZERO_C_MIR_VERIFY_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
bool z_mir_verify_direct_contracts(IrProgram *ir);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,318 @@
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#include "process_exec.h"
|
||||
#include "process_path.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#if defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
void z_process_argv_init(ZProcessArgv *argv) {
|
||||
argv->items = NULL;
|
||||
argv->len = 0;
|
||||
argv->cap = 0;
|
||||
}
|
||||
|
||||
bool z_process_argv_push(ZProcessArgv *argv, const char *value) {
|
||||
if (!argv || !value || !value[0]) return false;
|
||||
size_t required = argv->len + 2;
|
||||
if (required > argv->cap) {
|
||||
size_t next = z_grow_capacity(argv->cap, required, 8);
|
||||
argv->items = z_checked_reallocarray(argv->items, next, sizeof(char *));
|
||||
argv->cap = next;
|
||||
}
|
||||
argv->items[argv->len++] = z_strdup(value);
|
||||
argv->items[argv->len] = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_process_argv_append_flag_text(ZProcessArgv *argv, const char *text, bool *suppress_stderr) {
|
||||
const char *cursor = text ? text : "";
|
||||
while (*cursor) {
|
||||
while (isspace((unsigned char)*cursor)) cursor++;
|
||||
if (!*cursor) break;
|
||||
|
||||
ZBuf token;
|
||||
zbuf_init(&token);
|
||||
while (*cursor && !isspace((unsigned char)*cursor)) {
|
||||
if (*cursor == '\'') {
|
||||
cursor++;
|
||||
bool closed = false;
|
||||
while (*cursor && *cursor != '\'') zbuf_append_char(&token, *cursor++);
|
||||
if (*cursor == '\'') {
|
||||
closed = true;
|
||||
cursor++;
|
||||
}
|
||||
if (!closed) {
|
||||
zbuf_free(&token);
|
||||
return false;
|
||||
}
|
||||
} else if (*cursor == '\\' && cursor[1]) {
|
||||
cursor++;
|
||||
zbuf_append_char(&token, *cursor++);
|
||||
} else {
|
||||
zbuf_append_char(&token, *cursor++);
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (token.len > 0) {
|
||||
if (strcmp(token.data, "2>/dev/null") == 0) {
|
||||
if (suppress_stderr) *suppress_stderr = true;
|
||||
} else {
|
||||
ok = z_process_argv_push(argv, token.data);
|
||||
}
|
||||
}
|
||||
zbuf_free(&token);
|
||||
if (!ok) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void z_process_argv_free(ZProcessArgv *argv) {
|
||||
if (!argv) return;
|
||||
for (size_t i = 0; i < argv->len; i++) free(argv->items[i]);
|
||||
free(argv->items);
|
||||
argv->items = NULL;
|
||||
argv->len = 0;
|
||||
argv->cap = 0;
|
||||
}
|
||||
|
||||
static bool z_process_existing_dir(const char *path) {
|
||||
#if defined(_WIN32)
|
||||
struct _stat st;
|
||||
return _stat(path, &st) == 0 && (st.st_mode & _S_IFDIR) != 0;
|
||||
#else
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool z_process_ensure_dir(const char *path) {
|
||||
if (!path || !path[0]) return false;
|
||||
#if defined(_WIN32)
|
||||
if (_mkdir(path) == 0) return true;
|
||||
#else
|
||||
if (mkdir(path, 0777) == 0) return true;
|
||||
#endif
|
||||
if (errno != EEXIST) return false;
|
||||
return z_process_existing_dir(path);
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
typedef struct _stat ZProcessOutputStat;
|
||||
#else
|
||||
typedef struct stat ZProcessOutputStat;
|
||||
#endif
|
||||
|
||||
static bool z_process_lstat_output(const char *path, ZProcessOutputStat *st) {
|
||||
if (!path || !path[0] || !st) return false;
|
||||
#if defined(_WIN32)
|
||||
return _stat(path, st) == 0;
|
||||
#else
|
||||
return lstat(path, st) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool z_process_output_is_regular(const ZProcessOutputStat *st) {
|
||||
#if defined(_WIN32)
|
||||
return st && (st->st_mode & _S_IFREG) != 0;
|
||||
#else
|
||||
return st && S_ISREG(st->st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool z_process_output_is_symlink(const ZProcessOutputStat *st) {
|
||||
#if defined(_WIN32)
|
||||
(void)st;
|
||||
return false;
|
||||
#else
|
||||
return st && S_ISLNK(st->st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool z_process_output_parent_ready(const char *path) {
|
||||
if (!path || !path[0]) return false;
|
||||
const char *last_sep = NULL;
|
||||
for (const char *cursor = path; *cursor; cursor++) {
|
||||
if (*cursor == '/' || *cursor == '\\') last_sep = cursor;
|
||||
}
|
||||
if (!last_sep) return true;
|
||||
if (last_sep == path) return z_process_existing_dir("/");
|
||||
#if defined(_WIN32)
|
||||
if (last_sep == path + 2 && isalpha((unsigned char)path[0]) && path[1] == ':') {
|
||||
char drive_root[4] = {path[0], ':', '\\', 0};
|
||||
return z_process_existing_dir(drive_root);
|
||||
}
|
||||
#endif
|
||||
ZBuf parent;
|
||||
zbuf_init(&parent);
|
||||
for (const char *cursor = path; cursor < last_sep; cursor++) zbuf_append_char(&parent, *cursor);
|
||||
bool ok = z_process_existing_dir(parent.data);
|
||||
zbuf_free(&parent);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool z_process_prepare_output_file(const char *path) {
|
||||
ZProcessOutputStat st;
|
||||
if (!path || !path[0]) return false;
|
||||
if (!z_process_output_parent_ready(path)) return false;
|
||||
if (!z_process_lstat_output(path, &st)) return errno == ENOENT;
|
||||
if (z_process_output_is_symlink(&st) || !z_process_output_is_regular(&st)) return false;
|
||||
if (remove(path) == 0) return true;
|
||||
return errno == ENOENT;
|
||||
}
|
||||
|
||||
bool z_process_output_file_ready(const char *path) {
|
||||
ZProcessOutputStat st;
|
||||
if (!z_process_lstat_output(path, &st)) return false;
|
||||
if (z_process_output_is_symlink(&st) || !z_process_output_is_regular(&st)) return false;
|
||||
return st.st_size > 0;
|
||||
}
|
||||
|
||||
bool z_process_executable_file_ready(const char *path) {
|
||||
if (!z_process_output_file_ready(path)) return false;
|
||||
#if defined(_WIN32)
|
||||
return true;
|
||||
#else
|
||||
return access(path, X_OK) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool z_process_mark_executable(const char *path) {
|
||||
if (!z_process_output_file_ready(path)) return false;
|
||||
#if defined(_WIN32)
|
||||
return true;
|
||||
#else
|
||||
if (chmod(path, 0755) != 0) return false;
|
||||
return z_process_executable_file_ready(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool z_process_remove_regular_file(const char *path) {
|
||||
ZProcessOutputStat st;
|
||||
if (!path || !path[0]) return false;
|
||||
if (!z_process_lstat_output(path, &st)) return errno == ENOENT;
|
||||
if (z_process_output_is_symlink(&st) || !z_process_output_is_regular(&st)) return false;
|
||||
if (remove(path) == 0) return true;
|
||||
return errno == ENOENT;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
static bool z_process_suppress_stream(FILE *stream) {
|
||||
return freopen("/dev/null", "w", stream) != NULL;
|
||||
}
|
||||
|
||||
static bool z_process_wait_success(pid_t pid) {
|
||||
int status = 0;
|
||||
while (waitpid(pid, &status, 0) < 0) {
|
||||
if (errno != EINTR) return false;
|
||||
}
|
||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool z_process_run_argv(const ZProcessArgv *argv, bool suppress_stdout, bool suppress_stderr, bool uses_zig_env) {
|
||||
if (!argv || argv->len == 0 || !argv->items || !argv->items[0]) return false;
|
||||
char *executable = z_process_resolve_executable(argv->items[0]);
|
||||
if (!executable) return false;
|
||||
#if defined(_WIN32)
|
||||
(void)suppress_stdout;
|
||||
(void)suppress_stderr;
|
||||
(void)uses_zig_env;
|
||||
int rc = _spawnv(_P_WAIT, executable, (const char *const *)argv->items);
|
||||
free(executable);
|
||||
return rc == 0;
|
||||
#else
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
free(executable);
|
||||
return false;
|
||||
}
|
||||
if (pid == 0) {
|
||||
if (uses_zig_env) {
|
||||
if (setenv("ZIG_GLOBAL_CACHE_DIR", ".zero/zig-global-cache", 1) != 0) _exit(127);
|
||||
if (setenv("ZIG_LOCAL_CACHE_DIR", ".zero/zig-local-cache", 1) != 0) _exit(127);
|
||||
}
|
||||
if (suppress_stdout && !z_process_suppress_stream(stdout)) _exit(127);
|
||||
if (suppress_stderr && !z_process_suppress_stream(stderr)) _exit(127);
|
||||
execv(executable, argv->items);
|
||||
_exit(127);
|
||||
}
|
||||
free(executable);
|
||||
return z_process_wait_success(pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
char *z_process_first_stdout_line(const char *const *argv, bool suppress_stderr) {
|
||||
if (!argv || !argv[0]) return z_strdup("");
|
||||
char *executable = z_process_resolve_executable(argv[0]);
|
||||
if (!executable) return z_strdup("");
|
||||
#if defined(_WIN32)
|
||||
(void)suppress_stderr;
|
||||
free(executable);
|
||||
return z_strdup("");
|
||||
#else
|
||||
int pipe_fd[2];
|
||||
if (pipe(pipe_fd) != 0) {
|
||||
free(executable);
|
||||
return z_strdup("");
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
close(pipe_fd[0]);
|
||||
close(pipe_fd[1]);
|
||||
free(executable);
|
||||
return z_strdup("");
|
||||
}
|
||||
if (pid == 0) {
|
||||
close(pipe_fd[0]);
|
||||
if (dup2(pipe_fd[1], STDOUT_FILENO) < 0) _exit(127);
|
||||
close(pipe_fd[1]);
|
||||
if (suppress_stderr && !z_process_suppress_stream(stderr)) _exit(127);
|
||||
execv(executable, (char *const *)argv);
|
||||
_exit(127);
|
||||
}
|
||||
free(executable);
|
||||
close(pipe_fd[1]);
|
||||
ZBuf line;
|
||||
zbuf_init(&line);
|
||||
bool capturing = true;
|
||||
bool read_ok = true;
|
||||
char bytes[256];
|
||||
while (true) {
|
||||
ssize_t n = read(pipe_fd[0], bytes, sizeof(bytes));
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
read_ok = false;
|
||||
break;
|
||||
}
|
||||
if (n == 0) break;
|
||||
for (ssize_t i = 0; capturing && i < n; i++) {
|
||||
if (bytes[i] == '\n' || bytes[i] == '\r') {
|
||||
capturing = false;
|
||||
} else if (line.len < 255) {
|
||||
zbuf_append_char(&line, bytes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
close(pipe_fd[0]);
|
||||
bool child_ok = z_process_wait_success(pid);
|
||||
char *out = read_ok && child_ok && line.data ? line.data : z_strdup("");
|
||||
if (out == line.data) line.data = NULL;
|
||||
zbuf_free(&line);
|
||||
return out;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef ZERO_PROCESS_EXEC_H
|
||||
#define ZERO_PROCESS_EXEC_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef struct {
|
||||
char **items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} ZProcessArgv;
|
||||
|
||||
void z_process_argv_init(Z_OUT ZProcessArgv *argv);
|
||||
bool z_process_argv_push(Z_INOUT ZProcessArgv *argv, Z_IN const char *value);
|
||||
bool z_process_argv_append_flag_text(Z_INOUT ZProcessArgv *argv, Z_IN const char *text, Z_INOUT bool *suppress_stderr);
|
||||
void z_process_argv_free(Z_INOUT ZProcessArgv *argv);
|
||||
bool z_process_ensure_dir(Z_IN const char *path);
|
||||
bool z_process_prepare_output_file(Z_IN const char *path);
|
||||
bool z_process_output_file_ready(Z_IN const char *path);
|
||||
bool z_process_executable_file_ready(Z_IN const char *path);
|
||||
bool z_process_mark_executable(Z_IN const char *path);
|
||||
bool z_process_remove_regular_file(Z_IN const char *path);
|
||||
bool z_process_run_argv(Z_IN const ZProcessArgv *argv, bool suppress_stdout, bool suppress_stderr, bool uses_zig_env);
|
||||
bool z_process_command_available(Z_IN const char *name);
|
||||
Z_RET_OWNED char *z_process_first_stdout_line(Z_IN const char *const *argv, bool suppress_stderr);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#include "process_exec.h"
|
||||
#include "process_path.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#if defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static bool process_command_name_safe(const char *name) {
|
||||
if (!name || !name[0]) return false;
|
||||
for (size_t i = 0; name[i]; i++) {
|
||||
unsigned char ch = (unsigned char)name[i];
|
||||
if (!(isalnum(ch) || ch == '_' || ch == '-' || ch == '.' || ch == '+')) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_has_path_separator(const char *path) {
|
||||
if (!path) return false;
|
||||
for (size_t i = 0; path[i]; i++) {
|
||||
if (path[i] == '/' || path[i] == '\\') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool process_path_executable(const char *path) {
|
||||
struct stat st;
|
||||
#if defined(_WIN32)
|
||||
return path && path[0] && stat(path, &st) == 0 && S_ISREG(st.st_mode);
|
||||
#else
|
||||
return path && path[0] && stat(path, &st) == 0 && S_ISREG(st.st_mode) && access(path, X_OK) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool process_path_segment_absolute(const char *segment, size_t len) {
|
||||
if (!segment || len == 0) return false;
|
||||
#if defined(_WIN32)
|
||||
if (len >= 3 && isalpha((unsigned char)segment[0]) && segment[1] == ':' && (segment[2] == '\\' || segment[2] == '/')) return true;
|
||||
return len >= 2 && segment[0] == '\\' && segment[1] == '\\';
|
||||
#else
|
||||
return segment[0] == '/';
|
||||
#endif
|
||||
}
|
||||
|
||||
char *z_process_resolve_executable(const char *name) {
|
||||
if (!name || !name[0]) return NULL;
|
||||
if (process_has_path_separator(name)) return process_path_executable(name) ? z_strdup(name) : NULL;
|
||||
if (!process_command_name_safe(name)) return NULL;
|
||||
const char *path = getenv("PATH");
|
||||
if (!path || !path[0]) return NULL;
|
||||
#if defined(_WIN32)
|
||||
const char delimiter = ';';
|
||||
#else
|
||||
const char delimiter = ':';
|
||||
#endif
|
||||
const char *cursor = path;
|
||||
while (true) {
|
||||
const char *end = strchr(cursor, delimiter);
|
||||
size_t dir_len = end ? (size_t)(end - cursor) : strlen(cursor);
|
||||
if (process_path_segment_absolute(cursor, dir_len)) {
|
||||
ZBuf candidate;
|
||||
zbuf_init(&candidate);
|
||||
for (size_t i = 0; i < dir_len; i++) zbuf_append_char(&candidate, cursor[i]);
|
||||
if (candidate.len > 0 && candidate.data[candidate.len - 1] != '/' && candidate.data[candidate.len - 1] != '\\') zbuf_append_char(&candidate, '/');
|
||||
zbuf_append(&candidate, name);
|
||||
bool ok = process_path_executable(candidate.data);
|
||||
#if defined(_WIN32)
|
||||
if (!ok) {
|
||||
zbuf_append(&candidate, ".exe");
|
||||
ok = process_path_executable(candidate.data);
|
||||
}
|
||||
#endif
|
||||
if (ok) return candidate.data;
|
||||
zbuf_free(&candidate);
|
||||
}
|
||||
if (!end) break;
|
||||
cursor = end + 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool z_process_command_available(const char *name) {
|
||||
char *executable = z_process_resolve_executable(name);
|
||||
bool ok = executable != NULL;
|
||||
free(executable);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef ZERO_PROCESS_PATH_H
|
||||
#define ZERO_PROCESS_PATH_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
Z_RET_OWNED char *z_process_resolve_executable(Z_IN const char *name);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "program_graph.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void z_program_graph_init(ZProgramGraph *graph) {
|
||||
*graph = (ZProgramGraph){
|
||||
.schema_version = 1,
|
||||
.validation_state = Z_PROGRAM_GRAPH_VALIDATION_DECODED,
|
||||
.module_identity = z_strdup("module:main"),
|
||||
};
|
||||
}
|
||||
|
||||
void z_program_graph_free(ZProgramGraph *graph) {
|
||||
if (!graph) return;
|
||||
for (size_t i = 0; i < graph->node_len; i++) {
|
||||
free(graph->nodes[i].id);
|
||||
free(graph->nodes[i].name);
|
||||
free(graph->nodes[i].type);
|
||||
free(graph->nodes[i].value);
|
||||
free(graph->nodes[i].path);
|
||||
free(graph->nodes[i].symbol_id);
|
||||
free(graph->nodes[i].type_id);
|
||||
free(graph->nodes[i].effect_id);
|
||||
free(graph->nodes[i].node_hash);
|
||||
}
|
||||
for (size_t i = 0; i < graph->edge_len; i++) {
|
||||
free(graph->edges[i].from);
|
||||
free(graph->edges[i].to);
|
||||
free(graph->edges[i].kind);
|
||||
}
|
||||
free(graph->module_identity);
|
||||
free(graph->graph_hash);
|
||||
free(graph->nodes);
|
||||
free(graph->edges);
|
||||
*graph = (ZProgramGraph){0};
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_H
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
typedef enum {
|
||||
Z_PROGRAM_GRAPH_NODE_MODULE,
|
||||
Z_PROGRAM_GRAPH_NODE_IMPORT,
|
||||
Z_PROGRAM_GRAPH_NODE_C_IMPORT,
|
||||
Z_PROGRAM_GRAPH_NODE_CONST,
|
||||
Z_PROGRAM_GRAPH_NODE_TYPE_ALIAS,
|
||||
Z_PROGRAM_GRAPH_NODE_SHAPE,
|
||||
Z_PROGRAM_GRAPH_NODE_INTERFACE,
|
||||
Z_PROGRAM_GRAPH_NODE_ENUM,
|
||||
Z_PROGRAM_GRAPH_NODE_CHOICE,
|
||||
Z_PROGRAM_GRAPH_NODE_FUNCTION,
|
||||
Z_PROGRAM_GRAPH_NODE_PARAM,
|
||||
Z_PROGRAM_GRAPH_NODE_FIELD,
|
||||
Z_PROGRAM_GRAPH_NODE_ENUM_CASE,
|
||||
Z_PROGRAM_GRAPH_NODE_CHOICE_CASE,
|
||||
Z_PROGRAM_GRAPH_NODE_BLOCK,
|
||||
Z_PROGRAM_GRAPH_NODE_LET,
|
||||
Z_PROGRAM_GRAPH_NODE_ASSIGNMENT,
|
||||
Z_PROGRAM_GRAPH_NODE_DEFER,
|
||||
Z_PROGRAM_GRAPH_NODE_CHECK,
|
||||
Z_PROGRAM_GRAPH_NODE_RETURN,
|
||||
Z_PROGRAM_GRAPH_NODE_EXPRESSION_STATEMENT,
|
||||
Z_PROGRAM_GRAPH_NODE_IF,
|
||||
Z_PROGRAM_GRAPH_NODE_WHILE,
|
||||
Z_PROGRAM_GRAPH_NODE_FOR,
|
||||
Z_PROGRAM_GRAPH_NODE_BREAK,
|
||||
Z_PROGRAM_GRAPH_NODE_CONTINUE,
|
||||
Z_PROGRAM_GRAPH_NODE_MATCH,
|
||||
Z_PROGRAM_GRAPH_NODE_RAISE,
|
||||
Z_PROGRAM_GRAPH_NODE_MATCH_ARM,
|
||||
Z_PROGRAM_GRAPH_NODE_IDENTIFIER,
|
||||
Z_PROGRAM_GRAPH_NODE_LITERAL,
|
||||
Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS,
|
||||
Z_PROGRAM_GRAPH_NODE_INDEX_ACCESS,
|
||||
Z_PROGRAM_GRAPH_NODE_SLICE,
|
||||
Z_PROGRAM_GRAPH_NODE_CALL,
|
||||
Z_PROGRAM_GRAPH_NODE_METHOD_CALL,
|
||||
Z_PROGRAM_GRAPH_NODE_CAST,
|
||||
Z_PROGRAM_GRAPH_NODE_BORROW,
|
||||
Z_PROGRAM_GRAPH_NODE_RESCUE,
|
||||
Z_PROGRAM_GRAPH_NODE_META,
|
||||
Z_PROGRAM_GRAPH_NODE_SHAPE_LITERAL,
|
||||
Z_PROGRAM_GRAPH_NODE_ARRAY_LITERAL,
|
||||
Z_PROGRAM_GRAPH_NODE_FIELD_INIT,
|
||||
Z_PROGRAM_GRAPH_NODE_TYPE_REF,
|
||||
Z_PROGRAM_GRAPH_NODE_EFFECT_REF,
|
||||
Z_PROGRAM_GRAPH_NODE_ERROR_VARIANT,
|
||||
Z_PROGRAM_GRAPH_NODE_EXPRESSION,
|
||||
Z_PROGRAM_GRAPH_NODE_STATEMENT,
|
||||
} ZProgramGraphNodeKind;
|
||||
|
||||
typedef enum {
|
||||
Z_PROGRAM_GRAPH_EDGE_TARGET_NODE,
|
||||
Z_PROGRAM_GRAPH_EDGE_TARGET_SYMBOL,
|
||||
Z_PROGRAM_GRAPH_EDGE_TARGET_TYPE,
|
||||
Z_PROGRAM_GRAPH_EDGE_TARGET_EFFECT,
|
||||
} ZProgramGraphEdgeTarget;
|
||||
|
||||
typedef enum {
|
||||
Z_PROGRAM_GRAPH_VALIDATION_DECODED,
|
||||
Z_PROGRAM_GRAPH_VALIDATION_SHAPE_VALID,
|
||||
Z_PROGRAM_GRAPH_VALIDATION_RESOLVED,
|
||||
Z_PROGRAM_GRAPH_VALIDATION_TYPED,
|
||||
Z_PROGRAM_GRAPH_VALIDATION_EFFECT_CHECKED,
|
||||
Z_PROGRAM_GRAPH_VALIDATION_BUILDABLE,
|
||||
} ZProgramGraphValidationState;
|
||||
|
||||
typedef struct {
|
||||
char *id;
|
||||
ZProgramGraphNodeKind kind;
|
||||
char *name;
|
||||
char *type;
|
||||
char *value;
|
||||
char *path;
|
||||
char *symbol_id;
|
||||
char *type_id;
|
||||
char *effect_id;
|
||||
char *node_hash;
|
||||
int line;
|
||||
int column;
|
||||
bool is_public;
|
||||
bool is_mutable;
|
||||
bool is_static;
|
||||
bool fallible;
|
||||
bool export_c;
|
||||
} ZProgramGraphNode;
|
||||
|
||||
typedef struct {
|
||||
char *from;
|
||||
char *to;
|
||||
char *kind;
|
||||
ZProgramGraphEdgeTarget target;
|
||||
size_t order;
|
||||
} ZProgramGraphEdge;
|
||||
|
||||
typedef struct ZProgramGraph {
|
||||
unsigned schema_version;
|
||||
ZProgramGraphValidationState validation_state;
|
||||
char *module_identity;
|
||||
char *graph_hash;
|
||||
ZProgramGraphNode *nodes;
|
||||
size_t node_len, node_cap;
|
||||
ZProgramGraphEdge *edges;
|
||||
size_t edge_len, edge_cap, next_id;
|
||||
bool canonical_source;
|
||||
} ZProgramGraph;
|
||||
|
||||
typedef struct {
|
||||
bool ok;
|
||||
ZProgramGraphValidationState state;
|
||||
char code[16];
|
||||
char message[160];
|
||||
char node_id[64];
|
||||
char edge_from[64];
|
||||
char edge_to[64];
|
||||
char edge_target[16];
|
||||
} ZProgramGraphValidation;
|
||||
|
||||
typedef struct {
|
||||
const char **slots;
|
||||
size_t cap;
|
||||
size_t len;
|
||||
} ZProgramGraphIdSet;
|
||||
|
||||
void z_program_graph_id_set_init(ZProgramGraphIdSet *set, size_t expected);
|
||||
void z_program_graph_id_set_free(ZProgramGraphIdSet *set);
|
||||
bool z_program_graph_id_set_has(const ZProgramGraphIdSet *set, const char *id);
|
||||
void z_program_graph_id_set_add(ZProgramGraphIdSet *set, const char *id);
|
||||
char *z_program_graph_source_node_base_id(const ZProgramGraph *graph, const ZProgramGraphNode *node, const ZProgramGraphEdge *owner_edge, const char *owner_new_id);
|
||||
char *z_program_graph_source_node_collision_id(const ZProgramGraphNode *node, const char *base_id, const ZProgramGraphIdSet *used, bool force_suffix);
|
||||
const char *z_program_graph_node_kind_name(ZProgramGraphNodeKind kind);
|
||||
const char *z_program_graph_edge_target_name(ZProgramGraphEdgeTarget target);
|
||||
const char *z_program_graph_validation_state_name(ZProgramGraphValidationState state);
|
||||
void z_program_graph_assign_source_node_ids(ZProgramGraph *graph);
|
||||
void z_program_graph_finalize_identities(ZProgramGraph *graph);
|
||||
bool z_program_graph_node_id_valid(const char *id);
|
||||
void z_program_graph_init(ZProgramGraph *graph);
|
||||
void z_program_graph_free(ZProgramGraph *graph);
|
||||
bool z_program_graph_clone(const ZProgramGraph *from, ZProgramGraph *out);
|
||||
bool z_program_graph_validate(const ZProgramGraph *graph, ZProgramGraphValidation *validation);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,312 @@
|
||||
#include "program_graph_adjacency.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int adjacency_text_cmp(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "");
|
||||
}
|
||||
|
||||
static int adjacency_size_cmp(size_t left, size_t right) {
|
||||
if (left < right) return -1;
|
||||
return left > right ? 1 : 0;
|
||||
}
|
||||
|
||||
static int adjacency_node_entry_cmp(const void *left, const void *right) {
|
||||
const ZProgramGraphAdjacencyNodeEntry *a = left;
|
||||
const ZProgramGraphAdjacencyNodeEntry *b = right;
|
||||
int cmp = adjacency_text_cmp(a->id, b->id);
|
||||
if (cmp != 0) return cmp;
|
||||
return adjacency_size_cmp(a->node_index, b->node_index);
|
||||
}
|
||||
|
||||
static int adjacency_owner_entry_cmp(const void *left, const void *right) {
|
||||
const ZProgramGraphAdjacencyEdgeEntry *a = left;
|
||||
const ZProgramGraphAdjacencyEdgeEntry *b = right;
|
||||
int cmp = adjacency_size_cmp((size_t)a->edge->target, (size_t)b->edge->target);
|
||||
if (cmp == 0) cmp = adjacency_text_cmp(a->edge->from, b->edge->from);
|
||||
if (cmp == 0) cmp = adjacency_text_cmp(a->edge->kind, b->edge->kind);
|
||||
if (cmp == 0) cmp = adjacency_size_cmp(a->edge->order, b->edge->order);
|
||||
if (cmp == 0) cmp = adjacency_size_cmp(a->edge_index, b->edge_index);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
static int adjacency_child_entry_cmp(const void *left, const void *right) {
|
||||
const ZProgramGraphAdjacencyEdgeEntry *a = left;
|
||||
const ZProgramGraphAdjacencyEdgeEntry *b = right;
|
||||
int cmp = adjacency_text_cmp(a->edge->to, b->edge->to);
|
||||
if (cmp == 0) cmp = adjacency_text_cmp(a->edge->kind, b->edge->kind);
|
||||
if (cmp == 0) cmp = adjacency_size_cmp(a->edge_index, b->edge_index);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
ZProgramGraphAdjacencyNodeEntry *z_program_graph_id_index_build(const char *const *ids, size_t len) {
|
||||
ZProgramGraphAdjacencyNodeEntry *entries = z_checked_calloc(len ? len : 1, sizeof(ZProgramGraphAdjacencyNodeEntry));
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
entries[i] = (ZProgramGraphAdjacencyNodeEntry){.id = ids ? ids[i] : NULL, .node_index = i};
|
||||
}
|
||||
qsort(entries, len, sizeof(ZProgramGraphAdjacencyNodeEntry), adjacency_node_entry_cmp);
|
||||
return entries;
|
||||
}
|
||||
|
||||
static size_t id_index_lower_bound(const ZProgramGraphAdjacencyNodeEntry *entries, size_t len, const char *id) {
|
||||
size_t low = 0;
|
||||
size_t high = len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
if (adjacency_text_cmp(entries[mid].id, id) < 0) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
size_t z_program_graph_id_index_find(const ZProgramGraphAdjacencyNodeEntry *entries, size_t len, const char *id) {
|
||||
if (!entries || len == 0 || !id) return SIZE_MAX;
|
||||
size_t low = id_index_lower_bound(entries, len, id);
|
||||
if (low < len && adjacency_text_cmp(entries[low].id, id) == 0) return entries[low].node_index;
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
void z_program_graph_id_index_run(const ZProgramGraphAdjacencyNodeEntry *entries, size_t len, const char *id, size_t *start, size_t *run_len) {
|
||||
if (start) *start = 0;
|
||||
if (run_len) *run_len = 0;
|
||||
if (!entries || len == 0 || !id) return;
|
||||
size_t low = id_index_lower_bound(entries, len, id);
|
||||
size_t high = low;
|
||||
while (high < len && adjacency_text_cmp(entries[high].id, id) == 0) high++;
|
||||
if (start) *start = low;
|
||||
if (run_len) *run_len = high - low;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per-process memo of the most recent adjacency sort. Compiler commands
|
||||
* rebuild adjacency for the same loaded graph several times per invocation
|
||||
* (validation, stdlib reference scan, resolution facts, semantic checks);
|
||||
* the memo keeps the sorted order as table indexes and replays it with a
|
||||
* linear copy. Following the lowering index precedent, it revalidates
|
||||
* against the graph identity, table addresses, lengths, id allocation
|
||||
* cursor, and graph hash, so a rebuilt or mutated graph that happens to
|
||||
* reuse an address can never serve a stale order. Sort comparators tiebreak
|
||||
* on table index, so a replayed order is exactly what a fresh sort returns.
|
||||
*/
|
||||
typedef struct {
|
||||
const ZProgramGraph *graph;
|
||||
const ZProgramGraphNode *nodes;
|
||||
const ZProgramGraphEdge *edges;
|
||||
size_t node_len;
|
||||
size_t edge_len;
|
||||
size_t next_id;
|
||||
char *graph_hash;
|
||||
size_t *node_order;
|
||||
size_t *owner_order;
|
||||
size_t *child_order;
|
||||
size_t node_edge_len;
|
||||
bool present;
|
||||
} AdjacencyMemo;
|
||||
|
||||
static AdjacencyMemo adjacency_memo;
|
||||
|
||||
static bool adjacency_memo_matches(const ZProgramGraph *graph) {
|
||||
return adjacency_memo.present && graph &&
|
||||
adjacency_memo.graph == graph &&
|
||||
adjacency_memo.nodes == graph->nodes &&
|
||||
adjacency_memo.edges == graph->edges &&
|
||||
adjacency_memo.node_len == graph->node_len &&
|
||||
adjacency_memo.edge_len == graph->edge_len &&
|
||||
adjacency_memo.next_id == graph->next_id &&
|
||||
adjacency_text_cmp(adjacency_memo.graph_hash, graph->graph_hash) == 0;
|
||||
}
|
||||
|
||||
static void adjacency_memo_store(const ZProgramGraph *graph, const ZProgramGraphAdjacency *adjacency) {
|
||||
free(adjacency_memo.graph_hash);
|
||||
free(adjacency_memo.node_order);
|
||||
free(adjacency_memo.owner_order);
|
||||
free(adjacency_memo.child_order);
|
||||
adjacency_memo = (AdjacencyMemo){
|
||||
.graph = graph,
|
||||
.nodes = graph->nodes,
|
||||
.edges = graph->edges,
|
||||
.node_len = graph->node_len,
|
||||
.edge_len = graph->edge_len,
|
||||
.next_id = graph->next_id,
|
||||
.graph_hash = z_strdup(graph->graph_hash ? graph->graph_hash : ""),
|
||||
.node_order = z_checked_calloc(graph->node_len ? graph->node_len : 1, sizeof(size_t)),
|
||||
.owner_order = z_checked_calloc(graph->edge_len ? graph->edge_len : 1, sizeof(size_t)),
|
||||
.child_order = z_checked_calloc(adjacency->node_edge_len ? adjacency->node_edge_len : 1, sizeof(size_t)),
|
||||
.node_edge_len = adjacency->node_edge_len,
|
||||
.present = true,
|
||||
};
|
||||
for (size_t i = 0; i < graph->node_len; i++) adjacency_memo.node_order[i] = adjacency->nodes_by_id[i].node_index;
|
||||
for (size_t i = 0; i < graph->edge_len; i++) adjacency_memo.owner_order[i] = adjacency->edges_by_owner[i].edge_index;
|
||||
for (size_t i = 0; i < adjacency->node_edge_len; i++) adjacency_memo.child_order[i] = adjacency->edges_by_child[i].edge_index;
|
||||
}
|
||||
|
||||
static bool adjacency_memo_replay(ZProgramGraphAdjacency *adjacency, const ZProgramGraph *graph) {
|
||||
if (!adjacency_memo_matches(graph)) return false;
|
||||
size_t node_len = graph->node_len;
|
||||
size_t edge_len = graph->edge_len;
|
||||
adjacency->nodes_by_id = z_checked_calloc(node_len ? node_len : 1, sizeof(ZProgramGraphAdjacencyNodeEntry));
|
||||
for (size_t i = 0; i < node_len; i++) {
|
||||
size_t node_index = adjacency_memo.node_order[i];
|
||||
adjacency->nodes_by_id[i] = (ZProgramGraphAdjacencyNodeEntry){.id = graph->nodes[node_index].id, .node_index = node_index};
|
||||
}
|
||||
adjacency->edges_by_owner = z_checked_calloc(edge_len ? edge_len : 1, sizeof(ZProgramGraphAdjacencyEdgeEntry));
|
||||
for (size_t i = 0; i < edge_len; i++) {
|
||||
size_t edge_index = adjacency_memo.owner_order[i];
|
||||
adjacency->edges_by_owner[i] = (ZProgramGraphAdjacencyEdgeEntry){.edge = &graph->edges[edge_index], .edge_index = edge_index};
|
||||
}
|
||||
adjacency->node_edge_len = adjacency_memo.node_edge_len;
|
||||
adjacency->edges_by_child = z_checked_calloc(adjacency_memo.node_edge_len ? adjacency_memo.node_edge_len : 1, sizeof(ZProgramGraphAdjacencyEdgeEntry));
|
||||
for (size_t i = 0; i < adjacency_memo.node_edge_len; i++) {
|
||||
size_t edge_index = adjacency_memo.child_order[i];
|
||||
adjacency->edges_by_child[i] = (ZProgramGraphAdjacencyEdgeEntry){.edge = &graph->edges[edge_index], .edge_index = edge_index};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void z_program_graph_adjacency_init(ZProgramGraphAdjacency *adjacency, const ZProgramGraph *graph) {
|
||||
if (!adjacency) return;
|
||||
*adjacency = (ZProgramGraphAdjacency){.graph = graph};
|
||||
if (graph && adjacency_memo_replay(adjacency, graph)) return;
|
||||
size_t node_len = graph ? graph->node_len : 0;
|
||||
size_t edge_len = graph ? graph->edge_len : 0;
|
||||
adjacency->nodes_by_id = z_checked_calloc(node_len ? node_len : 1, sizeof(ZProgramGraphAdjacencyNodeEntry));
|
||||
for (size_t i = 0; i < node_len; i++) {
|
||||
adjacency->nodes_by_id[i] = (ZProgramGraphAdjacencyNodeEntry){.id = graph->nodes[i].id, .node_index = i};
|
||||
}
|
||||
qsort(adjacency->nodes_by_id, node_len, sizeof(ZProgramGraphAdjacencyNodeEntry), adjacency_node_entry_cmp);
|
||||
adjacency->edges_by_owner = z_checked_calloc(edge_len ? edge_len : 1, sizeof(ZProgramGraphAdjacencyEdgeEntry));
|
||||
for (size_t i = 0; i < edge_len; i++) {
|
||||
adjacency->edges_by_owner[i] = (ZProgramGraphAdjacencyEdgeEntry){.edge = &graph->edges[i], .edge_index = i};
|
||||
}
|
||||
qsort(adjacency->edges_by_owner, edge_len, sizeof(ZProgramGraphAdjacencyEdgeEntry), adjacency_owner_entry_cmp);
|
||||
size_t node_edge_len = 0;
|
||||
while (node_edge_len < edge_len && adjacency->edges_by_owner[node_edge_len].edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE) {
|
||||
node_edge_len++;
|
||||
}
|
||||
adjacency->node_edge_len = node_edge_len;
|
||||
adjacency->edges_by_child = z_checked_calloc(node_edge_len ? node_edge_len : 1, sizeof(ZProgramGraphAdjacencyEdgeEntry));
|
||||
for (size_t i = 0; i < node_edge_len; i++) {
|
||||
adjacency->edges_by_child[i] = adjacency->edges_by_owner[i];
|
||||
}
|
||||
qsort(adjacency->edges_by_child, node_edge_len, sizeof(ZProgramGraphAdjacencyEdgeEntry), adjacency_child_entry_cmp);
|
||||
if (graph && graph->node_len > 0) adjacency_memo_store(graph, adjacency);
|
||||
}
|
||||
|
||||
void z_program_graph_adjacency_free(ZProgramGraphAdjacency *adjacency) {
|
||||
if (!adjacency) return;
|
||||
free(adjacency->nodes_by_id);
|
||||
free(adjacency->edges_by_owner);
|
||||
free(adjacency->edges_by_child);
|
||||
*adjacency = (ZProgramGraphAdjacency){0};
|
||||
}
|
||||
|
||||
size_t z_program_graph_adjacency_node_index(const ZProgramGraphAdjacency *adjacency, const char *id) {
|
||||
if (!adjacency || !adjacency->graph || !id || !id[0]) return SIZE_MAX;
|
||||
size_t low = 0;
|
||||
size_t high = adjacency->graph->node_len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
if (adjacency_text_cmp(adjacency->nodes_by_id[mid].id, id) < 0) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
if (low < adjacency->graph->node_len && adjacency_text_cmp(adjacency->nodes_by_id[low].id, id) == 0) {
|
||||
return adjacency->nodes_by_id[low].node_index;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
const ZProgramGraphNode *z_program_graph_adjacency_node(const ZProgramGraphAdjacency *adjacency, const char *id) {
|
||||
size_t index = z_program_graph_adjacency_node_index(adjacency, id);
|
||||
return index == SIZE_MAX ? NULL : &adjacency->graph->nodes[index];
|
||||
}
|
||||
|
||||
static int adjacency_owner_query_cmp(const ZProgramGraphAdjacencyEdgeEntry *entry, const char *from, const char *kind) {
|
||||
int cmp = adjacency_text_cmp(entry->edge->from, from);
|
||||
if (cmp == 0 && kind) cmp = adjacency_text_cmp(entry->edge->kind, kind);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
void z_program_graph_adjacency_owner_run(const ZProgramGraphAdjacency *adjacency, const char *from, const char *kind, size_t *start, size_t *len) {
|
||||
if (start) *start = 0;
|
||||
if (len) *len = 0;
|
||||
if (!adjacency || !from) return;
|
||||
size_t low = 0;
|
||||
size_t high = adjacency->node_edge_len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
if (adjacency_owner_query_cmp(&adjacency->edges_by_owner[mid], from, kind) < 0) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
size_t first = low;
|
||||
high = adjacency->node_edge_len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
if (adjacency_owner_query_cmp(&adjacency->edges_by_owner[mid], from, kind) <= 0) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
if (start) *start = first;
|
||||
if (len) *len = low - first;
|
||||
}
|
||||
|
||||
const ZProgramGraphEdge *z_program_graph_adjacency_owner_edge_at(const ZProgramGraphAdjacency *adjacency, size_t position) {
|
||||
if (!adjacency || position >= (adjacency->graph ? adjacency->graph->edge_len : 0)) return NULL;
|
||||
return adjacency->edges_by_owner[position].edge;
|
||||
}
|
||||
|
||||
static int adjacency_child_query_cmp(const ZProgramGraphAdjacencyEdgeEntry *entry, const char *to, const char *kind) {
|
||||
int cmp = adjacency_text_cmp(entry->edge->to, to);
|
||||
if (cmp == 0 && kind) cmp = adjacency_text_cmp(entry->edge->kind, kind);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
void z_program_graph_adjacency_child_run(const ZProgramGraphAdjacency *adjacency, const char *to, const char *kind, size_t *start, size_t *len) {
|
||||
if (start) *start = 0;
|
||||
if (len) *len = 0;
|
||||
if (!adjacency || !to) return;
|
||||
size_t low = 0;
|
||||
size_t high = adjacency->node_edge_len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
if (adjacency_child_query_cmp(&adjacency->edges_by_child[mid], to, kind) < 0) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
size_t first = low;
|
||||
high = adjacency->node_edge_len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
if (adjacency_child_query_cmp(&adjacency->edges_by_child[mid], to, kind) <= 0) low = mid + 1;
|
||||
else high = mid;
|
||||
}
|
||||
if (start) *start = first;
|
||||
if (len) *len = low - first;
|
||||
}
|
||||
|
||||
const ZProgramGraphEdge *z_program_graph_adjacency_child_edge_at(const ZProgramGraphAdjacency *adjacency, size_t position) {
|
||||
if (!adjacency || position >= adjacency->node_edge_len) return NULL;
|
||||
return adjacency->edges_by_child[position].edge;
|
||||
}
|
||||
|
||||
const ZProgramGraphEdge *z_program_graph_adjacency_first_child_edge(const ZProgramGraphAdjacency *adjacency, const char *to) {
|
||||
size_t start = 0;
|
||||
size_t len = 0;
|
||||
z_program_graph_adjacency_child_run(adjacency, to, NULL, &start, &len);
|
||||
const ZProgramGraphEdge *first = NULL;
|
||||
size_t first_index = SIZE_MAX;
|
||||
for (size_t i = start; i < start + len; i++) {
|
||||
if (adjacency->edges_by_child[i].edge_index < first_index) {
|
||||
first_index = adjacency->edges_by_child[i].edge_index;
|
||||
first = adjacency->edges_by_child[i].edge;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
bool z_program_graph_adjacency_has_child_edge(const ZProgramGraphAdjacency *adjacency, const char *to, const char *kind) {
|
||||
size_t start = 0;
|
||||
size_t len = 0;
|
||||
if (!kind) return false;
|
||||
z_program_graph_adjacency_child_run(adjacency, to, kind, &start, &len);
|
||||
return len > 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef ZERO_PROGRAM_GRAPH_ADJACENCY_H
|
||||
#define ZERO_PROGRAM_GRAPH_ADJACENCY_H
|
||||
|
||||
#include "program_graph.h"
|
||||
|
||||
typedef struct {
|
||||
const char *id;
|
||||
size_t node_index;
|
||||
} ZProgramGraphAdjacencyNodeEntry;
|
||||
|
||||
typedef struct {
|
||||
const ZProgramGraphEdge *edge;
|
||||
size_t edge_index;
|
||||
} ZProgramGraphAdjacencyEdgeEntry;
|
||||
|
||||
typedef struct {
|
||||
const ZProgramGraph *graph;
|
||||
ZProgramGraphAdjacencyNodeEntry *nodes_by_id;
|
||||
ZProgramGraphAdjacencyEdgeEntry *edges_by_owner;
|
||||
ZProgramGraphAdjacencyEdgeEntry *edges_by_child;
|
||||
size_t node_edge_len;
|
||||
} ZProgramGraphAdjacency;
|
||||
|
||||
ZProgramGraphAdjacencyNodeEntry *z_program_graph_id_index_build(const char *const *ids, size_t len);
|
||||
size_t z_program_graph_id_index_find(const ZProgramGraphAdjacencyNodeEntry *entries, size_t len, const char *id);
|
||||
void z_program_graph_id_index_run(const ZProgramGraphAdjacencyNodeEntry *entries, size_t len, const char *id, size_t *start, size_t *run_len);
|
||||
void z_program_graph_adjacency_init(ZProgramGraphAdjacency *adjacency, const ZProgramGraph *graph);
|
||||
void z_program_graph_adjacency_free(ZProgramGraphAdjacency *adjacency);
|
||||
size_t z_program_graph_adjacency_node_index(const ZProgramGraphAdjacency *adjacency, const char *id);
|
||||
const ZProgramGraphNode *z_program_graph_adjacency_node(const ZProgramGraphAdjacency *adjacency, const char *id);
|
||||
void z_program_graph_adjacency_owner_run(const ZProgramGraphAdjacency *adjacency, const char *from, const char *kind, size_t *start, size_t *len);
|
||||
const ZProgramGraphEdge *z_program_graph_adjacency_owner_edge_at(const ZProgramGraphAdjacency *adjacency, size_t position);
|
||||
void z_program_graph_adjacency_child_run(const ZProgramGraphAdjacency *adjacency, const char *to, const char *kind, size_t *start, size_t *len);
|
||||
const ZProgramGraphEdge *z_program_graph_adjacency_child_edge_at(const ZProgramGraphAdjacency *adjacency, size_t position);
|
||||
const ZProgramGraphEdge *z_program_graph_adjacency_first_child_edge(const ZProgramGraphAdjacency *adjacency, const char *to);
|
||||
bool z_program_graph_adjacency_has_child_edge(const ZProgramGraphAdjacency *adjacency, const char *to, const char *kind);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,172 @@
|
||||
#include "program_graph_contracts.h"
|
||||
|
||||
#include "program_graph_adjacency.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
char root[128];
|
||||
char path[256];
|
||||
char binding[128];
|
||||
const char *decl_path;
|
||||
int line;
|
||||
int column;
|
||||
bool mutable_borrow;
|
||||
} ZGraphActiveBorrow;
|
||||
|
||||
static bool borrow_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
static bool borrow_text_starts(const char *text, const char *prefix) {
|
||||
if (!text || !prefix) return false;
|
||||
return strncmp(text, prefix, strlen(prefix)) == 0;
|
||||
}
|
||||
|
||||
static const ZProgramGraphNode *borrow_child(const ZProgramGraphAdjacency *adjacency, const ZProgramGraphNode *node, const char *kind, size_t order) {
|
||||
if (!adjacency || !node || !kind) return NULL;
|
||||
size_t start = 0;
|
||||
size_t len = 0;
|
||||
z_program_graph_adjacency_owner_run(adjacency, node->id, kind, &start, &len);
|
||||
for (size_t i = start; i < start + len; i++) {
|
||||
const ZProgramGraphEdge *edge = z_program_graph_adjacency_owner_edge_at(adjacency, i);
|
||||
if (edge && edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && edge->order == order) {
|
||||
return z_program_graph_adjacency_node(adjacency, edge->to);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool borrow_append_path(char *path, size_t path_len, const char *field) {
|
||||
if (!path || path_len == 0 || !field || !field[0]) return false;
|
||||
size_t used = strlen(path);
|
||||
if (used > 0) {
|
||||
if (used + 1 >= path_len) return false;
|
||||
path[used++] = '.';
|
||||
path[used] = 0;
|
||||
}
|
||||
int written = snprintf(path + used, path_len - used, "%s", field);
|
||||
return written >= 0 && (size_t)written < path_len - used;
|
||||
}
|
||||
|
||||
static bool borrow_place_from_expr(const ZProgramGraphAdjacency *adjacency, const ZProgramGraphNode *expr, char *root, size_t root_len, char *path, size_t path_len) {
|
||||
if (!expr || !root || !path || root_len == 0 || path_len == 0) return false;
|
||||
if (expr->kind == Z_PROGRAM_GRAPH_NODE_IDENTIFIER) {
|
||||
snprintf(root, root_len, "%s", expr->name ? expr->name : "");
|
||||
path[0] = 0;
|
||||
return root[0] != 0;
|
||||
}
|
||||
if (expr->kind == Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS) {
|
||||
const ZProgramGraphNode *left = borrow_child(adjacency, expr, "left", 0);
|
||||
if (!borrow_place_from_expr(adjacency, left, root, root_len, path, path_len)) return false;
|
||||
return borrow_append_path(path, path_len, expr->name);
|
||||
}
|
||||
if (expr->kind == Z_PROGRAM_GRAPH_NODE_BORROW) {
|
||||
return borrow_place_from_expr(adjacency, borrow_child(adjacency, expr, "left", 0), root, root_len, path, path_len);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const ZGraphActiveBorrow *borrow_conflict(const ZGraphActiveBorrow *active, size_t active_len, const char *root, const char *path, bool mutable_op) {
|
||||
for (size_t i = 0; active && i < active_len; i++) {
|
||||
if (!borrow_text_eq(active[i].root, root) || !borrow_text_eq(active[i].path, path)) continue;
|
||||
if (mutable_op || active[i].mutable_borrow) return &active[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void borrow_format_place(char *out, size_t out_len, const char *root, const char *path) {
|
||||
if (!out || out_len == 0) return;
|
||||
if (path && path[0]) snprintf(out, out_len, "%s.%s", root ? root : "", path);
|
||||
else snprintf(out, out_len, "%s", root ? root : "");
|
||||
}
|
||||
|
||||
static bool fail_borrow_conflict(const ZProgramGraphNode *op, const ZGraphActiveBorrow *active, const char *root, const char *path, bool mutable_op, const char *fallback_path, ZDiag *diag) {
|
||||
if (diag) {
|
||||
char place[200];
|
||||
borrow_format_place(place, sizeof(place), root, path);
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 3029;
|
||||
diag->path = op && op->path && op->path[0] ? op->path : fallback_path;
|
||||
diag->line = op && op->line > 0 ? op->line : 1;
|
||||
diag->column = op && op->column > 0 ? op->column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "borrow conflicts with an active lexical borrow");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", mutable_op ? "unborrowed mutable lvalue" : "no active mutable borrow");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%.*s already has %s borrow", 80, place, active && active->mutable_borrow ? "a mutable" : "shared");
|
||||
snprintf(diag->help, sizeof(diag->help), "end the earlier borrow's scope before borrowing again");
|
||||
if (active) {
|
||||
diag->borrow_trace_count = 1;
|
||||
snprintf(diag->borrow_traces[0].root, sizeof(diag->borrow_traces[0].root), "%s", active->root);
|
||||
snprintf(diag->borrow_traces[0].path, sizeof(diag->borrow_traces[0].path), "%s", active->path);
|
||||
snprintf(diag->borrow_traces[0].kind, sizeof(diag->borrow_traces[0].kind), "%s", active->mutable_borrow ? "mutable" : "shared");
|
||||
snprintf(diag->borrow_traces[0].binding, sizeof(diag->borrow_traces[0].binding), "%s", active->binding);
|
||||
diag->borrow_traces[0].binding_decl_path = active->decl_path;
|
||||
diag->borrow_traces[0].binding_line = active->line;
|
||||
diag->borrow_traces[0].binding_column = active->column > 0 ? active->column : 1;
|
||||
}
|
||||
snprintf(diag->borrow_repair, sizeof(diag->borrow_repair), "move the conflicting borrow after the active borrow's lexical scope or put the earlier borrow in an inner block");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool borrow_let_contract_ok(const ZProgramGraphAdjacency *adjacency, const ZProgramGraphNode *stmt, ZGraphActiveBorrow *active, size_t *active_len, const char *path, ZDiag *diag) {
|
||||
const ZProgramGraphNode *expr = borrow_child(adjacency, stmt, "expr", 0);
|
||||
if (!expr || expr->kind != Z_PROGRAM_GRAPH_NODE_BORROW) return true;
|
||||
char root[128] = {0};
|
||||
char place[256] = {0};
|
||||
if (!borrow_place_from_expr(adjacency, expr, root, sizeof(root), place, sizeof(place))) return true;
|
||||
bool mutable_borrow = expr->is_mutable || borrow_text_starts(stmt->type, "mutref<");
|
||||
const ZGraphActiveBorrow *conflict = borrow_conflict(active, active_len ? *active_len : 0, root, place, mutable_borrow);
|
||||
if (conflict) return fail_borrow_conflict(expr, conflict, root, place, mutable_borrow, path, diag);
|
||||
if (active && active_len && *active_len < 64) {
|
||||
ZGraphActiveBorrow *slot = &active[(*active_len)++];
|
||||
snprintf(slot->root, sizeof(slot->root), "%s", root);
|
||||
snprintf(slot->path, sizeof(slot->path), "%s", place);
|
||||
snprintf(slot->binding, sizeof(slot->binding), "%s", stmt->name ? stmt->name : "");
|
||||
slot->decl_path = stmt->path;
|
||||
slot->line = stmt->line;
|
||||
slot->column = stmt->column;
|
||||
slot->mutable_borrow = mutable_borrow;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool borrow_assignment_contract_ok(const ZProgramGraphAdjacency *adjacency, const ZProgramGraphNode *stmt, const ZGraphActiveBorrow *active, size_t active_len, const char *path, ZDiag *diag) {
|
||||
const ZProgramGraphNode *target = borrow_child(adjacency, stmt, "target", 0);
|
||||
char root[128] = {0};
|
||||
char place[256] = {0};
|
||||
if (!borrow_place_from_expr(adjacency, target, root, sizeof(root), place, sizeof(place))) return true;
|
||||
const ZGraphActiveBorrow *conflict = borrow_conflict(active, active_len, root, place, true);
|
||||
return conflict ? fail_borrow_conflict(target, conflict, root, place, true, path, diag) : true;
|
||||
}
|
||||
|
||||
static bool borrow_block_contract_ok(const ZProgramGraphAdjacency *adjacency, const ZProgramGraphNode *block, const char *path, ZDiag *diag) {
|
||||
ZGraphActiveBorrow active[64] = {0};
|
||||
size_t active_len = 0;
|
||||
size_t start = 0;
|
||||
size_t len = 0;
|
||||
z_program_graph_adjacency_owner_run(adjacency, block->id, "statement", &start, &len);
|
||||
for (size_t i = start; i < start + len; i++) {
|
||||
const ZProgramGraphEdge *edge = z_program_graph_adjacency_owner_edge_at(adjacency, i);
|
||||
if (!edge || edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE) continue;
|
||||
const ZProgramGraphNode *stmt = z_program_graph_adjacency_node(adjacency, edge->to);
|
||||
if (!stmt) continue;
|
||||
if (stmt->kind == Z_PROGRAM_GRAPH_NODE_LET && !borrow_let_contract_ok(adjacency, stmt, active, &active_len, path, diag)) return false;
|
||||
if (stmt->kind == Z_PROGRAM_GRAPH_NODE_ASSIGNMENT && !borrow_assignment_contract_ok(adjacency, stmt, active, active_len, path, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_program_graph_borrow_contracts_ok(const ZProgramGraph *graph, const char *path, ZDiag *diag) {
|
||||
if (!graph || graph->node_len == 0) return true;
|
||||
ZProgramGraphAdjacency adjacency;
|
||||
z_program_graph_adjacency_init(&adjacency, graph);
|
||||
bool ok = true;
|
||||
for (size_t i = 0; ok && i < graph->node_len; i++) {
|
||||
if (graph->nodes[i].kind == Z_PROGRAM_GRAPH_NODE_BLOCK) ok = borrow_block_contract_ok(&adjacency, &graph->nodes[i], path, diag);
|
||||
}
|
||||
z_program_graph_adjacency_free(&adjacency);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "program_graph_build.h"
|
||||
|
||||
bool z_program_graph_artifact_source_present(const ZProgramGraphArtifactSource *source) {
|
||||
return source && source->graph_hash && source->graph_hash[0];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_BUILD_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_BUILD_H
|
||||
|
||||
#include "program_graph.h"
|
||||
|
||||
typedef struct {
|
||||
const char *artifact;
|
||||
const char *graph_hash;
|
||||
const char *module_identity;
|
||||
const char *lowering;
|
||||
const char *source_projection_state;
|
||||
bool canonical_source;
|
||||
} ZProgramGraphArtifactSource;
|
||||
|
||||
bool z_program_graph_artifact_source_present(const ZProgramGraphArtifactSource *source);
|
||||
bool z_program_graph_has_direct_entry_function(const ZProgramGraph *graph);
|
||||
bool z_program_graph_merge_embedded_std_graph_modules(ZProgramGraph *graph, const SourceInput *input, ZDiag *diag);
|
||||
bool z_program_graph_merge_embedded_std_graph_modules_timed(ZProgramGraph *graph, SourceInput *input, ZDiag *diag);
|
||||
bool z_program_graph_prepare_artifact_mir_input(const char *artifact_path, const ZTargetInfo *target, const char *emit_kind, const char *requested_backend, Program *program, SourceInput *input, IrProgram *ir, ZProgramGraphArtifactSource *source, ZDiag *diag);
|
||||
bool z_program_graph_prepare_repository_store_mir_input(const char *store_path, const ZTargetInfo *target, const char *emit_kind, const char *requested_backend, bool require_checked_program, bool write_mapped_mir_cache, Program *program, SourceInput *input, IrProgram *ir, ZProgramGraphArtifactSource *source, ZDiag *diag);
|
||||
#endif
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "program_graph_c_import.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool graph_c_import_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
static char *graph_c_import_dirname_copy(const char *path) {
|
||||
if (!path || !path[0]) return z_strdup(".");
|
||||
const char *slash = strrchr(path, '/');
|
||||
if (!slash) return z_strdup(".");
|
||||
if (slash == path) return z_strdup("/");
|
||||
return z_strndup(path, (size_t)(slash - path));
|
||||
}
|
||||
|
||||
static char *graph_c_import_join_path(const char *dir, const char *path) {
|
||||
if (!path || !path[0]) return z_strdup("");
|
||||
if (path[0] == '/') return z_strdup(path);
|
||||
if (!dir || !dir[0] || graph_c_import_text_eq(dir, ".")) return z_strdup(path);
|
||||
ZBuf joined;
|
||||
zbuf_init(&joined);
|
||||
zbuf_append(&joined, dir);
|
||||
if (joined.len > 0 && joined.data[joined.len - 1] != '/') zbuf_append_char(&joined, '/');
|
||||
zbuf_append(&joined, path);
|
||||
return joined.data ? joined.data : z_strdup(path);
|
||||
}
|
||||
|
||||
static bool graph_c_import_read_header_candidate(const char *path, char **out_header, char **out_resolved) {
|
||||
if (!path || !path[0] || !out_header || !out_resolved) return false;
|
||||
ZDiag diag = {0};
|
||||
char *header = z_read_file(path, &diag);
|
||||
if (!header) return false;
|
||||
*out_header = header;
|
||||
*out_resolved = z_strdup(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_c_import_read_header(const ZProgramGraphNode *c_import, const IrProgram *ir, char **out_header, char **out_resolved) {
|
||||
if (!c_import || !c_import->value || !c_import->value[0] || !out_header || !out_resolved) return false;
|
||||
*out_header = NULL;
|
||||
*out_resolved = NULL;
|
||||
if (graph_c_import_read_header_candidate(c_import->value, out_header, out_resolved)) return true;
|
||||
if (ir && ir->package_root && ir->package_root[0]) {
|
||||
char *package_relative = graph_c_import_join_path(ir->package_root, c_import->value);
|
||||
bool ok = graph_c_import_read_header_candidate(package_relative, out_header, out_resolved);
|
||||
free(package_relative);
|
||||
if (ok) return true;
|
||||
}
|
||||
char *source_dir = graph_c_import_dirname_copy(c_import->path);
|
||||
char *source_relative = graph_c_import_join_path(source_dir, c_import->value);
|
||||
if (graph_c_import_read_header_candidate(source_relative, out_header, out_resolved)) {
|
||||
free(source_relative);
|
||||
free(source_dir);
|
||||
return true;
|
||||
}
|
||||
free(source_relative);
|
||||
char *package_dir = graph_c_import_dirname_copy(source_dir);
|
||||
char *package_relative = graph_c_import_join_path(package_dir, c_import->value);
|
||||
bool ok = graph_c_import_read_header_candidate(package_relative, out_header, out_resolved);
|
||||
free(package_relative);
|
||||
free(package_dir);
|
||||
free(source_dir);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool z_program_graph_find_c_import_function(const ZProgramGraphNode *c_import, const IrProgram *ir, const ZTargetInfo *target, const char *symbol, ZCImportFunction *out) {
|
||||
if (!c_import || !symbol || !symbol[0] || !out) return false;
|
||||
char *header = NULL;
|
||||
char *resolved_header = NULL;
|
||||
if (!graph_c_import_read_header(c_import, ir, &header, &resolved_header)) return false;
|
||||
ZCImportFunctionVec functions = {0};
|
||||
z_c_header_parse_functions_for_target(header, target, &functions);
|
||||
free(header);
|
||||
for (size_t i = 0; i < functions.len; i++) {
|
||||
ZCImportFunction *function = &functions.items[i];
|
||||
if (!graph_c_import_text_eq(function->name, symbol)) continue;
|
||||
*out = *function;
|
||||
*function = (ZCImportFunction){0};
|
||||
out->import_header = z_strdup(c_import->value);
|
||||
out->import_resolved_header = z_strdup(resolved_header ? resolved_header : c_import->value);
|
||||
z_c_import_function_vec_free(&functions);
|
||||
free(resolved_header);
|
||||
return true;
|
||||
}
|
||||
z_c_import_function_vec_free(&functions);
|
||||
free(resolved_header);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_C_IMPORT_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_C_IMPORT_H
|
||||
|
||||
#include "c_import.h"
|
||||
#include "program_graph.h"
|
||||
|
||||
bool z_program_graph_find_c_import_function(const ZProgramGraphNode *c_import, const IrProgram *ir, const ZTargetInfo *target, const char *symbol, ZCImportFunction *out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "program_graph_c_import_metadata.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool graph_c_meta_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
void z_program_graph_clear_c_import_metadata(SourceInput *input) {
|
||||
if (!input) return;
|
||||
for (size_t i = 0; i < input->direct_c_import_header_count; i++) {
|
||||
free(input->direct_c_import_headers[i]);
|
||||
free(input->direct_c_import_resolved_headers[i]);
|
||||
}
|
||||
free(input->direct_c_import_headers);
|
||||
free(input->direct_c_import_resolved_headers);
|
||||
input->direct_c_import_headers = NULL;
|
||||
input->direct_c_import_resolved_headers = NULL;
|
||||
input->direct_c_import_header_count = 0;
|
||||
input->direct_c_import_call_count = 0;
|
||||
input->direct_c_import_symbol_count = 0;
|
||||
}
|
||||
|
||||
static bool graph_c_meta_has_header(const SourceInput *input, const char *header) {
|
||||
for (size_t i = 0; input && header && i < input->direct_c_import_header_count; i++) {
|
||||
if (graph_c_meta_text_eq(input->direct_c_import_headers[i], header) ||
|
||||
graph_c_meta_text_eq(input->direct_c_import_resolved_headers[i], header)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void graph_c_meta_record_header(SourceInput *input, const char *header) {
|
||||
if (!input || !header || !header[0] || graph_c_meta_has_header(input, header)) return;
|
||||
size_t next = input->direct_c_import_header_count + 1;
|
||||
input->direct_c_import_headers = z_checked_reallocarray(input->direct_c_import_headers, next, sizeof(char *));
|
||||
input->direct_c_import_resolved_headers = z_checked_reallocarray(input->direct_c_import_resolved_headers, next, sizeof(char *));
|
||||
input->direct_c_import_headers[input->direct_c_import_header_count] = z_strdup(header);
|
||||
input->direct_c_import_resolved_headers[input->direct_c_import_header_count] = NULL;
|
||||
input->direct_c_import_header_count = next;
|
||||
}
|
||||
|
||||
static const ZProgramGraphNode *graph_c_meta_find_node(const ZProgramGraph *graph, const char *id) {
|
||||
for (size_t i = 0; graph && id && i < graph->node_len; i++) {
|
||||
if (graph_c_meta_text_eq(graph->nodes[i].id, id)) return &graph->nodes[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const ZProgramGraphNode *graph_c_meta_child(const ZProgramGraph *graph, const ZProgramGraphNode *parent, const char *kind, size_t order) {
|
||||
for (size_t i = 0; graph && parent && kind && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (!graph_c_meta_text_eq(edge->from, parent->id) || !graph_c_meta_text_eq(edge->kind, kind) || edge->order != order) continue;
|
||||
return graph_c_meta_find_node(graph, edge->to);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool graph_c_meta_has_alias(const ZProgramGraph *graph, const char *alias) {
|
||||
for (size_t i = 0; graph && alias && alias[0] && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_C_IMPORT && graph_c_meta_text_eq(node->name, alias)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *graph_c_meta_root_identifier(const ZProgramGraph *graph, const ZProgramGraphNode *node) {
|
||||
if (!node) return NULL;
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_IDENTIFIER) return node->name;
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS || node->kind == Z_PROGRAM_GRAPH_NODE_METHOD_CALL) return graph_c_meta_root_identifier(graph, graph_c_meta_child(graph, node, "left", 0));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t graph_c_meta_count_calls(const ZProgramGraph *graph) {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (node->kind != Z_PROGRAM_GRAPH_NODE_METHOD_CALL) continue;
|
||||
const char *root = graph_c_meta_root_identifier(graph, graph_c_meta_child(graph, node, "left", 0));
|
||||
if (graph_c_meta_has_alias(graph, root)) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void z_program_graph_seed_c_import_metadata(SourceInput *input, const ZProgramGraph *graph) {
|
||||
if (!input || !graph) return;
|
||||
bool has_c_import = false;
|
||||
for (size_t i = 0; i < graph->node_len; i++) {
|
||||
if (graph->nodes[i].kind == Z_PROGRAM_GRAPH_NODE_C_IMPORT) {
|
||||
has_c_import = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_c_import) {
|
||||
input->direct_c_import_call_count = 0;
|
||||
input->direct_c_import_symbol_count = 0;
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (!graph_c_meta_text_eq(edge->kind, "cImport")) continue;
|
||||
const ZProgramGraphNode *node = graph_c_meta_find_node(graph, edge->to);
|
||||
if (node && node->kind == Z_PROGRAM_GRAPH_NODE_C_IMPORT) graph_c_meta_record_header(input, node->value);
|
||||
}
|
||||
input->direct_c_import_symbol_count = input->direct_c_import_header_count;
|
||||
input->direct_c_import_call_count = graph_c_meta_count_calls(graph);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_C_IMPORT_METADATA_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_C_IMPORT_METADATA_H
|
||||
|
||||
#include "program_graph.h"
|
||||
|
||||
void z_program_graph_clear_c_import_metadata(SourceInput *input);
|
||||
void z_program_graph_seed_c_import_metadata(SourceInput *input, const ZProgramGraph *graph);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "program_graph_check_gate.h"
|
||||
#include <string.h>
|
||||
|
||||
static bool check_gate_text_eq(const char *left, const char *right) {
|
||||
return left && right && strcmp(left, right) == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check-time buildability gate facts: `zero check` and `zero import` fail
|
||||
* with the same BLD004 "typed graph MIR unsupported" diagnostics `zero
|
||||
* build`/`zero run` would report when the typed graph cannot lower to MIR at
|
||||
* all. Target-specific backend buildability subsets (for example fs values
|
||||
* on the Mach-O direct backend), target/backend availability, and toolchain
|
||||
* readiness stay target-readiness facts only, so packages that build for
|
||||
* another supported target keep checking clean on every host.
|
||||
*/
|
||||
bool z_check_gate_diag_is_buildability_blocker(const ZDiag *diag) {
|
||||
if (!diag) return false;
|
||||
if (diag->code != 2004 && diag->code != 4004) return false;
|
||||
if (!diag->backend_blocker.present) return false;
|
||||
return check_gate_text_eq(diag->backend_blocker.stage, "lower");
|
||||
}
|
||||
|
||||
static bool check_gate_type_decl_is_enum_or_choice(const ZProgramGraph *graph, const char *name) {
|
||||
if (!graph || !name || !name[0]) return false;
|
||||
for (size_t i = 0; i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (node->kind != Z_PROGRAM_GRAPH_NODE_ENUM && node->kind != Z_PROGRAM_GRAPH_NODE_CHOICE) continue;
|
||||
if (check_gate_text_eq(node->name, name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_gate_is_non_entry_world_param(const ZProgramGraphNode *node) {
|
||||
return check_gate_text_eq(node ? node->type : NULL, "World") &&
|
||||
node->symbol_id && !strstr(node->symbol_id, "::value.main/param.");
|
||||
}
|
||||
|
||||
/*
|
||||
* The gate starts with the typed graph MIR gaps agents hit most often, so
|
||||
* conformance check-pass coverage for the remaining gaps stays valid while
|
||||
* each gap either gains lowering support or migrates to a check-failure
|
||||
* contract. Every gated construct fails `zero build` identically today; the
|
||||
* list only controls how much of that build signal check surfaces early.
|
||||
*/
|
||||
bool z_check_gate_diag_is_known_construct(const ZProgramGraph *graph, const ZDiag *diag) {
|
||||
if (!diag) return false;
|
||||
const char *message = diag->message;
|
||||
const char *actual = diag->actual;
|
||||
if (check_gate_text_eq(message, "typed graph MIR statement kind is unsupported")) return check_gate_text_eq(actual, "Defer");
|
||||
if (check_gate_text_eq(message, "typed graph MIR local type is unsupported")) return check_gate_type_decl_is_enum_or_choice(graph, actual);
|
||||
if (check_gate_text_eq(message, "typed graph MIR rescue supports fallible function calls with primitive fallbacks")) return true;
|
||||
if (check_gate_text_eq(message, "typed graph MIR literal type is unsupported")) return check_gate_text_eq(actual, "String");
|
||||
if (check_gate_text_eq(message, "typed graph MIR fallible return type is unsupported")) return check_gate_text_eq(actual, "String");
|
||||
if (check_gate_text_eq(message, "typed graph MIR parameter type is unsupported")) return check_gate_text_eq(actual, "ref<ByteBuf>") || check_gate_text_eq(actual, "World");
|
||||
if (check_gate_text_eq(message, "typed graph MIR reference parameter requires a shape whose fields are scalars or fixed scalar arrays")) return check_gate_text_eq(actual, "ref<ByteBuf>");
|
||||
if (check_gate_text_eq(message, "typed graph MIR call target is unsupported")) return check_gate_text_eq(actual, "readU32");
|
||||
if (check_gate_text_eq(message, "typed graph MIR allocator local requires FixedBufAlloc")) return check_gate_text_eq(actual, "PageAlloc") || check_gate_text_eq(actual, "GeneralAlloc");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fast filter: the gate only pays for merged lowering when the graph can
|
||||
* contain a gated construct at all. One node pass keeps construct-free
|
||||
* programs at pre-gate check and import cost.
|
||||
*/
|
||||
bool z_check_gate_scan_finds_known_construct(const ZProgramGraph *graph) {
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
switch (node->kind) {
|
||||
case Z_PROGRAM_GRAPH_NODE_DEFER:
|
||||
case Z_PROGRAM_GRAPH_NODE_RESCUE:
|
||||
case Z_PROGRAM_GRAPH_NODE_ENUM:
|
||||
case Z_PROGRAM_GRAPH_NODE_CHOICE:
|
||||
return true;
|
||||
case Z_PROGRAM_GRAPH_NODE_LET:
|
||||
if (check_gate_text_eq(node->type, "PageAlloc") || check_gate_text_eq(node->type, "GeneralAlloc")) return true;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_PARAM:
|
||||
if (check_gate_text_eq(node->type, "ref<ByteBuf>") || check_gate_is_non_entry_world_param(node)) return true;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_IDENTIFIER:
|
||||
case Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS:
|
||||
if (check_gate_text_eq(node->name, "readU32")) return true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_CHECK_GATE_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_CHECK_GATE_H
|
||||
|
||||
#include "program_graph.h"
|
||||
|
||||
bool z_check_gate_diag_is_buildability_blocker(const ZDiag *diag);
|
||||
bool z_check_gate_diag_is_known_construct(const ZProgramGraph *graph, const ZDiag *diag);
|
||||
bool z_check_gate_scan_finds_known_construct(const ZProgramGraph *graph);
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "program_graph.h"
|
||||
|
||||
static char *program_graph_clone_text(const char *text) {
|
||||
return text ? z_strdup(text) : NULL;
|
||||
}
|
||||
|
||||
static void program_graph_clone_node(const ZProgramGraphNode *from, ZProgramGraphNode *to) {
|
||||
*to = (ZProgramGraphNode){
|
||||
.id = program_graph_clone_text(from->id),
|
||||
.kind = from->kind,
|
||||
.name = program_graph_clone_text(from->name),
|
||||
.type = program_graph_clone_text(from->type),
|
||||
.value = program_graph_clone_text(from->value),
|
||||
.path = program_graph_clone_text(from->path),
|
||||
.symbol_id = program_graph_clone_text(from->symbol_id),
|
||||
.type_id = program_graph_clone_text(from->type_id),
|
||||
.effect_id = program_graph_clone_text(from->effect_id),
|
||||
.node_hash = program_graph_clone_text(from->node_hash),
|
||||
.line = from->line,
|
||||
.column = from->column,
|
||||
.is_public = from->is_public,
|
||||
.is_mutable = from->is_mutable,
|
||||
.is_static = from->is_static,
|
||||
.fallible = from->fallible,
|
||||
.export_c = from->export_c,
|
||||
};
|
||||
}
|
||||
|
||||
static void program_graph_clone_edge(const ZProgramGraphEdge *from, ZProgramGraphEdge *to) {
|
||||
*to = (ZProgramGraphEdge){
|
||||
.from = program_graph_clone_text(from->from),
|
||||
.to = program_graph_clone_text(from->to),
|
||||
.kind = program_graph_clone_text(from->kind),
|
||||
.target = from->target,
|
||||
.order = from->order,
|
||||
};
|
||||
}
|
||||
|
||||
bool z_program_graph_clone(const ZProgramGraph *from, ZProgramGraph *out) {
|
||||
if (!from || !out) return false;
|
||||
*out = (ZProgramGraph){
|
||||
.schema_version = from->schema_version,
|
||||
.validation_state = from->validation_state,
|
||||
.module_identity = program_graph_clone_text(from->module_identity),
|
||||
.graph_hash = program_graph_clone_text(from->graph_hash),
|
||||
.node_len = from->node_len,
|
||||
.node_cap = from->node_len,
|
||||
.edge_len = from->edge_len,
|
||||
.edge_cap = from->edge_len,
|
||||
.next_id = from->next_id,
|
||||
.canonical_source = from->canonical_source,
|
||||
};
|
||||
if (from->node_len > 0) {
|
||||
out->nodes = z_checked_calloc(from->node_len, sizeof(ZProgramGraphNode));
|
||||
for (size_t i = 0; i < from->node_len; i++) program_graph_clone_node(&from->nodes[i], &out->nodes[i]);
|
||||
}
|
||||
if (from->edge_len > 0) {
|
||||
out->edges = z_checked_calloc(from->edge_len, sizeof(ZProgramGraphEdge));
|
||||
for (size_t i = 0; i < from->edge_len; i++) program_graph_clone_edge(&from->edges[i], &out->edges[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
#include "program_graph_command.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
typedef struct {
|
||||
const char *name;
|
||||
ZProgramGraphInputMode input_mode;
|
||||
bool repository_store_input;
|
||||
bool graph_subcommand;
|
||||
bool supports_out;
|
||||
ZProgramGraphOutputContract out_contract;
|
||||
} ZProgramGraphCommandKind;
|
||||
|
||||
#define GRAPH_OUT(name, input_mode, repo) {name, input_mode, repo, true, true, {true, NULL, NULL, NULL, NULL}}
|
||||
#define GRAPH_NO_OUT(name, input_mode, repo, message, expected, actual, help) {name, input_mode, repo, true, false, {false, message, expected, actual, help}}
|
||||
|
||||
static const ZProgramGraphCommandKind z_graph_command_kinds[] = {
|
||||
GRAPH_NO_OUT(
|
||||
"init",
|
||||
Z_PROGRAM_GRAPH_INPUT_PATH,
|
||||
false,
|
||||
"init writes repository files and does not support --out",
|
||||
"zero init [--json] [--format text|binary] [--template cli|lib|package] [project-path]",
|
||||
"zero init --out",
|
||||
"init writes zero.toml or zero.json plus zero.graph at the selected project path; remove --out"
|
||||
),
|
||||
GRAPH_OUT("dump", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, true),
|
||||
GRAPH_OUT("import", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, false),
|
||||
{"check", Z_PROGRAM_GRAPH_INPUT_ARTIFACT, true, false, false, {false, "zero check does not support --out", "zero view --out <file.0> [graph-input]", "zero check --out", "run zero view to render canonical source, or run zero check without --out to typecheck the ProgramGraph input"}},
|
||||
GRAPH_NO_OUT(
|
||||
"query",
|
||||
Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT,
|
||||
true,
|
||||
"query does not support --out",
|
||||
"zero query [--json] [--fn <name>] [--find <text>] [--refs <name>] [--calls <name>] [--node <id>] [--depth <n>] [--full] [--handles] [--no-help] [graph-input|name]",
|
||||
"zero query --out",
|
||||
"queries are reported on stdout; remove --out"
|
||||
),
|
||||
GRAPH_NO_OUT(
|
||||
"inspect",
|
||||
Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT,
|
||||
true,
|
||||
"inspect does not support --out",
|
||||
"zero inspect [--json] [graph-input]",
|
||||
"zero inspect --out",
|
||||
"use zero dump or zero import with --out when you need a derived ProgramGraph artifact"
|
||||
),
|
||||
GRAPH_OUT("validate", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, true),
|
||||
GRAPH_OUT("view", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, true),
|
||||
GRAPH_NO_OUT("diff", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, true, "diff textconv output does not support --out", "zero diff [--fn <name>] [graph-input]", "zero diff --out", "diff prints a canonical review projection on stdout; use zero view --out <file.0> when you need to write a projection file"),
|
||||
GRAPH_NO_OUT(
|
||||
"source-map",
|
||||
Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT,
|
||||
true,
|
||||
"source-map does not support --out",
|
||||
"zero source-map [--json] [graph-input]",
|
||||
"zero source-map --out",
|
||||
"source maps are reported on stdout; remove --out"
|
||||
),
|
||||
GRAPH_NO_OUT(
|
||||
"reconcile",
|
||||
Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT,
|
||||
true,
|
||||
"reconcile does not support --out",
|
||||
"zero reconcile [--json] <base-graph-input> --source <edited-file.0|project|zero.toml|zero.json>",
|
||||
"zero reconcile --out",
|
||||
"reconciliation reports identity decisions on stdout; remove --out"
|
||||
),
|
||||
GRAPH_NO_OUT("status", Z_PROGRAM_GRAPH_INPUT_SOURCE, false, "status does not support --out", "zero status [--json] [project|zero.toml|zero.json|file.0]", "zero status --out", "status is reported on stdout; remove --out"),
|
||||
GRAPH_NO_OUT("verify-projection", Z_PROGRAM_GRAPH_INPUT_SOURCE, false, "verify-projection does not support --out", "zero verify-projection [--json] [project|zero.toml|zero.json|file.0]", "zero verify-projection --out", "verify-projection is a no-write check; remove --out"),
|
||||
GRAPH_NO_OUT("export", Z_PROGRAM_GRAPH_INPUT_SOURCE, false, "export writes fixed source projection paths and does not support --out", "zero export [--json] [project|zero.toml|zero.json|file.0]", "zero export --out", "export writes repository source projections from zero.graph; remove --out"),
|
||||
GRAPH_NO_OUT("merge", Z_PROGRAM_GRAPH_INPUT_SOURCE, false, "merge writes the target zero.graph and does not support --out", "zero merge --base <base-zero.graph> --left <left-zero.graph> --right <right-zero.graph> [--format text|binary] [project|zero.toml|zero.json|file.0]", "zero merge --out", "merge writes the repository graph store selected by the input path; remove --out"),
|
||||
GRAPH_OUT("size", Z_PROGRAM_GRAPH_INPUT_ARTIFACT, true),
|
||||
GRAPH_OUT("build", Z_PROGRAM_GRAPH_INPUT_ARTIFACT, true),
|
||||
GRAPH_OUT("run", Z_PROGRAM_GRAPH_INPUT_ARTIFACT, true),
|
||||
GRAPH_OUT("patch", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, true),
|
||||
GRAPH_NO_OUT(
|
||||
"test",
|
||||
Z_PROGRAM_GRAPH_INPUT_ARTIFACT,
|
||||
true,
|
||||
"zero test does not support --out",
|
||||
"zero test [--json] [--filter <name>] [--target <target>] [graph-input]",
|
||||
"zero test --out",
|
||||
"test results are reported on stdout; remove --out"
|
||||
),
|
||||
GRAPH_OUT("roundtrip", Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT, true),
|
||||
};
|
||||
|
||||
#undef GRAPH_OUT
|
||||
#undef GRAPH_NO_OUT
|
||||
|
||||
static const ZProgramGraphCommandKind *graph_kind(const char *kind) {
|
||||
for (size_t i = 0; kind && i < sizeof(z_graph_command_kinds) / sizeof(z_graph_command_kinds[0]); i++) {
|
||||
if (strcmp(kind, z_graph_command_kinds[i].name) == 0) return &z_graph_command_kinds[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool z_program_graph_command_kind_is_known(const char *kind) { const ZProgramGraphCommandKind *item = graph_kind(kind); return item && item->graph_subcommand; }
|
||||
|
||||
ZProgramGraphInputMode z_program_graph_command_input_mode(const char *kind) { const ZProgramGraphCommandKind *item = graph_kind(kind); return item ? item->input_mode : Z_PROGRAM_GRAPH_INPUT_UNKNOWN; }
|
||||
|
||||
bool z_program_graph_command_can_use_repository_store(const char *kind) { const ZProgramGraphCommandKind *item = graph_kind(kind); return item && item->repository_store_input; }
|
||||
|
||||
bool z_program_graph_command_kind_supports_out(const char *kind) { const ZProgramGraphCommandKind *item = graph_kind(kind); return item && item->supports_out; }
|
||||
|
||||
ZProgramGraphOutputContract z_program_graph_command_output_contract(const char *kind) {
|
||||
static const ZProgramGraphOutputContract fallback = {
|
||||
false,
|
||||
"output requires an output-capable graph command",
|
||||
"zero dump|validate|roundtrip [--format text|binary] --out <program-graph-artifact> [graph-input]",
|
||||
"zero --out",
|
||||
"use zero view --out <file.0> for canonical source, or choose a graph subcommand with command-specific output",
|
||||
};
|
||||
const ZProgramGraphCommandKind *item = graph_kind(kind);
|
||||
return item ? item->out_contract : fallback;
|
||||
}
|
||||
|
||||
void z_program_graph_print_command_help(void) {
|
||||
printf("Usage: zero init [--template cli|lib|package] [project-path]; zero query|view|diff|dump|inspect|validate|source-map|roundtrip [--json] [graph-input]; zero status|verify-projection|import|export|merge [--json] [project|zero.toml|zero.json|file.0]\n\n");
|
||||
printf("Graph-first project usage: zero init [--json] [--manifest toml|json] [--format text|binary] [--template cli|lib|package] [project-path]\n");
|
||||
printf("Output usage: zero dump|validate|roundtrip [--json] [--format text|binary] --out <program-graph-artifact> [graph-input]; zero import [--json] [--format text|binary] --out <program-graph-artifact> [project|zero.toml|zero.json|file.0]\n");
|
||||
printf("View output usage: zero view [--json] [--fn <name> [--around <text>|--handles]] [--outline <module-or-file>] [--out <file.0>] [graph-input]\n");
|
||||
printf("Diff textconv usage: zero diff [--fn <name>] [graph-input]\n");
|
||||
printf("Source map usage: zero source-map [--json] [graph-input]\n");
|
||||
printf("Query usage: zero query [--json] [--fn <name>] [--find <text>] [--refs <name>] [--calls <name>] [--node <id>] [--depth <n>] [--full] [--handles] [--no-help] [graph-input|name]\n");
|
||||
printf("Reconcile usage: zero reconcile [--json] <base-graph-input> --source <edited-file.0|project|zero.toml|zero.json>\n");
|
||||
printf("Repository projection usage: zero status|verify-projection [--json] [project|zero.toml|zero.json|file.0|zero.graph]; zero import [--json] [--format text|binary] [project|zero.toml|zero.json|file.0]; zero export [--json] [project|zero.toml|zero.json|file.0]; zero merge --base <base-zero.graph> --left <left-zero.graph> --right <right-zero.graph> [--json] [project|zero.toml|zero.json|file.0]\n");
|
||||
printf("Size usage: zero size [--json] [--target <target>] [--out <artifact>] [graph-input]\n");
|
||||
printf("Patch usage: zero patch [--json] [--check-only|--dry-run] [--format text|binary] [--out <program-graph-artifact>] [graph-input] (<patch-file>|--op <operation>|--replace-fn <name> --body-file <file|->|--replace-in-fn <name> --old <text> --new <text>|--rewrite <pattern> --to <template> [--fn <name>] [--apply])\n");
|
||||
printf(" In a graph-first package, zero patch --op <operation> defaults to the current directory.\nPatch operation help: zero patch --op help\n\n");
|
||||
printf("Build usage: zero build [--json] [--emit exe|obj|llvm-ir] [--backend direct|llvm|<direct-emitter>] [--target <target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <file>] [graph-input]\n\n");
|
||||
printf("Run usage: zero run [--target <host-target>] [--profile debug|dev|release-fast|release-small|tiny|audit] [--release <profile>] [--out <file>] [graph-input] [-- args...]\n\n");
|
||||
printf("Test usage: zero test [--json] [--filter <name>] [--target <target>] [graph-input]\n\n");
|
||||
printf("Inspect modules, symbols, capabilities, static metadata, stdlib helpers, or deterministic ProgramGraph inputs.\n\n");
|
||||
printf("Graph commands:\n");
|
||||
printf(" init create a graph-first package with zero.graph as compiler input\n");
|
||||
printf(" dump print or write only the deterministic ProgramGraph\n");
|
||||
printf(" import import source projections into zero.graph, or convert source into a standalone ProgramGraph artifact\n");
|
||||
printf(" export export zero.graph into human-readable .0 source projections\n");
|
||||
printf(" query report compact module, function, body, and patch facts for agents\n");
|
||||
printf(" inspect report semantic graph and compiler facts\n");
|
||||
printf(" validate read ProgramGraph input and optionally write its normalized artifact form\n");
|
||||
printf(" view render ProgramGraph input as a generated Zero view\n");
|
||||
printf(" diff print canonical review text for Git textconv diff drivers\n");
|
||||
printf(" source-map map graph nodes to source ranges and semantic identity facts\n");
|
||||
printf(" reconcile compare a prior graph with edited source and report identity decisions\n");
|
||||
printf(" status report repository graph projection state without writing files\n");
|
||||
printf(" verify-projection check checked-in projection drift without writing files\n");
|
||||
printf(" merge combine independent repository graph store edits by durable node id\n");
|
||||
printf(" roundtrip compare graph semantics after direct ProgramGraph lowering\n");
|
||||
printf("\nRepository store encoding:\n");
|
||||
printf(" binary is the default zero.graph encoding\n");
|
||||
printf(" --format text writes readable repository stores when explicitly requested\n");
|
||||
printf(" --format binary opts explicit graph artifact outputs into binary storage\n");
|
||||
printf(" reads auto-detect text and binary stores and graph artifacts\n");
|
||||
printf("\nCommon patch operations:\n");
|
||||
printf(" addMain\n");
|
||||
printf(" addCheckWrite fn=\"main\" text=\"hello\\n\"\n");
|
||||
printf(" addFunction name=\"add\" ret=\"i32\"\n");
|
||||
printf(" addParam fn=\"add\" name=\"left\" type=\"i32\"\n");
|
||||
printf(" addParamTo fn=\"add\" name=\"bias\" type=\"i32\" default=\"0\"\n");
|
||||
printf(" setConst name=\"limit\" value=\"64\"\n");
|
||||
printf(" setReturnType fn=\"add\" type=\"i64\"\n");
|
||||
printf(" addReturnBinary fn=\"add\" name=\"+\" left=\"left\" right=\"right\" type=\"i32\"\n");
|
||||
printf(" addLetLiteral fn=\"main\" name=\"count\" type=\"u32\" value=\"0\"\n");
|
||||
printf(" addLetBinary fn=\"add\" name=\"sum\" type=\"i32\" operator=\"+\" left=\"left\" right=\"right\"\n");
|
||||
printf(" addReturnValue fn=\"identity\" value=\"input\" type=\"i32\"\n");
|
||||
printf(" addReturnExpr fn=\"maybe\" expr=\"null\"\n");
|
||||
printf(" appendStmt fn=\"main\" stmt=\"check std.http.listen(world, 3000_u16)\"\n");
|
||||
printf(" addCheckWriteValue fn=\"main\" value=\"message\" type=\"String\"\n");
|
||||
printf(" addTest name=\"addition works\" call=\"add\" arg0=\"40\" arg1=\"2\" expect=\"42\" type=\"i32\"\n");
|
||||
printf(" addTestBody name=\"api add\" ... end\n");
|
||||
printf(" renameTest name=\"api add\" value=\"api add route\"\n");
|
||||
printf(" deleteTest name=\"api add\"\n");
|
||||
printf(" upsertFunction handle ... end\n");
|
||||
printf(" replaceFunctionBody main ... end\n");
|
||||
printf(" replaceBlockBody #block_id ... end\n");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_COMMAND_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_COMMAND_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
bool supports_out;
|
||||
const char *message;
|
||||
const char *expected;
|
||||
const char *actual;
|
||||
const char *help;
|
||||
} ZProgramGraphOutputContract;
|
||||
|
||||
typedef enum {
|
||||
Z_PROGRAM_GRAPH_INPUT_UNKNOWN = 0, Z_PROGRAM_GRAPH_INPUT_SOURCE,
|
||||
Z_PROGRAM_GRAPH_INPUT_ARTIFACT, Z_PROGRAM_GRAPH_INPUT_SOURCE_OR_ARTIFACT,
|
||||
Z_PROGRAM_GRAPH_INPUT_PATH,
|
||||
} ZProgramGraphInputMode;
|
||||
|
||||
bool z_program_graph_command_kind_is_known(const char *kind);
|
||||
ZProgramGraphInputMode z_program_graph_command_input_mode(const char *kind);
|
||||
bool z_program_graph_command_can_use_repository_store(const char *kind);
|
||||
bool z_program_graph_command_kind_supports_out(const char *kind);
|
||||
ZProgramGraphOutputContract z_program_graph_command_output_contract(const char *kind);
|
||||
void z_program_graph_print_command_help(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,569 @@
|
||||
#include "program_graph_compare.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int compare_text_cmp(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "");
|
||||
}
|
||||
|
||||
static bool compare_text_eq(const char *left, const char *right) {
|
||||
return compare_text_cmp(left, right) == 0;
|
||||
}
|
||||
|
||||
static bool compare_fail(
|
||||
ZProgramGraphCompare *out,
|
||||
const char *code,
|
||||
const char *message,
|
||||
const char *field,
|
||||
size_t left_index,
|
||||
size_t right_index,
|
||||
size_t left_count,
|
||||
size_t right_count
|
||||
) {
|
||||
if (out) {
|
||||
out->ok = false;
|
||||
snprintf(out->code, sizeof(out->code), "%s", code ? code : "GRC000");
|
||||
snprintf(out->message, sizeof(out->message), "%s", message ? message : "program graphs differ");
|
||||
snprintf(out->field, sizeof(out->field), "%s", field ? field : "");
|
||||
out->left_index = left_index;
|
||||
out->right_index = right_index;
|
||||
out->left_count = left_count;
|
||||
out->right_count = right_count;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t compare_missing_index(void) {
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
static int compare_size_t_value(size_t left, size_t right) {
|
||||
if (left < right) return -1;
|
||||
if (left > right) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool compare_edge_kind(const ZProgramGraphEdge *edge, const char *kind) {
|
||||
return edge && compare_text_eq(edge->kind, kind);
|
||||
}
|
||||
|
||||
static const char *const compare_owned_edge_order[] = {
|
||||
"cImport",
|
||||
"import",
|
||||
"const",
|
||||
"alias",
|
||||
"shape",
|
||||
"interface",
|
||||
"enum",
|
||||
"choice",
|
||||
"function",
|
||||
"typeParam",
|
||||
"param",
|
||||
"type",
|
||||
"returnType",
|
||||
"effect",
|
||||
"error",
|
||||
"field",
|
||||
"method",
|
||||
"case",
|
||||
"target",
|
||||
"declaredType",
|
||||
"default",
|
||||
"value",
|
||||
"expr",
|
||||
"left",
|
||||
"right",
|
||||
"arg",
|
||||
"rangeEnd",
|
||||
"then",
|
||||
"else",
|
||||
"guard",
|
||||
"arm",
|
||||
"body",
|
||||
"statement",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static size_t compare_owned_edge_kind_order(const char *kind) {
|
||||
for (size_t i = 0; compare_owned_edge_order[i]; i++) {
|
||||
if (compare_text_eq(kind, compare_owned_edge_order[i])) return i;
|
||||
}
|
||||
return compare_missing_index();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *id;
|
||||
size_t index;
|
||||
} CompareIdEntry;
|
||||
|
||||
typedef struct {
|
||||
size_t from;
|
||||
size_t child;
|
||||
size_t kind_order;
|
||||
size_t order;
|
||||
} CompareOwnedEdge;
|
||||
|
||||
typedef struct {
|
||||
const ZProgramGraph *graph;
|
||||
CompareIdEntry *node_ids;
|
||||
CompareIdEntry *symbol_ids;
|
||||
CompareIdEntry *type_ids;
|
||||
CompareIdEntry *effect_ids;
|
||||
size_t node_id_len;
|
||||
size_t symbol_id_len;
|
||||
size_t type_id_len;
|
||||
size_t effect_id_len;
|
||||
bool *declared_type_ref;
|
||||
bool *skip_node;
|
||||
CompareOwnedEdge *owned_edges;
|
||||
size_t owned_edge_len;
|
||||
size_t *node_order;
|
||||
size_t node_order_len;
|
||||
size_t *rank;
|
||||
size_t *semantic_edges;
|
||||
size_t semantic_edge_len;
|
||||
} CompareIndex;
|
||||
|
||||
typedef struct {
|
||||
size_t raw_index;
|
||||
size_t from_rank;
|
||||
size_t to_rank;
|
||||
ZProgramGraphEdgeTarget target;
|
||||
size_t order;
|
||||
const char *kind;
|
||||
} CompareEdgeFact;
|
||||
|
||||
typedef const char *(*CompareNodeIdGetter)(const ZProgramGraphNode *);
|
||||
|
||||
static const char *compare_node_id_value(const ZProgramGraphNode *node) {
|
||||
return node ? node->id : NULL;
|
||||
}
|
||||
|
||||
static const char *compare_node_symbol_id_value(const ZProgramGraphNode *node) {
|
||||
return node ? node->symbol_id : NULL;
|
||||
}
|
||||
|
||||
static const char *compare_node_type_id_value(const ZProgramGraphNode *node) {
|
||||
return node ? node->type_id : NULL;
|
||||
}
|
||||
|
||||
static const char *compare_node_effect_id_value(const ZProgramGraphNode *node) {
|
||||
return node ? node->effect_id : NULL;
|
||||
}
|
||||
|
||||
static int compare_id_entry_cmp(const void *left_ptr, const void *right_ptr) {
|
||||
const CompareIdEntry *left = left_ptr;
|
||||
const CompareIdEntry *right = right_ptr;
|
||||
int text = compare_text_cmp(left->id, right->id);
|
||||
if (text != 0) return text;
|
||||
if (left->index < right->index) return -1;
|
||||
if (left->index > right->index) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static CompareIdEntry *compare_build_id_index(const ZProgramGraph *graph, CompareNodeIdGetter getter, size_t *out_len) {
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const char *id = getter(&graph->nodes[i]);
|
||||
if (id && id[0] != '\0') len++;
|
||||
}
|
||||
CompareIdEntry *entries = z_checked_calloc(len ? len : 1, sizeof(CompareIdEntry));
|
||||
size_t write = 0;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const char *id = getter(&graph->nodes[i]);
|
||||
if (!id || id[0] == '\0') continue;
|
||||
entries[write++] = (CompareIdEntry){.id = id, .index = i};
|
||||
}
|
||||
if (write > 1) qsort(entries, write, sizeof(CompareIdEntry), compare_id_entry_cmp);
|
||||
if (out_len) *out_len = write;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static size_t compare_lookup_id(const CompareIdEntry *entries, size_t len, const char *id) {
|
||||
if (!entries || !id || id[0] == '\0') return compare_missing_index();
|
||||
size_t low = 0;
|
||||
size_t high = len;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
int cmp = compare_text_cmp(entries[mid].id, id);
|
||||
if (cmp < 0) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
if (low < len && compare_text_eq(entries[low].id, id)) return entries[low].index;
|
||||
return compare_missing_index();
|
||||
}
|
||||
|
||||
static size_t compare_index_node_by_id(const CompareIndex *index, const char *id) {
|
||||
return compare_lookup_id(index ? index->node_ids : NULL, index ? index->node_id_len : 0, id);
|
||||
}
|
||||
|
||||
static size_t compare_index_node_by_symbol_id(const CompareIndex *index, const char *id) {
|
||||
return compare_lookup_id(index ? index->symbol_ids : NULL, index ? index->symbol_id_len : 0, id);
|
||||
}
|
||||
|
||||
static size_t compare_index_node_by_type_id(const CompareIndex *index, const char *id) {
|
||||
return compare_lookup_id(index ? index->type_ids : NULL, index ? index->type_id_len : 0, id);
|
||||
}
|
||||
|
||||
static size_t compare_index_node_by_effect_id(const CompareIndex *index, const char *id) {
|
||||
return compare_lookup_id(index ? index->effect_ids : NULL, index ? index->effect_id_len : 0, id);
|
||||
}
|
||||
|
||||
static bool compare_index_has_module_named(const CompareIndex *index, const char *name) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
for (size_t i = 0; graph && name && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_MODULE && compare_text_eq(node->name, name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void compare_index_build_skip_nodes(CompareIndex *index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
size_t len = graph && graph->node_len ? graph->node_len : 1;
|
||||
index->declared_type_ref = z_checked_calloc(len, sizeof(bool));
|
||||
index->skip_node = z_checked_calloc(len, sizeof(bool));
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE || !compare_edge_kind(edge, "declaredType")) continue;
|
||||
size_t target = compare_index_node_by_id(index, edge->to);
|
||||
if (target != compare_missing_index()) index->declared_type_ref[target] = true;
|
||||
}
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
bool declared_type_ref = node->kind == Z_PROGRAM_GRAPH_NODE_TYPE_REF && index->declared_type_ref[i];
|
||||
bool std_import = node->name && strncmp(node->name, "std.", strlen("std.")) == 0;
|
||||
bool local_import = node->kind == Z_PROGRAM_GRAPH_NODE_IMPORT && !std_import && compare_index_has_module_named(index, node->name);
|
||||
index->skip_node[i] = declared_type_ref || local_import;
|
||||
}
|
||||
}
|
||||
|
||||
static bool compare_index_skip_node(const CompareIndex *index, size_t node_index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
return graph && node_index < graph->node_len && index->skip_node && index->skip_node[node_index];
|
||||
}
|
||||
|
||||
static bool compare_index_skip_edge(const CompareIndex *index, const ZProgramGraphEdge *edge) {
|
||||
if (!index || !index->graph || !edge) return false;
|
||||
size_t source = compare_index_node_by_id(index, edge->from);
|
||||
if (source != compare_missing_index() && compare_index_skip_node(index, source)) return true;
|
||||
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE) return false;
|
||||
size_t target = compare_index_node_by_id(index, edge->to);
|
||||
return target != compare_missing_index() && compare_index_skip_node(index, target);
|
||||
}
|
||||
|
||||
static int compare_owned_edge_cmp(const void *left_ptr, const void *right_ptr) {
|
||||
const CompareOwnedEdge *left = left_ptr;
|
||||
const CompareOwnedEdge *right = right_ptr;
|
||||
int cmp = compare_size_t_value(left->from, right->from);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = compare_size_t_value(left->kind_order, right->kind_order);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = compare_size_t_value(left->order, right->order);
|
||||
if (cmp != 0) return cmp;
|
||||
return compare_size_t_value(left->child, right->child);
|
||||
}
|
||||
|
||||
static void compare_index_build_owned_edges(CompareIndex *index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE || compare_owned_edge_kind_order(edge->kind) == compare_missing_index()) continue;
|
||||
if (compare_index_node_by_id(index, edge->from) == compare_missing_index()) continue;
|
||||
if (compare_index_node_by_id(index, edge->to) == compare_missing_index()) continue;
|
||||
count++;
|
||||
}
|
||||
index->owned_edges = z_checked_calloc(count ? count : 1, sizeof(CompareOwnedEdge));
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE) continue;
|
||||
size_t kind_order = compare_owned_edge_kind_order(edge->kind);
|
||||
if (kind_order == compare_missing_index()) continue;
|
||||
size_t from = compare_index_node_by_id(index, edge->from);
|
||||
size_t child = compare_index_node_by_id(index, edge->to);
|
||||
if (from == compare_missing_index() || child == compare_missing_index()) continue;
|
||||
index->owned_edges[index->owned_edge_len++] = (CompareOwnedEdge){
|
||||
.from = from,
|
||||
.child = child,
|
||||
.kind_order = kind_order,
|
||||
.order = edge->order,
|
||||
};
|
||||
}
|
||||
if (index->owned_edge_len > 1) qsort(index->owned_edges, index->owned_edge_len, sizeof(CompareOwnedEdge), compare_owned_edge_cmp);
|
||||
}
|
||||
|
||||
static size_t compare_index_first_owned_edge(const CompareIndex *index, size_t from, size_t kind_order) {
|
||||
size_t low = 0;
|
||||
size_t high = index ? index->owned_edge_len : 0;
|
||||
while (low < high) {
|
||||
size_t mid = low + (high - low) / 2;
|
||||
const CompareOwnedEdge *edge = &index->owned_edges[mid];
|
||||
if (edge->from < from || (edge->from == from && edge->kind_order < kind_order)) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
static void compare_index_push_semantic_node(const CompareIndex *index, size_t node_index, bool *visited, size_t *order, size_t *len) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
if (!graph || node_index >= graph->node_len || visited[node_index]) return;
|
||||
visited[node_index] = true;
|
||||
if (!compare_index_skip_node(index, node_index)) order[(*len)++] = node_index;
|
||||
for (size_t kind_order = 0; compare_owned_edge_order[kind_order]; kind_order++) {
|
||||
size_t edge_index = compare_index_first_owned_edge(index, node_index, kind_order);
|
||||
while (index &&
|
||||
edge_index < index->owned_edge_len &&
|
||||
index->owned_edges[edge_index].from == node_index &&
|
||||
index->owned_edges[edge_index].kind_order == kind_order) {
|
||||
size_t child = index->owned_edges[edge_index].child;
|
||||
if (!visited[child]) compare_index_push_semantic_node(index, child, visited, order, len);
|
||||
edge_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void compare_index_build_node_order(CompareIndex *index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
size_t node_len = graph && graph->node_len ? graph->node_len : 1;
|
||||
index->node_order = z_checked_calloc(node_len, sizeof(size_t));
|
||||
index->rank = z_checked_calloc(node_len, sizeof(size_t));
|
||||
bool *visited = z_checked_calloc(node_len, sizeof(bool));
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) index->rank[i] = compare_missing_index();
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
if (graph->nodes[i].kind == Z_PROGRAM_GRAPH_NODE_MODULE) compare_index_push_semantic_node(index, i, visited, index->node_order, &index->node_order_len);
|
||||
}
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
if (!visited[i]) compare_index_push_semantic_node(index, i, visited, index->node_order, &index->node_order_len);
|
||||
}
|
||||
for (size_t i = 0; i < index->node_order_len; i++) {
|
||||
if (index->node_order[i] < (graph ? graph->node_len : 0)) index->rank[index->node_order[i]] = i;
|
||||
}
|
||||
free(visited);
|
||||
}
|
||||
|
||||
static size_t compare_index_semantic_rank_for_raw_node(const CompareIndex *index, size_t raw_index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
if (!graph || !index->rank || raw_index >= graph->node_len || compare_index_skip_node(index, raw_index)) return compare_missing_index();
|
||||
return index->rank[raw_index];
|
||||
}
|
||||
|
||||
static void compare_index_build_semantic_edges(CompareIndex *index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
if (!compare_index_skip_edge(index, &graph->edges[i])) count++;
|
||||
}
|
||||
index->semantic_edges = z_checked_calloc(count ? count : 1, sizeof(size_t));
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
if (!compare_index_skip_edge(index, &graph->edges[i])) index->semantic_edges[index->semantic_edge_len++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t compare_index_target_rank(const CompareIndex *index, const ZProgramGraphEdge *edge) {
|
||||
if (!edge) return compare_missing_index();
|
||||
size_t raw_index = compare_missing_index();
|
||||
switch (edge->target) {
|
||||
case Z_PROGRAM_GRAPH_EDGE_TARGET_NODE:
|
||||
raw_index = compare_index_node_by_id(index, edge->to);
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_EDGE_TARGET_SYMBOL:
|
||||
raw_index = compare_index_node_by_symbol_id(index, edge->to);
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_EDGE_TARGET_TYPE:
|
||||
raw_index = compare_index_node_by_type_id(index, edge->to);
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_EDGE_TARGET_EFFECT:
|
||||
raw_index = compare_index_node_by_effect_id(index, edge->to);
|
||||
break;
|
||||
}
|
||||
return compare_index_semantic_rank_for_raw_node(index, raw_index);
|
||||
}
|
||||
|
||||
static void compare_index_init(CompareIndex *index, const ZProgramGraph *graph) {
|
||||
*index = (CompareIndex){.graph = graph};
|
||||
index->node_ids = compare_build_id_index(graph, compare_node_id_value, &index->node_id_len);
|
||||
index->symbol_ids = compare_build_id_index(graph, compare_node_symbol_id_value, &index->symbol_id_len);
|
||||
index->type_ids = compare_build_id_index(graph, compare_node_type_id_value, &index->type_id_len);
|
||||
index->effect_ids = compare_build_id_index(graph, compare_node_effect_id_value, &index->effect_id_len);
|
||||
compare_index_build_skip_nodes(index);
|
||||
compare_index_build_owned_edges(index);
|
||||
compare_index_build_node_order(index);
|
||||
compare_index_build_semantic_edges(index);
|
||||
}
|
||||
|
||||
static void compare_index_free(CompareIndex *index) {
|
||||
if (!index) return;
|
||||
free(index->node_ids);
|
||||
free(index->symbol_ids);
|
||||
free(index->type_ids);
|
||||
free(index->effect_ids);
|
||||
free(index->declared_type_ref);
|
||||
free(index->skip_node);
|
||||
free(index->owned_edges);
|
||||
free(index->node_order);
|
||||
free(index->rank);
|
||||
free(index->semantic_edges);
|
||||
*index = (CompareIndex){0};
|
||||
}
|
||||
|
||||
static int compare_edge_fact_cmp(const void *left_ptr, const void *right_ptr) {
|
||||
const CompareEdgeFact *left = left_ptr;
|
||||
const CompareEdgeFact *right = right_ptr;
|
||||
int cmp = compare_size_t_value(left->from_rank, right->from_rank);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = compare_size_t_value(left->to_rank, right->to_rank);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = compare_size_t_value((size_t)left->target, (size_t)right->target);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = compare_size_t_value(left->order, right->order);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = compare_text_cmp(left->kind, right->kind);
|
||||
if (cmp != 0) return cmp;
|
||||
return compare_size_t_value(left->raw_index, right->raw_index);
|
||||
}
|
||||
|
||||
static bool compare_edge_fact_eq(const CompareEdgeFact *left, const CompareEdgeFact *right) {
|
||||
return left &&
|
||||
right &&
|
||||
left->from_rank == right->from_rank &&
|
||||
left->to_rank == right->to_rank &&
|
||||
left->target == right->target &&
|
||||
left->order == right->order &&
|
||||
compare_text_eq(left->kind, right->kind);
|
||||
}
|
||||
|
||||
static CompareEdgeFact *compare_build_edge_facts(const CompareIndex *index) {
|
||||
const ZProgramGraph *graph = index ? index->graph : NULL;
|
||||
size_t count = index ? index->semantic_edge_len : 0;
|
||||
CompareEdgeFact *facts = z_checked_calloc(count ? count : 1, sizeof(CompareEdgeFact));
|
||||
for (size_t i = 0; graph && index && i < count; i++) {
|
||||
size_t raw_index = index->semantic_edges[i];
|
||||
const ZProgramGraphEdge *edge = &graph->edges[raw_index];
|
||||
size_t from_raw = compare_index_node_by_id(index, edge->from);
|
||||
facts[i] = (CompareEdgeFact){
|
||||
.raw_index = raw_index,
|
||||
.from_rank = compare_index_semantic_rank_for_raw_node(index, from_raw),
|
||||
.to_rank = compare_index_target_rank(index, edge),
|
||||
.target = edge->target,
|
||||
.order = edge->order,
|
||||
.kind = edge->kind,
|
||||
};
|
||||
}
|
||||
if (count > 1) qsort(facts, count, sizeof(CompareEdgeFact), compare_edge_fact_cmp);
|
||||
return facts;
|
||||
}
|
||||
|
||||
static bool compare_node_text_field(const char *field, const char *left, const char *right, size_t left_index, size_t right_index, ZProgramGraphCompare *out) {
|
||||
if (compare_text_eq(left, right)) return true;
|
||||
return compare_fail(out, "GRC004", "node semantic field differs", field, left_index, right_index, 0, 0);
|
||||
}
|
||||
|
||||
static bool compare_node_bool_field(const char *field, bool left, bool right, size_t left_index, size_t right_index, ZProgramGraphCompare *out) {
|
||||
if (left == right) return true;
|
||||
return compare_fail(out, "GRC005", "node flag differs", field, left_index, right_index, 0, 0);
|
||||
}
|
||||
|
||||
static bool compare_node_type_is_source_semantic(ZProgramGraphNodeKind kind) {
|
||||
return kind == Z_PROGRAM_GRAPH_NODE_FUNCTION || kind == Z_PROGRAM_GRAPH_NODE_PARAM ||
|
||||
kind == Z_PROGRAM_GRAPH_NODE_CONST || kind == Z_PROGRAM_GRAPH_NODE_LET ||
|
||||
kind == Z_PROGRAM_GRAPH_NODE_LITERAL || kind == Z_PROGRAM_GRAPH_NODE_TYPE_REF ||
|
||||
kind == Z_PROGRAM_GRAPH_NODE_TYPE_ALIAS || kind == Z_PROGRAM_GRAPH_NODE_FIELD ||
|
||||
kind == Z_PROGRAM_GRAPH_NODE_ENUM_CASE || kind == Z_PROGRAM_GRAPH_NODE_CHOICE_CASE;
|
||||
}
|
||||
|
||||
static bool compare_node_type_field(const ZProgramGraphNode *left, const ZProgramGraphNode *right, size_t left_index, size_t right_index, ZProgramGraphCompare *out) {
|
||||
if (!left || !compare_node_type_is_source_semantic(left->kind)) return true;
|
||||
return compare_node_text_field("type", left ? left->type : NULL, right ? right->type : NULL, left_index, right_index, out);
|
||||
}
|
||||
|
||||
static bool compare_nodes(const CompareIndex *left, const CompareIndex *right, ZProgramGraphCompare *out) {
|
||||
const ZProgramGraph *left_graph = left ? left->graph : NULL;
|
||||
const ZProgramGraph *right_graph = right ? right->graph : NULL;
|
||||
size_t left_count = left ? left->node_order_len : 0;
|
||||
size_t right_count = right ? right->node_order_len : 0;
|
||||
if (left_count != right_count) {
|
||||
return compare_fail(out, "GRC002", "node count differs", "nodes", 0, 0, left_count, right_count);
|
||||
}
|
||||
for (size_t i = 0; i < left_count; i++) {
|
||||
size_t left_index = left->node_order[i];
|
||||
size_t right_index = right->node_order[i];
|
||||
const ZProgramGraphNode *left_node = &left_graph->nodes[left_index];
|
||||
const ZProgramGraphNode *right_node = &right_graph->nodes[right_index];
|
||||
if (left_node->kind != right_node->kind) {
|
||||
return compare_fail(out, "GRC003", "node kind differs", "kind", left_index, right_index, 0, 0);
|
||||
}
|
||||
if (!compare_node_text_field("name", left_node->name, right_node->name, left_index, right_index, out) ||
|
||||
!compare_node_type_field(left_node, right_node, left_index, right_index, out) ||
|
||||
!compare_node_text_field("value", left_node->value, right_node->value, left_index, right_index, out) ||
|
||||
!compare_node_bool_field("public", left_node->is_public, right_node->is_public, left_index, right_index, out) ||
|
||||
!compare_node_bool_field("mutable", left_node->is_mutable, right_node->is_mutable, left_index, right_index, out) ||
|
||||
!compare_node_bool_field("static", left_node->is_static, right_node->is_static, left_index, right_index, out) ||
|
||||
!compare_node_bool_field("fallible", left_node->fallible, right_node->fallible, left_index, right_index, out) ||
|
||||
!compare_node_bool_field("exportC", left_node->export_c, right_node->export_c, left_index, right_index, out)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compare_edges(const CompareIndex *left, const CompareIndex *right, ZProgramGraphCompare *out) {
|
||||
size_t left_count = left ? left->semantic_edge_len : 0;
|
||||
size_t right_count = right ? right->semantic_edge_len : 0;
|
||||
if (left_count != right_count) {
|
||||
return compare_fail(out, "GRC006", "edge count differs", "edges", 0, 0, left_count, right_count);
|
||||
}
|
||||
CompareEdgeFact *left_facts = compare_build_edge_facts(left);
|
||||
CompareEdgeFact *right_facts = compare_build_edge_facts(right);
|
||||
for (size_t i = 0; i < left_count; i++) {
|
||||
if (!compare_edge_fact_eq(&left_facts[i], &right_facts[i])) {
|
||||
size_t left_index = left_facts[i].raw_index;
|
||||
size_t right_index = right_facts[i].raw_index;
|
||||
size_t left_from = left_facts[i].from_rank;
|
||||
size_t left_to = left_facts[i].to_rank;
|
||||
free(left_facts);
|
||||
free(right_facts);
|
||||
return compare_fail(out, "GRC007", "edge semantic fact differs", "edge", left_index, right_index, left_from, left_to);
|
||||
}
|
||||
}
|
||||
free(left_facts);
|
||||
free(right_facts);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_program_graph_semantic_compare(const ZProgramGraph *left, const ZProgramGraph *right, ZProgramGraphCompare *out) {
|
||||
if (out) *out = (ZProgramGraphCompare){.ok = true};
|
||||
if (!left || !right) return compare_fail(out, "GRC001", "program graph is missing", "graph", 0, 0, left ? 1 : 0, right ? 1 : 0);
|
||||
CompareIndex left_index;
|
||||
CompareIndex right_index;
|
||||
compare_index_init(&left_index, left);
|
||||
compare_index_init(&right_index, right);
|
||||
if (out) {
|
||||
out->left_semantic_nodes = left_index.node_order_len;
|
||||
out->right_semantic_nodes = right_index.node_order_len;
|
||||
out->left_semantic_edges = left_index.semantic_edge_len;
|
||||
out->right_semantic_edges = right_index.semantic_edge_len;
|
||||
}
|
||||
bool ok = true;
|
||||
if (left->schema_version != right->schema_version) {
|
||||
ok = compare_fail(out, "GRC012", "schema version differs", "schemaVersion", 0, 0, left->schema_version, right->schema_version);
|
||||
} else if (!compare_text_eq(left->module_identity, right->module_identity)) {
|
||||
ok = compare_fail(out, "GRC013", "module identity differs", "moduleIdentity", 0, 0, 0, 0);
|
||||
} else if (!compare_nodes(&left_index, &right_index, out)) {
|
||||
ok = false;
|
||||
} else if (!compare_edges(&left_index, &right_index, out)) {
|
||||
ok = false;
|
||||
}
|
||||
compare_index_free(&left_index);
|
||||
compare_index_free(&right_index);
|
||||
if (ok && out) out->ok = true;
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_COMPARE_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_COMPARE_H
|
||||
|
||||
#include "program_graph.h"
|
||||
|
||||
typedef struct {
|
||||
bool ok;
|
||||
char code[16];
|
||||
char message[160];
|
||||
char field[32];
|
||||
size_t left_index;
|
||||
size_t right_index;
|
||||
size_t left_count;
|
||||
size_t right_count;
|
||||
size_t left_semantic_nodes;
|
||||
size_t right_semantic_nodes;
|
||||
size_t left_semantic_edges;
|
||||
size_t right_semantic_edges;
|
||||
} ZProgramGraphCompare;
|
||||
|
||||
bool z_program_graph_semantic_compare(const ZProgramGraph *left, const ZProgramGraph *right, ZProgramGraphCompare *out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
#include "program_graph_build.h"
|
||||
@@ -0,0 +1,265 @@
|
||||
#include "program_graph_contracts.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool contract_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
static bool contract_text_span_eq(const char *start, const char *end, const char *text) {
|
||||
if (!start || !end || !text) return false;
|
||||
while (start < end && *text) {
|
||||
if (*start++ != *text++) return false;
|
||||
}
|
||||
return start == end && *text == '\0';
|
||||
}
|
||||
|
||||
static bool contract_starts_with(const char *text, const char *prefix) {
|
||||
size_t prefix_len = prefix ? strlen(prefix) : 0;
|
||||
return text && prefix && strncmp(text, prefix, prefix_len) == 0;
|
||||
}
|
||||
|
||||
static bool identifier_start(char ch) {
|
||||
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
|
||||
}
|
||||
|
||||
static bool identifier_part(char ch) {
|
||||
return identifier_start(ch) || (ch >= '0' && ch <= '9');
|
||||
}
|
||||
|
||||
static bool reserved_word_span(const char *start, const char *end) {
|
||||
const char *keywords[] = {
|
||||
"as", "break", "check", "choice", "const", "continue", "defer", "else", "enum", "export", "extern", "false",
|
||||
"fn", "for", "fun", "if", "import", "in", "let", "match", "meta", "mut", "null", "packed", "pub",
|
||||
"raise", "raises", "rescue", "ret", "return", "shape", "static", "test", "true", "type",
|
||||
"use", "var", "while", NULL
|
||||
};
|
||||
for (size_t i = 0; keywords[i]; i++) {
|
||||
if (contract_text_span_eq(start, end, keywords[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool identifier_valid(const char *text) {
|
||||
if (!text || !identifier_start(*text)) return false;
|
||||
const char *cursor = text + 1;
|
||||
for (; *cursor; cursor++) {
|
||||
if (!identifier_part(*cursor)) return false;
|
||||
}
|
||||
return !reserved_word_span(text, cursor);
|
||||
}
|
||||
|
||||
static bool module_name_valid(const char *text) {
|
||||
if (!text || !*text) return false;
|
||||
const char *cursor = text;
|
||||
for (;;) {
|
||||
const char *segment = cursor;
|
||||
if (!identifier_start(*cursor)) return false;
|
||||
cursor++;
|
||||
while (identifier_part(*cursor)) cursor++;
|
||||
if (reserved_word_span(segment, cursor)) return false;
|
||||
if (*cursor == '\0') return true;
|
||||
if (*cursor != '.') return false;
|
||||
cursor++;
|
||||
if (*cursor == '\0') return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool module_is_stdlib(const char *module) {
|
||||
return contract_starts_with(module, "std.");
|
||||
}
|
||||
|
||||
static bool graph_has_module_named(const ZProgramGraph *graph, const char *name) {
|
||||
for (size_t i = 0; graph && name && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_MODULE && contract_text_eq(node->name, name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool literal_case_valid(const char *text) {
|
||||
const char *cursor = text;
|
||||
if (!cursor || !*cursor) return false;
|
||||
if (*cursor == '-') cursor++;
|
||||
if (!isdigit((unsigned char)*cursor)) return false;
|
||||
while (*cursor) {
|
||||
if (!isdigit((unsigned char)*cursor) && *cursor != '_') return false;
|
||||
cursor++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool match_case_valid(const char *text) {
|
||||
return contract_text_eq(text, "true") ||
|
||||
contract_text_eq(text, "false") ||
|
||||
identifier_valid(text) ||
|
||||
literal_case_valid(text);
|
||||
}
|
||||
|
||||
static bool operator_valid(const char *text) {
|
||||
const char *ops[] = {"+", "-", "*", "/", "%", "&&", "||", "==", "!=", "<", "<=", ">", ">=", "+%", "+|", NULL};
|
||||
for (size_t i = 0; ops[i]; i++) {
|
||||
if (contract_text_eq(text, ops[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool fail_node(ZDiag *diag, const ZProgramGraphNode *node, const char *path, const char *message, const char *expected, const char *actual, const char *help) {
|
||||
if (diag) {
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 2002;
|
||||
diag->path = node && node->path && node->path[0] ? node->path : path;
|
||||
diag->line = node && node->line > 0 ? node->line : 1;
|
||||
diag->column = node && node->column > 0 ? node->column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message ? message : "cannot validate program graph contract");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", expected ? expected : "valid ProgramGraph semantic contract");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual ? actual : (node ? z_program_graph_node_kind_name(node->kind) : "missing graph node"));
|
||||
if (help) snprintf(diag->help, sizeof(diag->help), "%s", help);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool require_identifier(const ZProgramGraphNode *node, const char *text, const char *path, const char *message, ZDiag *diag) {
|
||||
if (identifier_valid(text)) return true;
|
||||
return fail_node(diag,
|
||||
node,
|
||||
path,
|
||||
message ? message : "program graph name is not valid Zero identifier syntax",
|
||||
"identifier",
|
||||
text && text[0] ? text : "missing name",
|
||||
"use a non-keyword identifier");
|
||||
}
|
||||
|
||||
static bool require_top_level_identifier(const ZProgramGraphNode *node, const char *kind, const char *path, const char *message, bool allow_test_name, ZDiag *diag) {
|
||||
const char *name = node ? node->name : NULL;
|
||||
if (!require_identifier(node, name, path, message, diag)) return false;
|
||||
if (!contract_starts_with(name, "__zero_")) return true;
|
||||
if (allow_test_name && contract_starts_with(name, "__zero_test_")) return true;
|
||||
if (contract_starts_with(name, "__zero_std_")) return true;
|
||||
char expected[128];
|
||||
snprintf(expected, sizeof(expected), "top-level %s name without the __zero_ prefix", kind ? kind : "declaration");
|
||||
return fail_node(diag,
|
||||
node,
|
||||
path,
|
||||
"program graph declaration uses a reserved compiler-internal symbol name",
|
||||
expected,
|
||||
name,
|
||||
"rename the declaration; __zero_ names are reserved for compiler-provided helpers");
|
||||
}
|
||||
|
||||
static bool import_contract_ok(const ZProgramGraph *graph, const ZProgramGraphNode *node, const char *path, ZDiag *diag) {
|
||||
const char *module = node->name ? node->name : "";
|
||||
const char *alias = node->value && node->value[0] ? node->value : NULL;
|
||||
if (!module_name_valid(module)) {
|
||||
return fail_node(diag, node, path, "program graph import module is not valid Zero import syntax", "dot-separated module identifiers", module, "use module names like std.mem or package.local");
|
||||
}
|
||||
if (alias && !identifier_valid(alias)) {
|
||||
return fail_node(diag, node, path, "program graph import alias is not valid Zero identifier syntax", "identifier", alias, "use an identifier alias or clear the alias value");
|
||||
}
|
||||
if (!module_is_stdlib(module) && !graph_has_module_named(graph, module)) {
|
||||
return fail_node(diag, node, path, "program graph import target module is missing", "Module node for package-local import or std.* import", module, "add the imported module to the artifact or remove the import");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool name_contract_node_ok(const ZProgramGraph *graph, const ZProgramGraphNode *node, const char *path, ZDiag *diag) {
|
||||
switch (node->kind) {
|
||||
case Z_PROGRAM_GRAPH_NODE_IMPORT:
|
||||
if (!import_contract_ok(graph, node, path, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_C_IMPORT:
|
||||
if (!require_identifier(node, node->name, path, "program graph C import alias is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_CONST:
|
||||
if (!require_top_level_identifier(node, "const", path, "program graph const name is not valid Zero identifier syntax", false, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_TYPE_ALIAS:
|
||||
if (!require_top_level_identifier(node, "type-alias", path, "program graph alias name is not valid Zero identifier syntax", false, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_SHAPE:
|
||||
if (!require_top_level_identifier(node, "shape", path, "program graph type name is not valid Zero identifier syntax", false, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_INTERFACE:
|
||||
if (!require_top_level_identifier(node, "interface", path, "program graph interface name is not valid Zero identifier syntax", false, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_ENUM:
|
||||
if (!require_top_level_identifier(node, "enum", path, "program graph enum name is not valid Zero identifier syntax", false, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_CHOICE:
|
||||
if (!require_top_level_identifier(node, "choice", path, "program graph choice name is not valid Zero identifier syntax", false, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_FUNCTION:
|
||||
if (!require_top_level_identifier(node, "function", path, "program graph function name is not valid Zero identifier syntax", true, diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_PARAM:
|
||||
case Z_PROGRAM_GRAPH_NODE_FIELD:
|
||||
case Z_PROGRAM_GRAPH_NODE_ENUM_CASE:
|
||||
case Z_PROGRAM_GRAPH_NODE_CHOICE_CASE:
|
||||
if (!require_identifier(node, node->name, path, "program graph parameter name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_FIELD_INIT:
|
||||
if (!require_identifier(node, node->name, path, "program graph field initializer name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_IDENTIFIER:
|
||||
if (!require_identifier(node, node->name, path, "program graph identifier name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_FIELD_ACCESS:
|
||||
if (!require_identifier(node, node->name, path, "program graph field access name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_CALL:
|
||||
case Z_PROGRAM_GRAPH_NODE_METHOD_CALL:
|
||||
if (node->name && node->name[0] && !identifier_valid(node->name) && !operator_valid(node->name)) {
|
||||
return fail_node(diag, node, path, "program graph call callee name is not valid Zero identifier syntax", "identifier", node->name, "use an identifier callee or a supported operator node");
|
||||
}
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_RESCUE:
|
||||
if (!require_identifier(node, node->name, path, "program graph rescue binding name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_MATCH_ARM:
|
||||
if (!match_case_valid(node->name)) {
|
||||
return fail_node(diag, node, path, "program graph match case name is not valid Zero match syntax", "identifier, true, false, or number", node->name && node->name[0] ? node->name : "missing name", "use a non-keyword case name");
|
||||
}
|
||||
if (node->value && node->value[0] && !require_identifier(node, node->value, path, "program graph match payload name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_LET:
|
||||
if (!require_identifier(node, node->name, path, "program graph binding name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_FOR:
|
||||
if (!require_identifier(node, node->name, path, "program graph loop binding name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
case Z_PROGRAM_GRAPH_NODE_RAISE:
|
||||
if (!require_identifier(node, node->name, path, "program graph raise error name is not valid Zero identifier syntax", diag)) return false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_program_graph_name_contracts_ok(const ZProgramGraph *graph, const char *path, ZDiag *diag) {
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
if (!name_contract_node_ok(graph, &graph->nodes[i], path, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t z_program_graph_name_contract_violations(const ZProgramGraph *graph, const char *path, ZDiag *out, size_t cap) {
|
||||
size_t found = 0;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
ZDiag diag = {0};
|
||||
if (name_contract_node_ok(graph, &graph->nodes[i], path, &diag)) continue;
|
||||
if (out && found < cap) out[found] = diag;
|
||||
found++;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool z_program_graph_semantic_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag) {
|
||||
if (!z_program_graph_effect_contracts_ok(graph, resolution, path, diag)) return false;
|
||||
if (!z_program_graph_memory_contracts_ok(graph, resolution, path, diag)) return false;
|
||||
if (!z_program_graph_fixed_array_length_contracts_ok(graph, resolution, path, diag)) return false;
|
||||
return z_program_graph_borrow_contracts_ok(graph, path, diag);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef ZERO_C_PROGRAM_GRAPH_CONTRACTS_H
|
||||
#define ZERO_C_PROGRAM_GRAPH_CONTRACTS_H
|
||||
|
||||
#include "program_graph.h"
|
||||
#include "program_graph_resolve.h"
|
||||
|
||||
bool z_program_graph_name_contracts_ok(const ZProgramGraph *graph, const char *path, ZDiag *diag);
|
||||
size_t z_program_graph_name_contract_violations(const ZProgramGraph *graph, const char *path, ZDiag *out, size_t cap);
|
||||
size_t z_program_graph_fixed_array_length_contract_violations(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *out, size_t cap);
|
||||
size_t z_program_graph_effect_contract_violations(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *out, size_t cap);
|
||||
size_t z_program_graph_memory_contract_violations(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *out, size_t cap);
|
||||
bool z_program_graph_semantic_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag);
|
||||
bool z_program_graph_effect_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag);
|
||||
bool z_program_graph_memory_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag);
|
||||
bool z_program_graph_fixed_array_length_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag);
|
||||
bool z_program_graph_borrow_contracts_ok(const ZProgramGraph *graph, const char *path, ZDiag *diag);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,403 @@
|
||||
#include "program_graph_contracts.h"
|
||||
|
||||
#include "std_sig.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool effect_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
static bool effect_text_present(const char *text) {
|
||||
return text && text[0];
|
||||
}
|
||||
|
||||
static const ZProgramGraphNode *effect_node_by_id(const ZProgramGraph *graph, const char *id) {
|
||||
for (size_t i = 0; graph && id && i < graph->node_len; i++) {
|
||||
if (effect_text_eq(graph->nodes[i].id, id)) return &graph->nodes[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const ZProgramGraphResolutionReference *effect_ref_for_node(const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *node, const char *kind) {
|
||||
for (size_t i = 0; resolution && node && i < resolution->reference_len; i++) {
|
||||
const ZProgramGraphResolutionReference *ref = &resolution->references[i];
|
||||
if ((!kind || effect_text_eq(ref->kind, kind)) && effect_text_eq(ref->node_id, node->id)) return ref;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const ZProgramGraphResolutionReference *effect_call_ref(const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *call) {
|
||||
return effect_ref_for_node(resolution, call, "call");
|
||||
}
|
||||
|
||||
static const ZProgramGraphEdge *effect_owner_edge(const ZProgramGraph *graph, const char *node_id) {
|
||||
for (size_t i = 0; graph && node_id && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && effect_text_eq(edge->to, node_id)) return edge;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool effect_is_under_kind(const ZProgramGraph *graph, const ZProgramGraphNode *node, ZProgramGraphNodeKind kind) {
|
||||
const char *current = node ? node->id : NULL;
|
||||
for (size_t depth = 0; graph && current && depth < graph->node_len; depth++) {
|
||||
const ZProgramGraphEdge *owner = effect_owner_edge(graph, current);
|
||||
const ZProgramGraphNode *owner_node = owner ? effect_node_by_id(graph, owner->from) : NULL;
|
||||
if (!owner_node) return false;
|
||||
if (owner_node->kind == kind) return true;
|
||||
current = owner_node->id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const ZProgramGraphNode *effect_child(const ZProgramGraph *graph, const ZProgramGraphNode *node, const char *kind, size_t order) {
|
||||
for (size_t i = 0; graph && node && kind && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (edge->target == Z_PROGRAM_GRAPH_EDGE_TARGET_NODE && edge->order == order && effect_text_eq(edge->from, node->id) && effect_text_eq(edge->kind, kind)) {
|
||||
return effect_node_by_id(graph, edge->to);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const ZProgramGraphNode *effect_owner_function(const ZProgramGraph *graph, const ZProgramGraphNode *node) {
|
||||
const char *current = node ? node->id : NULL;
|
||||
for (size_t depth = 0; graph && current && depth < graph->node_len; depth++) {
|
||||
const ZProgramGraphEdge *owner = effect_owner_edge(graph, current);
|
||||
const ZProgramGraphNode *owner_node = owner ? effect_node_by_id(graph, owner->from) : NULL;
|
||||
if (!owner_node) return NULL;
|
||||
if (owner_node->kind == Z_PROGRAM_GRAPH_NODE_FUNCTION) return owner_node;
|
||||
current = owner_node->id;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool effect_ref_kind(const ZProgramGraphResolutionReference *ref, const char *kind) {
|
||||
return ref && effect_text_eq(ref->target_kind, kind);
|
||||
}
|
||||
|
||||
static bool effect_call_fallible(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *call, const ZProgramGraphResolutionReference **ref_out) {
|
||||
const ZProgramGraphResolutionReference *ref = effect_call_ref(resolution, call);
|
||||
if (ref_out) *ref_out = ref;
|
||||
if (!ref || !ref->resolved) return false;
|
||||
if (effect_text_eq(ref->qualified_name, "world.out.write")) return true;
|
||||
const ZProgramGraphNode *target = effect_node_by_id(graph, ref->target_node);
|
||||
if (target && target->kind == Z_PROGRAM_GRAPH_NODE_FUNCTION && target->fallible) return true;
|
||||
if (effect_ref_kind(ref, "stdlib") || effect_ref_kind(ref, "graphBackedStdlib")) {
|
||||
const ZStdHelperInfo *helper = z_std_helper_find(ref->qualified_name);
|
||||
return helper && z_std_helper_is_fallible(helper);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *effect_literal_type(const ZProgramGraphNode *node, char *scratch, size_t scratch_len) {
|
||||
const char *value = node ? node->value : NULL;
|
||||
if (effect_text_eq(value, "true") || effect_text_eq(value, "false")) return "Bool";
|
||||
if (value && value[0] == '"') return "String";
|
||||
if (value && (value[0] == '-' || (value[0] >= '0' && value[0] <= '9'))) {
|
||||
const char *suffix = strrchr(value, '_');
|
||||
if (suffix && (suffix[1] == 'i' || suffix[1] == 'u' || suffix[1] == 'f') && scratch && scratch_len > strlen(suffix + 1)) {
|
||||
snprintf(scratch, scratch_len, "%s", suffix + 1);
|
||||
return scratch;
|
||||
}
|
||||
// Unsuffixed numeric literals adapt to the expected type during
|
||||
// checking, so the contract treats them as unknown here.
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool effect_type_var_at(const char *type, const char *p) {
|
||||
bool left_boundary = p == type || !((p[-1] >= 'a' && p[-1] <= 'z') || (p[-1] >= 'A' && p[-1] <= 'Z') || (p[-1] >= '0' && p[-1] <= '9') || p[-1] == '_');
|
||||
char next = p[1];
|
||||
bool right_boundary = !((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') || (next >= '0' && next <= '9') || next == '_');
|
||||
return left_boundary && right_boundary;
|
||||
}
|
||||
|
||||
static const char *effect_type_var_pos(const char *type) {
|
||||
for (const char *p = type; p && *p; p++) {
|
||||
if (*p >= 'A' && *p <= 'Z' && effect_type_var_at(type, p)) return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool effect_type_has_type_var(const char *type) {
|
||||
return effect_type_var_pos(type) != NULL;
|
||||
}
|
||||
|
||||
// Unify a stdlib template such as Span<T> against a concrete type such as
|
||||
// Span<u8> and capture the binding for the single type variable.
|
||||
static bool effect_bind_type_var(const char *template_type, const char *concrete, char *binding, size_t binding_len) {
|
||||
const char *var = template_type ? effect_type_var_pos(template_type) : NULL;
|
||||
if (!var || !concrete || !concrete[0] || effect_type_has_type_var(concrete)) return false;
|
||||
size_t prefix_len = (size_t)(var - template_type);
|
||||
const char *suffix = var + 1;
|
||||
size_t suffix_len = strlen(suffix);
|
||||
size_t concrete_len = strlen(concrete);
|
||||
if (concrete_len <= prefix_len + suffix_len) return false;
|
||||
if (strncmp(concrete, template_type, prefix_len) != 0) return false;
|
||||
if (strcmp(concrete + concrete_len - suffix_len, suffix) != 0) return false;
|
||||
size_t bound_len = concrete_len - prefix_len - suffix_len;
|
||||
if (bound_len + 1 > binding_len) return false;
|
||||
memcpy(binding, concrete + prefix_len, bound_len);
|
||||
binding[bound_len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool effect_substitute_type_var(const char *template_type, const char *binding, char *out, size_t out_len) {
|
||||
const char *var = template_type ? effect_type_var_pos(template_type) : NULL;
|
||||
if (!var || !binding || !binding[0]) return false;
|
||||
size_t prefix_len = (size_t)(var - template_type);
|
||||
const char *suffix = var + 1;
|
||||
size_t needed = prefix_len + strlen(binding) + strlen(suffix) + 1;
|
||||
if (needed > out_len) return false;
|
||||
memcpy(out, template_type, prefix_len);
|
||||
out[prefix_len] = '\0';
|
||||
strcat(out, binding);
|
||||
strcat(out, suffix);
|
||||
return true;
|
||||
}
|
||||
|
||||
#define EFFECT_EXPR_TYPE_MAX_DEPTH 16
|
||||
|
||||
static const char *effect_expr_type_at_depth(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *expr, char *scratch, size_t scratch_len, size_t depth);
|
||||
|
||||
// Instantiate a generic stdlib return template (Span<T>, Maybe<T>, ...) by
|
||||
// unifying the helper's parameter templates against the concrete argument
|
||||
// types at this call. Returns "" when no binding can be established so the
|
||||
// caller treats the type as unknown instead of comparing a raw template,
|
||||
// matching `zero check`, which always typechecks instantiations.
|
||||
static const char *effect_stdlib_instantiated_type(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *call, const ZStdHelperInfo *helper, char *scratch, size_t scratch_len, size_t depth) {
|
||||
const char *template_type = helper && helper->return_type ? helper->return_type : "";
|
||||
if (!effect_type_has_type_var(template_type)) return template_type;
|
||||
if (!helper || depth >= EFFECT_EXPR_TYPE_MAX_DEPTH) return "";
|
||||
char binding[96] = {0};
|
||||
for (int i = 0; i < helper->arg_count && i < Z_STD_HELPER_MAX_ARGS; i++) {
|
||||
const char *param_template = helper->arg_types[i];
|
||||
if (!param_template || !effect_type_has_type_var(param_template)) continue;
|
||||
const ZProgramGraphNode *arg = effect_child(graph, call, "arg", (size_t)i);
|
||||
char arg_scratch[96] = {0};
|
||||
const char *arg_type = effect_expr_type_at_depth(graph, resolution, arg, arg_scratch, sizeof(arg_scratch), depth + 1);
|
||||
if (effect_bind_type_var(param_template, arg_type, binding, sizeof(binding))) break;
|
||||
binding[0] = '\0';
|
||||
}
|
||||
if (!binding[0]) return "";
|
||||
if (!effect_substitute_type_var(template_type, binding, scratch, scratch_len)) return "";
|
||||
return scratch;
|
||||
}
|
||||
|
||||
static const char *effect_expr_type_at_depth(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *expr, char *scratch, size_t scratch_len, size_t depth) {
|
||||
if (!expr || depth >= EFFECT_EXPR_TYPE_MAX_DEPTH) return "";
|
||||
if (effect_text_present(expr->type) && !effect_type_has_type_var(expr->type)) return expr->type;
|
||||
if (expr->kind == Z_PROGRAM_GRAPH_NODE_LITERAL) return effect_literal_type(expr, scratch, scratch_len);
|
||||
const ZProgramGraphResolutionReference *ref = effect_ref_for_node(resolution, expr, NULL);
|
||||
const ZProgramGraphNode *target = ref ? effect_node_by_id(graph, ref->target_node) : NULL;
|
||||
if (target && effect_text_present(target->type) && !effect_type_has_type_var(target->type)) return target->type;
|
||||
if (ref && (effect_ref_kind(ref, "stdlib") || effect_ref_kind(ref, "graphBackedStdlib"))) {
|
||||
const ZStdHelperInfo *helper = z_std_helper_find(ref->qualified_name);
|
||||
if (!helper) return "";
|
||||
return effect_stdlib_instantiated_type(graph, resolution, expr, helper, scratch, scratch_len, depth);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char *effect_expr_type(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *expr, char *scratch, size_t scratch_len) {
|
||||
return effect_expr_type_at_depth(graph, resolution, expr, scratch, scratch_len, 0);
|
||||
}
|
||||
|
||||
static bool fail_wrong_return_type(const ZProgramGraphNode *stmt, const char *expected, const char *actual, const char *path, ZDiag *diag) {
|
||||
if (diag) {
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 3007;
|
||||
diag->path = stmt && stmt->path && stmt->path[0] ? stmt->path : path;
|
||||
diag->line = stmt && stmt->line > 0 ? stmt->line : 1;
|
||||
diag->column = stmt && stmt->column > 0 ? stmt->column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "return type does not match function return type");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", expected && expected[0] ? expected : "Void");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", actual && actual[0] ? actual : "Void");
|
||||
snprintf(diag->help, sizeof(diag->help), "return a value compatible with the function signature");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool fail_missing_return_value(const ZProgramGraphNode *fun, const char *path, ZDiag *diag) {
|
||||
if (diag) {
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 3007;
|
||||
diag->path = fun && fun->path && fun->path[0] ? fun->path : path;
|
||||
diag->line = fun && fun->line > 0 ? fun->line : 1;
|
||||
diag->column = 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "non-void function must return a value on every path");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "%s", fun && fun->type ? fun->type : "non-Void");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "function body may fall through");
|
||||
snprintf(diag->help, sizeof(diag->help), "add explicit `ret` or `raise` on every path");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool effect_maybe_accepts_present_value(const char *expected, const char *actual) { const char *prefix = "Maybe<"; size_t prefix_len = strlen(prefix), expected_len = expected ? strlen(expected) : 0; if (!expected || !actual || strncmp(expected, prefix, prefix_len) != 0 || expected_len <= prefix_len + 1 || expected[expected_len - 1] != '>') return false; size_t inner_len = expected_len - prefix_len - 1; return strlen(actual) == inner_len && strncmp(expected + prefix_len, actual, inner_len) == 0; }
|
||||
|
||||
static bool effect_type_apply_inner(const char *type, const char *name, char *inner, size_t inner_len) {
|
||||
size_t name_len = strlen(name);
|
||||
size_t type_len = type ? strlen(type) : 0;
|
||||
if (!type || type_len < name_len + 3) return false;
|
||||
if (strncmp(type, name, name_len) != 0 || type[name_len] != '<' || type[type_len - 1] != '>') return false;
|
||||
size_t body_len = type_len - name_len - 2;
|
||||
if (body_len + 1 > inner_len) return false;
|
||||
memcpy(inner, type + name_len + 1, body_len);
|
||||
inner[body_len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool effect_type_array_element(const char *type, char *element, size_t element_len) {
|
||||
if (!type || type[0] != '[') return false;
|
||||
const char *close = strchr(type, ']');
|
||||
if (!close || !close[1]) return false;
|
||||
if (strlen(close + 1) + 1 > element_len) return false;
|
||||
snprintf(element, element_len, "%s", close + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool effect_type_is_scalar(const char *type) {
|
||||
static const char *const names[] = {"Bool", "String", "Void", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "usize", "isize", "f32", "f64", NULL};
|
||||
for (size_t i = 0; type && names[i]; i++) {
|
||||
if (effect_text_eq(type, names[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define EFFECT_TYPE_COMPARE_MAX_DEPTH 8
|
||||
|
||||
// The contract only reasons about a closed set of type forms. Anything else
|
||||
// (aliases, shapes, interfaces, multi-argument generics) is left to the full
|
||||
// checker so the contract never rejects code that `zero check` accepts.
|
||||
static bool effect_type_understood(const char *type, size_t depth) {
|
||||
if (!type || !type[0] || depth > EFFECT_TYPE_COMPARE_MAX_DEPTH) return false;
|
||||
if (effect_type_is_scalar(type)) return true;
|
||||
char inner[96];
|
||||
if (effect_type_apply_inner(type, "Span", inner, sizeof(inner)) ||
|
||||
effect_type_apply_inner(type, "MutSpan", inner, sizeof(inner)) ||
|
||||
effect_type_apply_inner(type, "Maybe", inner, sizeof(inner)) ||
|
||||
effect_type_array_element(type, inner, sizeof(inner))) {
|
||||
return effect_type_understood(inner, depth + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool effect_readable_byte_span_like(const char *type) {
|
||||
return effect_text_eq(type, "String") || effect_text_eq(type, "Span<u8>") || effect_text_eq(type, "MutSpan<u8>");
|
||||
}
|
||||
|
||||
// Mirrors the acceptance side of the checker's types_compatible relation for
|
||||
// the forms the contract understands; returns true (accept) when unsure.
|
||||
static bool effect_types_compatible(const char *expected, const char *actual, size_t depth) {
|
||||
if (depth > EFFECT_TYPE_COMPARE_MAX_DEPTH) return true;
|
||||
if (effect_text_eq(expected, actual)) return true;
|
||||
if (effect_maybe_accepts_present_value(expected, actual)) return true;
|
||||
if (!effect_type_understood(expected, 0) || !effect_type_understood(actual, 0)) return true;
|
||||
if ((effect_text_eq(expected, "String") || effect_text_eq(expected, "Span<u8>")) && effect_readable_byte_span_like(actual)) return true;
|
||||
char expected_inner[96];
|
||||
char actual_inner[96];
|
||||
if (effect_type_apply_inner(expected, "Span", expected_inner, sizeof(expected_inner))) {
|
||||
if (effect_type_apply_inner(actual, "Span", actual_inner, sizeof(actual_inner)) ||
|
||||
effect_type_apply_inner(actual, "MutSpan", actual_inner, sizeof(actual_inner)) ||
|
||||
effect_type_array_element(actual, actual_inner, sizeof(actual_inner))) {
|
||||
return effect_types_compatible(expected_inner, actual_inner, depth + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (effect_type_apply_inner(expected, "MutSpan", expected_inner, sizeof(expected_inner)) &&
|
||||
effect_type_apply_inner(actual, "MutSpan", actual_inner, sizeof(actual_inner))) {
|
||||
return effect_types_compatible(expected_inner, actual_inner, depth + 1);
|
||||
}
|
||||
if (effect_type_apply_inner(expected, "Maybe", expected_inner, sizeof(expected_inner)) &&
|
||||
effect_type_apply_inner(actual, "Maybe", actual_inner, sizeof(actual_inner))) {
|
||||
return effect_types_compatible(expected_inner, actual_inner, depth + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool effect_function_has_return(const ZProgramGraph *graph, const ZProgramGraphNode *fun) {
|
||||
for (size_t i = 0; graph && fun && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_RETURN && effect_owner_function(graph, node) == fun) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool effect_function_return_contract_ok(const ZProgramGraph *graph, const ZProgramGraphNode *fun, const char *path, ZDiag *diag) {
|
||||
if (!fun || !effect_text_present(fun->type) || effect_text_eq(fun->type, "Void")) return true;
|
||||
const ZProgramGraphEdge *owner = effect_owner_edge(graph, fun->id);
|
||||
const ZProgramGraphNode *parent = owner ? effect_node_by_id(graph, owner->from) : NULL;
|
||||
if (parent && parent->kind == Z_PROGRAM_GRAPH_NODE_INTERFACE && effect_text_eq(owner->kind, "method")) return true;
|
||||
if (effect_function_has_return(graph, fun)) return true;
|
||||
return fail_missing_return_value(fun, path, diag);
|
||||
}
|
||||
|
||||
static bool effect_return_contract_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *stmt, const char *path, ZDiag *diag) {
|
||||
const ZProgramGraphNode *fun = effect_owner_function(graph, stmt);
|
||||
const char *expected = fun && effect_text_present(fun->type) ? fun->type : "Void";
|
||||
const ZProgramGraphNode *expr = effect_child(graph, stmt, "expr", 0);
|
||||
char actual_scratch[64] = {0};
|
||||
const char *actual = effect_expr_type(graph, resolution, expr, actual_scratch, sizeof(actual_scratch));
|
||||
if (!expr) actual = "Void";
|
||||
if (effect_type_has_type_var(expected) || effect_type_has_type_var(actual)) return true;
|
||||
if (!effect_text_present(actual) || effect_types_compatible(expected, actual, 0)) return true;
|
||||
return fail_wrong_return_type(stmt, expected, actual, path, diag);
|
||||
}
|
||||
|
||||
static bool effect_call_checked(const ZProgramGraph *graph, const ZProgramGraphNode *call) {
|
||||
return effect_is_under_kind(graph, call, Z_PROGRAM_GRAPH_NODE_CHECK) ||
|
||||
effect_is_under_kind(graph, call, Z_PROGRAM_GRAPH_NODE_RESCUE);
|
||||
}
|
||||
|
||||
static bool fail_unchecked_fallible_call(const ZProgramGraphNode *call, const ZProgramGraphResolutionReference *ref, const char *path, ZDiag *diag) {
|
||||
if (diag) {
|
||||
const char *name = ref && ref->qualified_name && ref->qualified_name[0] ? ref->qualified_name : (call && call->name ? call->name : "");
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 1003;
|
||||
diag->path = call && call->path && call->path[0] ? call->path : path;
|
||||
diag->line = call && call->line > 0 ? call->line : 1;
|
||||
diag->column = call && call->column > 0 ? call->column : 1;
|
||||
diag->length = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "fallible function call must be checked");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "check fallible_call ...");
|
||||
snprintf(diag->actual, sizeof(diag->actual), "call to '%s'", name);
|
||||
snprintf(diag->help, sizeof(diag->help), "prefix the call with check in a function marked with `raises`");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool effect_node_contract_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const ZProgramGraphNode *node, const char *path, ZDiag *diag) {
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_FUNCTION && !effect_function_return_contract_ok(graph, node, path, diag)) return false;
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_RETURN && !effect_return_contract_ok(graph, resolution, node, path, diag)) return false;
|
||||
if (node->kind == Z_PROGRAM_GRAPH_NODE_CALL || node->kind == Z_PROGRAM_GRAPH_NODE_METHOD_CALL) {
|
||||
const ZProgramGraphResolutionReference *ref = NULL;
|
||||
if (effect_call_fallible(graph, resolution, node, &ref) && !effect_call_checked(graph, node)) {
|
||||
return fail_unchecked_fallible_call(node, ref, path, diag);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_program_graph_effect_contracts_ok(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *diag) {
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
if (!effect_node_contract_ok(graph, resolution, &graph->nodes[i], path, diag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t z_program_graph_effect_contract_violations(const ZProgramGraph *graph, const ZProgramGraphResolutionFacts *resolution, const char *path, ZDiag *out, size_t cap) {
|
||||
size_t found = 0;
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
ZDiag diag = {0};
|
||||
if (effect_node_contract_ok(graph, resolution, &graph->nodes[i], path, &diag)) continue;
|
||||
if (out && found < cap) out[found] = diag;
|
||||
found++;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
#include "program_graph_format.h"
|
||||
#include "program_graph_build.h"
|
||||
#include "program_graph_import.h"
|
||||
#include "program_graph_resolve.h"
|
||||
#include "program_graph_semantics.h"
|
||||
#include "program_graph_store.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool graph_format_text_eq(const char *left, const char *right) {
|
||||
return strcmp(left ? left : "", right ? right : "") == 0;
|
||||
}
|
||||
|
||||
static bool graph_format_text_present(const char *text) {
|
||||
return text && text[0];
|
||||
}
|
||||
|
||||
static void graph_format_free_node_fields(ZProgramGraphNode *node) {
|
||||
if (!node) return;
|
||||
free(node->id);
|
||||
free(node->name);
|
||||
free(node->type);
|
||||
free(node->value);
|
||||
free(node->path);
|
||||
free(node->symbol_id);
|
||||
free(node->type_id);
|
||||
free(node->effect_id);
|
||||
free(node->node_hash);
|
||||
*node = (ZProgramGraphNode){0};
|
||||
}
|
||||
|
||||
static void graph_format_free_edge_fields(ZProgramGraphEdge *edge) {
|
||||
if (!edge) return;
|
||||
free(edge->from);
|
||||
free(edge->to);
|
||||
free(edge->kind);
|
||||
*edge = (ZProgramGraphEdge){0};
|
||||
}
|
||||
|
||||
static void graph_format_append_quoted(ZBuf *buf, const char *text) {
|
||||
zbuf_append_char(buf, '"');
|
||||
for (const char *p = text ? text : ""; *p; p++) {
|
||||
unsigned char ch = (unsigned char)*p;
|
||||
switch (ch) {
|
||||
case '\\': zbuf_append(buf, "\\\\"); break;
|
||||
case '"': zbuf_append(buf, "\\\""); break;
|
||||
case '\n': zbuf_append(buf, "\\n"); break;
|
||||
case '\r': zbuf_append(buf, "\\r"); break;
|
||||
case '\t': zbuf_append(buf, "\\t"); break;
|
||||
default:
|
||||
if (ch < 0x20) {
|
||||
const char *hex = "0123456789abcdef";
|
||||
char escape[7] = {'\\', 'u', '0', '0', hex[ch >> 4], hex[ch & 0x0f], 0};
|
||||
zbuf_append(buf, escape);
|
||||
} else {
|
||||
zbuf_append_char(buf, (char)ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
zbuf_append_char(buf, '"');
|
||||
}
|
||||
|
||||
static void graph_format_append_validation_json(ZBuf *buf, const ZProgramGraphValidation *validation) {
|
||||
bool ok = !validation || validation->ok;
|
||||
zbuf_append(buf, "{\"state\":");
|
||||
graph_format_append_quoted(buf, z_program_graph_validation_state_name(validation ? validation->state : Z_PROGRAM_GRAPH_VALIDATION_SHAPE_VALID));
|
||||
zbuf_appendf(buf, ",\"ok\":%s,\"diagnostics\":[", ok ? "true" : "false");
|
||||
if (!ok) {
|
||||
zbuf_append(buf, "{\"code\":");
|
||||
graph_format_append_quoted(buf, validation->code);
|
||||
zbuf_append(buf, ",\"message\":");
|
||||
graph_format_append_quoted(buf, validation->message);
|
||||
zbuf_append(buf, ",\"node\":");
|
||||
graph_format_append_quoted(buf, validation->node_id);
|
||||
zbuf_append(buf, ",\"edge\":{\"from\":");
|
||||
graph_format_append_quoted(buf, validation->edge_from);
|
||||
zbuf_append(buf, ",\"to\":");
|
||||
graph_format_append_quoted(buf, validation->edge_to);
|
||||
zbuf_append(buf, ",\"target\":");
|
||||
graph_format_append_quoted(buf, validation->edge_target);
|
||||
zbuf_append(buf, "}}");
|
||||
}
|
||||
zbuf_append(buf, "]}");
|
||||
}
|
||||
|
||||
static void graph_format_append_failed_json(ZBuf *buf) {
|
||||
zbuf_append(buf, "{\"schemaVersion\":1,\"canonicalSource\":false,\"moduleIdentity\":\"module:main\",\"graphHash\":\"\",\"validation\":{\"state\":\"decoded\",\"ok\":false,\"diagnostics\":[{\"code\":\"GRF001\",\"message\":\"program graph construction failed\"}]},\"counts\":{\"nodes\":0,\"edges\":0},\"nodes\":[],\"edges\":[]}");
|
||||
}
|
||||
|
||||
static void graph_format_append_failed_dump(ZBuf *buf) {
|
||||
zbuf_append(buf, "zero-graph v1\n");
|
||||
zbuf_append(buf, "origin source-text\n");
|
||||
zbuf_append(buf, "module \"main\"\n");
|
||||
zbuf_append(buf, "hash \"\"\n");
|
||||
zbuf_append(buf, "validation \"decoded\" failed\n");
|
||||
zbuf_append(buf, "diagnostic code:\"GRF001\" message:\"program graph construction failed\"\n");
|
||||
}
|
||||
|
||||
static bool graph_format_parse_fail(ZDiag *diag, size_t line, const char *message) {
|
||||
if (diag) {
|
||||
diag->code = 1;
|
||||
diag->path = "program graph dump";
|
||||
diag->line = (int)(line ? line : 1);
|
||||
diag->column = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "%s", message ? message : "invalid program graph dump");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool graph_format_next_line(const char **cursor, char **out) {
|
||||
const char *start = cursor ? *cursor : NULL;
|
||||
if (!start || !*start) return false;
|
||||
const char *end = start;
|
||||
while (*end && *end != '\n') end++;
|
||||
size_t len = (size_t)(end - start);
|
||||
if (len > 0 && start[len - 1] == '\r') len--;
|
||||
*out = z_strndup(start, len);
|
||||
*cursor = *end == '\n' ? end + 1 : end;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_literal(const char **cursor, const char *literal) {
|
||||
size_t len = strlen(literal);
|
||||
if (strncmp(*cursor, literal, len) != 0) return false;
|
||||
*cursor += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int graph_format_hex_value(char ch) {
|
||||
if (ch >= '0' && ch <= '9') return ch - '0';
|
||||
if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
|
||||
if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_quoted(const char **cursor, char **out) {
|
||||
if (**cursor != '"') return false;
|
||||
(*cursor)++;
|
||||
ZBuf buf;
|
||||
zbuf_init(&buf);
|
||||
while (**cursor && **cursor != '"') {
|
||||
unsigned char ch = (unsigned char)**cursor;
|
||||
if (ch != '\\') {
|
||||
zbuf_append_char(&buf, (char)ch);
|
||||
(*cursor)++;
|
||||
continue;
|
||||
}
|
||||
(*cursor)++;
|
||||
switch (**cursor) {
|
||||
case '\\': zbuf_append_char(&buf, '\\'); (*cursor)++; break;
|
||||
case '"': zbuf_append_char(&buf, '"'); (*cursor)++; break;
|
||||
case 'n': zbuf_append_char(&buf, '\n'); (*cursor)++; break;
|
||||
case 'r': zbuf_append_char(&buf, '\r'); (*cursor)++; break;
|
||||
case 't': zbuf_append_char(&buf, '\t'); (*cursor)++; break;
|
||||
case 'u': {
|
||||
if ((*cursor)[1] != '0' || (*cursor)[2] != '0' || !(*cursor)[3] || !(*cursor)[4]) {
|
||||
zbuf_free(&buf);
|
||||
return false;
|
||||
}
|
||||
int high = graph_format_hex_value((*cursor)[3]);
|
||||
int low = graph_format_hex_value((*cursor)[4]);
|
||||
if (high < 0 || low < 0) {
|
||||
zbuf_free(&buf);
|
||||
return false;
|
||||
}
|
||||
zbuf_append_char(&buf, (char)((high << 4) | low));
|
||||
*cursor += 5;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
zbuf_free(&buf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (**cursor != '"') {
|
||||
zbuf_free(&buf);
|
||||
return false;
|
||||
}
|
||||
(*cursor)++;
|
||||
*out = buf.data ? buf.data : z_strdup("");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_size(const char **cursor, size_t *out) {
|
||||
if (!isdigit((unsigned char)**cursor)) return false;
|
||||
size_t value = 0;
|
||||
while (isdigit((unsigned char)**cursor)) {
|
||||
unsigned digit = (unsigned)(**cursor - '0');
|
||||
if (value > (SIZE_MAX - digit) / 10) return false;
|
||||
value = value * 10 + digit;
|
||||
(*cursor)++;
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_int(const char **cursor, int *out) {
|
||||
size_t value = 0;
|
||||
if (!graph_format_parse_size(cursor, &value) || value > (size_t)INT_MAX) return false;
|
||||
*out = (int)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_bool(const char **cursor, bool *out) {
|
||||
if (strncmp(*cursor, "true", 4) == 0) {
|
||||
*cursor += 4;
|
||||
*out = true;
|
||||
return true;
|
||||
}
|
||||
if (strncmp(*cursor, "false", 5) == 0) {
|
||||
*cursor += 5;
|
||||
*out = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_attr_key(const char **cursor, char **out) {
|
||||
if (!cursor || !*cursor || **cursor != ' ') return false;
|
||||
(*cursor)++;
|
||||
const char *start = *cursor;
|
||||
while (isalnum((unsigned char)**cursor) || **cursor == '_' || **cursor == '-') (*cursor)++;
|
||||
if (*cursor == start || **cursor != ':') return false;
|
||||
*out = z_strndup(start, (size_t)(*cursor - start));
|
||||
(*cursor)++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_assign_text(char **slot, char *value) {
|
||||
if (*slot) {
|
||||
free(value);
|
||||
return false;
|
||||
}
|
||||
*slot = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_text_attr(const char **cursor, char **slot) {
|
||||
char *value = NULL;
|
||||
if (!graph_format_parse_quoted(cursor, &value)) return false;
|
||||
return graph_format_assign_text(slot, value);
|
||||
}
|
||||
|
||||
static bool graph_format_parse_handle_token(const char **cursor, char **out) {
|
||||
const char *start = cursor ? *cursor : NULL;
|
||||
if (!start || *start != '#') return false;
|
||||
const char *end = start + 1;
|
||||
while (isalnum((unsigned char)*end) || *end == '_' || *end == '-' || *end == '.') end++;
|
||||
if (end == start + 1) return false;
|
||||
*out = z_strndup(start, (size_t)(end - start));
|
||||
*cursor = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_name_token(const char **cursor, char **out) {
|
||||
const char *start = cursor ? *cursor : NULL;
|
||||
if (!start || !(isalpha((unsigned char)*start) || *start == '_')) return false;
|
||||
const char *end = start + 1;
|
||||
while (isalnum((unsigned char)*end) || *end == '_' || *end == '-') end++;
|
||||
*out = z_strndup(start, (size_t)(end - start));
|
||||
*cursor = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_ref_token(const char **cursor, char **out) {
|
||||
const char *start = cursor ? *cursor : NULL;
|
||||
if (!start || !*start || isspace((unsigned char)*start)) return false;
|
||||
const char *end = start;
|
||||
while (*end && !isspace((unsigned char)*end)) end++;
|
||||
*out = z_strndup(start, (size_t)(end - start));
|
||||
*cursor = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_node_kind_from_name(const char *name, ZProgramGraphNodeKind *out) {
|
||||
for (int kind = Z_PROGRAM_GRAPH_NODE_MODULE; kind <= Z_PROGRAM_GRAPH_NODE_STATEMENT; kind++) {
|
||||
if (graph_format_text_eq(z_program_graph_node_kind_name((ZProgramGraphNodeKind)kind), name)) {
|
||||
*out = (ZProgramGraphNodeKind)kind;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool graph_format_edge_target_from_name(const char *name, ZProgramGraphEdgeTarget *out) {
|
||||
for (int target = Z_PROGRAM_GRAPH_EDGE_TARGET_NODE; target <= Z_PROGRAM_GRAPH_EDGE_TARGET_EFFECT; target++) {
|
||||
if (graph_format_text_eq(z_program_graph_edge_target_name((ZProgramGraphEdgeTarget)target), name)) {
|
||||
*out = (ZProgramGraphEdgeTarget)target;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *graph_format_module_storage_name(const char *identity) {
|
||||
if (!identity || !identity[0]) return "main";
|
||||
if (strncmp(identity, "module:", strlen("module:")) == 0) return identity + strlen("module:");
|
||||
return identity;
|
||||
}
|
||||
|
||||
static char *graph_format_module_identity_from_storage(const char *name) {
|
||||
if (!name || !name[0]) return z_strdup("module:main");
|
||||
if (strncmp(name, "module:", strlen("module:")) == 0 ||
|
||||
strncmp(name, "package:", strlen("package:")) == 0) {
|
||||
return z_strdup(name);
|
||||
}
|
||||
ZBuf identity;
|
||||
zbuf_init(&identity);
|
||||
zbuf_append(&identity, "module:");
|
||||
zbuf_append(&identity, name);
|
||||
return identity.data ? identity.data : z_strdup("module:main");
|
||||
}
|
||||
|
||||
static bool graph_format_validation_state_from_name(const char *name, ZProgramGraphValidationState *out) {
|
||||
for (int state = Z_PROGRAM_GRAPH_VALIDATION_DECODED; state <= Z_PROGRAM_GRAPH_VALIDATION_BUILDABLE; state++) {
|
||||
if (graph_format_text_eq(z_program_graph_validation_state_name((ZProgramGraphValidationState)state), name)) {
|
||||
*out = (ZProgramGraphValidationState)state;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_node_line(const char *line, ZProgramGraphNode *out) {
|
||||
ZProgramGraphNode node = {0};
|
||||
char *kind_name = NULL;
|
||||
const char *cursor = line;
|
||||
bool seen_public = false;
|
||||
bool seen_mutable = false;
|
||||
bool seen_static = false;
|
||||
bool seen_fallible = false;
|
||||
bool seen_export_c = false;
|
||||
bool seen_line = false;
|
||||
bool seen_column = false;
|
||||
bool ok = graph_format_parse_literal(&cursor, "node ") &&
|
||||
graph_format_parse_handle_token(&cursor, &node.id) &&
|
||||
graph_format_parse_literal(&cursor, " ") &&
|
||||
graph_format_parse_name_token(&cursor, &kind_name) &&
|
||||
graph_format_node_kind_from_name(kind_name, &node.kind);
|
||||
while (ok && *cursor) {
|
||||
char *key = NULL;
|
||||
ok = graph_format_parse_attr_key(&cursor, &key);
|
||||
if (!ok) {
|
||||
free(key);
|
||||
break;
|
||||
}
|
||||
if (graph_format_text_eq(key, "name")) ok = graph_format_parse_text_attr(&cursor, &node.name);
|
||||
else if (graph_format_text_eq(key, "type")) ok = graph_format_parse_text_attr(&cursor, &node.type);
|
||||
else if (graph_format_text_eq(key, "value")) ok = graph_format_parse_text_attr(&cursor, &node.value);
|
||||
else if (graph_format_text_eq(key, "path")) ok = graph_format_parse_text_attr(&cursor, &node.path);
|
||||
else if (graph_format_text_eq(key, "line")) {
|
||||
ok = !seen_line && graph_format_parse_int(&cursor, &node.line);
|
||||
seen_line = true;
|
||||
} else if (graph_format_text_eq(key, "column")) {
|
||||
ok = !seen_column && graph_format_parse_int(&cursor, &node.column);
|
||||
seen_column = true;
|
||||
} else if (graph_format_text_eq(key, "public")) {
|
||||
ok = !seen_public && graph_format_parse_bool(&cursor, &node.is_public);
|
||||
seen_public = true;
|
||||
} else if (graph_format_text_eq(key, "mutable")) {
|
||||
ok = !seen_mutable && graph_format_parse_bool(&cursor, &node.is_mutable);
|
||||
seen_mutable = true;
|
||||
} else if (graph_format_text_eq(key, "static")) {
|
||||
ok = !seen_static && graph_format_parse_bool(&cursor, &node.is_static);
|
||||
seen_static = true;
|
||||
} else if (graph_format_text_eq(key, "fallible")) {
|
||||
ok = !seen_fallible && graph_format_parse_bool(&cursor, &node.fallible);
|
||||
seen_fallible = true;
|
||||
} else if (graph_format_text_eq(key, "exportC")) {
|
||||
ok = !seen_export_c && graph_format_parse_bool(&cursor, &node.export_c);
|
||||
seen_export_c = true;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
free(key);
|
||||
}
|
||||
free(kind_name);
|
||||
if (!ok) {
|
||||
graph_format_free_node_fields(&node);
|
||||
return false;
|
||||
}
|
||||
*out = node;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_edge_line(const char *line, ZProgramGraphEdge *out) {
|
||||
ZProgramGraphEdge edge = {0};
|
||||
const char *cursor = line;
|
||||
bool seen_target = false;
|
||||
bool seen_order = false;
|
||||
edge.target = Z_PROGRAM_GRAPH_EDGE_TARGET_NODE;
|
||||
bool ok = graph_format_parse_literal(&cursor, "edge ") &&
|
||||
graph_format_parse_handle_token(&cursor, &edge.from) &&
|
||||
graph_format_parse_literal(&cursor, " ") &&
|
||||
graph_format_parse_name_token(&cursor, &edge.kind) &&
|
||||
graph_format_parse_literal(&cursor, " ") &&
|
||||
graph_format_parse_ref_token(&cursor, &edge.to);
|
||||
while (ok && *cursor) {
|
||||
char *key = NULL;
|
||||
ok = graph_format_parse_attr_key(&cursor, &key);
|
||||
if (!ok) {
|
||||
free(key);
|
||||
break;
|
||||
}
|
||||
if (graph_format_text_eq(key, "target")) {
|
||||
char *target_name = NULL;
|
||||
ok = !seen_target &&
|
||||
graph_format_parse_name_token(&cursor, &target_name) &&
|
||||
graph_format_edge_target_from_name(target_name, &edge.target);
|
||||
seen_target = true;
|
||||
free(target_name);
|
||||
} else if (graph_format_text_eq(key, "order")) {
|
||||
ok = !seen_order && graph_format_parse_size(&cursor, &edge.order);
|
||||
seen_order = true;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
free(key);
|
||||
}
|
||||
if (!ok) {
|
||||
graph_format_free_edge_fields(&edge);
|
||||
return false;
|
||||
}
|
||||
*out = edge;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool graph_format_parse_validation_line(const char *line, ZProgramGraphValidationState *state, bool *ok_status) {
|
||||
char *state_name = NULL;
|
||||
const char *cursor = line;
|
||||
bool parsed = graph_format_parse_literal(&cursor, "validation ") &&
|
||||
graph_format_parse_quoted(&cursor, &state_name) &&
|
||||
graph_format_validation_state_from_name(state_name, state) &&
|
||||
graph_format_parse_literal(&cursor, " ");
|
||||
if (parsed && strncmp(cursor, "failed", 6) == 0) {
|
||||
cursor += 6;
|
||||
*ok_status = false;
|
||||
} else {
|
||||
parsed = false;
|
||||
}
|
||||
parsed = parsed && *cursor == 0;
|
||||
free(state_name);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static void graph_format_copy_node_identity_input(ZProgramGraphNode *dst, const ZProgramGraphNode *src) {
|
||||
dst->id = z_strdup(src->id ? src->id : "");
|
||||
dst->kind = src->kind;
|
||||
dst->name = z_strdup(src->name ? src->name : "");
|
||||
dst->type = z_strdup(src->type ? src->type : "");
|
||||
dst->value = z_strdup(src->value ? src->value : "");
|
||||
dst->path = z_strdup(src->path ? src->path : "");
|
||||
dst->line = src->line;
|
||||
dst->column = src->column;
|
||||
dst->is_public = src->is_public;
|
||||
dst->is_mutable = src->is_mutable;
|
||||
dst->is_static = src->is_static;
|
||||
dst->fallible = src->fallible;
|
||||
dst->export_c = src->export_c;
|
||||
}
|
||||
|
||||
static void graph_format_replace_text(char **slot, const char *value) {
|
||||
if (!slot) return;
|
||||
free(*slot);
|
||||
*slot = z_strdup(value ? value : "");
|
||||
}
|
||||
|
||||
static bool graph_format_apply_checked_identities(ZProgramGraph *graph) {
|
||||
ZProgramGraph expected;
|
||||
z_program_graph_init(&expected);
|
||||
expected.schema_version = graph->schema_version;
|
||||
free(expected.module_identity);
|
||||
expected.module_identity = z_strdup(graph->module_identity ? graph->module_identity : "");
|
||||
expected.nodes = z_checked_calloc(graph->node_len, sizeof(ZProgramGraphNode));
|
||||
expected.node_len = graph->node_len;
|
||||
expected.node_cap = graph->node_len;
|
||||
expected.edges = z_checked_calloc(graph->edge_len, sizeof(ZProgramGraphEdge));
|
||||
expected.edge_len = graph->edge_len;
|
||||
expected.edge_cap = graph->edge_len;
|
||||
for (size_t i = 0; i < graph->node_len; i++) graph_format_copy_node_identity_input(&expected.nodes[i], &graph->nodes[i]);
|
||||
for (size_t i = 0; i < graph->edge_len; i++) {
|
||||
expected.edges[i].from = z_strdup(graph->edges[i].from ? graph->edges[i].from : "");
|
||||
expected.edges[i].to = z_strdup(graph->edges[i].to ? graph->edges[i].to : "");
|
||||
expected.edges[i].kind = z_strdup(graph->edges[i].kind ? graph->edges[i].kind : "");
|
||||
expected.edges[i].target = graph->edges[i].target;
|
||||
expected.edges[i].order = graph->edges[i].order;
|
||||
}
|
||||
z_program_graph_finalize_identities(&expected);
|
||||
bool ok = graph_format_text_eq(expected.graph_hash, graph->graph_hash);
|
||||
for (size_t i = 0; ok && i < graph->node_len; i++) {
|
||||
if (graph_format_text_present(graph->nodes[i].symbol_id)) ok = graph_format_text_eq(expected.nodes[i].symbol_id, graph->nodes[i].symbol_id);
|
||||
if (ok && graph_format_text_present(graph->nodes[i].type_id)) ok = graph_format_text_eq(expected.nodes[i].type_id, graph->nodes[i].type_id);
|
||||
if (ok && graph_format_text_present(graph->nodes[i].effect_id)) ok = graph_format_text_eq(expected.nodes[i].effect_id, graph->nodes[i].effect_id);
|
||||
if (ok && graph_format_text_present(graph->nodes[i].node_hash)) ok = graph_format_text_eq(expected.nodes[i].node_hash, graph->nodes[i].node_hash);
|
||||
}
|
||||
if (ok) {
|
||||
graph_format_replace_text(&graph->graph_hash, expected.graph_hash);
|
||||
for (size_t i = 0; i < graph->node_len; i++) {
|
||||
graph_format_replace_text(&graph->nodes[i].symbol_id, expected.nodes[i].symbol_id);
|
||||
graph_format_replace_text(&graph->nodes[i].type_id, expected.nodes[i].type_id);
|
||||
graph_format_replace_text(&graph->nodes[i].effect_id, expected.nodes[i].effect_id);
|
||||
graph_format_replace_text(&graph->nodes[i].node_hash, expected.nodes[i].node_hash);
|
||||
}
|
||||
}
|
||||
z_program_graph_free(&expected);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void graph_format_count_records(const char *cursor, size_t *node_count, size_t *edge_count) {
|
||||
const char *scan = cursor;
|
||||
char *line = NULL;
|
||||
while (graph_format_next_line(&scan, &line)) {
|
||||
if (strncmp(line, "node ", 5) == 0) (*node_count)++;
|
||||
else if (strncmp(line, "edge ", 5) == 0) (*edge_count)++;
|
||||
free(line);
|
||||
line = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool z_program_graph_parse_dump(const char *text, ZProgramGraph *out, ZDiag *diag) {
|
||||
if (!text || !out) return graph_format_parse_fail(diag, 1, "program graph dump is missing");
|
||||
z_program_graph_init(out);
|
||||
const char *cursor = text;
|
||||
char *line = NULL;
|
||||
size_t line_no = 0;
|
||||
size_t node_count = 0;
|
||||
size_t edge_count = 0;
|
||||
bool validation_ok = true;
|
||||
const char *error_message = "invalid program graph dump";
|
||||
size_t error_line = 1;
|
||||
|
||||
#define FAIL_AT(line, message) \
|
||||
do { \
|
||||
error_line = (line); \
|
||||
error_message = (message); \
|
||||
goto fail; \
|
||||
} while (0)
|
||||
#define FAIL(message) FAIL_AT(line_no, message)
|
||||
#define NEXT_REQUIRED_LINE() \
|
||||
do { \
|
||||
free(line); \
|
||||
line = NULL; \
|
||||
line_no++; \
|
||||
if (!graph_format_next_line(&cursor, &line)) FAIL("incomplete program graph dump"); \
|
||||
} while (0)
|
||||
|
||||
NEXT_REQUIRED_LINE();
|
||||
if (!graph_format_text_eq(line, "zero-graph v1")) {
|
||||
if (strncmp(line, "zero-graph v", 12) == 0) FAIL("unknown program graph schema version");
|
||||
FAIL("expected zero-graph v1 header");
|
||||
}
|
||||
out->schema_version = 1;
|
||||
NEXT_REQUIRED_LINE();
|
||||
if (graph_format_text_eq(line, "origin source-text")) out->canonical_source = false;
|
||||
else FAIL("expected program graph origin");
|
||||
NEXT_REQUIRED_LINE();
|
||||
const char *module_cursor = line;
|
||||
char *module_storage_name = NULL;
|
||||
free(out->module_identity);
|
||||
out->module_identity = NULL;
|
||||
if (!graph_format_parse_literal(&module_cursor, "module ") ||
|
||||
!graph_format_parse_quoted(&module_cursor, &module_storage_name) ||
|
||||
*module_cursor != 0) {
|
||||
free(module_storage_name);
|
||||
FAIL("expected module field");
|
||||
}
|
||||
out->module_identity = graph_format_module_identity_from_storage(module_storage_name);
|
||||
free(module_storage_name);
|
||||
NEXT_REQUIRED_LINE();
|
||||
const char *hash_cursor = line;
|
||||
if (!graph_format_parse_literal(&hash_cursor, "hash ") ||
|
||||
!graph_format_parse_quoted(&hash_cursor, &out->graph_hash) ||
|
||||
*hash_cursor != 0) FAIL("expected hash field");
|
||||
out->validation_state = Z_PROGRAM_GRAPH_VALIDATION_DECODED;
|
||||
graph_format_count_records(cursor, &node_count, &edge_count);
|
||||
out->nodes = z_checked_calloc(node_count, sizeof(ZProgramGraphNode));
|
||||
out->node_cap = node_count;
|
||||
out->edges = z_checked_calloc(edge_count, sizeof(ZProgramGraphEdge));
|
||||
out->edge_cap = edge_count;
|
||||
free(line);
|
||||
line = NULL;
|
||||
while (graph_format_next_line(&cursor, &line)) {
|
||||
line_no++;
|
||||
if (line[0] == 0) {
|
||||
free(line);
|
||||
line = NULL;
|
||||
continue;
|
||||
}
|
||||
if (graph_format_parse_validation_line(line, &out->validation_state, &validation_ok)) {
|
||||
if (!validation_ok) {
|
||||
size_t validation_line = line_no;
|
||||
NEXT_REQUIRED_LINE();
|
||||
if (strncmp(line, "diagnostic ", 11) != 0) FAIL("expected diagnostic field after failed validation");
|
||||
FAIL_AT(validation_line, "program graph input reports failed validation");
|
||||
}
|
||||
} else if (strncmp(line, "node ", 5) == 0) {
|
||||
if (out->node_len >= out->node_cap) FAIL("too many node records");
|
||||
if (!graph_format_parse_node_line(line, &out->nodes[out->node_len])) FAIL("invalid node record");
|
||||
if (!out->nodes[out->node_len].path) out->nodes[out->node_len].path = z_strdup("");
|
||||
if (out->nodes[out->node_len].line <= 0) out->nodes[out->node_len].line = (int)line_no;
|
||||
if (out->nodes[out->node_len].column <= 0) out->nodes[out->node_len].column = 1;
|
||||
out->node_len++;
|
||||
} else if (strncmp(line, "edge ", 5) == 0) {
|
||||
if (out->edge_len >= out->edge_cap) FAIL("too many edge records");
|
||||
if (!graph_format_parse_edge_line(line, &out->edges[out->edge_len])) FAIL("invalid edge record");
|
||||
out->edge_len++;
|
||||
} else {
|
||||
FAIL("unexpected content after graph header");
|
||||
}
|
||||
free(line);
|
||||
line = NULL;
|
||||
}
|
||||
if (validation_ok && !graph_format_apply_checked_identities(out)) FAIL_AT(1, "program graph identities do not match graph content");
|
||||
#undef NEXT_REQUIRED_LINE
|
||||
#undef FAIL
|
||||
#undef FAIL_AT
|
||||
return true;
|
||||
|
||||
fail:
|
||||
z_program_graph_free(out);
|
||||
free(line);
|
||||
return graph_format_parse_fail(diag, error_line, error_message);
|
||||
}
|
||||
|
||||
static bool graph_format_storage_validation_fail(const char *path, const ZProgramGraphValidation *validation, ZDiag *diag) {
|
||||
if (diag) {
|
||||
*diag = (ZDiag){0};
|
||||
diag->code = 1005;
|
||||
diag->path = path;
|
||||
diag->line = 1;
|
||||
diag->column = 1;
|
||||
snprintf(diag->message, sizeof(diag->message), "stored program graph failed validation: %s",
|
||||
validation && validation->message[0] ? validation->message : "invalid graph shape");
|
||||
snprintf(diag->expected, sizeof(diag->expected), "shape-valid program graph");
|
||||
if (validation && validation->edge_from[0]) {
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%.16s from:%.32s to:%.32s target:%.16s",
|
||||
validation->code[0] ? validation->code : "invalid graph",
|
||||
validation->edge_from,
|
||||
validation->edge_to,
|
||||
validation->edge_target);
|
||||
} else {
|
||||
snprintf(diag->actual, sizeof(diag->actual), "%s", validation && validation->code[0] ? validation->code : "invalid graph");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_program_graph_load(const char *path, ZProgramGraph *out, ZDiag *diag) {
|
||||
if (z_program_graph_store_path_is_binary(path)) {
|
||||
ZProgramGraphStore store;
|
||||
z_program_graph_store_init(&store);
|
||||
bool ok = z_program_graph_store_load_path(path, &store, diag);
|
||||
if (ok) {
|
||||
*out = store.graph;
|
||||
store.graph = (ZProgramGraph){0};
|
||||
}
|
||||
z_program_graph_store_free(&store);
|
||||
if (!ok && diag && !diag->path) diag->path = path;
|
||||
return ok;
|
||||
}
|
||||
char *text = z_read_file(path, diag);
|
||||
if (!text) return false;
|
||||
bool parsed = z_program_graph_parse_dump(text, out, diag);
|
||||
free(text);
|
||||
if (!parsed) {
|
||||
if (diag) diag->path = path;
|
||||
return false;
|
||||
}
|
||||
ZProgramGraphValidation validation = {0};
|
||||
if (!z_program_graph_validate(out, &validation)) {
|
||||
z_program_graph_free(out);
|
||||
return graph_format_storage_validation_fail(path, &validation, diag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool z_program_graph_save_format(const char *path, const ZProgramGraph *graph, ZProgramGraphStoreFormat format, ZDiag *diag) {
|
||||
ZProgramGraphValidation validation = {0};
|
||||
if (!z_program_graph_validate(graph, &validation)) return graph_format_storage_validation_fail(path, &validation, diag);
|
||||
ZProgramGraph storage = graph ? *graph : (ZProgramGraph){0};
|
||||
storage.canonical_source = false;
|
||||
if (format == Z_PROGRAM_GRAPH_STORE_FORMAT_BINARY) {
|
||||
return z_program_graph_store_write_generated_path_format(path, &storage, Z_PROGRAM_GRAPH_STORE_FORMAT_BINARY, NULL, diag);
|
||||
}
|
||||
ZBuf dump; zbuf_init(&dump);
|
||||
z_program_graph_append_dump(&dump, &storage, &validation);
|
||||
ZProgramGraph parsed;
|
||||
if (!z_program_graph_parse_dump(dump.data ? dump.data : "", &parsed, diag)) {
|
||||
if (diag) diag->path = path;
|
||||
zbuf_free(&dump);
|
||||
return false;
|
||||
}
|
||||
z_program_graph_free(&parsed);
|
||||
bool ok = z_write_file(path, dump.data ? dump.data : "", diag);
|
||||
zbuf_free(&dump);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool z_program_graph_save(const char *path, const ZProgramGraph *graph, ZDiag *diag) {
|
||||
return z_program_graph_save_format(path, graph, Z_PROGRAM_GRAPH_STORE_FORMAT_TEXT, diag);
|
||||
}
|
||||
|
||||
void z_program_graph_append_json(ZBuf *buf, const ZProgramGraph *graph, const ZProgramGraphValidation *validation) {
|
||||
zbuf_appendf(buf, "{\"schemaVersion\":%u,\"canonicalSource\":%s,\"moduleIdentity\":", graph ? graph->schema_version : 1, graph && graph->canonical_source ? "true" : "false");
|
||||
graph_format_append_quoted(buf, graph ? graph->module_identity : "module:main");
|
||||
zbuf_append(buf, ",\"graphHash\":");
|
||||
graph_format_append_quoted(buf, graph ? graph->graph_hash : NULL);
|
||||
zbuf_append(buf, ",\"validation\":");
|
||||
graph_format_append_validation_json(buf, validation);
|
||||
zbuf_appendf(buf, ",\"counts\":{\"nodes\":%zu,\"edges\":%zu},\"nodes\":[", graph ? graph->node_len : 0, graph ? graph->edge_len : 0);
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
if (i > 0) zbuf_append(buf, ",");
|
||||
zbuf_append(buf, "{\"id\":");
|
||||
graph_format_append_quoted(buf, node->id);
|
||||
zbuf_append(buf, ",\"kind\":");
|
||||
graph_format_append_quoted(buf, z_program_graph_node_kind_name(node->kind));
|
||||
zbuf_append(buf, ",\"name\":");
|
||||
graph_format_append_quoted(buf, node->name);
|
||||
zbuf_append(buf, ",\"type\":");
|
||||
graph_format_append_quoted(buf, node->type);
|
||||
zbuf_append(buf, ",\"value\":");
|
||||
graph_format_append_quoted(buf, node->value);
|
||||
zbuf_append(buf, ",\"symbolId\":");
|
||||
graph_format_append_quoted(buf, node->symbol_id);
|
||||
zbuf_append(buf, ",\"typeId\":");
|
||||
graph_format_append_quoted(buf, node->type_id);
|
||||
zbuf_append(buf, ",\"effectId\":");
|
||||
graph_format_append_quoted(buf, node->effect_id);
|
||||
zbuf_append(buf, ",\"nodeHash\":");
|
||||
graph_format_append_quoted(buf, node->node_hash);
|
||||
zbuf_append(buf, ",\"path\":");
|
||||
graph_format_append_quoted(buf, node->path);
|
||||
zbuf_appendf(buf, ",\"line\":%d,\"column\":%d,\"public\":%s,\"mutable\":%s,\"static\":%s,\"fallible\":%s,\"exportC\":%s}",
|
||||
node->line, node->column, node->is_public ? "true" : "false", node->is_mutable ? "true" : "false", node->is_static ? "true" : "false", node->fallible ? "true" : "false", node->export_c ? "true" : "false");
|
||||
}
|
||||
zbuf_append(buf, "],\"edges\":[");
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
if (i > 0) zbuf_append(buf, ",");
|
||||
zbuf_append(buf, "{\"from\":");
|
||||
graph_format_append_quoted(buf, edge->from);
|
||||
zbuf_append(buf, ",\"to\":");
|
||||
graph_format_append_quoted(buf, edge->to);
|
||||
zbuf_append(buf, ",\"kind\":");
|
||||
graph_format_append_quoted(buf, edge->kind);
|
||||
zbuf_append(buf, ",\"target\":");
|
||||
graph_format_append_quoted(buf, z_program_graph_edge_target_name(edge->target));
|
||||
zbuf_appendf(buf, ",\"order\":%zu}", edge->order);
|
||||
}
|
||||
zbuf_append(buf, "],\"resolution\":");
|
||||
z_program_graph_append_resolution_json(buf, graph);
|
||||
zbuf_append(buf, ",\"semantics\":");
|
||||
z_program_graph_append_semantics_json(buf, graph);
|
||||
zbuf_append(buf, "}");
|
||||
}
|
||||
|
||||
static void graph_format_append_text_field(ZBuf *buf, const char *name, const char *value, bool include_empty) {
|
||||
if (!value || (!include_empty && !value[0])) return;
|
||||
zbuf_append_char(buf, ' ');
|
||||
zbuf_append(buf, name);
|
||||
zbuf_append_char(buf, ':');
|
||||
graph_format_append_quoted(buf, value);
|
||||
}
|
||||
|
||||
static void graph_format_append_bool_field(ZBuf *buf, const char *name, bool value) {
|
||||
if (!value) return;
|
||||
zbuf_appendf(buf, " %s:true", name);
|
||||
}
|
||||
|
||||
static void graph_format_append_int_field(ZBuf *buf, const char *name, int value) {
|
||||
if (value <= 0) return;
|
||||
zbuf_appendf(buf, " %s:%d", name, value);
|
||||
}
|
||||
|
||||
static bool graph_format_edge_order_is_semantic(const char *kind) {
|
||||
static const char *const ordered_kinds[] = {
|
||||
"alias",
|
||||
"arg",
|
||||
"arm",
|
||||
"cImport",
|
||||
"case",
|
||||
"choice",
|
||||
"const",
|
||||
"constraint",
|
||||
"enum",
|
||||
"field",
|
||||
"function",
|
||||
"import",
|
||||
"interface",
|
||||
"method",
|
||||
"param",
|
||||
"shape",
|
||||
"statement",
|
||||
"staticParam",
|
||||
"typeArg",
|
||||
"typeParam",
|
||||
"variant",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(ordered_kinds) / sizeof(ordered_kinds[0]); i++) {
|
||||
if (graph_format_text_eq(kind, ordered_kinds[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void z_program_graph_append_dump(ZBuf *buf, const ZProgramGraph *graph, const ZProgramGraphValidation *validation) {
|
||||
(void)validation;
|
||||
zbuf_appendf(buf, "zero-graph v%u\n", graph ? graph->schema_version : 1);
|
||||
zbuf_append(buf, "origin source-text\n");
|
||||
zbuf_append(buf, "module ");
|
||||
graph_format_append_quoted(buf, graph_format_module_storage_name(graph ? graph->module_identity : "module:main"));
|
||||
zbuf_append_char(buf, '\n');
|
||||
zbuf_append(buf, "hash ");
|
||||
graph_format_append_quoted(buf, graph ? graph->graph_hash : NULL);
|
||||
zbuf_append(buf, "\n\n");
|
||||
for (size_t i = 0; graph && i < graph->node_len; i++) {
|
||||
const ZProgramGraphNode *node = &graph->nodes[i];
|
||||
zbuf_append(buf, "node ");
|
||||
zbuf_append(buf, node->id ? node->id : "");
|
||||
zbuf_append_char(buf, ' ');
|
||||
zbuf_append(buf, z_program_graph_node_kind_name(node->kind));
|
||||
graph_format_append_text_field(buf, "name", node->name, false);
|
||||
graph_format_append_text_field(buf, "type", node->type, false);
|
||||
graph_format_append_text_field(buf, "value", node->value, node->kind == Z_PROGRAM_GRAPH_NODE_LITERAL || node->kind == Z_PROGRAM_GRAPH_NODE_C_IMPORT);
|
||||
graph_format_append_text_field(buf, "path", node->path, false);
|
||||
graph_format_append_int_field(buf, "line", node->line);
|
||||
graph_format_append_int_field(buf, "column", node->column);
|
||||
graph_format_append_bool_field(buf, "public", node->is_public);
|
||||
graph_format_append_bool_field(buf, "mutable", node->is_mutable);
|
||||
graph_format_append_bool_field(buf, "static", node->is_static);
|
||||
graph_format_append_bool_field(buf, "fallible", node->fallible);
|
||||
graph_format_append_bool_field(buf, "exportC", node->export_c);
|
||||
zbuf_append_char(buf, '\n');
|
||||
}
|
||||
for (size_t i = 0; graph && i < graph->edge_len; i++) {
|
||||
const ZProgramGraphEdge *edge = &graph->edges[i];
|
||||
zbuf_append(buf, "edge ");
|
||||
zbuf_append(buf, edge->from ? edge->from : "");
|
||||
zbuf_append_char(buf, ' ');
|
||||
zbuf_append(buf, edge->kind ? edge->kind : "");
|
||||
zbuf_append_char(buf, ' ');
|
||||
zbuf_append(buf, edge->to ? edge->to : "");
|
||||
if (edge->target != Z_PROGRAM_GRAPH_EDGE_TARGET_NODE) zbuf_appendf(buf, " target:%s", z_program_graph_edge_target_name(edge->target));
|
||||
if (edge->order != 0 || graph_format_edge_order_is_semantic(edge->kind)) zbuf_appendf(buf, " order:%zu", edge->order);
|
||||
zbuf_append_char(buf, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
void z_append_program_graph_json(ZBuf *buf, const SourceInput *input, const Program *program) {
|
||||
ZProgramGraph graph;
|
||||
if (!z_program_graph_from_program(input, program, &graph)) {
|
||||
graph_format_append_failed_json(buf);
|
||||
return;
|
||||
}
|
||||
graph.canonical_source = input && input->canonical_text_source;
|
||||
if (!z_program_graph_merge_embedded_std_graph_modules(&graph, input, NULL)) {
|
||||
z_program_graph_free(&graph);
|
||||
graph_format_append_failed_json(buf);
|
||||
return;
|
||||
}
|
||||
ZProgramGraphValidation validation = {0};
|
||||
z_program_graph_validate(&graph, &validation);
|
||||
z_program_graph_append_json(buf, &graph, &validation);
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
void z_append_program_graph_dump(ZBuf *buf, const SourceInput *input, const Program *program, bool json) {
|
||||
ZProgramGraph graph;
|
||||
if (!z_program_graph_from_program(input, program, &graph)) {
|
||||
if (json) graph_format_append_failed_json(buf);
|
||||
else graph_format_append_failed_dump(buf);
|
||||
return;
|
||||
}
|
||||
graph.canonical_source = input && input->canonical_text_source;
|
||||
if (!z_program_graph_merge_embedded_std_graph_modules(&graph, input, NULL)) {
|
||||
z_program_graph_free(&graph);
|
||||
if (json) graph_format_append_failed_json(buf);
|
||||
else graph_format_append_failed_dump(buf);
|
||||
return;
|
||||
}
|
||||
ZProgramGraphValidation validation = {0};
|
||||
z_program_graph_validate(&graph, &validation);
|
||||
if (json) z_program_graph_append_json(buf, &graph, &validation);
|
||||
else z_program_graph_append_dump(buf, &graph, &validation);
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user