diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8d3678b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,55 @@ +name: Build + +on: + push: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + container: ghcr.io/grumpycoders/pcsx-redux-build:latest + + steps: + - uses: actions/checkout@v4 + + - name: Nugget + # Checked out rather than vendored: nugget's psyqo-lua consumes psxlua, + # so a submodule here would close a dependency cycle. + uses: actions/checkout@v4 + with: + repository: pcsx-redux/nugget + path: nugget + + - name: Library + run: make -C src psx -j2 + + - name: Library without the parser + run: | + make -C src clean + make -C src psx-noparser -j2 + + - name: Compiler + run: | + make -C src clean + make -C src psx-luac NUGGET=$GITHUB_WORKSPACE/nugget -j2 + + - name: pcsx-redux + # --appimage-extract rather than mounting it: the runner has no FUSE. + run: | + curl -sL -o pcsx-redux.zip https://distrib.app/pub/org/pcsx-redux/project/dev-linux-x64/latest + unzip -q pcsx-redux.zip + ./PCSX-Redux-*.AppImage --appimage-extract > /dev/null + + - name: Compile a script on the console + # pcsx-redux brings SDL video up even under -testmode, which is why + # pcsx-redux's own CI runs its tests under xvfb-run. + run: xvfb-run tests/run-luac.sh "$GITHUB_WORKSPACE/squashfs-root/AppRun" + + - uses: actions/upload-artifact@v4 + with: + name: luac.ps-exe + path: src/luac.ps-exe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf493bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.o +*.a +luac +lua +*.ps-exe +*.elf +*.map +*.dep diff --git a/README b/README index 2d04b6a..ecf5d4f 100644 --- a/README +++ b/README @@ -8,7 +8,7 @@ further information about Lua, see doc/readme.html. This version of Lua has been modified to work on the Sony PlayStation 1. -There are now only three platforms available for compilation: +There are now only four platforms available for compilation: - `psx`: This platform is used to compile Lua for the Sony PlayStation 1. This will only generate a library file named "liblua.a". The binary footprint of this library will be roughly 110kB. @@ -16,10 +16,25 @@ There are now only three platforms available for compilation: 1 without the parser. Only precompiled bytecode can be loaded by this version. This will only generate a library file named "liblua-noparser.a". The binary footprint of this library will be roughly 85kB. +- `psx-luac`: This platform builds the Lua compiler itself as a ps-exe, named + "luac.ps-exe". Running the compiler on the target is what makes its bytecode + a sure thing: the number representation, the pointer size and the endianness + are the target's by construction rather than by a matching host build. - `host32`: This platform is used to compile Lua for a 32-bit host system. This - will generate the Lua interpreter (lua) and the lua compiler (luac). The - compiler should be able to generate bytecode that can be run on the Sony - PlayStation 1. + will generate the Lua interpreter (lua). It no longer generates the compiler; + use `psx-luac` for that, since a 32-bit host build is not available in most + environments any more. + +The `psx-luac` platform is the only part of this repository that needs nugget, +and it is not vendored: nugget's psyqo-lua consumes psxlua, so a submodule here +would close a dependency cycle. Point NUGGET at a checkout instead. + + make -C src psx-luac NUGGET=/path/to/nugget + +luac.ps-exe reads its arguments from unmapped memory at 0x40000000 and does its +file I/O over PCDRV, so it runs under an emulator that serves both. src/args.lua +is the pcsx-redux side: call createArgsBuffer() with the arguments you would +have passed on a command line, and load it alongside the executable. The following key differences exist between the original Lua 5.2.4 and this version of Lua: diff --git a/src/Makefile b/src/Makefile index 50919ae..da7499f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -53,7 +53,7 @@ MYOBJS= # == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE ======= -PLATS= host32 psx psx-noparser +PLATS= host32 psx psx-noparser psx-luac ifeq ($(NOPARSER),true) LUA_A= liblua-noparser.a @@ -78,8 +78,8 @@ LUA_O= lua.o LUAC_T= luac LUAC_O= luac.o -ALL_O= $(BASE_O) $(LUA_O) $(LUAC_O) -ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) +ALL_O= $(BASE_O) $(LUA_O) +ALL_T= $(LUA_A) $(LUA_T) ALL_A= $(LUA_A) # Targets start here. @@ -98,11 +98,9 @@ $(LUA_A): $(BASE_O) $(LUA_T): $(LUA_O) $(LUA_A) $(CC) -o $@ $(LDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) -$(LUAC_T): $(LUAC_O) $(LUA_A) - $(CC) -o $@ $(LDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) - clean: $(RM) $(ALL_T) $(ALL_O) $(ALL_A) liblua-noparser.a lnoparser.o + $(RM) $(LUAC_T) $(LUAC_O) psx-heap.o psx-glue.o luac.elf luac.map luac.ps-exe *.dep depend: @$(CC) $(CFLAGS) -MM l*.c @@ -132,6 +130,9 @@ psx: psx-noparser: $(MAKE) a TARGET=psx NOPARSER=true +psx-luac: psx + $(MAKE) -f luac-psx.mk + # list targets that do not create files (but not all makes understand .PHONY) .PHONY: all $(PLATS) default o a clean depend echo none diff --git a/src/args.lua b/src/args.lua new file mode 100644 index 0000000..fca5cd2 --- /dev/null +++ b/src/args.lua @@ -0,0 +1,33 @@ +local ffi = require 'ffi' +local uint8_t = ffi.typeof 'uint8_t[?]' +local argsBuffer +local processName = 'luac.ps-exe' + +function createArgsBuffer(...) + local args = { ... } + local spaceNeeded = #processName + 2 + for i, v in ipairs(args) do + local a = tostring(v) + spaceNeeded = spaceNeeded + #a + 1 + end + argsBuffer = ffi.new(uint8_t, spaceNeeded) + local ptr = ffi.cast('uint8_t *', argsBuffer) + ffi.copy(ptr, processName) + ptr = ptr + #processName + 1 + for i, v in ipairs(args) do + local a = tostring(v) + ffi.copy(ptr, a) + ptr = ptr + #a + 1 + end + ptr[0] = 0 +end + +function UnknownMemoryRead(address, size) + if size ~= 1 then return 0xffffffff end + local offset = address - 0x40000000 + if offset < 0 then return 0xff end + if offset >= ffi.sizeof(argsBuffer) then return 0xff end + return argsBuffer[offset] +end + +createArgsBuffer() diff --git a/src/luac-psx.mk b/src/luac-psx.mk new file mode 100644 index 0000000..05a117c --- /dev/null +++ b/src/luac-psx.mk @@ -0,0 +1,35 @@ +# Builds luac as a ps-exe, so the compiler runs on the console (or in an +# emulator) instead of on the host. Input and output go through PCDRV, and +# argv is read from unmapped memory at 0x40000000 - see args.lua. +# +# psxlua does not depend on nugget: nugget's psyqo-lua consumes psxlua, so +# vendoring it here would close a cycle. Point NUGGET at a checkout instead. +# +# make psx-luac NUGGET=/path/to/nugget +# +# liblua.a must be built for the same target first; the psx-luac rule in +# Makefile does that for you. + +ifeq ($(strip $(NUGGET)),) +$(error NUGGET is not set - point it at a nugget checkout, e.g. make psx-luac NUGGET=../../nugget) +endif + +TARGET = luac +TYPE = ps-exe + +SRCS = \ +luac.c \ +psx-heap.c \ +psx-glue.s \ +$(NUGGET)/common/syscalls/printf.s \ +$(NUGGET)/common/crt0/memory-s.s \ +$(NUGGET)/common/crt0/memory-c.c \ + +LIBRARIES = liblua.a + +CPPFLAGS += -I. -DLUA_COMPAT_ALL + +include $(NUGGET)/common.mk + +liblua.a: + $(MAKE) -f Makefile a TARGET=psx diff --git a/src/luac.c b/src/luac.c index 7fc4c08..14c73a0 100644 --- a/src/luac.c +++ b/src/luac.c @@ -4,53 +4,57 @@ ** See Copyright Notice in lua.h */ -#include -#include -#include -#include - #define luac_c #define LUA_CORE +#define LUA_TARGET_PSX -#include "lua.h" -#include "lauxlib.h" +#include "common/hardware/pcsxhw.h" +#include "common/kernel/pcdrv.h" +#include "common/syscalls/syscalls.h" + +#define EXIT_FAILURE -1 +#define EXIT_SUCCESS 0 +#include "lauxlib.h" +#include "lctype.h" +#include "llibc.h" #include "lobject.h" #include "lstate.h" +#include "lua.h" #include "lundump.h" static void PrintFunction(const Proto* f, int full); -#define luaU_print PrintFunction - -#define PROGNAME "luac" /* default program name */ -#define OUTPUT PROGNAME ".out" /* default output file */ - -static int listing=0; /* list bytecodes? */ -static int dumping=1; /* dump bytecodes? */ -static int stripping=0; /* strip debug information? */ -static char Output[]={ OUTPUT }; /* default output file name */ -static const char* output=Output; /* actual output file name */ -static const char* progname=PROGNAME; /* actual program name */ - -static void fatal(const char* message) -{ - fprintf(stderr,"%s: %s\n",progname,message); - exit(EXIT_FAILURE); +#define luaU_print PrintFunction + +#define PROGNAME "luac" /* default program name */ +#define OUTPUT PROGNAME ".out" /* default output file */ + +#define ARGS_BASE 0x40000000 /* unmapped; args.lua serves this through UnknownMemoryRead */ +#define MAXARGS 64 /* size of the argv array main() builds from ARGS_BASE */ + +static int listing = 0; /* list bytecodes? */ +static int dumping = 1; /* dump bytecodes? */ +static int stripping = 0; /* strip debug information? */ +static char Output[] = {OUTPUT}; /* default output file name */ +static const char* output = Output; /* actual output file name */ +static const char* progname = PROGNAME; /* actual program name */ + +static void fatal(const char* message) { + ramsyscall_printf("%s: %s\n", progname, message); + pcsx_exit(EXIT_FAILURE); } -static void cannot(const char* what) -{ - fprintf(stderr,"%s: cannot %s %s: %s\n",progname,what,output,strerror(errno)); - exit(EXIT_FAILURE); +static void cannot(const char* what) { + ramsyscall_printf("%s: cannot %s %s\n", progname, what, output); + pcsx_exit(EXIT_FAILURE); } -static void usage(const char* message) -{ - if (*message=='-') - fprintf(stderr,"%s: unrecognized option " LUA_QS "\n",progname,message); - else - fprintf(stderr,"%s: %s\n",progname,message); - fprintf(stderr, +static void usage(const char* message) { + if (*message == '-') + ramsyscall_printf("%s: unrecognized option " LUA_QS "\n", progname, message); + else + ramsyscall_printf("%s: %s\n", progname, message); + ramsyscall_printf( "usage: %s [options] [filenames]\n" "Available options are:\n" " -l list (use -l -l for full listing)\n" @@ -59,156 +63,139 @@ static void usage(const char* message) " -s strip debug information\n" " -v show version information\n" " -- stop handling options\n" - " - stop handling options and process stdin\n" ,progname,Output); - exit(EXIT_FAILURE); + pcsx_exit(EXIT_FAILURE); } -#define IS(s) (strcmp(argv[i],s)==0) - -static int doargs(int argc, char* argv[]) -{ - int i; - int version=0; - if (argv[0]!=NULL && *argv[0]!=0) progname=argv[0]; - for (i=1; itop+(i)) - -static const Proto* combine(lua_State* L, int n) -{ - if (n==1) - return toproto(L,-1); - else - { - Proto* f; - int i=n; - if (lua_load(L,reader,&i,"=(" PROGNAME ")",NULL)!=LUA_OK) fatal(lua_tostring(L,-1)); - f=toproto(L,-1); - for (i=0; ip[i]=toproto(L,i-n-1); - if (f->p[i]->sizeupvalues>0) f->p[i]->upvalues[0].instack=0; - } - f->sizelineinfo=0; - return f; - } +#define toproto(L, i) getproto(L->top + (i)) + +static const Proto* combine(lua_State* L, int n) { + if (n == 1) + return toproto(L, -1); + else { + Proto* f; + int i = n; + if (lua_load(L, reader, &i, "=(" PROGNAME ")", NULL) != LUA_OK) fatal(lua_tostring(L, -1)); + f = toproto(L, -1); + for (i = 0; i < n; i++) { + f->p[i] = toproto(L, i - n - 1); + if (f->p[i]->sizeupvalues > 0) f->p[i]->upvalues[0].instack = 0; + } + f->sizelineinfo = 0; + return f; + } } -static int writer(lua_State* L, const void* p, size_t size, void* u) -{ - UNUSED(L); - return (fwrite(p,size,1,(FILE*)u)!=1) && (size!=0); +static int writer(lua_State* L, const void* p, size_t size, void* u) { + UNUSED(L); + int r = PCwrite(*(int*)u, p, size); + return (r < 0 || (size_t)r != size) && (size != 0); } -LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, - const char *mode); +LUALIB_API int(luaL_loadfilex)(lua_State* L, const char* filename, const char* mode); -#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) +#define luaL_loadfile(L, f) luaL_loadfilex(L, f, NULL) typedef struct LoadF { - int n; /* number of pre-read characters */ - FILE *f; /* file being read */ - char buff[LUAL_BUFFERSIZE]; /* area for reading file */ + int n; /* number of pre-read characters */ + int f; /* file being read */ + char buff[LUAL_BUFFERSIZE]; /* area for reading file */ } LoadF; +static const char* getF(lua_State* L, void* ud, size_t* size) { + LoadF* lf = (LoadF*)ud; + (void)L; /* not used */ + if (lf->n > 0) { /* are there pre-read characters to be read? */ + *size = lf->n; /* return them (chars already in buffer) */ + lf->n = 0; /* no more pre-read characters */ + } else { /* read a block from file */ + int read = PCread(lf->f, lf->buff, sizeof(lf->buff)); /* read block */ + if (read <= 0) return NULL; + *size = read; + } + return lf->buff; +} -static const char *getF (lua_State *L, void *ud, size_t *size) { - LoadF *lf = (LoadF *)ud; - (void)L; /* not used */ - if (lf->n > 0) { /* are there pre-read characters to be read? */ - *size = lf->n; /* return them (chars already in buffer) */ - lf->n = 0; /* no more pre-read characters */ - } - else { /* read a block from file */ - /* 'fread' can return > 0 *and* set the EOF flag. If next call to - 'getF' called 'fread', it might still wait for user input. - The next check avoids this problem. */ - if (feof(lf->f)) return NULL; - *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); /* read block */ - } - return lf->buff; +static int errfile(lua_State* L, const char* what, int fnameindex) { + const char* filename = lua_tostring(L, fnameindex) + 1; + lua_pushfstring(L, "cannot %s %s", what, filename); + lua_remove(L, fnameindex); + return LUA_ERRFILE; } +#define LUAC_EOF (-1) -static int errfile (lua_State *L, const char *what, int fnameindex) { - const char *serr = strerror(errno); - const char *filename = lua_tostring(L, fnameindex) + 1; - lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); - lua_remove(L, fnameindex); - return LUA_ERRFILE; +static int luaA_getc(int f) { + int c = 0; + int r = PCread(f, &c, 1); + return r == 1 ? c : LUAC_EOF; } - -static int skipBOM (LoadF *lf) { - const char *p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ - int c; - lf->n = 0; - do { - c = getc(lf->f); - if (c == EOF || c != *(const unsigned char *)p++) return c; - lf->buff[lf->n++] = c; /* to be read by the parser */ - } while (*p != '\0'); - lf->n = 0; /* prefix matched; discard it */ - return getc(lf->f); /* return next character */ +static int skipBOM(LoadF* lf) { + const char* p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ + int c; + lf->n = 0; + do { + c = luaA_getc(lf->f); + if (c == LUAC_EOF || c != *(const unsigned char*)p++) return c; + lf->buff[lf->n++] = c; /* to be read by the parser */ + } while (*p != '\0'); + lf->n = 0; /* prefix matched; discard it */ + return luaA_getc(lf->f); /* return next character */ } /* @@ -218,95 +205,86 @@ static int skipBOM (LoadF *lf) { ** first "valid" character of the file (after the optional BOM and ** a first-line comment). */ -static int skipcomment (LoadF *lf, int *cp) { - int c = *cp = skipBOM(lf); - if (c == '#') { /* first line is a comment (Unix exec. file)? */ - do { /* skip first line */ - c = getc(lf->f); - } while (c != EOF && c != '\n') ; - *cp = getc(lf->f); /* skip end-of-line, if present */ - return 1; /* there was a comment */ - } - else return 0; /* no comment */ +static int skipcomment(LoadF* lf, int* cp) { + int c = *cp = skipBOM(lf); + if (c == '#') { /* first line is a comment (Unix exec. file)? */ + do { /* skip first line */ + c = luaA_getc(lf->f); + } while (c != LUAC_EOF && c != '\n'); + *cp = luaA_getc(lf->f); /* skip end-of-line, if present */ + return 1; /* there was a comment */ + } else + return 0; /* no comment */ } - -LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, - const char *mode) { - LoadF lf; - int status, readstatus; - int c; - int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ - if (filename == NULL) { - lua_pushliteral(L, "=stdin"); - lf.f = stdin; - } - else { - lua_pushfstring(L, "@%s", filename); - lf.f = fopen(filename, "r"); - if (lf.f == NULL) return errfile(L, "open", fnameindex); - } - if (skipcomment(&lf, &c)) /* read initial portion */ - lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */ - if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ - lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ - if (lf.f == NULL) return errfile(L, "reopen", fnameindex); - skipcomment(&lf, &c); /* re-read initial portion */ - } - if (c != EOF) - lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ - status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); - readstatus = ferror(lf.f); - if (filename) fclose(lf.f); /* close file (even in case of errors) */ - if (readstatus) { - lua_settop(L, fnameindex); /* ignore results from `lua_load' */ - return errfile(L, "read", fnameindex); - } - lua_remove(L, fnameindex); - return status; +LUALIB_API int luaL_loadfilex(lua_State* L, const char* filename, const char* mode) { + LoadF lf; + int status; + int c; + int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ + if (filename == NULL) { + return LUA_ERRFILE; + } else { + const char * f = lua_pushfstring(L, "@%s", filename); + lf.f = PCopen(f + 1, 0, 0); + if (lf.f < 0) return errfile(L, "open", fnameindex); + } + if (skipcomment(&lf, &c)) /* read initial portion */ + lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */ + if (c != LUAC_EOF) lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ + status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); + PCclose(lf.f); + lua_remove(L, fnameindex); + return status; } -static int pmain(lua_State* L) -{ - int argc=(int)lua_tointeger(L,1); - char** argv=(char**)lua_touserdata(L,2); - const Proto* f; - int i; - if (!lua_checkstack(L,argc)) fatal("too many input files"); - for (i=0; i1); - if (dumping) - { - FILE* D= (output==NULL) ? stdout : fopen(output,"wb"); - if (D==NULL) cannot("open"); - lua_lock(L); - luaU_dump(L,f,writer,D,stripping); - lua_unlock(L); - if (ferror(D)) cannot("write"); - if (fclose(D)) cannot("close"); - } - return 0; +static int pmain(lua_State* L) { + int argc = (int)lua_tointeger(L, 1); + char** argv = (char**)lua_touserdata(L, 2); + const Proto* f; + int i; + if (!lua_checkstack(L, argc)) fatal("too many input files"); + for (i = 0; i < argc; i++) { + if (luaL_loadfile(L, argv[i]) != LUA_OK) fatal(lua_tostring(L, -1)); + } + f = combine(L, argc); + if (listing) luaU_print(f, listing > 1); + if (dumping) { + const char * fname = lua_pushfstring(L, "%s", output); + int D = PCcreat(fname, 0); + lua_pop(L, 1); + if (D < 0) cannot("open"); + lua_lock(L); + luaU_dump(L, f, writer, &D, stripping); + lua_unlock(L); + if (PCclose(D) != 0) cannot("close"); + } + return 0; } -int main(int argc, char* argv[]) -{ - lua_State* L; - int i=doargs(argc,argv); - argc-=i; argv+=i; - if (argc<=0) usage("no input files given"); - L=luaL_newstate(); - if (L==NULL) fatal("cannot create state: not enough memory"); - lua_pushcfunction(L,&pmain); - lua_pushinteger(L,argc); - lua_pushlightuserdata(L,argv); - if (lua_pcall(L,2,0,0)!=LUA_OK) fatal(lua_tostring(L,-1)); - lua_close(L); - return EXIT_SUCCESS; +int main() { + const char* argv[MAXARGS] = {0}; + const char* argsPtr = (const char*)ARGS_BASE; + int argc = 0; + lua_State* L; + while (argc < MAXARGS) { + int len = luaA_strlen(argsPtr); + if (len == 0) break; + argv[argc++] = argsPtr; + argsPtr += len + 1; + } + if (argc == MAXARGS && luaA_strlen(argsPtr) != 0) fatal("too many arguments"); + int i = doargs(argc, argv); + argc -= i; + if (argc <= 0) usage("no input files given"); + L = luaL_newstate(); + if (L == NULL) fatal("cannot create state: not enough memory"); + lua_pushcfunction(L, &pmain); + lua_pushinteger(L, argc); + lua_pushlightuserdata(L, argv + i); + if (lua_pcall(L, 2, 0, 0) != LUA_OK) fatal(lua_tostring(L, -1)); + lua_close(L); + pcsx_exit(EXIT_SUCCESS); } /* @@ -315,9 +293,6 @@ int main(int argc, char* argv[]) ** See Copyright Notice in lua.h */ -#include -#include - #define luac_c #define LUA_CORE @@ -325,215 +300,244 @@ int main(int argc, char* argv[]) #include "lobject.h" #include "lopcodes.h" -#define VOID(p) ((const void*)(p)) - -static void PrintString(const TString* ts) -{ - const char* s=getstr(ts); - size_t i,n=ts->tsv.len; - printf("%c",'"'); - for (i=0; itsv.len; + ramsyscall_printf("%c", '"'); + for (i = 0; i < n; i++) { + int c = (int)(unsigned char)s[i]; + switch (c) { + case '"': + ramsyscall_printf("\\\""); + break; + case '\\': + ramsyscall_printf("\\\\"); + break; + case '\a': + ramsyscall_printf("\\a"); + break; + case '\b': + ramsyscall_printf("\\b"); + break; + case '\f': + ramsyscall_printf("\\f"); + break; + case '\n': + ramsyscall_printf("\\n"); + break; + case '\r': + ramsyscall_printf("\\r"); + break; + case '\t': + ramsyscall_printf("\\t"); + break; + case '\v': + ramsyscall_printf("\\v"); + break; + default: + if (lisprint(c)) + ramsyscall_printf("%c", c); + else + ramsyscall_printf("\\%03d", c); + } + } + ramsyscall_printf("%c", '"'); } -static void PrintConstant(const Proto* f, int i) -{ - const TValue* o=&f->k[i]; - switch (ttypenv(o)) - { - case LUA_TNIL: - printf("nil"); - break; - case LUA_TBOOLEAN: - printf(bvalue(o) ? "true" : "false"); - break; - case LUA_TNUMBER: - printf(LUA_NUMBER_FMT,nvalue(o)); - break; - case LUA_TSTRING: - PrintString(rawtsvalue(o)); - break; - default: /* cannot happen */ - printf("? type=%d",ttype(o)); - break; - } +static void PrintConstant(const Proto* f, int i) { + const TValue* o = &f->k[i]; + switch (ttypenv(o)) { + case LUA_TNIL: + ramsyscall_printf("nil"); + break; + case LUA_TBOOLEAN: + ramsyscall_printf(bvalue(o) ? "true" : "false"); + break; + case LUA_TNUMBER: + ramsyscall_printf(LUA_NUMBER_FMT, nvalue(o)); + break; + case LUA_TSTRING: + PrintString(rawtsvalue(o)); + break; + default: /* cannot happen */ + ramsyscall_printf("? type=%d", ttype(o)); + break; + } } #define UPVALNAME(x) ((f->upvalues[x].name) ? getstr(f->upvalues[x].name) : "-") -#define MYK(x) (-1-(x)) - -static void PrintCode(const Proto* f) -{ - const Instruction* code=f->code; - int pc,n=f->sizecode; - for (pc=0; pc0) printf("[%d]\t",line); else printf("[-]\t"); - printf("%-9s\t",luaP_opnames[o]); - switch (getOpMode(o)) - { - case iABC: - printf("%d",a); - if (getBMode(o)!=OpArgN) printf(" %d",ISK(b) ? (MYK(INDEXK(b))) : b); - if (getCMode(o)!=OpArgN) printf(" %d",ISK(c) ? (MYK(INDEXK(c))) : c); - break; - case iABx: - printf("%d",a); - if (getBMode(o)==OpArgK) printf(" %d",MYK(bx)); - if (getBMode(o)==OpArgU) printf(" %d",bx); - break; - case iAsBx: - printf("%d %d",a,sbx); - break; - case iAx: - printf("%d",MYK(ax)); - break; - } - switch (o) - { - case OP_LOADK: - printf("\t; "); PrintConstant(f,bx); - break; - case OP_GETUPVAL: - case OP_SETUPVAL: - printf("\t; %s",UPVALNAME(b)); - break; - case OP_GETTABUP: - printf("\t; %s",UPVALNAME(b)); - if (ISK(c)) { printf(" "); PrintConstant(f,INDEXK(c)); } - break; - case OP_SETTABUP: - printf("\t; %s",UPVALNAME(a)); - if (ISK(b)) { printf(" "); PrintConstant(f,INDEXK(b)); } - if (ISK(c)) { printf(" "); PrintConstant(f,INDEXK(c)); } - break; - case OP_GETTABLE: - case OP_SELF: - if (ISK(c)) { printf("\t; "); PrintConstant(f,INDEXK(c)); } - break; - case OP_SETTABLE: - case OP_ADD: - case OP_SUB: - case OP_MUL: - case OP_DIV: - case OP_POW: - case OP_EQ: - case OP_LT: - case OP_LE: - if (ISK(b) || ISK(c)) - { - printf("\t; "); - if (ISK(b)) PrintConstant(f,INDEXK(b)); else printf("-"); - printf(" "); - if (ISK(c)) PrintConstant(f,INDEXK(c)); else printf("-"); +#define MYK(x) (-1 - (x)) + +static void PrintCode(const Proto* f) { + const Instruction* code = f->code; + int pc, n = f->sizecode; + for (pc = 0; pc < n; pc++) { + Instruction i = code[pc]; + OpCode o = GET_OPCODE(i); + int a = GETARG_A(i); + int b = GETARG_B(i); + int c = GETARG_C(i); + int ax = GETARG_Ax(i); + int bx = GETARG_Bx(i); + int sbx = GETARG_sBx(i); + int line = getfuncline(f, pc); + ramsyscall_printf("\t%d\t", pc + 1); + if (line > 0) + ramsyscall_printf("[%d]\t", line); + else + ramsyscall_printf("[-]\t"); + ramsyscall_printf("%-9s\t", luaP_opnames[o]); + switch (getOpMode(o)) { + case iABC: + ramsyscall_printf("%d", a); + if (getBMode(o) != OpArgN) ramsyscall_printf(" %d", ISK(b) ? (MYK(INDEXK(b))) : b); + if (getCMode(o) != OpArgN) ramsyscall_printf(" %d", ISK(c) ? (MYK(INDEXK(c))) : c); + break; + case iABx: + ramsyscall_printf("%d", a); + if (getBMode(o) == OpArgK) ramsyscall_printf(" %d", MYK(bx)); + if (getBMode(o) == OpArgU) ramsyscall_printf(" %d", bx); + break; + case iAsBx: + ramsyscall_printf("%d %d", a, sbx); + break; + case iAx: + ramsyscall_printf("%d", MYK(ax)); + break; + } + switch (o) { + case OP_LOADK: + ramsyscall_printf("\t; "); + PrintConstant(f, bx); + break; + case OP_GETUPVAL: + case OP_SETUPVAL: + ramsyscall_printf("\t; %s", UPVALNAME(b)); + break; + case OP_GETTABUP: + ramsyscall_printf("\t; %s", UPVALNAME(b)); + if (ISK(c)) { + ramsyscall_printf(" "); + PrintConstant(f, INDEXK(c)); + } + break; + case OP_SETTABUP: + ramsyscall_printf("\t; %s", UPVALNAME(a)); + if (ISK(b)) { + ramsyscall_printf(" "); + PrintConstant(f, INDEXK(b)); + } + if (ISK(c)) { + ramsyscall_printf(" "); + PrintConstant(f, INDEXK(c)); + } + break; + case OP_GETTABLE: + case OP_SELF: + if (ISK(c)) { + ramsyscall_printf("\t; "); + PrintConstant(f, INDEXK(c)); + } + break; + case OP_SETTABLE: + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_POW: + case OP_EQ: + case OP_LT: + case OP_LE: + if (ISK(b) || ISK(c)) { + ramsyscall_printf("\t; "); + if (ISK(b)) + PrintConstant(f, INDEXK(b)); + else + ramsyscall_printf("-"); + ramsyscall_printf(" "); + if (ISK(c)) + PrintConstant(f, INDEXK(c)); + else + ramsyscall_printf("-"); + } + break; + case OP_JMP: + case OP_FORLOOP: + case OP_FORPREP: + case OP_TFORLOOP: + ramsyscall_printf("\t; to %d", sbx + pc + 2); + break; + case OP_CLOSURE: + ramsyscall_printf("\t; %p", VOID(f->p[bx])); + break; + case OP_SETLIST: + if (c == 0) + ramsyscall_printf("\t; %d", (int)code[++pc]); + else + ramsyscall_printf("\t; %d", c); + break; + case OP_EXTRAARG: + ramsyscall_printf("\t; "); + PrintConstant(f, ax); + break; + default: + break; + } + ramsyscall_printf("\n"); } - break; - case OP_JMP: - case OP_FORLOOP: - case OP_FORPREP: - case OP_TFORLOOP: - printf("\t; to %d",sbx+pc+2); - break; - case OP_CLOSURE: - printf("\t; %p",VOID(f->p[bx])); - break; - case OP_SETLIST: - if (c==0) printf("\t; %d",(int)code[++pc]); else printf("\t; %d",c); - break; - case OP_EXTRAARG: - printf("\t; "); PrintConstant(f,ax); - break; - default: - break; - } - printf("\n"); - } } -#define SS(x) ((x==1)?"":"s") -#define S(x) (int)(x),SS(x) - -static void PrintHeader(const Proto* f) -{ - const char* s=f->source ? getstr(f->source) : "=?"; - if (*s=='@' || *s=='=') - s++; - else if (*s==LUA_SIGNATURE[0]) - s="(bstring)"; - else - s="(string)"; - printf("\n%s <%s:%d,%d> (%d instruction%s at %p)\n", - (f->linedefined==0)?"main":"function",s, - f->linedefined,f->lastlinedefined, - S(f->sizecode),VOID(f)); - printf("%d%s param%s, %d slot%s, %d upvalue%s, ", - (int)(f->numparams),f->is_vararg?"+":"",SS(f->numparams), - S(f->maxstacksize),S(f->sizeupvalues)); - printf("%d local%s, %d constant%s, %d function%s\n", - S(f->sizelocvars),S(f->sizek),S(f->sizep)); +#define SS(x) ((x == 1) ? "" : "s") +#define S(x) (int)(x), SS(x) + +static void PrintHeader(const Proto* f) { + const char* s = f->source ? getstr(f->source) : "=?"; + if (*s == '@' || *s == '=') + s++; + else if (*s == LUA_SIGNATURE[0]) + s = "(bstring)"; + else + s = "(string)"; + ramsyscall_printf("\n%s <%s:%d,%d> (%d instruction%s at %p)\n", (f->linedefined == 0) ? "main" : "function", s, f->linedefined, + f->lastlinedefined, S(f->sizecode), VOID(f)); + ramsyscall_printf("%d%s param%s, %d slot%s, %d upvalue%s, ", (int)(f->numparams), f->is_vararg ? "+" : "", SS(f->numparams), + S(f->maxstacksize), S(f->sizeupvalues)); + ramsyscall_printf("%d local%s, %d constant%s, %d function%s\n", S(f->sizelocvars), S(f->sizek), S(f->sizep)); +} + +static void PrintDebug(const Proto* f) { + int i, n; + n = f->sizek; + ramsyscall_printf("constants (%d) for %p:\n", n, VOID(f)); + for (i = 0; i < n; i++) { + ramsyscall_printf("\t%d\t", i + 1); + PrintConstant(f, i); + ramsyscall_printf("\n"); + } + n = f->sizelocvars; + ramsyscall_printf("locals (%d) for %p:\n", n, VOID(f)); + for (i = 0; i < n; i++) { + ramsyscall_printf("\t%d\t%s\t%d\t%d\n", i, getstr(f->locvars[i].varname), f->locvars[i].startpc + 1, + f->locvars[i].endpc + 1); + } + n = f->sizeupvalues; + ramsyscall_printf("upvalues (%d) for %p:\n", n, VOID(f)); + for (i = 0; i < n; i++) { + ramsyscall_printf("\t%d\t%s\t%d\t%d\n", i, UPVALNAME(i), f->upvalues[i].instack, f->upvalues[i].idx); + } } -static void PrintDebug(const Proto* f) -{ - int i,n; - n=f->sizek; - printf("constants (%d) for %p:\n",n,VOID(f)); - for (i=0; isizelocvars; - printf("locals (%d) for %p:\n",n,VOID(f)); - for (i=0; ilocvars[i].varname),f->locvars[i].startpc+1,f->locvars[i].endpc+1); - } - n=f->sizeupvalues; - printf("upvalues (%d) for %p:\n",n,VOID(f)); - for (i=0; iupvalues[i].instack,f->upvalues[i].idx); - } +static void PrintFunction(const Proto* f, int full) { + int i, n = f->sizep; + PrintHeader(f); + PrintCode(f); + if (full) PrintDebug(f); + for (i = 0; i < n; i++) PrintFunction(f->p[i], full); } -static void PrintFunction(const Proto* f, int full) -{ - int i,n=f->sizep; - PrintHeader(f); - PrintCode(f); - if (full) PrintDebug(f); - for (i=0; ip[i],full); +int luaI_sprintf(char *str, const char *format, ...) { + *str = 0; + return 0; } diff --git a/src/psx-glue.s b/src/psx-glue.s new file mode 100644 index 0000000..324cd8d --- /dev/null +++ b/src/psx-glue.s @@ -0,0 +1,37 @@ + .set SBUS_DEV8_CTRL, 0x1f80101C + + .section .start, "ax", @progbits + .set noreorder + .align 2 + .global main + .global _start + .type _start, @function + +_start: + lw $t2, SBUS_DEV8_CTRL + lui $t0, 8 + lui $t1, 1 +_check_dev8: + bge $t2, $t0, _store_dev8 + nop + b _check_dev8 + add $t2, $t1 +_store_dev8: + sw $t2, SBUS_DEV8_CTRL + + la $t0, __bss_start + la $t1, __bss_end + + beq $t0, $t1, _bss_init_skip + nop + +_bss_init: + sw $0, 0($t0) + addiu $t0, 4 + bne $t0, $t1, _bss_init + nop + +_bss_init_skip: + + j main + nop diff --git a/src/psx-heap.c b/src/psx-heap.c new file mode 100644 index 0000000..6092aab --- /dev/null +++ b/src/psx-heap.c @@ -0,0 +1,60 @@ +/* +** Glue needed to build luac as a ps-exe. psxlua leaves the allocator to +** whoever embeds it (see luaI_realloc/luaI_free in llibc.h); an embedder with +** a heap of its own supplies these by --defsym, but luac is a standalone +** program, so it takes the kernel heap. Only luac-psx.mk compiles this file. +*/ + +#include + +#include "common/syscalls/syscalls.h" + +void* memcpy(void* dst, const void* src, size_t n); + +extern char __bss_end[]; +extern char __sp[]; + +/* Room left below the stack pointer for the parser's recursion. */ +#define STACK_RESERVE 0x10000 + +/* +** The kernel's malloc has no realloc, and psxlua's luaI_realloc does not get +** told the old size, so each block carries it. Eight bytes keeps whatever +** alignment the kernel handed back. +*/ +typedef struct { + size_t size; + size_t reserved; +} Header; + +static int heapReady; + +static void initHeap(void) { + char* base = (char*)((((unsigned)__bss_end) + 7) & ~7u); + char* top = (char*)((unsigned)__sp - STACK_RESERVE); + heapReady = 1; + syscall_userInitheap(base, (size_t)(top - base)); +} + +void luaI_free(void* ptr) { + if (ptr == NULL) return; + syscall_userFree((Header*)ptr - 1); +} + +void* luaI_realloc(void* ptr, size_t size) { + Header* block; + if (!heapReady) initHeap(); + if (size == 0) { + luaI_free(ptr); + return NULL; + } + block = (Header*)syscall_userMalloc(size + sizeof(Header)); + if (block == NULL) return NULL; + block->size = size; + if (ptr != NULL) { + Header* old = (Header*)ptr - 1; + memcpy(block + 1, ptr, old->size < size ? old->size : size); + syscall_userFree(old); + } + return block + 1; +} diff --git a/tests/run-luac.sh b/tests/run-luac.sh new file mode 100755 index 0000000..4af1c30 --- /dev/null +++ b/tests/run-luac.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# +# Compiles tests/sample.lua with luac.ps-exe under an emulator and checks the +# bytecode it produced is the target's, not the host's. +# +# make -C src psx-luac NUGGET=/path/to/nugget +# tests/run-luac.sh /path/to/pcsx-redux +# +# The emulator has to serve two things the executable depends on: PCDRV for +# file I/O, and the unmapped read at 0x40000000 that args.lua answers with the +# argument buffer. + +set -e + +EMU=${1:?usage: $0 } +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +EXE=$ROOT/src/luac.ps-exe + +if [ ! -f "$EXE" ]; then + echo "$EXE is missing; build it with make -C src psx-luac NUGGET=..." >&2 + exit 1 +fi + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +cp "$EXE" "$ROOT/tests/sample.lua" "$ROOT/src/args.lua" "$WORK/" +echo "createArgsBuffer('-o', 'sample.luac', 'sample.lua')" >> "$WORK/args.lua" + +# -no-ui keeps it off a display, and the timeout is there because an emulator +# that never reaches pcsx_exit would otherwise hold a CI job open for hours. +timeout 120 "$EMU" -no-ui -testmode -run -stdout -lua_stdout \ + -pcdrv -pcdrvbase "$WORK" \ + -dofile "$WORK/args.lua" -loadexe "$WORK/luac.ps-exe" + +if [ ! -s "$WORK/sample.luac" ]; then + echo "luac.ps-exe produced no bytecode" >&2 + exit 1 +fi + +# Lua 5.2 signature, little endian, then sizeof int, size_t, Instruction and +# lua_Number, then the integral-number flag. Compiling on the target is what +# makes these the target's by construction. +EXPECTED=1b4c75615200010404040401 +GOT=$(od -An -tx1 -N12 "$WORK/sample.luac" | tr -d ' \n') + +if [ "$GOT" != "$EXPECTED" ]; then + echo "bytecode header is $GOT, expected $EXPECTED" >&2 + exit 1 +fi + +echo "sample.luac: $(wc -c < "$WORK/sample.luac") bytes, header $GOT" diff --git a/tests/sample.lua b/tests/sample.lua new file mode 100644 index 0000000..1b71f3e --- /dev/null +++ b/tests/sample.lua @@ -0,0 +1,3 @@ +local squares = {} +for i = 1, 10 do squares[i] = i * i end +return squares