1
0
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:
sehugg
2024-10-26 22:54:43 +00:00
parent debfa9ec3e
commit 686be02a7f
121 changed files with 1754 additions and 295 deletions
+3
View File
@@ -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
}
+56 -6
View File
@@ -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; },
}
}
}
+1
View File
@@ -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
View File
@@ -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'},
+25
View File
@@ -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());
});
});
*/
+295
View File
@@ -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
View File
@@ -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() }] };
}
}
}
+14 -1
View File
@@ -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;
+59
View File
@@ -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
};
}
}
+1 -1
View File
@@ -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
View File
@@ -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);
}
+2
View File
@@ -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 = {