mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
worker: converted merlin32/nesasm to WASI
This commit is contained in:
1 parent
66376e5900
commit
2e8202fd87
7 files changed
+156
-5616
No files matched your search
+71
-44
@@ -1,13 +1,15 @@
|
||||
|
||||
import { WorkerError, CodeListingMap } from "../../common/workertypes";
|
||||
import { BuildStep, BuildStepResult, populateFiles, putWorkFile, anyTargetChanged, gatherFiles, staleFiles } from "../builder";
|
||||
import { parseListing, makeErrorMatcher } from "../listingutils";
|
||||
import { loadNative, emglobal, moduleInstFn, execMain, print_fn } from "../wasmutils";
|
||||
import { BuildStep, BuildStepResult, populateFiles, putWorkFile, anyTargetChanged, gatherFiles, staleFiles, store } from "../builder";
|
||||
import { parseListing, makeErrorMatcher, re_crlf } from "../listingutils";
|
||||
import { loadNative, emglobal, moduleInstFn, execMain, print_fn, loadWASMBinary } from "../wasmutils";
|
||||
import { EmscriptenModule } from "../wasmutils";
|
||||
import { WASIRunner } from "../../common/wasi/wasishim";
|
||||
|
||||
// http://www.nespowerpak.com/nesasm/
|
||||
let nesasmWasiModule: WebAssembly.Module | null = null;
|
||||
|
||||
export function assembleNESASM(step: BuildStep): BuildStepResult {
|
||||
loadNative("nesasm");
|
||||
var re_filename = /\#\[(\d+)\]\s+(\S+)/;
|
||||
var re_insn = /\s+(\d+)\s+([0-9A-F]+):([0-9A-F]+)/;
|
||||
var re_error = /\s+(.+)/;
|
||||
@@ -38,23 +40,36 @@ export function assembleNESASM(step: BuildStep): BuildStepResult {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var Module: EmscriptenModule = emglobal.nesasm({
|
||||
instantiateWasm: moduleInstFn('nesasm'),
|
||||
noInitialRun: true,
|
||||
print: match_fn
|
||||
});
|
||||
var FS = Module.FS;
|
||||
populateFiles(step, FS, {
|
||||
gatherFiles(step, {
|
||||
mainFilePath: 'main.a'
|
||||
});
|
||||
var binpath = step.prefix + '.nes';
|
||||
var lstpath = step.prefix + '.lst';
|
||||
var sympath = step.prefix + '.fns';
|
||||
execMain(step, Module, [step.path, '-s', "-l", "2"]);
|
||||
if (!nesasmWasiModule) {
|
||||
nesasmWasiModule = new WebAssembly.Module(loadWASMBinary("nesasm"));
|
||||
}
|
||||
const wasi = new WASIRunner();
|
||||
wasi.initSync(nesasmWasiModule);
|
||||
for (let file of step.files) {
|
||||
wasi.fs.putFile("./" + file, store.getFileData(file));
|
||||
}
|
||||
wasi.addPreopenDirectory(".");
|
||||
wasi.setArgs(['nesasm', step.path, '-s', "-l", "2"]);
|
||||
try {
|
||||
wasi.run();
|
||||
} catch (e) {
|
||||
errors.push({ line: 0, msg: "" + e });
|
||||
}
|
||||
const stdout = wasi.fds[1].getBytesAsString();
|
||||
for (let line of stdout.split(re_crlf)) {
|
||||
match_fn(line);
|
||||
}
|
||||
// parse main listing, get errors and listings for each file
|
||||
var listings: CodeListingMap = {};
|
||||
var alst;
|
||||
try {
|
||||
var alst = FS.readFile(lstpath, { 'encoding': 'utf8' });
|
||||
alst = wasi.fs.getFile("./" + lstpath).getBytesAsString();
|
||||
// 16 00:C004 8E 17 40 STX $4017 ; disable APU frame IRQ
|
||||
var asmlines = parseListing(alst, /^\s*(\d+)\s+([0-9A-F]+):([0-9A-F]+)\s+([0-9A-F ]+?) (.*)/i, 1, 3, 4);
|
||||
putWorkFile(lstpath, alst);
|
||||
@@ -70,9 +85,9 @@ export function assembleNESASM(step: BuildStep): BuildStepResult {
|
||||
}
|
||||
// read binary rom output and symbols
|
||||
var aout, asym;
|
||||
aout = FS.readFile(binpath);
|
||||
aout = wasi.fs.getFile("./" + binpath).getBytes();
|
||||
try {
|
||||
asym = FS.readFile(sympath, { 'encoding': 'utf8' });
|
||||
asym = wasi.fs.getFile("./" + sympath).getBytesAsString();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
errors.push({ line: 0, msg: "No symbol table generated, maybe missing ENDM or segment overflow?" });
|
||||
@@ -116,50 +131,62 @@ Line | # File Line | Line Type | MX | Reloc | Size | Address Object
|
||||
=> Creating Output file 'pcs.bin_S01__Output.txt'
|
||||
|
||||
*/
|
||||
let merlin32WasiModule: WebAssembly.Module | null = null;
|
||||
|
||||
export function assembleMerlin32(step: BuildStep): BuildStepResult {
|
||||
loadNative("merlin32");
|
||||
var errors = [];
|
||||
var lstfiles = [];
|
||||
gatherFiles(step, { mainFilePath: "main.lnk" });
|
||||
var objpath = step.prefix + ".bin";
|
||||
if (staleFiles(step, [objpath])) {
|
||||
var args = ['-v', step.path];
|
||||
var merlin32: EmscriptenModule = emglobal.merlin32({
|
||||
instantiateWasm: moduleInstFn('merlin32'),
|
||||
noInitialRun: true,
|
||||
print: (s: string) => {
|
||||
var m = /\s*=>\s*Creating Output file '(.+?)'/.exec(s);
|
||||
if (m) {
|
||||
lstfiles.push(m[1]);
|
||||
}
|
||||
var errpos = s.indexOf('Error');
|
||||
if (errpos >= 0) {
|
||||
s = s.slice(errpos + 6).trim();
|
||||
var mline = /\bline (\d+)\b/.exec(s);
|
||||
var mpath = /\bfile '(.+?)'/.exec(s);
|
||||
errors.push({
|
||||
line: parseInt(mline[1]) || 0,
|
||||
msg: s,
|
||||
path: mpath[1] || step.path,
|
||||
});
|
||||
}
|
||||
},
|
||||
printErr: print_fn,
|
||||
});
|
||||
var FS = merlin32.FS;
|
||||
populateFiles(step, FS);
|
||||
execMain(step, merlin32, args);
|
||||
if (!merlin32WasiModule) {
|
||||
merlin32WasiModule = new WebAssembly.Module(loadWASMBinary("merlin32"));
|
||||
}
|
||||
const wasi = new WASIRunner();
|
||||
wasi.initSync(merlin32WasiModule);
|
||||
for (let file of step.files) {
|
||||
wasi.fs.putFile("./" + file, store.getFileData(file));
|
||||
}
|
||||
wasi.addPreopenDirectory(".");
|
||||
wasi.setArgs(['merlin32', ...args]);
|
||||
try {
|
||||
wasi.run();
|
||||
} catch (e) {
|
||||
errors.push({ line: 0, msg: "" + e });
|
||||
}
|
||||
const stdout = wasi.fds[1].getBytesAsString();
|
||||
const stderr = wasi.fds[2].getBytesAsString();
|
||||
for (let s of stdout.split(re_crlf)) {
|
||||
var m = /\s*=>\s*Creating Output file '(.+?)'/.exec(s);
|
||||
if (m) {
|
||||
lstfiles.push(m[1]);
|
||||
}
|
||||
var errpos = s.indexOf('Error');
|
||||
if (errpos >= 0) {
|
||||
s = s.slice(errpos + 6).trim();
|
||||
var mline = /\bline (\d+)\b/.exec(s);
|
||||
var mpath = /\bfile '(.+?)'/.exec(s);
|
||||
errors.push({
|
||||
line: mline ? parseInt(mline[1]) : 0,
|
||||
msg: s,
|
||||
path: mpath ? mpath[1] : step.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (stderr)
|
||||
console.log(stderr);
|
||||
if (errors.length)
|
||||
return { errors: errors };
|
||||
|
||||
var errout = null;
|
||||
try {
|
||||
errout = FS.readFile("error_output.txt", { encoding: 'utf8' });
|
||||
errout = wasi.fs.getFile("./error_output.txt").getBytesAsString();
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
var objout = FS.readFile(objpath, { encoding: 'binary' });
|
||||
var objout = wasi.fs.getFile("./" + objpath).getBytes();
|
||||
putWorkFile(objpath, objout);
|
||||
if (!anyTargetChanged(step, [objpath]))
|
||||
return;
|
||||
@@ -168,7 +195,7 @@ export function assembleMerlin32(step: BuildStep): BuildStepResult {
|
||||
var segments = [];
|
||||
var listings: CodeListingMap = {};
|
||||
lstfiles.forEach((lstfn) => {
|
||||
var lst = FS.readFile(lstfn, { encoding: 'utf8' }) as string;
|
||||
var lst = wasi.fs.getFile("./" + lstfn).getBytesAsString() as string;
|
||||
lst.split('\n').forEach((line) => {
|
||||
var toks = line.split(/\s*\|\s*/);
|
||||
if (toks && toks[6]) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
|
||||
var nesasm = (function() {
|
||||
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
|
||||
return (
|
||||
function(nesasm) {
|
||||
nesasm = nesasm || {};
|
||||
|
||||
var Module=typeof nesasm!=="undefined"?nesasm:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}Module["arguments"]=[];Module["thisProgram"]="./this.program";Module["quit"]=function(status,toThrow){throw toThrow};Module["preRun"]=[];Module["postRun"]=[];var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_HAS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_HAS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_NODE=ENVIRONMENT_HAS_NODE&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}else{return scriptDirectory+path}}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_NODE){scriptDirectory=__dirname+"/";var nodeFS;var nodePath;read_=function shell_read(filename,binary){var ret;if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);ret=nodeFS["readFileSync"](filename);return binary?ret:ret.toString()};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}Module["arguments"]=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);Module["quit"]=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof quit==="function"){Module["quit"]=function(status){quit(status)}}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)};setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||(typeof console!=="undefined"?console.log.bind(console):typeof print!=="undefined"?print:null);var err=Module["printErr"]||(typeof printErr!=="undefined"?printErr:typeof console!=="undefined"&&console.warn.bind(console)||out);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=undefined;var STACK_ALIGN=16;function dynamicAlloc(size){var ret=HEAP32[DYNAMICTOP_PTR>>2];var end=ret+size+15&-16;if(end>_emscripten_get_heap_size()){abort()}HEAP32[DYNAMICTOP_PTR>>2]=end;return ret}function getNativeTypeSize(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return 4}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0,"getNativeTypeSize invalid bits "+bits+", type "+type);return bits/8}else{return 0}}}}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}var asm2wasmImports={"f64-rem":function(x,y){return x%y},"debugger":function(){debugger}};var jsCallStartIndex=1;var functionPointers=new Array(0);function convertJsFunctionToWasm(func,sig){var typeSection=[1,0,1,96];var sigRet=sig.slice(0,1);var sigParam=sig.slice(1);var typeCodes={"i":127,"j":126,"f":125,"d":124};typeSection.push(sigParam.length);for(var i=0;i<sigParam.length;++i){typeSection.push(typeCodes[sigParam[i]])}if(sigRet=="v"){typeSection.push(0)}else{typeSLine truncated
|
||||
|
||||
|
||||
return nesasm
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = nesasm;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return nesasm; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["nesasm"] = nesasm;
|
||||
|
||||
Binary file not shown.
@@ -345,6 +345,88 @@ describe('Worker', function() {
|
||||
it('should compile CC65 flags', function(done) {
|
||||
compile('cc65', '#define CC65_FLAGS -Or,-g,-j\nint main() {\nint x=1;\nreturn x+2;\n}', 'apple2', done, 416+58, 3);
|
||||
});
|
||||
|
||||
// merlin32 is driven by a .lnk link file that names the sources to assemble
|
||||
var MERLIN32_LNK = ' TYP $06\n' +
|
||||
' DSK main.bin\n' +
|
||||
' ORG $2000\n' +
|
||||
' ASM hello.asm\n';
|
||||
function merlin32Msgs(asm) {
|
||||
return [{
|
||||
updates: [
|
||||
{path:'main.lnk', data:MERLIN32_LNK},
|
||||
{path:'hello.asm', data:asm}
|
||||
],
|
||||
buildsteps: [
|
||||
{path:'main.lnk', platform:'apple2', tool:'merlin32', mainfile:true,
|
||||
files:['main.lnk', 'hello.asm']}
|
||||
]
|
||||
}];
|
||||
}
|
||||
it('should assemble Merlin32', function(done) {
|
||||
var asm = ' ORG $2000\n' +
|
||||
'START LDA #$C1\n' +
|
||||
' STA $0400\n' +
|
||||
' RTS\n';
|
||||
doBuild(merlin32Msgs(asm), function(err, msg) {
|
||||
if (err) return done(err);
|
||||
try {
|
||||
// LDA #$C1 / STA $0400 / RTS
|
||||
assert.deepEqual(Array.from(msg.output), [0xa9, 0xc1, 0x8d, 0x00, 0x04, 0x60]);
|
||||
var lst = msg.listings['hello.asm'];
|
||||
assert.equal(lst.lines[1].line, 2);
|
||||
assert.equal(lst.lines[1].offset, 0x2000);
|
||||
assert.equal(lst.lines[2].offset, 0x2002);
|
||||
done();
|
||||
} catch (e) { done(e); }
|
||||
}, 6, [5], 0);
|
||||
});
|
||||
it('should NOT assemble Merlin32 (syntax error)', function(done) {
|
||||
var asm = ' ORG $2000\n' +
|
||||
'START LDA #$C1\n' +
|
||||
' BOGUS $0400\n' +
|
||||
' RTS\n';
|
||||
doBuild(merlin32Msgs(asm), function(err, msg) {
|
||||
if (err) return done(err);
|
||||
try {
|
||||
assert.equal(msg.errors[0].line, 3);
|
||||
assert.equal(msg.errors[0].path, 'hello.asm');
|
||||
done();
|
||||
} catch (e) { done(e); }
|
||||
}, 0, 0, 1, {ignoreErrorPath:true});
|
||||
});
|
||||
|
||||
// nesasm needs the iNES header directives and a bank/org before any code
|
||||
var NESASM_HEADER = '\t.inesprg 1\n\t.ineschr 0\n\t.inesmap 0\n\t.inesmir 0\n';
|
||||
it('should assemble NESASM', function(done) {
|
||||
var asm = NESASM_HEADER +
|
||||
'\t.list\n\t.bank 0\n\t.org $8000\n' +
|
||||
'Start:\n\tlda #$c1\n\tsta $0400\n\trts\n' +
|
||||
'\t.bank 1\n\t.org $fffa\n\t.dw 0,Start,0\n';
|
||||
compile('nesasm', asm, 'nes', function(err, msg) {
|
||||
if (err) return done(err);
|
||||
try {
|
||||
assert.deepEqual(Array.from(msg.output.slice(0,4)), [0x4e, 0x45, 0x53, 0x1a]); // "NES\x1a"
|
||||
assert.equal(msg.symbolmap['Start'], 0x8000);
|
||||
var lst = msg.listings['src.lst'];
|
||||
assert.equal(lst.lines[1].offset, 0x8000);
|
||||
assert.equal(lst.lines[1].insns, 'A9 C1');
|
||||
assert.equal(lst.lines[2].offset, 0x8002);
|
||||
done();
|
||||
} catch (e) { done(e); }
|
||||
}, 16400, [5], 0);
|
||||
});
|
||||
it('should NOT assemble NESASM (unknown instruction)', function(done) {
|
||||
var asm = NESASM_HEADER + '\t.bank 0\n\t.org $8000\n\tbogus #$c1\n\trts\n';
|
||||
compile('nesasm', asm, 'nes', function(err, msg) {
|
||||
if (err) return done(err);
|
||||
try {
|
||||
assert.equal(msg.errors[0].line, 7);
|
||||
assert.ok(/Unknown instruction/.test(msg.errors[0].msg));
|
||||
done();
|
||||
} catch (e) { done(e); }
|
||||
}, 0, 0, 1);
|
||||
});
|
||||
/*
|
||||
it('should compile gameboy', function(done) {
|
||||
compile('sdcc', '//#link "gb/sfr.sgb"\n//#link "gb/crt0.sgb"\nvoid main(void){}\n', 'gb', done, 416+58, 3);
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('Tool metadata registry', function () {
|
||||
it('covers the previously-scattered editor styles and help URLs', function () {
|
||||
// spot-check ids that used to live in TOOL_TO_SOURCE_STYLE / TOOL_TO_HELPURL
|
||||
for (let id of ['dasm', 'acme', 'cc65', 'ca65', 'sdcc', 'cmoc', 'zmac',
|
||||
'nesasm', 'bataribasic', 'fastbasic', 'wiz', 'silice', 'ecs', 'dialog']) {
|
||||
'nesasm', 'merlin32', 'bataribasic', 'fastbasic', 'wiz', 'silice', 'ecs', 'dialog']) {
|
||||
assert.ok(TOOL_META[id], `expected tool '${id}' in registry`);
|
||||
}
|
||||
assert.strictEqual(TOOL_META['cc65'].editorStyle, 'text/x-csrc');
|
||||
@@ -95,6 +95,8 @@ describe('Tool metadata registry', function () {
|
||||
assert.deepStrictEqual(deps('!src "foo.acme"\n', 'acme'), ['foo.acme']);
|
||||
assert.deepStrictEqual(deps('%% #include "foo.dg"\n', 'dialog'), ['foo.dg']);
|
||||
assert.deepStrictEqual(deps(' USE foo.xasm\n', 'xasm6809'), ['foo.xasm']);
|
||||
// merlin32 link files name their sources with ASM
|
||||
assert.deepStrictEqual(deps(' ASM hello.asm\n', 'merlin32'), ['hello.asm']);
|
||||
// ecs uses both its own import and the shared .include directive
|
||||
assert.deepStrictEqual(deps('.include "vcs.h"\nimport "lib.ecs"\n', 'ecs'),
|
||||
['vcs.h', 'lib.ecs']);
|
||||
|
||||
Reference in new issue
Block a user