diff --git a/Programs/CosmOS/Apps/Snake.asm b/Programs/CosmOS/Apps/Snake.asm new file mode 100644 index 0000000..81f36cf --- /dev/null +++ b/Programs/CosmOS/Apps/Snake.asm @@ -0,0 +1,672 @@ +; Snake, as an application CosmOS can load and run. +; +; The first program written for this machine that is played rather than watched. It needs +; key mode: in line mode the terminal holds what is typed until Return, so steering would +; mean pressing a direction and then Enter, and by the time it arrived the snake would +; have been into the wall for some time. +; +; It asks the console once a frame whether a key is waiting, and never waits for one. The +; console holds the next key until it is asked, so nothing typed between frames is lost, +; and a script of moves plays back one move to a frame. +; +; WHY IT POLLS RATHER THAN INTERRUPTS. The console can raise an interrupt line when a byte +; arrives, which is the better shape for a game: the loop would never look at the console +; at all. A loaded program cannot use it. Installing a handler means putting an address in +; the vector table, and the loadable program format carries only code and data - a program +; that is not the one the machine booted from has no way to say what its vectors are. So +; interrupts belong to boot images for now, and this asks once a frame, which is what the +; machines this one is pretending to be did anyway. +; +; THE BOARD IS A PAGE, and that is the whole trick this program turns on. Sixteen by +; sixteen is 256 squares, so a square number is a byte, and the board is aligned so that +; the square number IS the low byte of its address. Reaching a square is writing its +; number into the low half of a stored pointer and loading the pointer back - no +; multiplying, no carrying, and the row and column fall out as the two nibbles. +; +; The body is a second page, used as a ring of square numbers with the oldest segment at +; the tail. Moving is putting a square on the head end and taking one off the tail end, +; so the cost of a move does not depend on how long the snake is. The ring wraps at 256 +; by itself, because an index into it is a byte and a byte is all it can be. +; +; Note that #Include console.asm comes at the END of this file. A loadable program starts +; at the first byte of its code, so the first instruction in this file has to be the one +; the program begins with. + +#Include services.asm + +#Program + + #Base 0x2000 + +start: + ; The two pointers whose low byte is a square number. Both regions are page aligned, so + ; the high byte written here is the whole of what does not change, and nothing after + ; this ever has to work out an address. + SETD.0 Board + SETD.1 CellAddress + STD.0.1 + SETD.0 Body + SETD.1 BodyAddress + STD.0.1 + + ; Everything is set here rather than trusted to be zero, because a program that is run + ; twice without being loaded again finds its Data Segment exactly as the last run left + ; it. The board is the obvious half of that; the score and the direction are the half + ; that would be missed. + CALL resetState + CALL clearBoard + CALL placeSnake + CALL placeFood + + SETD.0 ClearScreen + CALL printString + + ; Key mode, so that one key is one byte and arrives when it is pressed. It is put back + ; before this returns, and CosmOS puts it back too in case a program stops without + ; doing so. + INIA 0x01 + OUTA 0x02 + +gameLoop: + CALL takeKey + SETD.0 Quitting + LDA.0 + BNA gameOver + + CALL advance + SETD.0 Dead + LDA.0 + BNA gameOver + + CALL draw + CALL pause + BRI gameLoop + +gameOver: + ; Drawn once more so the last thing on the screen is the position it ended in. + CALL draw + SETD.0 Won + LDA.0 + BNA gameOverWon + SETD.0 Quitting + LDA.0 + BNA gameOverQuit + SETD.0 DeadText + BRI gameOverSay +gameOverWon: + SETD.0 WonText + BRI gameOverSay +gameOverQuit: + SETD.0 QuitText +gameOverSay: + CALL printString + CALL newLine + RSTA + OUTA 0x02 ; Line mode, the way it was found. + SWI osExit + +; ---- Reaching a square ---- +; +; A holds a square number. Leaves DP3 pointing at that square of the board. +; +; DP3 because a subroutine cannot hand back any of the others: CALL saves DP0 through DP2 +; and RET puts them back, so an assignment to one of them here would be undone on the way +; out. The same reason means a caller must not keep anything in DP3 across one of these. +cellPointer: + SETD.0 CellAddress + INCD.0 + STA.0 ; The square number is the low half of its own address. + DECD.0 + LDD.3.0 + RET + +; A holds a position in the body ring. Leaves DP3 pointing at it. +bodyPointer: + SETD.0 BodyAddress + INCD.0 + STA.0 + DECD.0 + LDD.3.0 + RET + +; ---- Setting up ---- + +resetState: + RSTA + SETD.0 Score + STA.0 + SETD.0 Dead + STA.0 + SETD.0 Quitting + STA.0 + SETD.0 Won + STA.0 + SETD.0 Length + STA.0 + SETD.0 BodyHead + STA.0 + SETD.0 BodyTail + STA.0 + INIA 0x03 + SETD.0 Direction + STA.0 ; Moving right, which is where the three segments point. + + ; The seed is copied rather than used in place, so that a second run starts the same + ; game as the first. A game that came out differently every time would be nicer to + ; play and impossible to record. + SETD.0 RandomSeed + SETD.1 RandomState + LDA.0 + STA.1 + INCD.0 + INCD.1 + LDA.0 + STA.1 + RET + +clearBoard: + SETD.0 Board + RSTB +clearBoardSquare: + RSTA + STA.0 + INCD.0 + INCB + BNB clearBoardSquare ; B comes back to zero after all 256 squares. + RET + +; Three segments across the middle of the board, oldest first, so the leftmost is the +; tail and the rightmost is the head. +placeSnake: + INIA 0x86 + CALL addSegment + INIA 0x87 + CALL addSegment + INIA 0x88 + CALL addSegment + RET + +; A holds a square. Marks it as snake and puts it on the head end of the ring. +addSegment: + PSHA + CALL cellPointer + INIB 0x01 + STB.3 + + SETD.0 Length + LDA.0 + CALL bodyPointer ; The ring fills forwards from zero while setting up. + POPA + STA.3 + + SETD.0 HeadCell + STA.0 ; The newest segment is always the head. + SETD.0 Length + LDA.0 + INCA + STA.0 + DECA + SETD.0 BodyHead + STA.0 ; Which is at Length minus one. + RET + +; ---- Food ---- +; +; A square is chosen at random, and if something is already there the search walks +; forwards until it finds somewhere empty. That does two jobs with one loop: it keeps the +; food off the snake, and it means the generator never has to be asked twice. +placeFood: + CALL randomByte + MVQA + SETD.3 FoodStart + STA.3 +placeFoodLook: + CALL cellPointer + LDB.3 + BRB placeFoodHere ; Empty, and A still holds which square it was. + INCA + SETD.3 FoodStart + LDB.3 + XOR ; All the way round to where the search began? + BRQ placeFoodFull + BRI placeFoodLook + +placeFoodHere: + INIB 0x02 + STB.3 ; DP3 is still on the square that was found empty. + RET + +placeFoodFull: + ; Nowhere to put it, which means the snake is the board. There is no way to lose from + ; here and nothing left to do, so it counts as finishing rather than as an error. + SETD.0 Won + INIA 0x01 + STA.0 + SETD.0 Dead + STA.0 + RET + +; A sixteen bit shift register, rotated right one bit a time, with the bit that falls off +; the bottom fed back into four places along it. Q comes back holding the high half, which +; is the part that changes least predictably. +; +; SHR rotates A and B together as one sixteen bit register, so the bit that leaves the +; bottom of B arrives at the top of A. That is not the shift a shift register wants - it +; wants that bit gone - so where the bit came round is exactly where the feedback goes, +; and one XOR both clears it and applies the taps. +randomByte: + SETD.3 RandomState + LDA.3 + INCD.3 + LDB.3 + SHR + PSHB + INIB 0x80 + AND + POPB + BRQ randomNoFeedback + PSHB + INIB 0x34 ; 0x80 clears the bit that came round, 0xB4 is the taps. + XOR + MVQA + POPB +randomNoFeedback: + STB.3 + DECD.3 + STA.3 + RSTB + OR ; Q is A, which is what a routine hands back in. + RET + +; ---- Steering ---- +; +; One key a frame, and never a wait for one. The console keeps the next key until it is +; asked for, so a key pressed while the snake was moving is still there next frame. +takeKey: + INA 0x01 + INIB 0x01 ; READY: is there a byte to be had? + AND + BRQ takeKeyDone + INA 0x00 + + INIB 0x71 ; q + XOR + BRQ takeKeyQuit + INIB 0x77 ; w + XOR + BRQ takeKeyUp + INIB 0x73 ; s + XOR + BRQ takeKeyDown + INIB 0x61 ; a + XOR + BRQ takeKeyLeft + INIB 0x64 ; d + XOR + BRQ takeKeyRight +takeKeyDone: + RET + +takeKeyUp: + RSTA + BRI takeKeyTurn +takeKeyDown: + INIA 0x01 + BRI takeKeyTurn +takeKeyLeft: + INIA 0x02 + BRI takeKeyTurn +takeKeyRight: + INIA 0x03 + +takeKeyTurn: + ; A snake cannot turn back into itself. The four directions are numbered so that two + ; opposite ones differ in exactly their lowest bit and nothing else, which makes the + ; whole test one XOR against one. + PSHA + SETD.0 Direction + LDB.0 + XOR + MVQA + INIB 0x01 + XOR + POPA + BRQ takeKeyDone ; Opposite, so it is not a turn anybody can make. + SETD.0 Direction + STA.0 + RET + +takeKeyQuit: + SETD.0 Quitting + INIA 0x01 + STA.0 + RET + +; ---- Moving ---- + +advance: + CALL step + SETD.0 Dead + LDA.0 + BNA advanceDone ; Into a wall, and there is nowhere to move to. + + ; What is in the square the head is moving into? + SETD.0 NextCell + LDA.0 + CALL cellPointer + LDB.3 + BRB advanceMove ; Empty. + DECB + DECB + BRB advanceEat ; It held a two, which is food. + + ; A one, so it is the snake. There is exactly one square of itself a snake may move + ; into, and that is the one the tail is standing on, because the tail is leaving it in + ; the same move. This is what lets a snake follow itself round a corner instead of + ; dying on the segment that is getting out of its way. + ; + ; Asked as a question about the tail rather than by taking the tail off and looking at + ; what is left. Both give the same answer, but this one does not have to be undone when + ; the answer is that the snake is dead, and a dead snake that had already lost its tail + ; would be drawn a segment short in the last frame anybody sees. + CALL tailCell + MVQB + SETD.0 NextCell + LDA.0 + XOR + BNQ advanceHitSelf + +advanceMove: + CALL removeTail + CALL addHead + RET + +advanceEat: + RSTB + STB.3 ; The food is gone. DP3 is still on that square. + ; No tail comes off, and that is the whole of what growing is. + CALL addHead + SETD.0 Score + LDA.0 + INCA + STA.0 + CALL placeFood +advanceDone: + RET + +advanceHitSelf: + SETD.0 Dead + INIA 0x01 + STA.0 + RET + +; Where the head would go, or a wall. The row is the high nibble of a square number and +; the column is the low one, so every edge of the board is a question about one nibble. +step: + SETD.0 HeadCell + LDA.0 + SETD.0 Direction + LDB.0 + BRB stepUp + DECB + BRB stepDown + DECB + BRB stepLeft + BRI stepRight + +stepUp: + INIB 0xF0 + AND + BRQ stepWall ; The top row is where the high nibble is zero. + CCF + INIB 0x10 + SUB + BRI stepMoved + +stepDown: + PSHA + INIB 0xF0 + AND + MVQA + INIB 0xF0 + XOR + POPA + BRQ stepWall ; The bottom row is where the high nibble is fifteen. + CCF + INIB 0x10 + ADD + BRI stepMoved + +stepLeft: + INIB 0x0F + AND + BRQ stepWall + CCF + INIB 0x01 + SUB + BRI stepMoved + +stepRight: + PSHA + INIB 0x0F + AND + MVQA + INIB 0x0F + XOR + POPA + BRQ stepWall + CCF + INIB 0x01 + ADD + +stepMoved: + MVQA + SETD.0 NextCell + STA.0 + RET + +stepWall: + SETD.0 Dead + INIA 0x01 + STA.0 + RET + +addHead: + SETD.0 NextCell + LDA.0 + CALL cellPointer + INIB 0x01 + STB.3 + + SETD.0 BodyHead + LDA.0 + INCA + STA.0 ; The ring wraps at 256 on its own, which is why it is a page. + CALL bodyPointer + SETD.0 NextCell + LDA.0 + STA.3 + + SETD.0 HeadCell + STA.0 + SETD.0 Length + LDA.0 + INCA + STA.0 + RET + +; Q is the square the oldest segment is standing on. +tailCell: + SETD.0 BodyTail + LDA.0 + CALL bodyPointer + LDA.3 + RSTB + OR + RET + +removeTail: + SETD.0 BodyTail + LDA.0 + CALL bodyPointer + LDA.3 ; Which square the oldest segment is standing on. + CALL cellPointer + RSTB + STB.3 + + SETD.0 BodyTail + LDA.0 + INCA + STA.0 + SETD.0 Length + LDA.0 + DECA + STA.0 + RET + +; ---- Drawing ---- +; +; The whole board, every frame, from the top left corner. Sixteen by sixteen is small +; enough that working out what changed would cost more than sending it all again. +draw: + SETD.0 CursorHome + CALL printString + SETD.0 BorderText + CALL printString + CALL newLine + + SETD.2 Board ; Walks the board a square at a time, in order. + RSTB ; Which square, which is also its row and column. +drawRow: + INIA 0x7C ; | + OUTA 0x00 +drawSquare: + SETD.0 HeadCell + LDA.0 + XOR ; Zero on the one square the head is standing on. + BRQ drawHead + LDA.2 + BRA drawEmpty + DECA + BRA drawBody + INIA 0x2A ; * + BRI drawPut +drawHead: + INIA 0x40 ; @ + BRI drawPut +drawBody: + INIA 0x23 ; # + BRI drawPut +drawEmpty: + INIA 0x20 +drawPut: + OUTA 0x00 + INCD.2 + INCB + INIA 0x0F + AND + BNQ drawSquare ; Sixteen to a row. + INIA 0x7C + OUTA 0x00 + CALL newLine + BNB drawRow ; And sixteen rows, after which B is back to zero. + + SETD.0 BorderText + CALL printString + CALL newLine + SETD.0 ScoreText + CALL printString + SETD.0 Score + LDA.0 + CALL printByteDecimal + SETD.0 KeysText + CALL printString + CALL newLine + RET + +; ---- Waiting ---- +; +; There is no clock on this machine, so time is counted in instructions. At the emulated +; rate this is about an eighth of a second, which is a speed a person can play at. Running +; the emulator faster or slower moves it, and that is the honest answer: the machine has +; no way to know how long a second is and this program is not going to pretend it does. +pause: + RSTB +pauseOuter: + RSTA +pauseInner: + DECA + BNA pauseInner + DECB + BNB pauseOuter + RET + +#Data + + #Base 0x1000 + +HeadCell: + 0x00 +NextCell: + 0x00 +Direction: + 0x00 +BodyHead: + 0x00 +BodyTail: + 0x00 +Length: + 0x00 +Score: + 0x00 +Dead: + 0x00 +Quitting: + 0x00 +Won: + 0x00 +FoodStart: + 0x00 + +; A square number written into the low byte of one of these makes it the address of that +; square. The high byte is set once at the start and never changes, which is the whole +; reason both regions are page aligned. +CellAddress: + 0x00 0x00 +BodyAddress: + 0x00 0x00 + +; Anything but zero will do, because a shift register that reaches zero stays there. +RandomSeed: + 0xAC 0xE1 +RandomState: + 0x00 0x00 + +ClearScreen: + 0x1B + "[2J" +CursorHome: + 0x1B + "[H" + +BorderText: +"+----------------+" +ScoreText: +" score " +KeysText: +" wasd steers, q stops" + +DeadText: +"you ran into something" +QuitText: +"stopped" +WonText: +"the board is full and there is nothing left to eat" + + #Align 0x0100 +Board: + #Reserve 0d256 +Body: + #Reserve 0d256 + +#Include console.asm diff --git a/Programs/CosmOS/Source/services.asm b/Programs/CosmOS/Source/services.asm index 236a6f5..99ea515 100644 --- a/Programs/CosmOS/Source/services.asm +++ b/Programs/CosmOS/Source/services.asm @@ -5,15 +5,22 @@ ; name, because a line with a name and nothing after it declares what a vector is called ; and what number it has without claiming to implement it. ; -; The order here is what fixes the numbers, and it is fixed in one file, so the two sides -; cannot disagree about them and nobody has to write a number down. Adding a service goes -; at the end: putting one in the middle would renumber everything after it, and any -; program already assembled against the old numbers would call the wrong thing. +; THE NUMBERS ARE WRITTEN DOWN HERE, and that is the only place they are written. They +; used to be decided by the order of the lines, which worked and was quietly fragile: a +; service inserted in the middle renumbered everything after it, and a program already +; assembled against the old numbers would go on calling the number rather than the name. +; Worse, the numbers a program got for its OWN traps moved depending on whether it had +; included this file, and a program that had not was given 16 - which is osPrintString. +; +; So these are pinned. They come from the range set aside for numbers that two separately +; assembled programs have to agree about; everything a program names for itself is drawn +; from higher up and cannot collide with these however it is built. Adding a service takes +; the next free number here and disturbs nothing. ; ; Written by Anachronaut #Vectors - osPrintString ; DP0 names a string. Prints it. - osReadLine ; DP0 names somewhere to put a line read from the console. - osExit ; Give the machine back to the system. + osPrintString 0d16 ; DP0 names a string. Prints it. + osReadLine 0d17 ; DP0 names somewhere to put a line read from the console. + osExit 0d18 ; Give the machine back to the system. diff --git a/Programs/testPrograms/diagnostics/pinnedVectorRange.asm b/Programs/testPrograms/diagnostics/pinnedVectorRange.asm new file mode 100644 index 0000000..6449c0d --- /dev/null +++ b/Programs/testPrograms/diagnostics/pinnedVectorRange.asm @@ -0,0 +1,19 @@ +; A vector pinned to a number that is not anybody's to give. +; +; 100 is in the range the assembler hands out by itself. A program that pinned one there +; would be claiming a number that the assembler might also give to the next trap somebody +; declared, which is exactly the collision pinning exists to prevent. + +#Program + +start: + SWI mine + HALT + +myHandler: + RETI + +#Vectors + + Boot start + mine 0d100 myHandler diff --git a/Programs/testPrograms/diagnostics/pinnedVectorTaken.asm b/Programs/testPrograms/diagnostics/pinnedVectorTaken.asm new file mode 100644 index 0000000..a952194 --- /dev/null +++ b/Programs/testPrograms/diagnostics/pinnedVectorTaken.asm @@ -0,0 +1,28 @@ +; Two vectors pinned to the same number. +; +; This is the mistake pinning makes possible: numbers the assembler hands out cannot +; collide, and numbers a person writes down can. Since the whole point of a pinned number +; is that something outside this program is going to use it, two names sharing one is a +; disagreement that has to be caught here rather than discovered by whatever calls the +; wrong one. +; +; The service names in services.asm sit at 16, 17 and 18, so a program that includes them +; and pins its own trap at one of those is caught by this same check. + +#Program + +start: + SWI first + HALT + +firstHandler: + RETI + +secondHandler: + RETI + +#Vectors + + Boot start + first 0d20 firstHandler + second 0d20 secondHandler diff --git a/Programs/testPrograms/pinnedVectorTest.asm b/Programs/testPrograms/pinnedVectorTest.asm new file mode 100644 index 0000000..9e473e3 --- /dev/null +++ b/Programs/testPrograms/pinnedVectorTest.asm @@ -0,0 +1,78 @@ +; Vectors with numbers written down, and vectors numbered by the assembler, in one program. +; +; A software vector number matters only when two separately assembled programs have to +; agree about it. The system's services are the case that exists today: something calls +; osExit without having been assembled alongside whatever implements it, so both sides +; have to mean the same number by that name. +; +; Numbers from 16 to 63 are for exactly that and are never handed out. Everything from 64 +; up is handed out in the order it is written, and belongs to one program. +; +; This pins one at each end of the reserved range and leaves a third to the assembler. All +; three are then called by name, which is the only way a program ever refers to one - the +; number is a fact about the machine, not something a program says twice. +; +; Correct output is: +; pinned vectors +; twenty +; sixty three +; automatic +; done + +#Include console.asm + +#Program + +start: + SETD.0 Banner + CALL printString + CALL newLine + + SWI atTwenty + SWI atSixtyThree + SWI automatic + + SETD.0 DoneText + CALL printString + CALL newLine + HALT + +; Each of these says which one it is, so a number going to the wrong place is a wrong word +; rather than a silence. +twentyHandler: + SETD.0 TwentyText + CALL printString + CALL newLine + RETI + +sixtyThreeHandler: + SETD.0 SixtyThreeText + CALL printString + CALL newLine + RETI + +automaticHandler: + SETD.0 AutomaticText + CALL printString + CALL newLine + RETI + +#Data + +Banner: +"pinned vectors" +TwentyText: +"twenty" +SixtyThreeText: +"sixty three" +AutomaticText: +"automatic" +DoneText: +"done" + +#Vectors + + Boot start + atTwenty 0d20 twentyHandler + atSixtyThree 0d63 sixtyThreeHandler + automatic automaticHandler diff --git a/Source/Assembler/assembly.h b/Source/Assembler/assembly.h index f7f2c3b..40a7e1f 100644 --- a/Source/Assembler/assembly.h +++ b/Source/Assembler/assembly.h @@ -68,8 +68,25 @@ #define VECTOR_BANK_FAULT 4 // Vectors 5 to 15 are held back for faults that do not exist yet, so that each cause // can have an entry of its own rather than sharing one and needing a cause register to -// tell them apart. Everything from 16 up belongs to programs. -#define VECTOR_FIRST_FREE 16 +// tell them apart. Everything from 16 up belongs to programs, in two halves. +// +// PINNED, 16 to 63. Numbers in here are never handed out by the assembler; a program that +// wants one writes it down. This is where anything TWO SEPARATELY ASSEMBLED PROGRAMS have +// to agree about lives - the system's services, and any library that stays resident and is +// called through the vector table. +// +// AUTOMATIC, 64 up. Numbered by the assembler in the order they are written. These belong +// to one program and nothing outside it can name them, so what number they get does not +// matter as long as it is not somebody else's. +// +// THE SPLIT IS THE POINT. With one range for both, what number a program's own traps got +// depended on what it had included: adding a line that included the system's service names +// silently pushed every trap after it along by three. Worse, a program that did NOT include +// them was given 16, which is osPrintString, so installing its own handler would have +// replaced a system service by accident. Numbers that must agree are now written out loud, +// and numbers that need not agree are drawn from somewhere nobody else is looking. +#define VECTOR_FIRST_PINNED 16 +#define VECTOR_FIRST_AUTO 64 // The first address the vector table occupies, and so the first address that program // text may not use. diff --git a/Source/Assembler/secondPass.c b/Source/Assembler/secondPass.c index e0af59f..224ada7 100644 --- a/Source/Assembler/secondPass.c +++ b/Source/Assembler/secondPass.c @@ -239,11 +239,22 @@ static uint16_t resolveHandler(intermediateElement *intermediateArray, int at, c return (uint16_t)address; } +// Is anything already using this software vector number? +static int vectorNumberTaken(uint8_t index) { + for (int i = 0; i < vectorArrayCount; i++) { + if (vectorArray[i].base == SOFTWARE_VECTOR_BASE && vectorArray[i].index == index) { + return 1; + } + } + return 0; +} + void populateVectorTable(intermediateElement *intermediateArray, int arraySize) { - // Software vectors a program names for itself are numbered in the order they are - // written, starting above the block held back for faults. A programmer never types - // one, so there is no way to land on a reserved vector by accident. - int nextFreeVector = VECTOR_FIRST_FREE; + // Software vectors a program names for itself and does not pin are numbered in the + // order they are written, from the automatic range. A programmer never types one of + // these, so there is no way to land on a reserved vector or on somebody else's + // agreed number by accident. + int nextFreeVector = VECTOR_FIRST_AUTO; int i = nextVectorToken(intermediateArray, arraySize, 0); while (i >= 0) { @@ -264,13 +275,31 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize) continue; } - int handlerToken = nextVectorToken(intermediateArray, arraySize, i + 1); + // A number on the same line, between the name and any handler, PINS the vector. + // Nothing else can appear there: a label may not begin with a digit, so a value in + // this position is a number and can be nothing else. + int after = nextVectorToken(intermediateArray, arraySize, i + 1); + int pinned = -1; + if (sameLine(intermediateArray, i, after) && intermediateArray[after].type == VALUE) { + pinned = intermediateArray[after].byteValue; + after = nextVectorToken(intermediateArray, arraySize, after + 1); + } + int handlerToken = after; int hasHandler = sameLine(intermediateArray, i, handlerToken); int already = findVector(token); if (already >= 0) { // Met before. A handler now is somebody implementing what was declared // earlier, which is how one shared file can serve both sides. + // + // A number here has to be the one it was declared with. Saying it again is + // allowed, because an implementer repeating what it is implementing is + // harmless; saying a different one means the two sides disagree about which + // vector this is, which is the whole thing pinning exists to prevent. + if (pinned >= 0 && pinned != vectorArray[already].index) { + vectorError("That vector was already given a different number.", + &intermediateArray[i]); + } if (!hasHandler) { vectorError("That vector is declared more than once.", &intermediateArray[i]); } @@ -292,7 +321,29 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize) break; } } - if (!reserved) { + if (reserved && pinned >= 0) { + // Boot, SoftReset and BadOpcode are where the machine looks, not where a + // program says to look, so their numbers are not anybody's to choose. + vectorError("That vector's number is fixed by the machine and cannot be given.", + &intermediateArray[i]); + } + if (!reserved && pinned >= 0) { + if (pinned < VECTOR_FIRST_PINNED || pinned >= VECTOR_FIRST_AUTO) { + fprintf(stderr, RED "Error: %d is not a vector number that can be given.\n" + " Numbers below %d belong to the machine, and %d and up are\n" + " handed out by the assembler. A vector two programs have to\n" + " agree about takes one from %d to %d.\n" RESET, + pinned, VECTOR_FIRST_PINNED, VECTOR_FIRST_AUTO, + VECTOR_FIRST_PINNED, VECTOR_FIRST_AUTO - 1); + printf("File: %s at line %d.\n", + intermediateArray[i].fileName, intermediateArray[i].lineNumber); + exit(1); + } + if (vectorNumberTaken((uint8_t)pinned)) { + vectorError("Another vector already has that number.", &intermediateArray[i]); + } + index = (uint8_t)pinned; + } else if (!reserved) { if (nextFreeVector > 255) { vectorError("There are no software vectors left to give this one.", &intermediateArray[i]); } diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 16fe14b..001797e 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -28,13 +28,36 @@ static int consoleEnded = 0; static int consolePushback = -1; // A byte already taken from the host, or -1. static int consoleInterrupts = 0; // Whether an arriving byte puts the line up. static struct termios consoleSavedTerminal; -static int consoleTerminalSaved = 0; +static int consoleTerminalSaved = 0; // There is a copy of how the terminal was found. +static int consoleTerminalRaw = 0; // The terminal is currently in this machine's mode. +static int consoleGuardsInstalled = 0; // The handlers below are in place. + +// Hands the terminal back exactly as it was found, WITHOUT forgetting what the program +// asked for. Separate from consoleRestore because the two are wanted in different places: +// a machine that is stopping wants both, and a machine that is being suspended wants only +// this, since it is going to carry on wanting keys when it is resumed. +static void consoleReleaseTerminal(void) { + if (consoleTerminalRaw) { + tcsetattr(STDIN_FILENO, TCSANOW, &consoleSavedTerminal); + consoleTerminalRaw = 0; + } +} + +static void consoleTakeTerminal(void) { + if (consoleTerminalRaw || !consoleTerminalSaved) { + return; + } + struct termios raw = consoleSavedTerminal; + raw.c_lflag &= (tcflag_t)~(ICANON | ECHO); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == 0) { + consoleTerminalRaw = 1; + } +} void consoleRestore(void) { - if (consoleTerminalSaved) { - tcsetattr(STDIN_FILENO, TCSANOW, &consoleSavedTerminal); - consoleTerminalSaved = 0; - } + consoleReleaseTerminal(); consoleKeyMode = 0; // Whatever the console was in the middle of asking for is withdrawn along with the // mode. A line left standing here would be answered by whatever ran next, which had @@ -43,14 +66,72 @@ void consoleRestore(void) { clearInterrupt(PORT_CONSOLE); } -// Restores the terminal and then dies the way it would have died anyway, so that the -// shell sees the signal it was expecting rather than a machine that exited quietly. -static void consoleSignalHandler(int signalNumber) { - consoleRestore(); +// ---- Giving the terminal back whatever happens ---- +// +// A machine that stops in key mode and does not undo it leaves the shell that started it +// with no echo and no line editing, which is a far worse failure than anything the program +// was doing, and one the user has no obvious way to connect to this program. +// +// atexit covers stopping on purpose and nothing else. It does NOT run when a process is +// killed by a signal, so every way of dying that matters has to be caught and undone by +// hand. The list below is every signal whose default action ends the process and which can +// be caught at all - SIGKILL and SIGSTOP cannot, and nothing can be done about those. +// +// SIGHUP is on the list for a specific reason, learned the hard way: it is what arrives +// when the terminal or the session that started this machine goes away, which is exactly +// what happens when whatever launched it crashes. Handling INT and TERM and stopping there +// covers the polite endings and misses the one that actually leaves a broken terminal +// behind. + +// Puts the terminal back and then dies the way it would have died anyway, so that whatever +// is waiting sees the signal it expected rather than a machine that exited quietly. +static void consoleFatalSignal(int signalNumber) { + consoleReleaseTerminal(); signal(signalNumber, SIG_DFL); raise(signalNumber); } +static void consoleContinueSignal(int signalNumber); + +// Suspending is not dying, so the terminal goes back but the mode is remembered. Whoever +// gets the terminal next is entitled to find it as they left it, and this machine is +// entitled to have its keys again when it is resumed. +static void consoleStopSignal(int signalNumber) { + consoleReleaseTerminal(); + signal(SIGCONT, consoleContinueSignal); + signal(signalNumber, SIG_DFL); + raise(signalNumber); +} + +static void consoleContinueSignal(int signalNumber) { + (void)signalNumber; + signal(SIGTSTP, consoleStopSignal); + signal(SIGCONT, consoleContinueSignal); + if (consoleKeyMode) { + consoleTakeTerminal(); + } +} + +static void consoleInstallGuards(void) { + if (consoleGuardsInstalled) { + return; + } + consoleGuardsInstalled = 1; + // Installed on the first use of key mode rather than at startup, so a run that never + // asks for it installs nothing at all. + atexit(consoleRestore); + + static const int fatal[] = { + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, + SIGBUS, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM + }; + for (size_t i = 0; i < sizeof(fatal) / sizeof(fatal[0]); i++) { + signal(fatal[i], consoleFatalSignal); + } + signal(SIGTSTP, consoleStopSignal); + signal(SIGCONT, consoleContinueSignal); +} + static void consoleSetMode(int wantKeys) { if (wantKeys == consoleKeyMode) { return; @@ -71,17 +152,9 @@ static void consoleSetMode(int wantKeys) { return; } consoleTerminalSaved = 1; - // Registered on the first use rather than at startup, so a run that never asks - // for key mode installs nothing at all. - atexit(consoleRestore); - signal(SIGINT, consoleSignalHandler); - signal(SIGTERM, consoleSignalHandler); } - struct termios raw = consoleSavedTerminal; - raw.c_lflag &= (tcflag_t)~(ICANON | ECHO); - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; - tcsetattr(STDIN_FILENO, TCSANOW, &raw); + consoleInstallGuards(); + consoleTakeTerminal(); } // Puts the line up if the console has something to say and has been asked to say it. diff --git a/Source/Emulator/io.h b/Source/Emulator/io.h index 0a4ab16..70dfd23 100644 --- a/Source/Emulator/io.h +++ b/Source/Emulator/io.h @@ -95,10 +95,13 @@ // ask the console to be, it can also ask the console what it currently is. #define CONSOLE_STATUS_INTERRUPT 0x08 -// Puts the terminal back the way it was found. Registered with atexit and called from the -// signal handlers, because a machine that stops in key mode and does not undo it leaves -// the shell that started it unusable, which is a far worse failure than anything the -// program was doing. +// Puts the terminal back the way it was found. Registered with atexit and called from a +// handler for every signal that can end this process and be caught, because a machine that +// stops in key mode and does not undo it leaves the shell that started it unusable, which +// is a far worse failure than anything the program was doing. atexit alone is not enough: +// it does not run when a process is killed, and the ending that matters most is SIGHUP, +// which is what arrives when whatever launched this machine dies and takes the terminal +// with it. void consoleRestore(void); // One byte from the console, waiting if it has to. Everything that reads standard input diff --git a/SplitBit Assembler Manual.md b/SplitBit Assembler Manual.md index e7d93dd..916b172 100644 --- a/SplitBit Assembler Manual.md +++ b/SplitBit Assembler Manual.md @@ -193,11 +193,11 @@ A line with a name and nothing after it declares the name and its number without ``` ; services.asm, included by both sides #Vectors - osPrint - osExit + osPrint 0d16 + osExit 0d17 ``` -Because the order is fixed in that one file, both sides give them the same numbers, and neither has to write a number down. +The numbers are there because this is the case where a number has to be agreed. Everything else in a Vector Segment is numbered by the assembler, which can only see one program at a time — and that is exactly the situation where it cannot help. See Numbers You Write Down below. Five names already mean something: @@ -209,7 +209,7 @@ Five names already mean something: | GuardViolation | A device refused a write, because it landed inside a raised fence. | | BankFault | A bank was named that has nothing in it, or an access ran past its end. | -Anything else you name is a software interrupt of your own. You do not choose its number and you never write one: the assembler allocates them in the order they appear, starting above the range held back for faults that do not exist yet. That is the same bargain as labels everywhere else in SplitBit assembly, where you name a thing and let the assembler work out where it went. +Anything else you name is a software interrupt of your own. Usually you do not choose its number: the assembler allocates them in the order they appear, from vector 64 upwards. That is the same bargain as labels everywhere else in SplitBit assembly, where you name a thing and let the assembler work out where it went. You then use the name as the operand of SWI: @@ -221,7 +221,36 @@ A device is different, because its number is not a choice. A device interrupts o The assembler will refuse two handlers for the same vector, a name used with SWI that no Vector Segment gives a handler to, and a handler that is not a label. -Vectors 5 through 15 are held back for faults that have not been defined yet. They have no names, so there is currently no way to write a handler for one, and none is needed: each will be given a name of its own as the fault it stands for is defined, the way GuardViolation and BankFault were. Because you never write a vector number, there is no way to land on one of them by accident either. +### Numbers You Write Down: + +A vector number is worth arguing about in exactly one situation: when two programs that are **not assembled together** have to mean the same thing by a name. A program calling `osExit` was built long before, and separately from, whatever implements it. Nothing the assembler can see ties those two together, because it only ever sees one of them. + +For that case a number may be written between the name and the handler. + +``` +#Vectors + + osExit 0d18 handleExit ; pinned, and implemented here + osPrint 0d16 ; pinned, implemented by somebody else + openFile openHandler ; the assembler picks the number +``` + +The software vector space is divided so the two kinds cannot meet: + +| Vectors | Whose | +| --- | --- | +| 0 to 2 | Boot, SoftReset and BadOpcode. The machine's, and fixed. | +| 3 to 15 | Faults. Named as each is defined; the rest are held back. | +| 16 to 63 | **Pinned.** Never handed out. A number here is written down or it is not used. | +| 64 to 255 | **Automatic.** Handed out in order. These belong to one program and nothing outside it can name them. | + +You may give a number only in the pinned range. Below it belongs to the machine, and above it is where the assembler is allocating, so a number claimed there could be handed to something else in the same breath. + +**Why the split exists**, because it is not obvious and the reason is a real mistake that used to be possible. When both kinds came out of one range, the numbers a program got for its own traps depended on what it had included: adding a line that included a file naming three services pushed every trap after it along by three, and a program that had *not* included that file was given the first number in the range — which was a service. Installing its own handler there would have quietly replaced one. Now numbers that must agree are written down, and numbers that need not agree come from somewhere nobody else is looking, so a program's own vectors are its own regardless of how it was built. + +The assembler will refuse a number outside the pinned range, two names given the same number, a number on `Boot`, `SoftReset` or `BadOpcode`, whose numbers are the machine's, and a number that contradicts one the same name was already given. + +Vectors 3 through 15 are held back for faults, most of which have not been defined yet. They have no names, so there is currently no way to write a handler for one, and none is needed: each will be given a name of its own as the fault it stands for is defined, the way GuardViolation and BankFault were. Since a number may only be written in the pinned range, there is no way to land on one of them by accident either. ## Including Other Files: diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index 2109977..faf0a5a 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -62,9 +62,12 @@ The software vectors are given out like this: | 3 | A device refused a write, because it landed inside a raised fence. GuardViolation. | | 4 | A bank was named that has nothing in it, or an access ran past its end. BankFault. | | 5 to 15 | Held back for faults not yet defined. | -| 16 and up | A program's own, given out by the assembler in the order they are named. | +| 16 to 63 | Pinned. Numbers that separately assembled programs have to agree about, written down rather than allocated. | +| 64 and up | A program's own, given out by the assembler in the order they are named. | -A programmer does not write vector numbers. Handlers are named in the Vector Segment of an assembly file and used by name, the same way every other address in SplitBit is worked out by the assembler rather than typed. See the SplitBit Assembler Manual. +A programmer almost never writes a vector number. Handlers are named in the Vector Segment of an assembly file and used by name, the same way every other address in SplitBit is worked out by the assembler rather than typed. + +The exception is the range from 16 to 63, and it exists because the assembler only ever sees one program. A program calling `osExit` and the system implementing it are assembled separately, so nothing the assembler can look at ties the two together; the only thing that can is a number both sides write down. Those numbers come from that range, and the assembler never allocates one there, so a number agreed between two programs can never be handed to a third by accident. See the SplitBit Assembler Manual. The table holds two kinds of entry, and they behave differently when they are zero. diff --git a/Tests/docs.sh b/Tests/docs.sh index b22b440..444eb19 100755 --- a/Tests/docs.sh +++ b/Tests/docs.sh @@ -103,6 +103,34 @@ else: problems.append("%s (0x%02X) is a device class and has no row in the Devices" " table" % (name, value)) +# ---- The vector ranges the manuals quote are the ones the assembler uses ---- +# +# Both manuals print the boundary between numbers a program may pin and numbers the +# assembler hands out. Those are two constants in one header, and moving them without +# touching the manuals would leave every programmer reading a range that no longer exists +# and being refused a number the manual said was theirs. +header = read("Source/Assembler/assembly.h") +ranges = {name: int(value) + for name, value in re.findall(r'^#define (VECTOR_FIRST_[A-Z]+)\s+(\d+)$', + header, re.M)} +if set(ranges) != {"VECTOR_FIRST_PINNED", "VECTOR_FIRST_AUTO"}: + problems.append("the vector range constants are not the two this check knows about: %s" + % ", ".join(sorted(ranges)) if ranges else "none found") +else: + pinnedFrom = ranges["VECTOR_FIRST_PINNED"] + autoFrom = ranges["VECTOR_FIRST_AUTO"] + said = "%d to %d" % (pinnedFrom, autoFrom - 1) + for manual, text in [("Programming Manual", pm), ("Assembler Manual", am)]: + if said not in text: + problems.append("the %s does not say the pinned vectors are %s" + % (manual, said)) + if "%d and up" % autoFrom not in pm: + problems.append("the Programming Manual does not say the automatic vectors start" + " at %d" % autoFrom) + if "from vector %d upwards" % autoFrom not in am: + problems.append("the Assembler Manual does not say the automatic vectors start" + " at %d" % autoFrom) + # ---- Every console status bit is described ---- # # The status port is read by writing a mask and testing it, so a program can only use a bit diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 2590917..8401544 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -3,8 +3,9 @@ CosmOS > greet.sbx 210 hello.sbx 52 Life.sbx 1411 +Snake.sbx 2175 notes.txt 21 -4 files +5 files > load what? > no such file > not a program @@ -16,5 +17,5 @@ finished what should I call you? hello, Claude. that is all I do. finished > halted -Execution halted after 11647 cycles. +Execution halted after 12778 cycles. [exit 0] diff --git a/Tests/expected/cosmosSnake.out b/Tests/expected/cosmosSnake.out new file mode 100644 index 0000000..04e0db8 --- /dev/null +++ b/Tests/expected/cosmosSnake.out @@ -0,0 +1,293 @@ +CosmOS +> loaded, starting at 2000 +> +----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| ## | +| @ | +| | +| | +| | +| | +| * | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| # | +| # | +| @ | +| | +| | +| | +| * | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| # | +| # | +| @ | +| | +| | +| * | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| # | +| # | +| @ | +| | +| * | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| # | +| # | +| @ | +| * | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| # | +| # | +| * @ | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| # | +| * @# | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| * @## | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| * @## | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| * @## | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | +| *@## | +| | ++----------------+ + score 0 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| * | +| | +| | +| | +| | +| | +| | +| @### | +| | ++----------------+ + score 1 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| * | +| | +| | +| | +| | +| | +| | +| @### | +| | ++----------------+ + score 1 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| * | +| | +| | +| | +| | +| | +| | +|@### | +| | ++----------------+ + score 1 wasd steers, q stops ++----------------+ +| | +| | +| | +| | +| | +| | +| | +| * | +| | +| | +| | +| | +| | +| | +|@### | +| | ++----------------+ + score 1 wasd steers, q stops +you ran into something +finished +> +halted +Execution halted after 1910669 cycles. +[exit 0] diff --git a/Tests/expected/pinnedVectorTest.out b/Tests/expected/pinnedVectorTest.out new file mode 100644 index 0000000..87740db --- /dev/null +++ b/Tests/expected/pinnedVectorTest.out @@ -0,0 +1,7 @@ +pinned vectors +twenty +sixty three +automatic +done +Execution halted after 272 cycles. +[exit 0] diff --git a/Tests/input/cosmosSnake.in b/Tests/input/cosmosSnake.in new file mode 100644 index 0000000..32652f7 --- /dev/null +++ b/Tests/input/cosmosSnake.in @@ -0,0 +1,3 @@ +load Snake.sbx +run +ssssssaaaaaa \ No newline at end of file diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 3197b50..07fbceb 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -74,6 +74,12 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/ "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Life.asm" -o "$WORK/Life.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Life.sbx" >/dev/null +# Snake.sbx is the one that is played rather than watched. It reads the console a key at a +# time without ever waiting for one, so a script of moves drives it a move to a frame, and +# what is recorded is a whole game: turning, eating, growing, and running into a wall. +"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ + "$ROOT/Programs/CosmOS/Apps/Snake.asm" -o "$WORK/Snake.sbx" >/dev/null +"$TOOL" put "$DISKS/cosmos.img" "$WORK/Snake.sbx" >/dev/null printf 'this is not a program' > notes.txt "$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null diff --git a/Tests/manifest b/Tests/manifest index 23a32e6..7bec3bb 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -130,6 +130,12 @@ interruptFlagTest | testPrograms/interruptFlagTest.asm | run | - # These are the interrupt path end to end. Until #Vectors existed, none of them could # be written as a source file at all, because nothing could install a handler. vectorTest | testPrograms/vectorTest.asm | run | - | - +# Numbers written down and numbers handed out, in one program. A vector number only matters +# where two separately assembled programs have to agree about it, so those are pinned by +# hand from a range the assembler never allocates, and everything else is drawn from above +# it. This pins both ends of that range and leaves a third to the assembler, then calls all +# three by name: a number going astray shows up as the wrong word rather than as silence. +pinnedVectorTest | testPrograms/pinnedVectorTest.asm | run | - | - deviceTest | testPrograms/deviceTest.asm | run | - | - faultResumeTest | testPrograms/faultResumeTest.asm | run | - | - # The worked example out of the Assembler Manual, so the manual cannot go stale. @@ -219,11 +225,18 @@ cosmosLife | CosmOS/Source/cosmos.asm | run | cosmosLif # 54. The two together are what say the poll is reading the console rather than always # answering the same way. cosmosLifeKey | CosmOS/Source/cosmos.asm | run | cosmosLifeKey.in | - | disks/cosmos.img +# A whole game of Snake, played by a script. Life is watched; this is steered, and it is +# the first program on this machine that reads the console without ever waiting for it. +# One key is taken per frame, so twelve bytes in the pipe are twelve moves rather than +# twelve moves at once: six turn it down the board and six take it left onto the food. So +# what is recorded is a turn, a meal, a longer snake, and then a wall. +cosmosSnake | CosmOS/Source/cosmos.asm | run | cosmosSnake.in | - | disks/cosmos.img # The programs CosmOS loads, checked on their own so that a failure here reads as "the app # does not assemble" rather than as a broken disk image. app-greet | CosmOS/Apps/greet.asm | assemble | - | - app-hello | CosmOS/Apps/hello.asm | assemble | - | - app-Life | CosmOS/Apps/Life.asm | assemble | - | - +app-Snake | CosmOS/Apps/Snake.asm | assemble | - | - # ---- Programs driven by console input ---- inputTest | inputTest.asm | run | inputTest.in | - @@ -261,6 +274,11 @@ diagDuplicateLabel | testPrograms/diagnostics/duplicateLabel.asm | xfail | - diagBareInclude | testPrograms/diagnostics/bareInclude.asm | xfail | - | - diagUnknownVector | testPrograms/diagnostics/unknownVector.asm | xfail | - | - diagDuplicateVector | testPrograms/diagnostics/duplicateVector.asm | xfail | - | - +# The two mistakes that pinning a number makes possible. Numbers the assembler hands out +# cannot collide; numbers a person writes down can, and can also be written outside the +# range set aside for them. +diagPinnedRange | testPrograms/diagnostics/pinnedVectorRange.asm | xfail | - | - +diagPinnedTaken | testPrograms/diagnostics/pinnedVectorTaken.asm | xfail | - | - diagBareSWI | testPrograms/diagnostics/bareSWI.asm | xfail | - | - diagAlignOutside | testPrograms/diagnostics/alignOutside.asm | xfail | - | - diagBareAlign | testPrograms/diagnostics/bareAlign.asm | xfail | - | -