mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-04-20 00:17:04 +00:00
Deploying to gh-pages from @ sehugg/8bitworkshop@19c2032545 🚀
This commit is contained in:
@@ -443,6 +443,9 @@ export function getToolForFilename_6502(fn:string) : string {
|
||||
if (fn.endsWith(".acme")) return "acme";
|
||||
if (fn.endsWith(".wiz")) return "wiz";
|
||||
if (fn.endsWith(".ecs")) return "ecs";
|
||||
if (fn.endsWith(".cpp")) return "oscar64";
|
||||
if (fn.endsWith(".cc")) return "oscar64";
|
||||
if (fn.endsWith(".o64")) return "oscar64";
|
||||
return "dasm"; // .a
|
||||
}
|
||||
|
||||
|
||||
@@ -171,8 +171,6 @@ export enum WASIErrors {
|
||||
NOTCAPABLE = 76,
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class WASIFileDescriptor {
|
||||
fdindex: number = -1;
|
||||
protected data: Uint8Array = new Uint8Array(16);
|
||||
@@ -284,6 +282,14 @@ export class WASIMemoryFilesystem implements WASIFilesystem {
|
||||
this.files.set(name, file);
|
||||
return file;
|
||||
}
|
||||
putSymbolicLink(name: string, target: string, rights?: number) {
|
||||
if (!rights) rights = FDRights.PATH_SYMLINK;
|
||||
const file = new WASIFileDescriptor(name, FDType.SYMBOLIC_LINK, rights);
|
||||
file.write(new TextEncoder().encode(target));
|
||||
file.offset = 0;
|
||||
this.files.set(name, file);
|
||||
return file;
|
||||
}
|
||||
getFile(name: string) {
|
||||
let file = this.files.get(name);
|
||||
if (!file) {
|
||||
@@ -450,6 +456,7 @@ export class WASIRunner {
|
||||
const bytes = enc.encode(str);
|
||||
const len = Math.min(bytes.length, maxlen);
|
||||
this.mem8().set(bytes.subarray(0, len), ptr);
|
||||
return len;
|
||||
}
|
||||
peekUTF8(ptr: number, maxlen: number) {
|
||||
const bytes = this.mem8().subarray(ptr, ptr + maxlen);
|
||||
@@ -613,9 +620,9 @@ export class WASIRunner {
|
||||
if (dir == null) return WASIErrors.BADF;
|
||||
if (dir.type !== FDType.DIRECTORY) return WASIErrors.NOTDIR;
|
||||
const filename = this.peekUTF8(path_ptr, path_len);
|
||||
const path = dir.name + '/' + filename;
|
||||
const path = filename.startsWith('/') ? filename : dir.name + '/' + filename; // TODO?
|
||||
const fd = this.fs.getFile(path);
|
||||
console.log("path_filestat_get", dir+"", path, filestat_ptr, '->', fd+"");
|
||||
console.log("path_filestat_get", dir+"", filename, path, filestat_ptr, '->', fd+"");
|
||||
if (!fd) return WASIErrors.NOENT;
|
||||
this.poke64(filestat_ptr, fd.fdindex); // dev
|
||||
this.poke64(filestat_ptr + 8, 0); // ino
|
||||
@@ -626,6 +633,43 @@ export class WASIRunner {
|
||||
this.poke64(filestat_ptr + 48, 0); // mtim
|
||||
this.poke64(filestat_ptr + 56, 0); // ctim
|
||||
}
|
||||
path_readlink(dirfd: number, path_ptr: number, path_len: number, buf_ptr: number, buf_len: number, buf_used_ptr: number) {
|
||||
const dir = this.fds[dirfd];
|
||||
debug("path_readlink", dirfd, path_ptr, path_len, buf_ptr, buf_len, buf_used_ptr, dir+"");
|
||||
if (dir == null) return WASIErrors.BADF;
|
||||
if (dir.type !== FDType.DIRECTORY) return WASIErrors.NOTDIR;
|
||||
const filename = this.peekUTF8(path_ptr, path_len);
|
||||
const path = dir.name + '/' + filename;
|
||||
const fd = this.fs.getFile(path);
|
||||
debug("path_readlink", path, fd+"");
|
||||
if (!fd) return WASIErrors.NOENT;
|
||||
if (fd.type !== FDType.SYMBOLIC_LINK) return WASIErrors.INVAL;
|
||||
const target = fd.getBytesAsString();
|
||||
const len = this.pokeUTF8(target, buf_ptr, buf_len);
|
||||
this.poke32(buf_used_ptr, len);
|
||||
debug("path_readlink", path, '->', target);
|
||||
return WASIErrors.SUCCESS;
|
||||
}
|
||||
path_readlinkat(dirfd: number, path_ptr: number, path_len: number, buf_ptr: number, buf_len: number, buf_used_ptr: number) {
|
||||
return this.path_readlink(dirfd, path_ptr, path_len, buf_ptr, buf_len, buf_used_ptr);
|
||||
}
|
||||
path_unlink_file(dirfd: number, path_ptr: number, path_len: number) {
|
||||
const dir = this.fds[dirfd];
|
||||
if (dir == null) return WASIErrors.BADF;
|
||||
if (dir.type !== FDType.DIRECTORY) return WASIErrors.NOTDIR;
|
||||
const filename = this.peekUTF8(path_ptr, path_len);
|
||||
const path = dir.name + '/' + filename;
|
||||
const fd = this.fs.getFile(path);
|
||||
debug("path_unlink_file", dir+"", path, fd+"");
|
||||
if (!fd) return WASIErrors.NOENT;
|
||||
this.fs.getFile(path);
|
||||
return WASIErrors.SUCCESS;
|
||||
}
|
||||
clock_time_get(clock_id: number, precision: number, time_ptr: number) {
|
||||
const time = Date.now();
|
||||
this.poke64(time_ptr, time);
|
||||
return WASIErrors.SUCCESS;
|
||||
}
|
||||
getWASISnapshotPreview1() {
|
||||
return {
|
||||
args_sizes_get: this.args_sizes_get.bind(this),
|
||||
@@ -643,16 +687,22 @@ export class WASIRunner {
|
||||
fd_close: this.fd_close.bind(this),
|
||||
path_filestat_get: this.path_filestat_get.bind(this),
|
||||
random_get: this.random_get.bind(this),
|
||||
path_readlink: this.path_readlink.bind(this),
|
||||
path_unlink_file: this.path_unlink_file.bind(this),
|
||||
clock_time_get: this.clock_time_get.bind(this),
|
||||
fd_fdstat_set_flags() { warning("TODO: fd_fdstat_set_flags"); return WASIErrors.NOTSUP; },
|
||||
fd_readdir() { warning("TODO: fd_readdir"); return WASIErrors.NOTSUP; },
|
||||
path_unlink_file() { warning("TODO: path_unlink_file"); return WASIErrors.NOTSUP; },
|
||||
clock_time_get() { warning("TODO: clock_time_get"); return WASIErrors.NOTSUP; },
|
||||
fd_tell() { warning("TODO: fd_tell"); return WASIErrors.NOTSUP; },
|
||||
path_remove_directory() { warning("TODO: path_remove_directory"); return 0; },
|
||||
}
|
||||
}
|
||||
getEnv() {
|
||||
return {
|
||||
__syscall_unlinkat() { warning('TODO: unlink'); return WASIErrors.NOTSUP; },
|
||||
__syscall_faccessat() { warning("TODO: faccessat"); return WASIErrors.NOTSUP; },
|
||||
__syscall_readlinkat: this.path_readlinkat.bind(this),
|
||||
__syscall_getcwd() { warning("TODO: getcwd"); return WASIErrors.NOTSUP; },
|
||||
__syscall_rmdir() { warning("TODO: rmdir"); return WASIErrors.NOTSUP; },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ const TOOL_TO_SOURCE_STYLE = {
|
||||
'remote:llvm-mos': 'text/x-csrc',
|
||||
'cc7800': 'text/x-csrc',
|
||||
'armtcc': 'text/x-csrc',
|
||||
'oscar64': 'text/x-csrc',
|
||||
}
|
||||
|
||||
// TODO: move into tool class
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@ import { BaseMAME6502Platform } from "../common/mameplatform";
|
||||
const C64_PRESETS : Preset[] = [
|
||||
{id:'helloc.c', name:'Hello World', category:'C'},
|
||||
{id:'screen_ram.c', name:'Screen RAM'},
|
||||
{id:'siegegame.c', name:'Siege Game'},
|
||||
{id:'joymove.c', name:'Sprite Movement'},
|
||||
{id:'sprite_collision.c', name:'Sprite Collision'},
|
||||
{id:'scroll1.c', name:'Scrolling (Single Buffer)'},
|
||||
@@ -28,11 +29,13 @@ const C64_PRESETS : Preset[] = [
|
||||
{id:'musicplayer.c', name:'Music Player'},
|
||||
//{id:'sidtune.dasm', name:'Tiny SID Tune (ASM)'},
|
||||
{id:'siddemo.c', name:'SID Player Demo'},
|
||||
{id:'digisound.c', name:'Digi Sound Player'},
|
||||
{id:'climber.c', name:'Climber Game'},
|
||||
{id:'test_border_sprites.c', name:'Sprites in the Borders'},
|
||||
{id:'sprite_stretch.c', name:'Sprite Stretching'},
|
||||
{id:'linecrunch.c', name:'Linecrunch'},
|
||||
{id:'fld.c', name:'Flexible Line Distance'},
|
||||
{id:'plasma.c', name:'Plasma Demo'},
|
||||
{id:'siegegame.c', name:'Siege Game'},
|
||||
{id:'23matches.c', name:'23 Matches'},
|
||||
{id:'tgidemo.c', name:'TGI Graphics Demo'},
|
||||
{id:'upandaway.c', name:'Up, Up and Away'},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "assert";
|
||||
import { WASIRunner } from "../common/wasi/wasishim";
|
||||
import * as fs from "fs";
|
||||
import { loadWASIFilesystemZip, unzipWASIFilesystem } from "../worker/wasiutils";
|
||||
|
||||
async function loadWASM(filename: string) {
|
||||
const wasmdata = fs.readFileSync(`./src/worker/wasm/${filename}.wasm`);
|
||||
@@ -15,6 +16,9 @@ async function loadDASM() {
|
||||
async function loadCC7800() {
|
||||
return loadWASM('cc7800');
|
||||
}
|
||||
async function loadOscar64() {
|
||||
return loadWASM('oscar64');
|
||||
}
|
||||
|
||||
describe('test WASI DASM', function () {
|
||||
it('dasm help', async function () {
|
||||
@@ -78,3 +82,24 @@ describe('test WASI cc7800', function () {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
describe('test WASI oscar64', function () {
|
||||
it('oscar64 compile', async function () {
|
||||
let shim = await loadOscar64();
|
||||
const zipdata = fs.readFileSync(`./src/worker/fs/oscar64-fs.zip`);
|
||||
shim.fs = await unzipWASIFilesystem(zipdata, "/root/");
|
||||
shim.addPreopenDirectory("/root");
|
||||
shim.fs.putSymbolicLink("/proc/self/exe", "/root/bin/oscar64");
|
||||
shim.fs.putFile("/root/main.c", "#include <stdio.h>\nint main() { return 0; }");
|
||||
shim.setArgs(["oscar64", '-v', '-o=foo.prg', 'main.c']);
|
||||
let errno = shim.run();
|
||||
const stdout = shim.fds[1].getBytesAsString();
|
||||
console.log(stdout);
|
||||
const stderr = shim.fds[2].getBytesAsString();
|
||||
console.log(stderr);
|
||||
assert.strictEqual(errno, 0);
|
||||
assert.ok(stdout.indexOf('Starting oscar64') >= 0);
|
||||
console.log(shim.fs.getFile("./foo.asm").getBytesAsString());
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var wtu = require('./workertestutils.js');
|
||||
//const canvas = require('canvas');
|
||||
//const GIFEncoder = require('gif-encoder-2');
|
||||
//const fastpng = require('fast-png');
|
||||
|
||||
const dom = createTestDOM();
|
||||
includeInThisContext('gen/common/cpu/6809.js');
|
||||
includeInThisContext("javatari.js/release/javatari/javatari.js");
|
||||
Javatari.AUTO_START = false;
|
||||
includeInThisContext('tss/js/Log.js');
|
||||
//global.Log = require('tss/js/Log.js').Log;
|
||||
includeInThisContext('tss/js/tss/PsgDeviceChannel.js');
|
||||
includeInThisContext('tss/js/tss/MasterChannel.js');
|
||||
includeInThisContext('tss/js/tss/AudioLooper.js');
|
||||
//includeInThisContext("jsnes/dist/jsnes.min.js");
|
||||
global.jsnes = require("jsnes/dist/jsnes.min.js");
|
||||
|
||||
//var devices = require('gen/common/devices.js');
|
||||
var emu = require('gen/common/emu.js');
|
||||
var Keys = emu.Keys;
|
||||
var audio = require('gen/common/audio.js');
|
||||
var recorder = require('gen/common/recorder.js');
|
||||
//var _6502 = require('gen/common/cpu/MOS6502.js');
|
||||
var _apple2 = require('gen/platform/apple2.js');
|
||||
//var m_apple2 = require('gen/machine/apple2.js');
|
||||
var _vcs = require('gen/platform/vcs.js');
|
||||
var _nes = require('gen/platform/nes.js');
|
||||
var _vicdual = require('gen/platform/vicdual.js');
|
||||
var _mw8080bw = require('gen/platform/mw8080bw.js');
|
||||
var _galaxian = require('gen/platform/galaxian.js');
|
||||
var _vector = require('gen/platform/vector.js');
|
||||
var _williams = require('gen/platform/williams.js');
|
||||
var _sound_williams = require('gen/platform/sound_williams.js');
|
||||
var _astrocade = require('gen/platform/astrocade.js');
|
||||
var _atari8 = require('gen/platform/atari8.js');
|
||||
var _atari7800 = require('gen/platform/atari7800.js');
|
||||
var _coleco = require('gen/platform/coleco.js');
|
||||
var _sms = require('gen/platform/sms.js');
|
||||
var _c64 = require('gen/platform/c64.js');
|
||||
var _vectrex = require('gen/platform/vectrex.js');
|
||||
var _zx = require('gen/platform/zx.js');
|
||||
var _atari8 = require('gen/platform/atari8.js');
|
||||
var util = require('gen/common/util.js');
|
||||
util.loadScript = function (s) { console.log('tried to load', s); } // for vcs
|
||||
|
||||
//
|
||||
|
||||
var keycallback;
|
||||
var lastrastervideo;
|
||||
var canvases = [];
|
||||
dom.window.HTMLCanvasElement.prototype.getContext = function () {
|
||||
canvases.push(this);
|
||||
this.data = new Uint32Array(228 * 312);
|
||||
return {
|
||||
getImageData: function (x, y, w, h) {
|
||||
if (this.data == null) {
|
||||
this.data = new Uint32Array(w * h);
|
||||
}
|
||||
return { data: this.data };
|
||||
},
|
||||
fillRect: function (x, y, w, h) { },
|
||||
drawImage: function (img, x, y, w, h) {
|
||||
},
|
||||
putImageData: function (d, w, h) {
|
||||
this.data.set(d.data);
|
||||
},
|
||||
};
|
||||
}
|
||||
global.navigator = {};
|
||||
|
||||
emu.RasterVideo = function (mainElement, width, height, options) {
|
||||
var buffer;
|
||||
var datau8;
|
||||
var datau32;
|
||||
this.create = function () {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
buffer = new ArrayBuffer(width * height * 4);
|
||||
datau8 = new Uint8Array(buffer);
|
||||
datau32 = new Uint32Array(buffer);
|
||||
lastrastervideo = this;
|
||||
}
|
||||
this.setKeyboardEvents = function (callback) {
|
||||
keycallback = callback;
|
||||
}
|
||||
this.getFrameData = function () { return datau32; }
|
||||
this.getImageData = function () { return { data: datau8, width: width, height: height }; }
|
||||
this.updateFrame = function () { }
|
||||
this.clearRect = function () { }
|
||||
this.setupMouseEvents = function () { }
|
||||
this.canvas = this;
|
||||
this.getContext = function () { return this; }
|
||||
this.fillRect = function () { }
|
||||
this.fillStyle = '';
|
||||
this.putImageData = function () { }
|
||||
}
|
||||
|
||||
emu.VectorVideo = function (mainElement, width, height, options) {
|
||||
this.create = function () {
|
||||
this.drawops = 0;
|
||||
}
|
||||
this.setKeyboardEvents = function (callback) {
|
||||
keycallback = callback;
|
||||
}
|
||||
this.clear = function () { }
|
||||
this.drawLine = function () { this.drawops++; }
|
||||
}
|
||||
|
||||
global.Worker = function () {
|
||||
this.msgcount = 0;
|
||||
this.postMessage = function () { this.msgcount++; }
|
||||
}
|
||||
|
||||
global.Mousetrap = function () {
|
||||
this.bind = function () { }
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/*
|
||||
interface RunStats {
|
||||
line: number;
|
||||
pcmin: number;
|
||||
pcmax: number;
|
||||
ops: {[op:number]:number};
|
||||
io: {[loc:number]:number};
|
||||
}
|
||||
*/
|
||||
|
||||
let STATS = {};
|
||||
|
||||
function getFrameStats(frame, line) {
|
||||
let key = Math.floor(frame/60) + '-' + line;
|
||||
let state = STATS[key];
|
||||
if (state == null) {
|
||||
state = newRunStats(frame, line);
|
||||
STATS[key] = state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function newRunStats(frame, line) {
|
||||
return {
|
||||
frame: frame,
|
||||
line: line,
|
||||
insns: 0,
|
||||
xmin: -1,
|
||||
xmax: 0,
|
||||
pcmin: -1,
|
||||
pcmax: 0,
|
||||
wait: 0,
|
||||
ops: {},
|
||||
ioread: {},
|
||||
iowrite: {},
|
||||
};
|
||||
}
|
||||
|
||||
function inc(map, amt) {
|
||||
if (map[amt] == null) map[amt] = 0;
|
||||
map[amt]++;
|
||||
}
|
||||
|
||||
function addRunStats(frame, ops) {
|
||||
let line = 0;
|
||||
let x = 0;
|
||||
let stat;
|
||||
let reads = [];
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
let op = ops[i];
|
||||
switch (op & 0xff000000) {
|
||||
case 0x7f000000: // FRAME
|
||||
line = 0;
|
||||
case 0x7e000000: // SCANLINE
|
||||
line++;
|
||||
x = 0;
|
||||
stat = getFrameStats(frame, line);
|
||||
break;
|
||||
case 0: // CLOCKS
|
||||
x += op & 0xffffff;
|
||||
break;
|
||||
case 0x01000000: // EXECUTE
|
||||
if (stat) {
|
||||
stat.insns++;
|
||||
let pc = op & 0xffff;
|
||||
if (stat.xmin == -1 || x < stat.xmin) stat.xmin = x;
|
||||
if (stat.xmax == 0 || x > stat.xmax) stat.xmax = x;
|
||||
if (stat.pcmin == -1 || pc < stat.pcmin) stat.pcmin = pc;
|
||||
if (stat.pcmax == 0 || pc > stat.pcmax) stat.pcmax = pc;
|
||||
let opcode = reads[pc];
|
||||
if (typeof opcode === 'number') {
|
||||
inc(stat.ops, opcode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x12000000: // MEM_READ
|
||||
reads[op & 0xffff] = (op >> 16) & 0xff;
|
||||
break;
|
||||
case 0x14000000: // IO_READ
|
||||
if (stat) inc(stat.ioread, op & 0xffff);
|
||||
break;
|
||||
case 0x15000000: // IO_WRITE
|
||||
if (stat) inc(stat.iowrite, op & 0xffff);
|
||||
break;
|
||||
case 0x1f000000: // WAIT
|
||||
if (stat) stat.wait += op & 0xffffff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
async function runPlatform(platid, romname, maxframes) {
|
||||
var outfilename = 'data.out';
|
||||
try { fs.unlinkSync(outfilename); } catch (e) { }
|
||||
if (!emu.PLATFORMS[platid]) throw new Error('no such platform: ' + platid);
|
||||
var platform = new emu.PLATFORMS[platid](emudiv);
|
||||
await platform.start();
|
||||
var emudiv = document.getElementById('emulator');
|
||||
var rom = fs.readFileSync(romname);
|
||||
rom = new Uint8Array(rom);
|
||||
platform.loadROM("ROM", rom);
|
||||
platform.reset(); // reset again
|
||||
platform.resume(); // so that recorder works
|
||||
// skip a few to let vsync settle or whatever
|
||||
for (let i = 0; i < 10; i++) platform.nextFrame();
|
||||
let proberec = platform.startProbing();
|
||||
proberec.reset(0x100000);
|
||||
proberec.singleFrame = true;
|
||||
// connect display (vcs)
|
||||
//var canv = canvas.createCanvas(228,312);
|
||||
//var imdata = canv.getContext('2d').getImageData(0,0,228,312);
|
||||
//let imu32 = new Uint32Array(imdata.data.buffer);
|
||||
let oldNextLine = Javatari.room.screen.getMonitor().nextLine;
|
||||
let imofs = 0;
|
||||
Javatari.room.screen.getMonitor().nextLine = (pixels, vsyncSignal) => {
|
||||
for (let i = 0; i < pixels.length; i++) {
|
||||
let rgba = pixels[i];
|
||||
proberec.logData(rgba | 0xff000000);
|
||||
//imu32[imofs++] = rgba;
|
||||
}
|
||||
return oldNextLine(pixels, vsyncSignal);
|
||||
}
|
||||
// build encoder
|
||||
//const encoder = new GIFEncoder(lastrastervideo.width, lastrastervideo.height);
|
||||
//const encoder = new GIFEncoder(228,312,'neuquant',true);
|
||||
//encoder.setDelay(Math.round(1000/60));
|
||||
//encoder.start();
|
||||
// run frames
|
||||
let tweakframe = 30;
|
||||
for (var i = 0; i < maxframes; i++) {
|
||||
imofs = 0;
|
||||
platform.nextFrame();
|
||||
/*
|
||||
var pngbuffer = fastpng.encode(imdata);
|
||||
assert(pngbuffer.length > 500); // make sure PNG is big enough
|
||||
try { fs.mkdirSync("./test"); } catch(e) { }
|
||||
try { fs.mkdirSync("./test/output"); } catch(e) { }
|
||||
try {
|
||||
fs.writeFileSync("./test/output/"+platid+"-"+romname.replaceAll('/','_')+"_"+i+".png", pngbuffer);
|
||||
} catch (e) { console.log(e) }
|
||||
*/
|
||||
//encoder.addFrame(imdata);
|
||||
let data = proberec.buf.slice(0, proberec.idx);
|
||||
addRunStats(i, data);
|
||||
//fs.appendFileSync(outfilename, data, null);
|
||||
|
||||
if ((i % tweakframe) == tweakframe-1) {
|
||||
let ctrl = platform.saveControlsState();
|
||||
ctrl.P0btn = Math.random() > 0.5;
|
||||
ctrl.P1btn = Math.random() > 0.5;
|
||||
ctrl.SA = Math.round(Math.random() * 255);
|
||||
ctrl.SB = Math.round(Math.random() * 255);
|
||||
platform.loadControlsState(ctrl);
|
||||
}
|
||||
|
||||
//console.log(i, data.length);
|
||||
proberec.clear();
|
||||
}
|
||||
platform.stopProbing();
|
||||
platform.pause();
|
||||
console.log(proberec);
|
||||
//console.log(STATS);
|
||||
fs.writeFileSync(outfilename, JSON.stringify(STATS));
|
||||
//encoder.finish();
|
||||
//fs.writeFileSync('test.gif', encoder.out.getData());
|
||||
return platform;
|
||||
}
|
||||
|
||||
const platformid = process.argv[2];
|
||||
const rompath = process.argv[3];
|
||||
runPlatform(platformid, rompath, 10000);
|
||||
+167
-40
@@ -2,12 +2,30 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { WorkerBuildStep, WorkerErrorResult, WorkerFileUpdate, WorkerResult, isOutputResult } from '../../common/workertypes';
|
||||
import { getRootBasePlatform, replaceAll } from '../../common/util';
|
||||
import { CodeListing, CodeListingMap, Segment, WorkerBuildStep, WorkerErrorResult, WorkerFileUpdate, WorkerResult, isOutputResult } from '../../common/workertypes';
|
||||
import { getFilenamePrefix, getRootBasePlatform, replaceAll } from '../../common/util';
|
||||
import { parseObjDump } from './clang';
|
||||
import { BuildStep } from '../builder';
|
||||
import { makeErrorMatcher } from '../listingutils';
|
||||
|
||||
interface ServerBuildTool {
|
||||
name: string;
|
||||
version: string;
|
||||
extensions: string[];
|
||||
archs: string[];
|
||||
platforms: string[];
|
||||
platform_configs: { [platform: string]: ServerBuildToolPlatformConfig };
|
||||
processErrors(step: WorkerBuildStep, errorData: string): Promise<WorkerErrorResult>;
|
||||
processOutput(step: WorkerBuildStep, outfile: string): Promise<WorkerResult>;
|
||||
}
|
||||
|
||||
interface ServerBuildToolPlatformConfig {
|
||||
binpath?: string;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
libargs?: string[];
|
||||
outfile?: string;
|
||||
}
|
||||
|
||||
const LLVM_MOS_TOOL: ServerBuildTool = {
|
||||
name: 'llvm-mos',
|
||||
@@ -15,6 +33,8 @@ const LLVM_MOS_TOOL: ServerBuildTool = {
|
||||
extensions: ['.c', '.cpp', '.s', '.S', '.C'],
|
||||
archs: ['6502'],
|
||||
platforms: ['atari8', 'c64', 'nes', 'pce', 'vcs'],
|
||||
processOutput: basicProcessOutput,
|
||||
processErrors: llvmMosProcessErrors,
|
||||
platform_configs: {
|
||||
default: {
|
||||
binpath: 'llvm-mos/bin',
|
||||
@@ -45,6 +65,130 @@ const LLVM_MOS_TOOL: ServerBuildTool = {
|
||||
}
|
||||
}
|
||||
|
||||
async function basicProcessOutput(step: WorkerBuildStep, outfile: string): Promise<WorkerResult> {
|
||||
let output = await fs.promises.readFile(outfile, { encoding: 'base64' });
|
||||
return { output };
|
||||
}
|
||||
|
||||
async function llvmMosProcessErrors(step: WorkerBuildStep, errorData: string): Promise<WorkerErrorResult> {
|
||||
errorData = errorData.replace(/(\/var\/folders\/.+?\/).+?:/g, ''); // TODO?
|
||||
let errors = [];
|
||||
// split errorData into lines
|
||||
let errorMatcher = makeErrorMatcher(errors, /([^:/]+):(\d+):(\d+):\s*(.+)/, 2, 4, step.path, 1);
|
||||
for (let line of errorData.split('\n')) {
|
||||
errorMatcher(line);
|
||||
}
|
||||
return { errors };
|
||||
}
|
||||
|
||||
|
||||
const OSCAR64_TOOL: ServerBuildTool = {
|
||||
name: 'oscar64',
|
||||
version: '',
|
||||
extensions: ['.c', '.cc', '.cpp'],
|
||||
archs: ['6502'],
|
||||
platforms: ['atari8', 'c64', 'nes'],
|
||||
processOutput: oscar64ProcessOutput,
|
||||
processErrors: oscar64ProcessErrors,
|
||||
platform_configs: {
|
||||
default: {
|
||||
binpath: 'oscar64/bin',
|
||||
command: 'oscar64',
|
||||
args: ['-Os', '-g', '-d__8BITWORKSHOP__', '-o=$OUTFILE', '$INFILES'],
|
||||
},
|
||||
c64: {
|
||||
outfile: 'a.prg',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function oscar64ProcessErrors(step: WorkerBuildStep, errorData: string): Promise<WorkerErrorResult> {
|
||||
let errors = [];
|
||||
// split errorData into lines
|
||||
let errorMatcher = makeErrorMatcher(errors, /\/([^(]+)\((\d+), (\d+)\) : \s*(.+)/, 2, 4, step.path, 1);
|
||||
for (let line of errorData.split('\n')) {
|
||||
errorMatcher(line);
|
||||
}
|
||||
return { errors };
|
||||
}
|
||||
|
||||
async function oscar64ProcessOutput(step: WorkerBuildStep, outpath: string): Promise<WorkerResult> {
|
||||
let prefix_path = outpath.replace(/\.\w+$/, '');
|
||||
let output = await fs.promises.readFile(outpath, { encoding: 'base64' });
|
||||
let listings: CodeListingMap = {};
|
||||
let symbolmap: { [sym: string]: number } = {};
|
||||
let debuginfo = {};
|
||||
let segments: Segment[] = [];
|
||||
// read segments
|
||||
{
|
||||
let txt = await fs.promises.readFile(prefix_path + '.map', { encoding: 'utf-8' });
|
||||
for (let line of txt.split("\n")) {
|
||||
// 0880 - 0887 : DATA, code
|
||||
const m1 = line.match(/([0-9a-f]+) - ([0-9a-f]+) : ([A-Z_]+), (.+)/);
|
||||
if (m1) {
|
||||
const name = m1[4];
|
||||
const start = parseInt(m1[1], 16);
|
||||
const end = parseInt(m1[2], 16);
|
||||
segments.push({
|
||||
name, start, size: end - start,
|
||||
});
|
||||
}
|
||||
// 0801 (0062) : startup, NATIVE_CODE:startup
|
||||
const m2 = line.match(/([0-9a-f]+) \(([0-9a-f]+)\) : ([^,]+), (.+)/);
|
||||
if (m2) {
|
||||
const addr = parseInt(m2[1], 16);
|
||||
const name = m2[3];
|
||||
symbolmap[name] = addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// read listings
|
||||
{
|
||||
let txt = await fs.promises.readFile(prefix_path + '.asm', { encoding: 'utf-8' });
|
||||
let lst : CodeListing = { lines: [], text: txt };
|
||||
let asm_lineno = 0;
|
||||
let c_lineno = 0;
|
||||
let c_path = '';
|
||||
const path = step.path;
|
||||
for (let line of txt.split("\n")) {
|
||||
asm_lineno++;
|
||||
//; 4, "/Users/sehugg/PuzzlingPlans/8bitworkshop/server-root/oscar64/main.c"
|
||||
let m2 = line.match(/;\s*(\d+), "(.+?)"/);
|
||||
if (m2) {
|
||||
c_lineno = parseInt(m2[1]);
|
||||
c_path = m2[2].split('/').pop(); // TODO
|
||||
}
|
||||
//0807 : 30 36 __ BMI $083f ; (startup + 62)
|
||||
let m = line.match(/([0-9a-f]+) : ([0-9a-f _]{8}) (.+)/);
|
||||
if (m) {
|
||||
let offset = parseInt(m[1], 16);
|
||||
let hex = m[2];
|
||||
let asm = m[3];
|
||||
if (c_path) {
|
||||
lst.lines.push({
|
||||
line: c_lineno,
|
||||
path: c_path,
|
||||
offset,
|
||||
iscode: true
|
||||
});
|
||||
c_path = '';
|
||||
c_lineno = 0;
|
||||
}
|
||||
/*
|
||||
lst.asmlines.push({
|
||||
line: asm_lineno,
|
||||
path,
|
||||
offset,
|
||||
insns: hex + ' ' + asm,
|
||||
iscode: true });
|
||||
*/
|
||||
}
|
||||
}
|
||||
listings[getFilenamePrefix(step.path) + '.lst'] = lst;
|
||||
}
|
||||
return { output, listings, symbolmap, segments, debuginfo };
|
||||
}
|
||||
|
||||
export function findBestTool(step: BuildStep) {
|
||||
if (!step?.tool) throw new Error('No tool specified');
|
||||
const [name, version] = step.tool.split('@');
|
||||
@@ -58,25 +202,9 @@ export function findBestTool(step: BuildStep) {
|
||||
|
||||
export const TOOLS: ServerBuildTool[] = [
|
||||
Object.assign({}, LLVM_MOS_TOOL, { version: 'latest' }),
|
||||
Object.assign({}, OSCAR64_TOOL, { version: 'latest' }),
|
||||
];
|
||||
|
||||
interface ServerBuildTool {
|
||||
name: string;
|
||||
version: string;
|
||||
extensions: string[];
|
||||
archs: string[];
|
||||
platforms: string[];
|
||||
platform_configs: { [platform: string]: ServerBuildToolPlatformConfig };
|
||||
}
|
||||
|
||||
interface ServerBuildToolPlatformConfig {
|
||||
binpath?: string;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
libargs?: string[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class ServerBuildEnv {
|
||||
|
||||
@@ -105,7 +233,16 @@ export class ServerBuildEnv {
|
||||
if (file.path.match(/[\\\/]/)) {
|
||||
throw new Error(`Invalid file path: ${file.path}`);
|
||||
}
|
||||
await fs.promises.writeFile(path.join(this.sessionDir, file.path), file.data);
|
||||
let data = file.data;
|
||||
if (typeof data === 'string' && data.startsWith('data:base64,')) {
|
||||
// convert data URL to base64
|
||||
let parts = data.split(',');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(`Invalid data URL: ${data}`);
|
||||
}
|
||||
data = Buffer.from(parts[1], 'base64');
|
||||
}
|
||||
await fs.promises.writeFile(path.join(this.sessionDir, file.path), data);
|
||||
}
|
||||
|
||||
async build(step: WorkerBuildStep, platform?: string): Promise<WorkerResult> {
|
||||
@@ -124,7 +261,7 @@ export class ServerBuildEnv {
|
||||
let args = config.args.slice(0); //copy array
|
||||
let command = config.command;
|
||||
// replace $OUTFILE
|
||||
let outfile = path.join(this.sessionDir, 'a.out'); // TODO? a.out
|
||||
let outfile = path.join(this.sessionDir, config.outfile || 'a.out');
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
args[i] = args[i].replace(/\$OUTFILE/g, outfile);
|
||||
args[i] = args[i].replace(/\$WORKDIR/g, this.sessionDir);
|
||||
@@ -156,8 +293,10 @@ export class ServerBuildEnv {
|
||||
let childProcess = spawn(command, args, {
|
||||
shell: true,
|
||||
cwd: this.rootdir,
|
||||
env: { PATH: path.join(this.rootdir, config.binpath)
|
||||
} });
|
||||
env: {
|
||||
PATH: path.join(this.rootdir, config.binpath)
|
||||
}
|
||||
});
|
||||
let outputData = '';
|
||||
let errorData = '';
|
||||
// TODO?
|
||||
@@ -173,14 +312,13 @@ export class ServerBuildEnv {
|
||||
if (platform === 'debug') {
|
||||
resolve(this.processDebugInfo(step));
|
||||
} else {
|
||||
resolve(this.processOutput(step));
|
||||
resolve(this.tool.processOutput(step, outfile));
|
||||
}
|
||||
} else {
|
||||
errorData = replaceAll(errorData, this.sessionDir, '');
|
||||
errorData = replaceAll(errorData, this.rootdir, '');
|
||||
// remove folder paths
|
||||
errorData = errorData.replace(/(\/var\/folders\/.+?\/).+?:/g, '');
|
||||
let errorResult = await this.processErrors(step, errorData);
|
||||
let errorResult = await this.tool.processErrors(step, errorData);
|
||||
if (errorResult.errors.length === 0) {
|
||||
errorResult.errors.push({ line: 0, msg: `Build failed.\n\n${errorData}` });
|
||||
}
|
||||
@@ -190,18 +328,7 @@ export class ServerBuildEnv {
|
||||
});
|
||||
}
|
||||
|
||||
async processErrors(step: WorkerBuildStep, errorData: string): Promise<WorkerErrorResult> {
|
||||
let errors = [];
|
||||
// split errorData into lines
|
||||
let errorMatcher = makeErrorMatcher(errors, /([^:/]+):(\d+):(\d+):\s*(.+)/, 2, 4, step.path, 1);
|
||||
for (let line of errorData.split('\n')) {
|
||||
errorMatcher(line);
|
||||
}
|
||||
return { errors };
|
||||
}
|
||||
|
||||
async processOutput(step: WorkerBuildStep): Promise<WorkerResult> {
|
||||
let outfile = path.join(this.sessionDir, 'a.out');
|
||||
async processOutput(step: WorkerBuildStep, outfile: string): Promise<WorkerResult> {
|
||||
let output = await fs.promises.readFile(outfile, { encoding: 'base64' });
|
||||
return { output };
|
||||
}
|
||||
@@ -220,7 +347,7 @@ export class ServerBuildEnv {
|
||||
try {
|
||||
let result = await this.build(step);
|
||||
// did we succeed?
|
||||
if (isOutputResult(result)) {
|
||||
if (step.tool == 'llvm-mos' && isOutputResult(result)) {
|
||||
// do the debug info
|
||||
const debugInfo = await this.build(step, 'debug');
|
||||
if (isOutputResult(debugInfo)) {
|
||||
@@ -230,7 +357,7 @@ export class ServerBuildEnv {
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
return { errors: [{line:0, msg: err.toString()}] };
|
||||
return { errors: [{ line: 0, msg: err.toString() }] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ app.get('/info', (req: Request, res: Response) => {
|
||||
res.json({ tools: TOOLS });
|
||||
});
|
||||
|
||||
app.get('/test', async (req: Request, res: Response, next) => {
|
||||
app.get('/test1', async (req: Request, res: Response, next) => {
|
||||
// quick test of the build
|
||||
try {
|
||||
const updates: WorkerFileUpdate[] = [{ path: 'test.c', data: 'int main() { return 0; }' }];
|
||||
@@ -43,6 +43,19 @@ app.get('/test', async (req: Request, res: Response, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/test2', async (req: Request, res: Response, next) => {
|
||||
// quick test of the build
|
||||
try {
|
||||
const updates: WorkerFileUpdate[] = [{ path: 'test.c', data: 'int main() { return 0; }' }];
|
||||
const buildStep: WorkerBuildStep = { tool: 'oscar64', platform: 'c64', files: ['test.c'] };
|
||||
const env = new ServerBuildEnv(SERVER_ROOT, 'test', TOOLS[1]);
|
||||
const result = await env.compileAndLink(buildStep, updates);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/build', async (req: Request, res: Response, next) => {
|
||||
try {
|
||||
const updates: WorkerFileUpdate[] = req.body.updates;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { WASIFilesystem, WASIMemoryFilesystem, WASIRunner } from "../../common/wasi/wasishim";
|
||||
import { BuildStep, BuildStepResult, gatherFiles, staleFiles, store, putWorkFile } from "../builder";
|
||||
import { makeErrorMatcher, msvcErrorMatcher } from "../listingutils";
|
||||
import { loadWASIFilesystemZip } from "../wasiutils";
|
||||
import { loadWASMBinary } from "../wasmutils";
|
||||
|
||||
let oscar64_fs: WASIFilesystem | null = null;
|
||||
let wasiModule: WebAssembly.Module | null = null;
|
||||
|
||||
export async function compileOscar64(step: BuildStep): Promise<BuildStepResult> {
|
||||
const errors = [];
|
||||
const rootDir = "/root/";
|
||||
gatherFiles(step, { mainFilePath: "main.c" });
|
||||
const destpath = (step.path || "main.c").replace(/\.[^.]+$/, ".prg");
|
||||
console.log('destpath', destpath);
|
||||
if (staleFiles(step, [destpath])) {
|
||||
if (!oscar64_fs) {
|
||||
oscar64_fs = await loadWASIFilesystemZip("oscar64-fs.zip", "/root/");
|
||||
}
|
||||
if (!wasiModule) {
|
||||
wasiModule = new WebAssembly.Module(loadWASMBinary("oscar64"));
|
||||
}
|
||||
const wasi = new WASIRunner();
|
||||
wasi.initSync(wasiModule);
|
||||
wasi.fs.setParent(oscar64_fs);
|
||||
for (let file of step.files) {
|
||||
wasi.fs.putFile(rootDir + file, store.getFileData(file));
|
||||
}
|
||||
//wasi.addPreopenDirectory("include");
|
||||
wasi.addPreopenDirectory("/root");
|
||||
wasi.setArgs(["oscar64", "-v", "-g", "-i=/root", step.path]);
|
||||
try {
|
||||
wasi.run();
|
||||
} catch (e) {
|
||||
errors.push(e);
|
||||
}
|
||||
// TODO
|
||||
let stdout = wasi.fds[1].getBytesAsString();
|
||||
let stderr = wasi.fds[2].getBytesAsString();
|
||||
console.log('stdout', stdout);
|
||||
console.log('stderr', stderr);
|
||||
// (58, 17) : error 3001: Could not open source file. 'stdlib.c'
|
||||
const matcher = makeErrorMatcher(errors, /\((\d+),\s+(\d+)\)\s+: error (\d+): (.+)/, 1, 4, step.path);
|
||||
for (let line of stderr.split('\n')) {
|
||||
matcher(line);
|
||||
}
|
||||
if (errors.length) {
|
||||
return { errors };
|
||||
}
|
||||
const output = wasi.fs.getFile(destpath).getBytes();
|
||||
putWorkFile(destpath, output);
|
||||
return {
|
||||
output,
|
||||
errors,
|
||||
//listings,
|
||||
//symbolmap
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export async function buildRemote(step: BuildStep): Promise<BuildStepResult> {
|
||||
let path = step.files[i];
|
||||
let entry = store.workfs[path];
|
||||
// convert to base64
|
||||
let data = typeof entry.data === 'string' ? entry.data : btoa(byteArrayToString(entry.data));
|
||||
let data = typeof entry.data === 'string' ? entry.data : "data:base64," + btoa(byteArrayToString(entry.data));
|
||||
updates.push({ path, data });
|
||||
}
|
||||
// build the command
|
||||
|
||||
+10
-5
@@ -9,11 +9,8 @@ export function loadBlobSync(path: string) {
|
||||
return xhr.response;
|
||||
}
|
||||
|
||||
export async function loadWASIFilesystemZip(zippath: string) {
|
||||
export async function unzipWASIFilesystem(zipdata: ArrayBuffer, rootPath: string = "./") {
|
||||
const jszip = new JSZip();
|
||||
const path = '../../src/worker/fs/' + zippath;
|
||||
const zipdata = loadBlobSync(path);
|
||||
console.log(zippath, zipdata);
|
||||
await jszip.loadAsync(zipdata);
|
||||
let fs = new WASIMemoryFilesystem();
|
||||
let promises = [];
|
||||
@@ -21,7 +18,7 @@ export async function loadWASIFilesystemZip(zippath: string) {
|
||||
if (zipEntry.dir) {
|
||||
fs.putDirectory(relativePath);
|
||||
} else {
|
||||
let path = './' + relativePath;
|
||||
let path = rootPath + relativePath;
|
||||
let prom = zipEntry.async("uint8array").then((data) => {
|
||||
fs.putFile(path, data);
|
||||
});
|
||||
@@ -31,3 +28,11 @@ export async function loadWASIFilesystemZip(zippath: string) {
|
||||
await Promise.all(promises);
|
||||
return fs;
|
||||
}
|
||||
|
||||
export async function loadWASIFilesystemZip(zippath: string, rootPath: string = "./") {
|
||||
const jszip = new JSZip();
|
||||
const path = '../../src/worker/fs/' + zippath;
|
||||
const zipdata = loadBlobSync(path);
|
||||
console.log(zippath, zipdata);
|
||||
return unzipWASIFilesystem(zipdata, rootPath);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as remote from './tools/remote'
|
||||
import * as acme from './tools/acme'
|
||||
import * as cc7800 from './tools/cc7800'
|
||||
import * as bataribasic from './tools/bataribasic'
|
||||
import * as oscar64 from './tools/oscar64'
|
||||
import { PLATFORM_PARAMS } from "./platforms";
|
||||
|
||||
export const TOOLS = {
|
||||
@@ -54,6 +55,7 @@ export const TOOLS = {
|
||||
'cc7800': cc7800.compileCC7800,
|
||||
'armtcc': arm.compileARMTCC,
|
||||
'armtcclink': arm.linkARMTCC,
|
||||
'oscar64': oscar64.compileOscar64,
|
||||
}
|
||||
|
||||
export const TOOL_PRELOADFS = {
|
||||
|
||||
Reference in New Issue
Block a user