Lua bytecode builder, cdrom fixes
This commit is contained in:
34
tools/luac_psx/Makefile
Normal file
34
tools/luac_psx/Makefile
Normal file
@@ -0,0 +1,34 @@
|
||||
# luac_psx - PS1 Lua bytecode compiler
|
||||
#
|
||||
# Minimal PS1 executable that compiles Lua source to bytecode.
|
||||
# Links only psyqo (allocator, xprintf) and psxlua (full parser).
|
||||
# No psxsplash code, no renderer, no GPU.
|
||||
|
||||
TARGET = luac_psx
|
||||
TYPE = ps-exe
|
||||
|
||||
SRCS = main.c
|
||||
|
||||
# Relative path to nugget root
|
||||
NUGGETDIR = ../../third_party/nugget
|
||||
|
||||
# Lua library (full parser variant - NOT noparser)
|
||||
LUADIR = $(NUGGETDIR)/third_party/psxlua/src
|
||||
LIBRARIES += $(LUADIR)/liblua.a
|
||||
|
||||
# Include paths
|
||||
CPPFLAGS += -I$(LUADIR)
|
||||
CPPFLAGS += -I$(NUGGETDIR)
|
||||
CPPFLAGS += -DLUA_TARGET_PSX
|
||||
|
||||
# psyqo build system (provides libpsyqo.a, linker scripts, toolchain config)
|
||||
include $(NUGGETDIR)/psyqo/psyqo.mk
|
||||
|
||||
# Build liblua.a if not already built
|
||||
$(LUADIR)/liblua.a:
|
||||
$(MAKE) -C $(NUGGETDIR)/third_party/psxlua psx
|
||||
|
||||
clean::
|
||||
$(MAKE) -C $(NUGGETDIR)/third_party/psxlua clean
|
||||
|
||||
.PHONY: $(LUADIR)/liblua.a
|
||||
324
tools/luac_psx/main.c
Normal file
324
tools/luac_psx/main.c
Normal file
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* luac_psx - PS1 Lua bytecode compiler
|
||||
*
|
||||
* A minimal PS1 executable that reads Lua source files via PCdrv,
|
||||
* compiles them using psxlua's compiler, and writes bytecode back
|
||||
* via PCdrv. Designed to run inside PCSX-Redux headless mode as
|
||||
* a build tool.
|
||||
*
|
||||
* Links only psyqo (allocator) and psxlua (full parser). No game
|
||||
* code, no renderer, no GPU.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "pcdrv.h"
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
/* Internal headers for luaU_dump (supports strip parameter) */
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
#include "lundump.h"
|
||||
|
||||
/* psyqo's xprintf provides sprintf/printf via PS1 syscalls */
|
||||
#include <psyqo/xprintf.h>
|
||||
|
||||
/* psyqo allocator - provides psyqo_realloc and psyqo_free */
|
||||
#include <psyqo/alloc.h>
|
||||
|
||||
/*
|
||||
* Lua runtime support functions.
|
||||
*
|
||||
* psxlua declares these as extern in llibc.h when LUA_TARGET_PSX
|
||||
* is defined. Normally they're provided via linker --defsym redirects
|
||||
* to psyqo functions, but we define them directly here.
|
||||
*/
|
||||
int luaI_sprintf(char *str, const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
int ret = vsprintf(str, format, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void luaI_free(void *ptr) {
|
||||
psyqo_free(ptr);
|
||||
}
|
||||
|
||||
void *luaI_realloc(void *ptr, size_t size) {
|
||||
return psyqo_realloc(ptr, size);
|
||||
}
|
||||
|
||||
/* Maximum source file size: 256KB should be plenty */
|
||||
#define MAX_SOURCE_SIZE (256 * 1024)
|
||||
|
||||
/* Maximum bytecode output size */
|
||||
#define MAX_OUTPUT_SIZE (256 * 1024)
|
||||
|
||||
/* Maximum manifest line length */
|
||||
#define MAX_LINE_LEN 256
|
||||
|
||||
/* Bytecode writer state */
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} WriterState;
|
||||
|
||||
/* lua_dump writer callback - accumulates bytecode into a buffer */
|
||||
static int bytecode_writer(lua_State *L, const void *p, size_t sz, void *ud) {
|
||||
WriterState *ws = (WriterState *)ud;
|
||||
if (ws->size + sz > ws->capacity) {
|
||||
printf("ERROR: bytecode output exceeds buffer capacity\n");
|
||||
return 1;
|
||||
}
|
||||
/* memcpy via byte loop - no libc on PS1 */
|
||||
const uint8_t *src = (const uint8_t *)p;
|
||||
uint8_t *dst = ws->buf + ws->size;
|
||||
for (size_t i = 0; i < sz; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
ws->size += sz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read an entire file via PCdrv into a buffer. Returns bytes read, or -1 on error. */
|
||||
static int read_file(const char *path, char *buf, int max_size) {
|
||||
int fd = PCopen(path, 0, 0); /* O_RDONLY = 0 */
|
||||
if (fd < 0) {
|
||||
printf("ERROR: cannot open '%s'\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
while (total < max_size) {
|
||||
int chunk = max_size - total;
|
||||
if (chunk > 2048) chunk = 2048; /* read in 2KB chunks */
|
||||
int n = PCread(fd, buf + total, chunk);
|
||||
if (n <= 0) break;
|
||||
total += n;
|
||||
}
|
||||
|
||||
PCclose(fd);
|
||||
return total;
|
||||
}
|
||||
|
||||
/* Write a buffer to a file via PCdrv. Returns 0 on success, -1 on error. */
|
||||
static int write_file(const char *path, const void *buf, int size) {
|
||||
int fd = PCcreat(path, 0);
|
||||
if (fd < 0) {
|
||||
printf("ERROR: cannot create '%s'\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint8_t *p = (const uint8_t *)buf;
|
||||
int remaining = size;
|
||||
while (remaining > 0) {
|
||||
int chunk = remaining;
|
||||
if (chunk > 2048) chunk = 2048; /* write in 2KB chunks */
|
||||
int n = PCwrite(fd, p, chunk);
|
||||
if (n < 0) {
|
||||
printf("ERROR: write failed for '%s'\n", path);
|
||||
PCclose(fd);
|
||||
return -1;
|
||||
}
|
||||
p += n;
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
PCclose(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Simple strlen - no libc on PS1 */
|
||||
static int str_len(const char *s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Error log buffer - accumulates error messages for the sentinel file */
|
||||
static char error_log[4096];
|
||||
static int error_log_len = 0;
|
||||
|
||||
static void error_log_append(const char *msg) {
|
||||
int len = str_len(msg);
|
||||
if (error_log_len + len + 1 < (int)sizeof(error_log)) {
|
||||
const char *src = msg;
|
||||
char *dst = error_log + error_log_len;
|
||||
while (*src) *dst++ = *src++;
|
||||
*dst++ = '\n';
|
||||
*dst = '\0';
|
||||
error_log_len += len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the sentinel file to signal completion */
|
||||
static void write_sentinel(const char *status) {
|
||||
/* For errors, write the error log as sentinel content */
|
||||
if (str_len(status) == 5 && status[0] == 'E') {
|
||||
/* "ERROR" - write error details */
|
||||
if (error_log_len > 0)
|
||||
write_file("__done__", error_log, error_log_len);
|
||||
else
|
||||
write_file("__done__", status, str_len(status));
|
||||
} else {
|
||||
write_file("__done__", status, str_len(status));
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse the next line from the manifest buffer. Returns line length, or -1 at end. */
|
||||
static int next_line(const char *buf, int buf_len, int *pos, char *line, int max_line) {
|
||||
if (*pos >= buf_len) return -1;
|
||||
|
||||
int i = 0;
|
||||
while (*pos < buf_len && i < max_line - 1) {
|
||||
char c = buf[*pos];
|
||||
(*pos)++;
|
||||
if (c == '\n') break;
|
||||
if (c == '\r') continue; /* skip CR */
|
||||
line[i++] = c;
|
||||
}
|
||||
line[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Compile a single Lua source file to bytecode */
|
||||
static int compile_file(const char *input_path, const char *output_path,
|
||||
char *source_buf, uint8_t *output_buf) {
|
||||
printf(" %s -> %s\n", input_path, output_path);
|
||||
|
||||
/* Read source */
|
||||
int source_len = read_file(input_path, source_buf, MAX_SOURCE_SIZE);
|
||||
if (source_len < 0) {
|
||||
error_log_append("ERROR: cannot open source file");
|
||||
error_log_append(input_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Create a fresh Lua state for each file to avoid accumulating memory */
|
||||
lua_State *L = luaL_newstate();
|
||||
if (!L) {
|
||||
printf("ERROR: cannot create Lua state (out of memory)\n");
|
||||
error_log_append("ERROR: cannot create Lua state (out of memory)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Compile source to bytecode */
|
||||
int status = luaL_loadbuffer(L, source_buf, source_len, input_path);
|
||||
if (status != LUA_OK) {
|
||||
const char *err = lua_tostring(L, -1);
|
||||
if (err) {
|
||||
printf("ERROR: %s\n", err);
|
||||
error_log_append(err);
|
||||
} else {
|
||||
printf("ERROR: compilation failed for '%s'\n", input_path);
|
||||
error_log_append("ERROR: compilation failed");
|
||||
error_log_append(input_path);
|
||||
}
|
||||
lua_close(L);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Dump bytecode (strip debug info to save space) */
|
||||
WriterState ws;
|
||||
ws.buf = output_buf;
|
||||
ws.size = 0;
|
||||
ws.capacity = MAX_OUTPUT_SIZE;
|
||||
|
||||
/* Use luaU_dump directly with strip=1 to remove debug info (line numbers,
|
||||
* local variable names, source filename). Saves significant space. */
|
||||
status = luaU_dump(L, getproto(L->top - 1), bytecode_writer, &ws, 1);
|
||||
lua_close(L);
|
||||
|
||||
if (status != 0) {
|
||||
printf("ERROR: bytecode dump failed for '%s'\n", input_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Write bytecode to output file */
|
||||
if (write_file(output_path, ws.buf, ws.size) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" OK (%d bytes source -> %d bytes bytecode)\n", source_len, (int)ws.size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* Initialize PCdrv */
|
||||
PCinit();
|
||||
|
||||
printf("luac_psx: PS1 Lua bytecode compiler\n");
|
||||
|
||||
/* Allocate work buffers */
|
||||
char *source_buf = (char *)psyqo_realloc(NULL, MAX_SOURCE_SIZE);
|
||||
uint8_t *output_buf = (uint8_t *)psyqo_realloc(NULL, MAX_OUTPUT_SIZE);
|
||||
char *manifest_buf = (char *)psyqo_realloc(NULL, MAX_SOURCE_SIZE);
|
||||
|
||||
if (!source_buf || !output_buf || !manifest_buf) {
|
||||
printf("ERROR: cannot allocate work buffers\n");
|
||||
write_sentinel("ERROR");
|
||||
while (1) {}
|
||||
}
|
||||
|
||||
/* Read manifest file */
|
||||
int manifest_len = read_file("manifest.txt", manifest_buf, MAX_SOURCE_SIZE);
|
||||
if (manifest_len < 0) {
|
||||
printf("ERROR: cannot read manifest.txt\n");
|
||||
write_sentinel("ERROR");
|
||||
while (1) {}
|
||||
}
|
||||
|
||||
/* Process manifest: pairs of lines (input, output) */
|
||||
int pos = 0;
|
||||
int file_count = 0;
|
||||
int error_count = 0;
|
||||
char input_path[MAX_LINE_LEN];
|
||||
char output_path[MAX_LINE_LEN];
|
||||
|
||||
while (1) {
|
||||
/* Read input path */
|
||||
int len = next_line(manifest_buf, manifest_len, &pos, input_path, MAX_LINE_LEN);
|
||||
if (len < 0) break;
|
||||
if (len == 0) continue; /* skip blank lines */
|
||||
|
||||
/* Read output path */
|
||||
len = next_line(manifest_buf, manifest_len, &pos, output_path, MAX_LINE_LEN);
|
||||
if (len <= 0) {
|
||||
printf("ERROR: manifest has unpaired entry for '%s'\n", input_path);
|
||||
error_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Compile */
|
||||
if (compile_file(input_path, output_path, source_buf, output_buf) != 0) {
|
||||
error_count++;
|
||||
break; /* stop on first error */
|
||||
}
|
||||
file_count++;
|
||||
}
|
||||
|
||||
/* Clean up */
|
||||
psyqo_free(source_buf);
|
||||
psyqo_free(output_buf);
|
||||
psyqo_free(manifest_buf);
|
||||
|
||||
/* Write sentinel */
|
||||
if (error_count > 0) {
|
||||
printf("FAILED: %d file(s) compiled, %d error(s)\n", file_count, error_count);
|
||||
write_sentinel("ERROR");
|
||||
} else {
|
||||
printf("SUCCESS: %d file(s) compiled\n", file_count);
|
||||
write_sentinel("OK");
|
||||
}
|
||||
|
||||
/* Halt - PCSX-Redux will kill us */
|
||||
while (1) {}
|
||||
return 0;
|
||||
}
|
||||
99
tools/luac_psx/pcdrv.h
Normal file
99
tools/luac_psx/pcdrv.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 PCSX-Redux authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
static inline int PCinit() {
|
||||
register int r asm("v0");
|
||||
__asm__ volatile("break 0, 0x101\n" : "=r"(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline int PCcreat(const char *name, int perms) {
|
||||
register const char *a0 asm("a0") = name;
|
||||
register const char *a1 asm("a1") = name;
|
||||
register int a2 asm("a2") = 0;
|
||||
register int v0 asm("v0");
|
||||
register int v1 asm("v1");
|
||||
__asm__ volatile("break 0, 0x102\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2));
|
||||
if (v0 == 0) return v1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int PCopen(const char *name, int flags, int perms) {
|
||||
register int a2 asm("a2") = flags;
|
||||
register const char *a0 asm("a0") = name;
|
||||
register const char *a1 asm("a1") = name;
|
||||
register int v0 asm("v0");
|
||||
register int v1 asm("v1");
|
||||
__asm__ volatile("break 0, 0x103\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2));
|
||||
if (v0 == 0) return v1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int PCclose(int fd) {
|
||||
register int a0 asm("a0") = fd;
|
||||
register int a1 asm("a1") = fd;
|
||||
register int v0 asm("v0");
|
||||
__asm__ volatile("break 0, 0x104\n" : "=r"(v0) : "r"(a0), "r"(a1) : "v1");
|
||||
return v0;
|
||||
}
|
||||
|
||||
static inline int PCread(int fd, void *buf, int len) {
|
||||
register int a0 asm("a0") = 0;
|
||||
register int a1 asm("a1") = fd;
|
||||
register int a2 asm("a2") = len;
|
||||
register void *a3 asm("a3") = buf;
|
||||
register int v0 asm("v0");
|
||||
register int v1 asm("v1");
|
||||
__asm__ volatile("break 0, 0x105\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2), "r"(a3) : "memory");
|
||||
if (v0 == 0) return v1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int PCwrite(int fd, const void *buf, int len) {
|
||||
register int a0 asm("a0") = 0;
|
||||
register int a1 asm("a1") = fd;
|
||||
register int a2 asm("a2") = len;
|
||||
register const void *a3 asm("a3") = buf;
|
||||
register int v0 asm("v0");
|
||||
register int v1 asm("v1");
|
||||
__asm__ volatile("break 0, 0x106\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2), "r"(a3));
|
||||
if (v0 == 0) return v1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int PClseek(int fd, int offset, int wheel) {
|
||||
register int a3 asm("a3") = wheel;
|
||||
register int a2 asm("a2") = offset;
|
||||
register int a0 asm("a0") = fd;
|
||||
register int a1 asm("a1") = fd;
|
||||
register int v0 asm("v0");
|
||||
register int v1 asm("v1");
|
||||
__asm__ volatile("break 0, 0x107\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2), "r"(a3));
|
||||
if (v0 == 0) return v1;
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user