M3: programs that bring their own vectors

> load Asm.sbx
    > run Keys.asm
    wrote Keys.sbx: program 558, data 85, labels 52
    > load Keys.sbx
    > run
    keys, by interrupt. q stops.
    ab
    the console has been handed back

The machine assembles a program carrying an interrupt handler, the loader
installs its vector, the console interrupts into it, and the shell takes the
vector back at exit. Byte for byte identical to the C assembler's, and
Tests/native.sh now checks a boot image and four loadable programs on every
run.

WHAT IT TOOK:

  A declaration and an implementation are the SAME ENTRY. services.asm says
  a service is called osPrintString and has number 16; cosmos.asm says
  osPrintString is handled by handlePrintString. The name is met twice and
  the second time fills in the handler, which is what lets one shared file
  serve both the caller and the implementer.

  So the first pass declares and the second implements. That is forced: a
  handler is an address, and no address is known until every label has been
  placed.

  Boot in a loadable program fills the entry field rather than being
  installed - vector zero is where the whole machine starts, and a program
  loaded into a running system has no business saying anything about that.
  A boot image is the one thing that does, so there it is installed like any
  other, behind a "VEC" marker in the SPBT file.

  Device is named by the port, and Device with the five reserved names are
  matched without regard to case, the way mnemonics are: they are part of
  the language rather than names the programmer chose. Devices have no names
  of their own, so they are given one nothing can type.

TWO BUGS, both of a kind worth naming.

The first: "is this a loadable program" was written out as an OR of the two
segment bases in seven places, and the sense wanted is the opposite in most
of them. One of the seven had it backwards and put a version ONE header on a
file carrying vectors, which a loader is right to refuse. It is one flag
now, settled once and tested the same way everywhere.

The second: finding the entry to write a handler into means calling vecFind,
which reads the entry's fields out - including the handler it does not have
yet. An address resolved into VecHandler before the find was overwritten
with zero by the find itself, and the file came out with a vector pointing
at address zero: a slot that looked installed and went nowhere. The
resolved address has a variable of its own now.

