From 5081307d81c3c616148a22a9de82b55969d16684 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Wed, 9 Feb 2022 07:39:41 -0600 Subject: [PATCH] ecs: bin pack temp vars --- src/common/ecs/binpack.ts | 96 +++++++++++++++++--------------------- src/common/ecs/compiler.ts | 4 +- src/common/ecs/ecs.ts | 65 ++++++++++++-------------- src/test/testecs.ts | 43 +++++++++++------ test/ecs/vcs1.txt | 5 +- 5 files changed, 110 insertions(+), 103 deletions(-) diff --git a/src/common/ecs/binpack.ts b/src/common/ecs/binpack.ts index d070d9e0..8b92517c 100644 --- a/src/common/ecs/binpack.ts +++ b/src/common/ecs/binpack.ts @@ -22,7 +22,7 @@ export interface Box { export interface PlacedBox extends Box { bin: Bin; - parent: Box; + parents: Box[]; place: BoxPlacement; } @@ -30,33 +30,22 @@ function boxesIntersect(a: Box, b: Box) : boolean { return !(b.left >= a.right || b.right <= a.left || b.top >= a.bottom || b.bottom <= a.top); } -function getBoxPlacements(b: PlacedBox) { - let posns : BoxPlacement[]; - let snugw = b.right - b.left == b.parent.right - b.parent.left; - let snugh = b.bottom - b.top == b.parent.bottom - b.parent.top; - if (snugw && snugh) { - posns = [BoxPlacement.TopLeft]; - } else if (snugw && !snugh) { - posns = [BoxPlacement.TopLeft, BoxPlacement.BottomLeft]; - } else if (!snugw && snugh) { - posns = [BoxPlacement.TopLeft, BoxPlacement.TopRight]; - } else { - posns = [BoxPlacement.TopLeft, BoxPlacement.TopRight, - BoxPlacement.BottomLeft, BoxPlacement.BottomRight]; - } - return posns; +function boxesContain(a: Box, b: Box) : boolean { + return b.left >= a.left && b.top >= a.top && b.right <= a.right && b.bottom <= a.bottom; } export class Bin { boxes: Box[] = []; free: Box[] = []; + extents: Box = {left:0,top:0,right:0,bottom:0}; constructor(public readonly binbounds: Box) { this.free.push(binbounds); } - getBoxes(bounds: Box, limit: number) : Box[] { + getBoxes(bounds: Box, limit: number, boxes?: Box[]) : Box[] { let result = []; - for (let box of this.boxes) { + if (!boxes) boxes = this.boxes; + for (let box of boxes) { //console.log(bounds, box, boxesIntersect(bounds, box)) if (boxesIntersect(bounds, box)) { result.push(box); @@ -66,7 +55,7 @@ export class Bin { return result; } fits(b: Box) { - if (!boxesIntersect(this.binbounds, b)) { + if (!boxesContain(this.binbounds, b)) { if (debug) console.log('out of bounds!', b.left,b.top,b.right,b.bottom); return false; } @@ -90,6 +79,7 @@ export class Bin { let score = 1 / (1 + dx + dy); if (score > bestscore) { best = f; + if (score == 1) break; } } } @@ -108,56 +98,51 @@ export class Bin { let score = 1 / (1 + box.left + box.top); if (score > bestscore) { best = f; + if (score == 1) break; } } } return best; } add(b: PlacedBox) { - if (debug) console.log('added', b.left,b.top,b.right,b.bottom); + if (debug) console.log('add', b.left,b.top,b.right,b.bottom); if (!this.fits(b)) { //console.log('collided with', this.getBoxes(b, 1)); throw new Error(`bad fit ${b.left} ${b.top} ${b.right} ${b.bottom}`) } // add box to list this.boxes.push(b); + this.extents.right = Math.max(this.extents.right, b.right); + this.extents.bottom = Math.max(this.extents.bottom, b.bottom); // delete bin - let i = this.free.indexOf(b.parent); - if (i < 0) throw new Error('cannot find parent'); - if (debug) console.log('removed',b.parent.left,b.parent.top,b.parent.right,b.parent.bottom); - this.free.splice(i, 1); - // split into new bins - switch (b.place) { - case BoxPlacement.TopLeft: - this.addFree( { top: b.top, left: b.right, bottom: b.bottom, right: b.parent.right } ); - this.addFree( { top: b.bottom, left: b.parent.left, bottom: b.parent.bottom, right: b.parent.right } ); - break; - case BoxPlacement.TopRight: - this.addFree( { top: b.top, left: b.parent.left, bottom: b.bottom, right: b.left } ); - this.addFree( { top: b.bottom, left: b.parent.left, bottom: b.parent.bottom, right: b.parent.right } ); - break; - case BoxPlacement.BottomLeft: - this.addFree( { top: b.parent.top, left: b.parent.left, bottom: b.top, right: b.parent.right } ); - this.addFree( { top: b.top, left: b.right, bottom: b.parent.bottom, right: b.parent.right } ); - break; - case BoxPlacement.BottomRight: - this.addFree( { top: b.parent.top, left: b.parent.left, bottom: b.top, right: b.parent.right } ); - this.addFree( { top: b.top, left: b.parent.left, bottom: b.parent.bottom, right: b.left } ); - break; + for (let p of b.parents) { + let i = this.free.indexOf(p); + if (i < 0) throw new Error('cannot find parent'); + if (debug) console.log('removed',p.left,p.top,p.right,p.bottom); + this.free.splice(i, 1); + // split into new bins + // make long columns + this.addFree(p.left, p.top, b.left, p.bottom); + this.addFree(b.right, p.top, p.right, p.bottom); + // make top caps + this.addFree(b.left, p.top, b.right, b.top); + this.addFree(b.left, b.bottom, b.right, p.bottom); } } - addFree(b: Box) { - if (b.bottom > b.top && b.right > b.left) { + addFree(left: number, top: number, right: number, bottom: number) { + if (bottom > top && right > left) { + let b = { left, top, right, bottom }; if (debug) console.log('free',b.left,b.top,b.right,b.bottom); this.free.push(b); } - // TODO: merge free boxes + // TODO: merge free boxes? } } export class Packer { bins : Bin[] = []; boxes : BoxConstraints[] = []; + defaultPlacement : BoxPlacement = BoxPlacement.TopLeft; //TODO pack() : boolean { for (let bc of this.boxes) { @@ -170,13 +155,15 @@ export class Packer { } bestPlacement(b: BoxConstraints) : PlacedBox | null { for (let bin of this.bins) { - let place : BoxPlacement = BoxPlacement.TopLeft; //TODO let parent = bin.bestFit(b); + let approx = false; if (!parent) { parent = bin.anyFit(b); + approx = true; if (debug) console.log('anyfit',parent?.left,parent?.top); } if (parent) { + let place = this.defaultPlacement; let box = { left: parent.left, top: parent.top, @@ -191,16 +178,21 @@ export class Packer { box.top = b.top; box.bottom = b.top + b.height; } - if (debug) console.log('place',box.left,box.top,box.right,box.bottom,parent?.left,parent?.top); - /* if (place == BoxPlacement.BottomLeft || place == BoxPlacement.BottomRight) { - box.top = parent.bottom - (box.bottom - box.top); + let h = box.bottom - box.top; + box.top = parent.bottom - h; + box.bottom = parent.bottom; } if (place == BoxPlacement.TopRight || place == BoxPlacement.BottomRight) { - box.left = parent.right - (box.right - box.left); + let w = box.right - box.left; + box.left = parent.right - w; + box.right = parent.right; } - */ - return { parent, place, bin, ...box }; + if (debug) console.log('place',box.left,box.top,box.right,box.bottom,parent?.left,parent?.top); + let parents = [parent]; + // if approx match, might overlap multiple free boxes + if (approx) parents = bin.getBoxes(box, 100, bin.free); + return { parents, place, bin, ...box }; } } if (debug) console.log('cannot place!', b.left,b.top,b.width,b.height); diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index 30b95465..5089dac8 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -226,10 +226,10 @@ export class ECSCompiler extends Tokenizer { // TODO: unused events? const event = this.expectIdent().str; this.expectToken('do'); - const all_modifiers = ['cyclecritical','asc','desc']; // TODO - const modifiers = this.parseModifiers(all_modifiers); // TODO: include modifiers in error msg const select = this.expectTokens(SELECT_TYPE).str as SelectType; // TODO: type check? + const all_modifiers = ['cyclecritical','asc','desc']; // TODO + const modifiers = this.parseModifiers(all_modifiers); let query = undefined; let join = undefined; if (select == 'once') { diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index 77441c5d..f37e4f80 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -339,6 +339,12 @@ export class Dialect_CA65 { else return `.byte (${b.symbol} >> ${b.bitofs})` // TODO? } } + tempLabel(sys: System) { + return `${sys.name}__tmp`; + } + equate(symbol: string, value: string): string { + return `${symbol} = ${value}`; + } } // TODO: merge with Dialect? @@ -372,6 +378,7 @@ class CodeSegment { class DataSegment { symbols: { [sym: string]: number } = {}; + equates: { [sym: string]: string } = {}; ofs2sym = new Map(); fieldranges: { [cfname: string]: FieldArray } = {}; size: number = 0; @@ -397,6 +404,7 @@ class DataSegment { } } dump(file: SourceFileExport, dialect: Dialect_CA65) { + // TODO: fewer lines for (let i = 0; i < this.size; i++) { let syms = this.ofs2sym.get(i); if (syms) { @@ -405,6 +413,9 @@ class DataSegment { } file.line(dialect.byte(this.initdata[i])); } + for (let [symbol,value] of Object.entries(this.equates)) { + file.line(dialect.equate(symbol, value)); + } } // TODO: move cfname functions in here too getFieldRange(component: ComponentType, fieldName: string) { @@ -699,7 +710,6 @@ class ActionEval { if (tempinc < 0 || tempinc >= this.sys.tempbytes) throw new ECSError(`this system only has ${this.sys.tempbytes} locals`, this.action); this.scope.updateTempLiveness(this.sys); return `${this.tmplabel}+${tempinc}`; - //return `TEMP+${this.scope.tempOffset}+${tempinc}`; } __bss_init(args: string[]) { return this.scope.allocateInitData(this.scope.bss); @@ -846,9 +856,6 @@ export class EntityScope implements SourceLocated { code = new CodeSegment(); componentsInScope = new Set(); eventSeq = 0; - tempOffset = 0; - tempSize = 0; - maxTempBytes = 0; resources = new Set(); state = new ActionCPUState(); isDemo = false; @@ -1088,13 +1095,7 @@ export class EntityScope implements SourceLocated { //s += `\n; event ${event}\n`; systems = systems.filter(s => this.systems.includes(s)); for (let sys of systems) { - // TODO: does this work if multiple actions? - // TODO: share storage - //if (sys.tempbytes) this.allocateTempBytes(sys.tempbytes); - let tmplabel = `${sys.name}_tmp`; - if (sys.tempbytes) this.bss.allocateBytes(tmplabel, sys.tempbytes); - //this.allocateTempBytes(1); - let numActions = 0; + let tmplabel = this.dialect.tempLabel(sys); for (let action of sys.actions) { if (action.event == event) { // TODO: use Tokenizer so error msgs are better @@ -1108,19 +1109,11 @@ export class EntityScope implements SourceLocated { // TODO: check that this happens once? codeeval.end(); this.getActionStats(action).callcount++; - numActions++; } } - // TODO: if (sys.tempbytes && numActions) this.allocateTempBytes(-sys.tempbytes); } return s; } - allocateTempBytes(n: number) { - if (n > 0) this.tempOffset = this.tempSize; - this.tempSize += n; - this.maxTempBytes = Math.max(this.tempSize, this.maxTempBytes); - if (n < 0) this.tempOffset = this.tempSize; - } getSystemStats(sys: System) : SystemStats { let stats = this.sysstats.get(sys); if (!stats) { @@ -1151,10 +1144,11 @@ export class EntityScope implements SourceLocated { this.resources.add(symbol); return symbol; } - allocateTempVars() { + private allocateTempVars() { let pack = new Packer(); - let maxTempBytes = 128; // TODO - pack.bins.push(new Bin({ left:0, top:0, bottom: this.eventSeq+1, right: maxTempBytes })); + let maxTempBytes = 128 - this.bss.size; // TODO: multiple data segs + let bssbin = new Bin({ left:0, top:0, bottom: this.eventSeq+1, right: maxTempBytes }); + pack.bins.push(bssbin); for (let sys of this.systems) { let stats = this.getSystemStats(sys); if (sys.tempbytes && stats.tempstartseq && stats.tempendseq) { @@ -1170,19 +1164,23 @@ export class EntityScope implements SourceLocated { } if (!pack.pack()) console.log('cannot pack temporary local vars'); // TODO console.log('tempvars', pack); - for (let b of pack.boxes) { - console.log((b as any).sys.name, b.box); + if (bssbin.extents.right > 0) { + this.bss.allocateBytes('TEMP', bssbin.extents.right); + for (let b of pack.boxes) { + let sys : System = (b as any).sys; + console.log(sys.name, b.box?.left); + this.bss.equates[this.dialect.tempLabel(sys)] = `TEMP+${b.box?.left}`; + } } } - analyzeEntities() { + private analyzeEntities() { this.buildSegments(); this.allocateSegment(this.bss, false); this.allocateSegment(this.rodata, true); this.allocateROData(this.rodata); } - generateCode() { + private generateCode() { let isMainScope = this.parent == null; - this.tempOffset = this.maxTempBytes = 0; let start; let initsys = this.em.getSystemByName('Init'); if (isMainScope && initsys) { @@ -1197,7 +1195,6 @@ export class EntityScope implements SourceLocated { this.code.addCodeFragment(code); } //this.showStats(); - this.allocateTempVars(); } showStats() { for (let sys of this.systems) { @@ -1207,16 +1204,10 @@ export class EntityScope implements SourceLocated { console.log(action.event, this.getActionStats(action)); } } - dump(file: SourceFileExport) { - this.analyzeEntities(); - this.generateCode(); - this.dumpCodeTo(file); - } private dumpCodeTo(file: SourceFileExport) { let dialect = this.dialect; file.line(dialect.startScope(this.name)); file.line(dialect.segment(`${this.name}_DATA`, 'bss')); - if (this.maxTempBytes) this.bss.allocateBytes('TEMP', this.maxTempBytes); this.bss.dump(file, dialect); file.line(dialect.segment(`${this.name}_RODATA`, 'rodata')); this.rodata.dump(file, dialect); @@ -1229,6 +1220,12 @@ export class EntityScope implements SourceLocated { } file.line(dialect.endScope(this.name)); } + dump(file: SourceFileExport) { + this.analyzeEntities(); + this.generateCode(); + this.allocateTempVars(); + this.dumpCodeTo(file); + } } export class EntityManager { diff --git a/src/test/testecs.ts b/src/test/testecs.ts index 62feb4b3..35649752 100644 --- a/src/test/testecs.ts +++ b/src/test/testecs.ts @@ -284,8 +284,6 @@ function testECS() { //console.log(em.archetypesMatching({ include:['xpos','ypos']})[0]) - root.analyzeEntities(); - root.generateCode(); let src = new SourceFileExport(); root.dump(src); //console.log(src.toString()); @@ -372,26 +370,28 @@ describe('Compiler', function() { let testdir = './test/ecs/'; let files = readdirSync(testdir).filter(f => f.endsWith('.ecs')); files.forEach((ecsfn) => { - let goodfn = ecsfn.replace('.ecs','.txt') - let srcpath = testdir + ecsfn; - let destpath = testdir + goodfn; + let asmfn = ecsfn.replace('.ecs','.asm'); + let goodfn = ecsfn.replace('.ecs','.txt'); + let ecspath = testdir + ecsfn; + let goodpath = testdir + goodfn; let dialect = new Dialect_CA65(); let em = new EntityManager(dialect); - em.mainPath = srcpath; + em.mainPath = ecspath; let compiler = new ECSCompiler(em); compiler.getImportFile = (path: string) => { return readFileSync(testdir + path, 'utf-8'); } - let code = readFileSync(srcpath, 'utf-8'); - compiler.parseFile(code, srcpath); + let code = readFileSync(ecspath, 'utf-8'); + compiler.parseFile(code, ecspath); let out = new SourceFileExport(); em.exportToFile(out); let outtxt = out.toString(); - let goodtxt = readFileSync(destpath, 'utf-8'); + let goodtxt = readFileSync(goodpath, 'utf-8'); if (outtxt.trim() != goodtxt.trim()) { - writeFileSync('/tmp/' + goodfn, outtxt, 'utf-8'); - console.log(spawnSync('/usr/bin/diff', [srcpath, destpath], {encoding:'utf-8'}).stdout); - throw new Error(ecsfn + ' did not match test file'); + let asmpath = '/tmp/' + asmfn; + writeFileSync(asmpath, outtxt, 'utf-8'); + console.log(spawnSync('/usr/bin/diff', [asmpath, goodpath], {encoding:'utf-8'}).stdout); + throw new Error(`files different; to fix: cp ${asmpath} ${goodpath}`); } }); }); @@ -418,13 +418,30 @@ describe('Box Packer', function() { ] ); }); - it('Should pack temp vars', function() { + it('Should pack top-aligned boxes', function() { + testPack( + [ + new Bin({ left:0, top:0, right:10, bottom:10 }) + ], [ + { width: 5, height: 7, top: 0 }, + { width: 5, height: 7, top: 1 }, + { width: 5, height: 1 }, + { width: 5, height: 1 }, + { width: 5, height: 3 }, + { width: 5, height: 1 }, + ] + ); + }); + it('Should pack top-aligned boxes', function() { testPack( [ new Bin({ left:0, top:0, right:10, bottom:10 }) ], [ { width: 3, height: 7, top: 0 }, { width: 3, height: 7, top: 1 }, + { width: 3, height: 7, top: 2 }, + { width: 5, height: 1 }, + { width: 3, height: 1 }, ] ); }); diff --git a/test/ecs/vcs1.txt b/test/ecs/vcs1.txt index d4008144..aba14e40 100644 --- a/test/ecs/vcs1.txt +++ b/test/ecs/vcs1.txt @@ -3,8 +3,9 @@ PFColor_pfcolor_b0: .res 1 .res 1 -Local_tmp: +TEMP: .res 1 +Local__tmp = TEMP+0 .code KernelSection_lines_b0: .byte 2 @@ -197,7 +198,7 @@ StaticKernel__kernel__7____exit: ;;; start action Local joybutton - inc Local_tmp+0 + inc Local__tmp+0 inc PFColor_pfcolor_b0 ;;; end action Local joybutton