Keys.asm and console.asm go on the CosmOS disk, so the whole path can be
watched rather than only tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-21 11:50:52 -04:00
co-authored by Claude Opus 5
parent 04424a8917
commit 20989c3439
7 changed files with 785 additions and 98 deletions
+614 -73
View File
@@ -53,12 +53,14 @@ start:
CALL passOne CALL passOne
BNQ stopped BNQ stopped
CALL settleFormat
CALL deriveName ; After the first pass: what it is called depends on whether a CALL deriveName ; After the first pass: what it is called depends on whether a
; #Base turned up, and that is not known until then. ; #Base turned up, and that is not known until then.
CALL layOutImage CALL layOutImage
BNQ stopped BNQ stopped
CALL passTwo CALL passTwo
BNQ stopped BNQ stopped
CALL writeVectors
CALL writeImage CALL writeImage
BNQ stopped BNQ stopped
@@ -299,22 +301,39 @@ labelNowhere:
; ---- A line of the Vector Segment ---- ; ---- A line of the Vector Segment ----
; ;
; A name on its own is a declaration and the assembler numbers it. A name with a number ; Four shapes, and which one it is cannot be known without looking ahead:
; after it is pinned, which is how anything two separately assembled programs must agree
; about is fixed. A name with a HANDLER after it says this program implements the vector,
; and that needs a Vector Segment in the output file, which is not built yet.
; ;
; Which of the three it is cannot be known without looking at the next token, so the next ; name declared, and the assembler gives it a number
; token is looked at and handed back if it turns out to belong to the following line. A ; name 0dNN declared with a number both sides have agreed
; LINE is what tells them apart: two names on one line are a name and its handler, and two ; name handler this program implements it
; names on two lines are two declarations. ; name 0dNN handler both at once
; Device 0xNN handler named by the port, because that is what decides it
;
; A LINE is what tells them apart. Two names on one line are a name and its handler; two
; names on two lines are two declarations. So the next token is read and handed back if it
; turns out to belong to the following entry.
;
; THE FIRST PASS DECLARES AND THE SECOND IMPLEMENTS. Handlers are addresses and no address
; is known until every label has been placed, so the second pass is the earliest a handler
; can be resolved - and by then the names are all in the table waiting for one.
doVectorLine: doVectorLine:
SETD.0 ClsType RSTA
LDA.0 SETD.0 VecPinnedGiven
INIB 0d5 STA.0
XOR SETD.0 VecHandlerHere
BNQ vectorNotAName STA.0
SETD.0 VecIsDevice
STA.0
SETD.0 TokText
SETD.1 WordDevice
CALL sameFolded
BNQ vectorNamed
INIA 0d1
SETD.0 VecIsDevice
STA.0
vectorNamed:
SETD.0 TokText SETD.0 TokText
SETD.1 VecName SETD.1 VecName
CALL srcKeepName CALL srcKeepName
@@ -322,82 +341,315 @@ doVectorLine:
SETD.2 TokLine SETD.2 TokLine
CALL numSet CALL numSet
CALL tokNext ; ---- Whatever else is on this line ----
BNQ vectorAutomatic ; The file ended, so that was a declaration on its own. CALL vectorNextOnLine
BNQ vectorLineEnds
SETD.0 TokLine
SETD.2 VecLineWas
CALL numCompare
BNQ vectorHandBack ; A different line, so it belongs to the next entry.
CALL clsToken
BNQ vectorFailed
SETD.0 ClsType SETD.0 ClsType
LDA.0 LDA.0
INIB 0d2 INIB 0d2
XOR XOR
BNQ vectorHasHandler BNQ vectorMaybeHandler
; A pinned number. Anything after it on the same line would be a handler. ; A number. Between a name and any handler it PINS the vector; after Device it says
; which port. Nothing else can appear there, because a label may not begin with a digit.
INIA 0d1
SETD.0 VecPinnedGiven
STA.0
SETD.0 VecPinned SETD.0 VecPinned
SETD.2 ClsValue SETD.2 ClsValue
LDA.2 LDA.2
STA.0 STA.0
CALL tokNext CALL vectorNextOnLine
BNQ vectorPinnedDone BNQ vectorLineEnds
SETD.0 TokLine
SETD.2 VecLineWas
CALL numCompare
BNQ vectorHandBackPinned
BRI vectorHasHandler
vectorPinnedDone: vectorMaybeHandler:
; A name here is the handler. Its address is wanted, and only the second pass has one.
SETD.0 ClsType
LDA.0
INIB 0d5
XOR
BNQ vectorOddToken
INIA 0d1
SETD.0 VecHandlerHere
STA.0
SETD.0 TokText
SETD.1 VecHandlerName
CALL srcKeepName
; Anything after the handler is a line that says too much.
CALL vectorNextOnLine
BRQ vectorTooMuch
vectorLineEnds:
SETD.0 Emitting
LDA.0
BNA vectorImplement
BRI vectorDeclareIt
; ---- The second pass: fill in the handler ----
vectorImplement:
SETD.0 VecHandlerHere
LDA.0
BRA vectorLineDone ; Nothing to implement, so nothing to do.
SETD.0 VecHandlerName
CALL labFind
BNQ vectorNoHandler
SETD.0 VecPutHandler
SETD.2 LabAddress
CALL numSet
SETD.0 VecIsDevice
LDA.0
BNA vectorImplementDevice
SETD.0 VecName
CALL vecFind
BNQ vectorLost
CALL vecWriteHandler
BRI vectorLineDone
vectorImplementDevice:
; A device brings no name, so there is nothing to look up: the entry is made now, in the
; place the line sits. Nothing can refer to it, which is why it needs no name of its own.
SETD.0 VecPinned SETD.0 VecPinned
LDA.0 LDA.0
CALL declareVector SETD.0 VecPutNumber
RET STA.0
INIA 0d1
SETD.0 VecPutBase
STA.0
SETD.0 DeviceName
SETD.2 VecPutNumber
LDA.2
CALL vecDeclare
BNQ vectorFailed
SETD.0 DeviceName
CALL vecFind
BNQ vectorLost
CALL vecWriteHandler
CALL vecFreshDeviceName
BRI vectorLineDone
vectorHandBackPinned: ; ---- The first pass: give it a number ----
CALL tokBack vectorDeclareIt:
BRI vectorPinnedDone SETD.0 VecIsDevice
LDA.0
BNA vectorDeviceCheck
vectorHandBack: SETD.0 VecName
CALL tokBack CALL vecFind
vectorAutomatic: BRQ vectorAlready
; A reserved name is where the machine looks rather than where a program says to look,
; so its number is not anybody's to choose.
CALL vectorReserved
BNQ vectorNotReserved
SETD.0 VecPinnedGiven
LDA.0
BNA vectorFixedNumber
BRI vectorSetNumber
vectorNotReserved:
SETD.0 VecPinnedGiven
LDA.0
BRA vectorAutoNumber
; A pinned number comes from the range set aside for what two separately assembled
; programs have to agree about. Below it belongs to the machine and above it is handed
; out by the assembler, so neither can be asked for.
SETD.0 VecPinned
LDA.0
INIB 0d16
CCF
SUB
BRC vectorBadNumber
SETD.0 VecPinned
LDA.0
INIB 0d64
CCF
SUB
BNC vectorBadNumber
BRI vectorSetNumber
vectorAutoNumber:
CALL vecTakeAuto CALL vecTakeAuto
SETD.0 VecPutNumber SETD.0 VecPutNumber
LDA.0 LDA.0
CALL declareVector SETD.0 VecPinned
RET
; Writes VecName down with the number in A - but only in the first pass. The second one
; walks the same lines and must not declare anything again, the same way it does not add a
; label again: the table is the first pass's answer and the second pass only reads it.
declareVector:
SETD.0 VecTaking
STA.0 STA.0
SETD.0 Emitting
vectorSetNumber:
SETD.0 VecPinned
LDA.0 LDA.0
BNA declareVectorSkip SETD.0 VecPutNumber
STA.0
RSTA
SETD.0 VecPutBase
STA.0
SETD.0 VecName SETD.0 VecName
SETD.2 VecTaking SETD.2 VecPutNumber
LDA.2 LDA.2
CALL vecDeclare CALL vecDeclare
RET BNQ vectorFailed
BRI vectorLineDone
declareVectorSkip: vectorAlready:
; Met before. A handler now is somebody implementing what was declared earlier; nothing
; but a handler means the name was declared twice.
SETD.0 VecHandlerHere
LDA.0
BRA vectorTwice
SETD.0 VecPinnedGiven
LDA.0
BRA vectorLineDone
SETD.0 VecPinned
LDA.0
SETD.2 VecNumber
LDB.2
XOR
BNQ vectorDisagrees ; The two sides name different vectors by one name.
BRI vectorLineDone
vectorDeviceCheck:
; Device says which port as a number, and then where to go.
SETD.0 VecPinnedGiven
LDA.0
BRA vectorDeviceBare
SETD.0 VecHandlerHere
LDA.0
BRA vectorDeviceBare
vectorLineDone:
RSTA RSTA
RSTB RSTB
CCF CCF
ADD ADD
RET RET
vectorHasHandler: ; The next token of this line, if there is one. Q is zero if there was, and it has been
SETD.0 HandlerText ; classified; otherwise it has been handed back for the next entry to have.
vectorNextOnLine:
CALL tokNext
BNQ vectorNextNone
SETD.0 TokLine
SETD.2 VecLineWas
CALL numCompare
BNQ vectorNextOther
CALL clsToken
BNQ vectorNextNone
RSTA
RSTB
CCF
ADD
RET
vectorNextOther:
CALL tokBack
vectorNextNone:
RSTA
INIB 0d1
CCF
ADD
RET
; Q is zero if VecName is one of the vectors the machine itself uses, and then VecPinned
; is the number it must have. These are matched without regard to case, the way mnemonics
; are: they are part of the language rather than names somebody chose.
vectorReserved:
RSTA
SETD.0 ReservedLeft
STA.0
SETD.0 ReservedNames
SETD.1 ReservedWalk
STD.0.1
reservedLoop:
SETD.0 ReservedLeft
LDA.0
SETD.2 ReservedCount
LDB.2
CCF
SUB
BRQ reservedNo
SETD.1 ReservedWalk
LDD.0.1
SETD.1 VecName
CALL sameFolded
BRQ reservedYes
INIA 0d16
SETD.0 ReservedWalk
CALL numAddByte
SETD.0 ReservedLeft
LDA.0
INCA
STA.0
BRI reservedLoop
reservedYes:
SETD.1 ReservedWalk
LDD.0.1
DPUP.0 0d15
LDA.0
SETD.0 VecPinned
STA.0
RSTA
RSTB
CCF
ADD
RET
reservedNo:
RSTA
INIB 0d1
CCF
ADD
RET
; Devices have no names of their own, so they are given one nothing can type: a space
; followed by a counter. It keeps them apart in a table that refuses a repeated name.
vecFreshDeviceName:
SETD.0 DeviceName
INCD.0
LDA.0
INCA
STA.0
RET
vectorFixedNumber:
SETD.0 FixedNumberText
CALL clsComplain CALL clsComplain
BRI vectorFailed BRI vectorFailed
vectorBadNumber:
vectorNotAName: SETD.0 BadNumberText
CALL clsComplain
BRI vectorFailed
vectorTwice:
SETD.0 VecTwiceText
CALL clsComplain
BRI vectorFailed
vectorDisagrees:
SETD.0 DisagreeNumberText
CALL clsComplain
BRI vectorFailed
vectorDeviceBare:
SETD.0 DeviceBareText
CALL clsComplain
BRI vectorFailed
vectorNoHandler:
SETD.0 NoHandlerText
CALL clsComplain
BRI vectorFailed
vectorLost:
SETD.0 LostVectorText
CALL clsComplain
BRI vectorFailed
vectorTooMuch:
SETD.0 TooMuchText
CALL clsComplain
BRI vectorFailed
vectorOddToken:
SETD.0 VectorOddText SETD.0 VectorOddText
CALL clsComplain CALL clsComplain
vectorFailed: vectorFailed:
@@ -1043,6 +1295,34 @@ dropColonDone:
; Where each segment's bytes will go, and the header in front of them. Both lengths are ; Where each segment's bytes will go, and the header in front of them. Both lengths are
; known now, which is the whole reason the first pass exists. ; known now, which is the whole reason the first pass exists.
; Which of the two file formats this is, settled once and written down.
;
; A program that says where it goes is a loadable one and gets the SBEX header; one that
; says nothing is a boot image and gets SPBT. The difference is not a version but a
; question of what the file needs of whatever reads it.
;
; ONE FLAG RATHER THAN THE TEST REPEATED. It is asked in seven places - the header, the
; extension, where the vectors go, whether they carry a marker, how long the file is, and
; whether a Boot line is the entry or a handler - and written out each time it read as an
; OR of the two bases, whose sense is the opposite of what most of those places want. One
; of the seven had it backwards and put a version one header on a file carrying vectors,
; which a loader is right to refuse.
settleFormat:
RSTA
SETD.0 Loadable
STA.0
SETD.0 ProgBased
LDA.0
SETD.2 DataBased
LDB.2
OR
BRQ settleFormatDone
INIA 0d1
SETD.0 Loadable
STA.0
settleFormatDone:
RET
layOutImage: layOutImage:
; How long each segment came out, which is where its cursor ended less where it began. ; How long each segment came out, which is where its cursor ended less where it began.
SETD.0 ImgProgLen SETD.0 ImgProgLen
@@ -1058,15 +1338,9 @@ layOutImage:
SETD.2 DataBase SETD.2 DataBase
CALL numTake CALL numTake
; A program that says where it goes is a loadable one and gets the SBEX header; one that SETD.0 Loadable
; says nothing is a boot image and gets SPBT. The difference is not a version but a
; question of what the file needs of whatever reads it.
SETD.0 ProgBased
LDA.0 LDA.0
SETD.2 DataBased BNA layOutLoadable
LDB.2
OR
BNQ layOutLoadable
; ---- A boot image ---- ; ---- A boot image ----
; ;
@@ -1297,6 +1571,206 @@ byteAt:
STA.0 STA.0
RET RET
; ---- The vectors the file carries ----
;
; Last in the file, after the code and the data, so that everything before them sits where
; a loader that knows nothing about vectors already expects to find it.
;
; Four bytes each: WHERE THE SLOT IS, then what goes in it. Saying the slot outright rather
; than the vector number means the loader does no arithmetic and does not have to know
; where either vector table begins, and one entry can name a software or a hardware vector
; without saying which it is.
writeVectors:
SETD.0 VecInstalled
CALL numZero
SETD.0 EntryAddress
SETD.2 ProgBase
CALL numSet
; Counted first, because a boot image writes the length of the run before the run.
CALL countVectors
SETD.0 ImgWalk
SETD.2 DataPut
CALL numSet
SETD.0 VecInstalled
LDA.0
INCD.0
LDB.0
OR
BRQ writeVectorsNone
SETD.0 Loadable
LDA.0
BNA writeVectorsRoom
; A boot image marks the run and says how long it is, the way it does for its segments.
SETD.0 MagicVEC
INIA 0d3
CALL putBytes
SETD.0 VecBytes
CALL putWord
writeVectorsRoom:
SETD.0 VecWhich
CALL numZero
writeVectorLoop:
SETD.0 VecWhich
SETD.2 VecCount
CALL numCompare
BNC writeVectorsDone
CALL vecSlotAt
CALL vecReadFields
SETD.0 VecHasHandler
LDA.0
BRA writeVectorNext
CALL vectorIsEntry
BRQ writeVectorNext ; Boot in a loadable program is the entry, not a handler.
; Where the slot is: the table it belongs to, plus two bytes for every vector before it.
SETD.0 SlotAt
SETD.2 SoftwareBase
CALL numSet
SETD.0 VecBase
LDA.0
BRA writeVectorSlot
SETD.0 SlotAt
SETD.2 HardwareBase
CALL numSet
writeVectorSlot:
SETD.0 VecNumber
LDA.0
SETD.0 SlotAt
CALL numAddByte
SETD.0 VecNumber
LDA.0
SETD.0 SlotAt
CALL numAddByte
SETD.0 SlotAt
CALL putWord
SETD.0 VecHandler
CALL putWord
writeVectorNext:
SETD.0 VecWhich
CALL numStep
BRI writeVectorLoop
writeVectorsDone:
SETD.0 ImgTotal
SETD.2 VecBytes
CALL numAdd
SETD.0 Loadable
LDA.0
BNA writeVectorsHeader
INIA 0d5
SETD.0 ImgTotal
CALL numAddByte ; The marker and the length in front of them.
writeVectorsHeader:
; A file that brings vectors needs something of its loader a version one loader does not
; know how to give, so it says version two and an older one refuses it rather than
; running the program without its handlers.
SETD.0 Loadable
LDA.0
BRA writeVectorsNone
SETD.0 Image
SETD.1 ImgWalk
STD.0.1
SETD.0 ImgWalk
INIA 0d4
CALL numAddByte
INIA 0d2
CALL putByte
SETD.0 VecInstalled
INCD.0
LDA.0
CALL putByte
writeVectorsNone:
; And where to start, which a Boot line fills in and everything else leaves as the first
; byte of the code.
SETD.0 Loadable
LDA.0
BRA writeVectorsOut
SETD.0 Image
SETD.1 ImgWalk
STD.0.1
SETD.0 ImgWalk
INIA 0d8
CALL numAddByte
SETD.0 EntryAddress
CALL putWord
writeVectorsOut:
RET
; How many vectors will be written, into VecInstalled, and how many bytes that is.
countVectors:
SETD.0 VecWhich
CALL numZero
countVectorLoop:
SETD.0 VecWhich
SETD.2 VecCount
CALL numCompare
BNC countVectorsDone
CALL vecSlotAt
CALL vecReadFields
SETD.0 VecHasHandler
LDA.0
BRA countVectorNext
CALL vectorIsEntry
BRQ countVectorTakeEntry
SETD.0 VecInstalled
CALL numStep
BRI countVectorNext
countVectorTakeEntry:
SETD.0 EntryAddress
SETD.2 VecHandler
CALL numSet
countVectorNext:
SETD.0 VecWhich
CALL numStep
BRI countVectorLoop
countVectorsDone:
SETD.0 VecBytes
SETD.2 VecInstalled
CALL numSet
SETD.0 VecBytes
SETD.2 VecBytes
CALL numAdd
SETD.0 VecBytes
SETD.2 VecBytes
CALL numAdd ; Four bytes an entry.
RET
; Q is zero if this entry is the Boot line of a LOADABLE program, which fills the entry
; field instead of being installed. Vector zero is where the whole machine starts, and a
; program being loaded into a running system has no business saying anything about that.
; A boot image is the one thing that does, so there it is installed like any other.
vectorIsEntry:
SETD.0 Loadable
LDA.0
BRA vectorNotEntry
SETD.0 VecBase
LDA.0
BNA vectorNotEntry
SETD.0 VecNumber
LDA.0
BNA vectorNotEntry
RSTA
RSTB
CCF
ADD
RET
vectorNotEntry:
RSTA
INIB 0d1
CCF
ADD
RET
writeImage: writeImage:
SETD.0 OutName SETD.0 OutName
SETD.1 Image SETD.1 Image
@@ -1369,12 +1843,9 @@ deriveEnd:
deriveAtDot: deriveAtDot:
SETD.1 DotAt SETD.1 DotAt
LDD.1.1 LDD.1.1
SETD.0 ProgBased SETD.0 Loadable
LDA.0 LDA.0
SETD.2 DataBased BNA deriveLoadable
LDB.2
OR
BNQ deriveLoadable
SETD.0 ExtensionBin SETD.0 ExtensionBin
BRI deriveCopy BRI deriveCopy
deriveLoadable: deriveLoadable:
@@ -1473,6 +1944,8 @@ DataBase:
0x00 0x00 0x00 0x00
ProgBased: ProgBased:
0x00 0x00
Loadable:
0x00
DataBased: DataBased:
0x00 0x00
ProgUsed: ProgUsed:
@@ -1495,8 +1968,56 @@ VecLineWas:
0x00 0x00 0x00 0x00
VecPinned: VecPinned:
0x00 0x00
VecTaking: VecPinnedGiven:
0x00 0x00
VecHandlerHere:
0x00
VecIsDevice:
0x00
VecInstalled:
0x00 0x00
VecBytes:
0x00 0x00
EntryAddress:
0x00 0x00
SlotAt:
0x00 0x00
SoftwareBase:
0xFC 0x00
HardwareBase:
0xFE 0x00
ReservedLeft:
0x00
ReservedWalk:
0x00 0x00
ReservedCount:
0d5
VecHandlerName:
#Reserve 0d23
; A name nothing can type, so that devices - which have no names of their own - can live in
; a table that refuses a repeated one. A space, then a counter.
DeviceName:
0x20 0x01 0x00
#Reserve 0d20
; The vectors that already mean something. Sixteen bytes each: fifteen of name, then the
; number the machine fixed for it.
ReservedNames:
"Boot"
#Reserve 0d10
0d0
"SoftReset"
#Reserve 0d5
0d1
"BadOpcode"
#Reserve 0d5
0d2
"GuardViolation"
0d3
"BankFault"
#Reserve 0d5
0d4
ProgPut: ProgPut:
0x00 0x00 0x00 0x00
@@ -1537,6 +2058,8 @@ MagicPRG:
"PRG" "PRG"
MagicDAT: MagicDAT:
"DAT" "DAT"
MagicVEC:
"VEC"
ExtensionBin: ExtensionBin:
".bin" ".bin"
ExtensionSbx: ExtensionSbx:
@@ -1556,6 +2079,8 @@ WordReserve:
"#Reserve" "#Reserve"
WordAlign: WordAlign:
"#Align" "#Align"
WordDevice:
"Device"
UsageText: UsageText:
"say which file to assemble, as in: run hello.asm "say which file to assemble, as in: run hello.asm
@@ -1597,8 +2122,24 @@ NumberBadText:
"a directive wants a number here, written 0x.. or 0d.." "a directive wants a number here, written 0x.. or 0d.."
UnknownVectorText: UnknownVectorText:
"no vector of that name is declared anywhere in this program" "no vector of that name is declared anywhere in this program"
HandlerText: FixedNumberText:
"this assembler cannot build a Vector Segment yet, so it cannot install a handler" "that vector's number is fixed by the machine and is not anybody's to give"
BadNumberText:
"that is not a vector number that can be given: below 16 belongs to the machine, and 64
and up is handed out by the assembler"
VecTwiceText:
"that vector is declared more than once"
DisagreeNumberText:
"that vector was already given a different number, so the two sides disagree about which
vector this name means"
DeviceBareText:
"Device has to say which port, as a number, and then where to go"
NoHandlerText:
"there is no label of that name for the handler to be"
LostVectorText:
"a vector went missing between the two passes"
TooMuchText:
"there is more on that line than a vector entry can be"
VectorOddText: VectorOddText:
"only names belong in the Vector Segment" "only names belong in the Vector Segment"
DisagreeText: DisagreeText:
+31
View File
@@ -673,6 +673,35 @@ clsDigitNo:
; ---- Odds and ends ---- ; ---- Odds and ends ----
; Q is zero if the strings at DP0 and DP1 are the same, ignoring case.
;
; For the words that are part of the LANGUAGE rather than names somebody chose: Device, and
; the vectors the machine already uses. Mnemonics are matched the same way, for the same
; reason - nobody should have to remember how the manual capitalised something.
sameFolded:
LDA.0
CALL clsUpper
SETD.2 ClsByte
LDA.2
SETD.2 ClsFoldHold
STA.2
LDA.1
CALL clsUpper
SETD.2 ClsByte
LDA.2
SETD.2 ClsFoldHold
LDB.2
CCF
SUB
BNQ sameFoldedDone
LDA.0
BRA sameFoldedDone ; They ended together, so they matched all the way.
INCD.0
INCD.1
BRI sameFolded
sameFoldedDone:
RET
; ClsLength becomes the byte in A. Everything but a string is a small number, and this is ; ClsLength becomes the byte in A. Everything but a string is a small number, and this is
; how a small number is written into a sixteen bit field. ; how a small number is written into a sixteen bit field.
clsSetLength: clsSetLength:
@@ -814,6 +843,8 @@ ClsDigitHold:
0x00 0x00
ClsByte: ClsByte:
0x00 0x00
ClsFoldHold:
0x00
ClsLeft: ClsLeft:
0x00 0x00
ClsLeft2: ClsLeft2:
+99 -16
View File
@@ -7,8 +7,23 @@
; ;
; FIXED FIELDS HERE, unlike the label table's arena. There are at most a couple of hundred ; FIXED FIELDS HERE, unlike the label table's arena. There are at most a couple of hundred
; of these against several hundred labels, and the names are short, so packing them would ; of these against several hundred labels, and the names are short, so packing them would
; cost more code than it saved. Twenty four bytes an entry: a name of up to twenty two with ; cost more code than it saved. Twenty eight bytes an entry:
; its zero, and the number. ;
; 0 23 the name, up to twenty two characters and a zero
; 23 1 the vector number
; 24 1 which table: 0 software, 1 hardware
; 25 2 the handler's address
; 27 1 whether a handler has been supplied
;
; A DECLARATION AND AN IMPLEMENTATION ARE THE SAME ENTRY. services.asm says a service is
; called osPrintString and has number 16; cosmos.asm says osPrintString is handled by
; handlePrintString. Both sides include the first file, so the name is met twice, and the
; second time fills in the handler rather than making a second entry. That is what lets one
; shared file serve a program that calls a service and the system that implements it.
;
; THE ORDER OF THIS TABLE IS THE ORDER OF THE OUTPUT FILE, and the order is declaration
; order rather than implementation order, because that is where the entry was made. It has
; to match what the other assembler does byte for byte.
; ;
; Numbers come from two places. A pinned one is written down in the source, and that is how ; Numbers come from two places. A pinned one is written down in the source, and that is how
; anything two separately assembled programs must agree about is fixed - the system's ; anything two separately assembled programs must agree about is fixed - the system's
@@ -27,7 +42,8 @@ vecReset:
STA.0 STA.0
RET RET
; Declares the name at DP0 with the number in A. Q is zero if it went in. ; Declares the name at DP0 with the number in A, in the table VecPutBase names. Q is zero
; if it went in.
vecDeclare: vecDeclare:
SETD.2 VecPutNumber SETD.2 VecPutNumber
STA.2 STA.2
@@ -66,6 +82,17 @@ vecDeclareFresh:
SETD.2 VecPutNumber SETD.2 VecPutNumber
LDA.2 LDA.2
STA.0 STA.0
INCD.0
SETD.2 VecPutBase
LDA.2
STA.0 ; Which table it lives in.
INCD.0
RSTA
STA.0
INCD.0
STA.0
INCD.0
STA.0 ; No handler yet, and no address to go with one.
SETD.0 VecCount SETD.0 VecCount
CALL numStep CALL numStep
@@ -127,16 +154,7 @@ vecFindLoop:
BRI vecFindLoop BRI vecFindLoop
vecFindGot: vecFindGot:
SETD.1 VecSlot CALL vecReadFields
LDD.0.1
INIA 0d23
SETD.0 VecSlot
CALL numAddByte
SETD.1 VecSlot
LDD.0.1
LDA.0
SETD.0 VecNumber
STA.0
RSTA RSTA
RSTB RSTB
CCF CCF
@@ -150,7 +168,62 @@ vecFindMissing:
ADD ADD
RET RET
; Where entry number VecWhich sits, into VecSlot. Twenty four bytes an entry. ; Everything the entry at VecSlot says, into VecNumber, VecBase, VecHandler and
; VecHasHandler. VecSlot is left pointing past the name, at the number.
vecReadFields:
INIA 0d23
SETD.0 VecSlot
CALL numAddByte
SETD.1 VecSlot
LDD.0.1
LDA.0
SETD.1 VecNumber
STA.1
INCD.0
LDA.0
SETD.1 VecBase
STA.1
INCD.0
LDA.0
SETD.1 VecHandler
STA.1
INCD.0
LDA.0
SETD.1 VecHandler
INCD.1
STA.1
INCD.0
LDA.0
SETD.1 VecHasHandler
STA.1
RET
; Gives the entry at VecSlot the handler in VecPutHandler. VecSlot must already have been
; walked past the name by vecReadFields, which is how it is always reached.
;
; A VARIABLE OF ITS OWN, not VecHandler, and that is not tidiness. Finding the entry to
; write to means calling vecFind, which reads the entry's fields out - including the
; handler it does not have yet. An address resolved into VecHandler before the find was
; overwritten with zero by the find itself, and the file came out with a vector pointing
; at address zero: a slot that looked installed and went nowhere.
vecWriteHandler:
SETD.1 VecSlot
LDD.0.1
INCD.0
INCD.0
SETD.2 VecPutHandler
LDA.2
STA.0
INCD.0
INCD.2
LDA.2
STA.0
INCD.0
INIA 0d1
STA.0
RET
; Where entry number VecWhich sits, into VecSlot. Twenty eight bytes an entry.
vecSlotAt: vecSlotAt:
SETD.0 VecNames SETD.0 VecNames
SETD.1 VecSlot SETD.1 VecSlot
@@ -165,7 +238,7 @@ vecSlotLoop:
LDB.0 LDB.0
OR OR
BRQ vecSlotDone BRQ vecSlotDone
INIA 0d24 INIA 0d28
SETD.0 VecSlot SETD.0 VecSlot
CALL numAddByte CALL numAddByte
SETD.0 VecSlotLeft SETD.0 VecSlotLeft
@@ -193,6 +266,16 @@ VecNumber:
0x00 0x00
VecPutNumber: VecPutNumber:
0x00 0x00
VecPutBase:
0x00
VecHandler:
0x00 0x00
VecPutHandler:
0x00 0x00
VecHasHandler:
0x00
VecBase:
0x00
VecNextAuto: VecNextAuto:
0x00 0x00
VecOne: VecOne:
@@ -211,4 +294,4 @@ VecName:
#Reserve 0d23 #Reserve 0d23
VecNames: VecNames:
#Reserve 0d1536 #Reserve 0d1792
+8 -1
View File
@@ -85,7 +85,8 @@ cosmos: $(COSMOS) $(APPS) $(NATIVE_ASM)
# Made from scratch every time, so that what is on it is what is in Apps/ now and not # Made from scratch every time, so that what is on it is what is in Apps/ now and not
# also whatever used to be. # also whatever used to be.
$(COSMOS_DISK): $(APPS) $(NATIVE_ASM) testPrograms/stringKeyword.asm \ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) testPrograms/stringKeyword.asm \
CosmOS/Apps/hello.asm CosmOS/Apps/Say.asm CosmOS/Source/services.asm CosmOS/Apps/hello.asm CosmOS/Apps/Say.asm CosmOS/Apps/Keys.asm \
CosmOS/Source/services.asm CosmOS/Source/console.asm
@mkdir -p $(@D) @mkdir -p $(@D)
rm -f $@ rm -f $@
$(DISKTOOL) format $@ 2048 4 $(DISKTOOL) format $@ 2048 4
@@ -98,12 +99,18 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) testPrograms/stringKeyword.asm \
@# written straight over the one the host tool put there - which means the next @# written straight over the one the host tool put there - which means the next
@# thing loaded is a program the machine built itself, in the same breath. @# thing loaded is a program the machine built itself, in the same breath.
@# @#
@# Keys.asm is the one that brings a vector of its own, so assembling it exercises the
@# version two header and the Vector Segment: the loader installs its handler, the
@# console interrupts into it, and the shell takes the vector back at exit.
@#
@# strings.asm is the odd one out on purpose: it has no #Include and no #Base, so it @# strings.asm is the odd one out on purpose: it has no #Include and no #Base, so it
@# comes out as a boot image rather than a loadable program, and the difference @# comes out as a boot image rather than a loadable program, and the difference
@# between the two is visible on one disk. @# between the two is visible on one disk.
$(DISKTOOL) put $@ CosmOS/Apps/hello.asm $(DISKTOOL) put $@ CosmOS/Apps/hello.asm
$(DISKTOOL) put $@ CosmOS/Apps/Say.asm $(DISKTOOL) put $@ CosmOS/Apps/Say.asm
$(DISKTOOL) put $@ CosmOS/Apps/Keys.asm
$(DISKTOOL) put $@ CosmOS/Source/services.asm $(DISKTOOL) put $@ CosmOS/Source/services.asm
$(DISKTOOL) put $@ CosmOS/Source/console.asm
$(DISKTOOL) put $@ testPrograms/stringKeyword.asm strings.asm $(DISKTOOL) put $@ testPrograms/stringKeyword.asm strings.asm
# The system as well as the disk. Building only the image leaves whatever cosmos.bin was # The system as well as the disk. Building only the image leaves whatever cosmos.bin was
+1 -1
View File
@@ -16,7 +16,7 @@ SplitBit is a custom 8 bit system designed for hobbyist projects and experimenta
- Loadable Programs: A program that was not booted from carries a header saying where it belongs, and Programs/loader.asm reads one off a disk, puts it there, and runs it. - Loadable Programs: A program that was not booted from carries a header saying where it belongs, and Programs/loader.asm reads one off a disk, puts it there, and runs it.
- An Operating System: CosmOS boots the machine, mounts a disk, lists what is on it, loads a program and runs it, and takes the machine back when it finishes. It comes with a library of programs to run, including a game and a line editor that writes files a person typed. - An Operating System: CosmOS boots the machine, mounts a disk, lists what is on it, loads a program and runs it, and takes the machine back when it finishes. It comes with a library of programs to run, including a game and a line editor that writes files a person typed.
- System Services: A loaded program reaches the console and the disk through numbered software interrupts rather than carrying a copy of the code that drives them. The numbers are written down in one file that both sides include, so neither ever types one. It took the editor from 4941 bytes to 1983 without changing a line of what it does. - System Services: A loaded program reaches the console and the disk through numbered software interrupts rather than carrying a copy of the code that drives them. The numbers are written down in one file that both sides include, so neither ever types one. It took the editor from 4941 bytes to 1983 without changing a line of what it does.
- A Native Assembler: SplitBit assembles SplitBit. Programs/CosmOS/Assembler/ is an assembler written in SplitBit assembly that runs under CosmOS, reads source off a SplitBit disk, and writes a binary back to it with no host involved. It builds boot images and loadable applications, following #Include, #Base, #Reserve and #Align, so a program assembled on the machine can then be loaded and run by it. Its output has to be byte for byte identical to what the C assembler produces from the same source, which is what Tests/native.sh checks. - A Native Assembler: SplitBit assembles SplitBit. Programs/CosmOS/Assembler/ is an assembler written in SplitBit assembly that runs under CosmOS, reads source off a SplitBit disk, and writes a binary back to it with no host involved. It builds boot images and loadable applications, following every directive the language has - including #Vectors, so a program that brings its own interrupt handlers can be assembled on the machine, loaded by it, and have its vectors installed and taken back again. Its output has to be byte for byte identical to what the C assembler produces from the same source, which is what Tests/native.sh checks.
- Streaming Reads: A file bigger than the machine's memory is read a block at a time, through services that keep nothing open between calls. CosmOS's own source is 104K against 64K of Data Memory, so this is what a self-hosted assembler will stand on. - Streaming Reads: A file bigger than the machine's memory is read a block at a time, through services that keep nothing open between calls. CosmOS's own source is 104K against 64K of Data Memory, so this is what a self-hosted assembler will stand on.
- Storage: A block device with 256 byte blocks and 16 megabytes of them, backed by an image file on the host. It knows blocks and not files, because a filesystem is meant to be software SplitBit runs. - Storage: A block device with 256 byte blocks and 16 megabytes of them, backed by an image file on the host. It knows blocks and not files, because a filesystem is meant to be software SplitBit runs.
- Memory Controller: Reads and writes Program Memory, moves blocks between memory banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program. - Memory Controller: Reads and writes Program Memory, moves blocks between memory banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program.
+20 -1
View File
@@ -520,6 +520,25 @@ wrote Say.sbx: program 46, data 93, labels 7
it says: built by the machine itself it says: built by the machine itself
``` ```
### Programs That Bring Vectors:
A `#Vectors` line that names a **handler** says this program implements that vector, and the file then carries a Vector Segment: four bytes an entry, the address of the slot and the address to put in it, after the code and the data so that everything before them sits where a loader knowing nothing about vectors already expects it. A loadable program carrying any says **version two**, and a loader that cannot install them refuses it rather than running the program without its handlers.
```
#Vectors
Boot start
Device 0x00 keyHandler
```
`Boot` in a loadable program fills the **entry** field rather than being installed. Vector zero is where the whole machine starts, and a program being loaded into a running system has no business saying anything about that. A boot image is the one thing that does, so there it is installed like any other.
`Device` is named by the port it is plugged into, because that is what decides which vector it arrives through. `Device`, `Boot`, `SoftReset`, `BadOpcode`, `GuardViolation` and `BankFault` are matched **without regard to case**, the way mnemonics are: they are part of the language rather than names the programmer chose.
**A declaration and an implementation are the same entry.** `services.asm` says a service is called `osPrintString` and has number 16; `cosmos.asm` says `osPrintString` is handled by `handlePrintString`. Both sides include the first file, so the name is met twice and the second time fills in the handler. That is what lets one shared file serve both a program that calls a service and the system that implements it — and it is why the first pass declares and the second implements, a handler being an address and no address being known until every label has been placed.
### What It Does Not Do Yet: ### What It Does Not Do Yet:
A `#Vectors` line that names a **handler** rather than only declaring a name. That needs a Vector Segment in the output file and the version two header that carries it, so a program bringing its own interrupt handlers cannot be built on the machine yet. It is refused by name rather than ignored, as everything unfinished here is: an assembler that quietly skipped a directive would produce a file that looked right and was the wrong length, which is the worst thing it could do. Nothing in the language. Every directive this manual describes is understood, and `Tests/native.sh` checks a boot image and four loadable programs — the last of them bringing a vector — against the host assembler byte for byte on every run.
What is left is scale rather than grammar: whether it can assemble CosmOS itself, which is 104,142 bytes of source across several files and 453 labels.
+12 -6
View File
@@ -10,9 +10,10 @@
# Then it runs what the machine built, because a file that matches and does not work would # Then it runs what the machine built, because a file that matches and does not work would
# mean both assemblers were wrong together. # mean both assemblers were wrong together.
# #
# It checks a boot image and three loadable programs. The loadable ones are the harder case # It checks a boot image and four loadable programs. The loadable ones are the harder case:
# and the interesting one: they include another file, they are based somewhere other than # they include another file, they are based somewhere other than zero, and every service
# zero, and every service they call is a name declared in that included file. # they call is a name declared in that included file. The last of them also brings a vector,
# which is the only thing here that asks anything of its loader.
# #
# Written by Anachronaut # Written by Anachronaut
@@ -62,8 +63,13 @@ mkdir -p "$WORK"
# The applications, and the file of service names they all include. These are the reason # The applications, and the file of service names they all include. These are the reason
# the second milestone exists: an assembler that cannot follow an #Include cannot build # the second milestone exists: an assembler that cannot follow an #Include cannot build
# anything that asks the system for anything. # anything that asks the system for anything.
APPS="Say greet Files" #
# Keys is the last of them and the hardest: it brings a vector of its own, so it needs a
# Vector Segment in the output and the version two header that says so. Nothing else here
# asks anything of its loader beyond code and data.
APPS="Say greet Files Keys"
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Source/services.asm" services.asm >/dev/null "$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Source/services.asm" services.asm >/dev/null
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Source/console.asm" console.asm >/dev/null
for app in $APPS; do for app in $APPS; do
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Apps/$app.asm" "$app.asm" >/dev/null "$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Apps/$app.asm" "$app.asm" >/dev/null
done done
@@ -101,8 +107,8 @@ check "it counted right" grep -q "program 17, data 14, labels 2" "$WORK/session.
# file in, #Base moves every label to where the program will really live, and every SWI in # file in, #Base moves every label to where the program will really live, and every SWI in
# them names a vector declared in a file the source never mentions by number. # them names a vector declared in a file the source never mentions by number.
for app in $APPS; do for app in $APPS; do
"$ASM" -I "$ROOT/Programs/CosmOS/Source" -o "$WORK/ref-$app.sbx" \ "$ASM" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/$app.asm" >/dev/null -o "$WORK/ref-$app.sbx" "$ROOT/Programs/CosmOS/Apps/$app.asm" >/dev/null
rm -f "$WORK/got-$app.sbx" rm -f "$WORK/got-$app.sbx"
"$TOOL" get "$WORK/native.img" "$app.sbx" "$WORK/got-$app.sbx" >/dev/null 2>&1 "$TOOL" get "$WORK/native.img" "$app.sbx" "$WORK/got-$app.sbx" >/dev/null 2>&1
if [ -f "$WORK/got-$app.sbx" ]; then if [ -f "$WORK/got-$app.sbx" ]; then