diff --git a/Programs/CosmOS/Assembler/labels.asm b/Programs/CosmOS/Assembler/labels.asm index 66cba7c..b80dbed 100644 --- a/Programs/CosmOS/Assembler/labels.asm +++ b/Programs/CosmOS/Assembler/labels.asm @@ -1,5 +1,22 @@ ; The label table: the only thing that survives between the two passes. ; +; ---- KEPT IN ORDER, so that a lookup can halve it ---- +; +; It used to be walked from the front, and that was most of the assembler. Every use of every +; label paid a scan of every label defined so far, so the cost grew with the program being +; built: assembling CosmOS on the machine took 1.83 billion cycles against the assembler's own +; 57 million - three times the source for thirty two times the time. +; +; Sorted and halved instead, it is 886 million: the walk was 52 per cent of the whole +; assembly. Small programs pay about a tenth more, because adding a label now moves the tail +; of the index up to make room and a short table was never expensive to walk. That is the +; right way round for a trade to fall. +; +; WHICH END IT IS SORTED FROM DOES NOT MATTER, and that is not carelessness. labAdd takes its +; insertion point from labFind, so the comparison that decides the order is the same one that +; searches it; turn it round and the table is built backwards and read backwards and nothing +; else can tell. Which is why breaking that comparison on purpose changes no output at all. +; ; Names are packed end to end in an arena and each index entry holds a pointer into it, ; rather than every entry carrying a field wide enough for the longest name. MEASURED on ; CosmOS, which is the biggest thing this will ever be asked to assemble: 453 labels @@ -72,10 +89,50 @@ labAddFresh: CALL numCompare BRC labAddCrowded ; The arena is smaller than where this name would end. - ; The index entry: where the name is about to go, and what it means. + ; ---- Room made for it where it belongs ---- + ; + ; The index is kept in order so that looking a name up can halve the table instead of + ; walking it, and labFind left LabLo at the place this name sorts to. Everything from there + ; on moves up four bytes, from the END backwards so that nothing is written over before it + ; has been read. + ; + ; It costs about a third of the table copied per label added, which against the walk it + ; replaces is nothing: the walk was paid once per USE of a label and this is paid once per + ; label, and there are several uses of each. SETD.0 LabWhich SETD.2 LabCount CALL numSet + +labAddShift: + SETD.0 LabWhich + SETD.2 LabLo + CALL numCompare + BRQ labAddPlace ; Down to where it goes, so there is nothing left to move. + + ; The one below this place, moved up into it. + CALL labEntryAt + SETD.1 LabEntry + LDD.1.1 ; DP1 is where it goes. + SETD.0 LabWhich + CALL numBack + CALL labEntryAt + SETD.0 LabEntry + LDD.0.0 ; DP0 is where it comes from. + + INIB 0d4 +labAddShiftByte: + LDA.0 + STA.1 + INCD.0 + INCD.1 + DECB + BNB labAddShiftByte + BRI labAddShift + +labAddPlace: + SETD.0 LabWhich + SETD.2 LabLo + CALL numSet CALL labEntryAt SETD.1 LabEntry @@ -149,14 +206,39 @@ labAddNo: labFind: SETD.2 LabSought STD.0.2 - SETD.0 LabWhich - CALL numZero -labFindLoop: - SETD.0 LabWhich + ; ---- Halving the table rather than walking it ---- + ; + ; The names are kept in order, so a lookup is eleven comparisons against two thousand + ; entries where a walk was six hundred and seventy. THAT WAS MOST OF THE ASSEMBLER: every + ; use of every label paid it, and it grew with the program being built, so assembling + ; CosmOS on the machine cost 1.83 billion cycles against the assembler's own 57 million - + ; three times the source for thirty two times the time. + ; + ; LabLo is left at the place the name WOULD go, which is what adding one needs and what a + ; walk could never have handed back. + SETD.0 LabLo + CALL numZero + SETD.0 LabHi SETD.2 LabCount + CALL numSet + +labFindStep: + SETD.0 LabLo + SETD.2 LabHi CALL numCompare - BNC labFindMissing ; Walked the whole table without a match. + BNC labFindMissing ; Nothing left between them, so it is not here. + + ; The one in the middle. Adding two numbers under two thousand cannot overflow sixteen + ; bits, so there is nothing to carry into. + SETD.0 LabWhich + SETD.2 LabLo + CALL numSet + SETD.0 LabWhich + SETD.2 LabHi + CALL numAdd + SETD.0 LabWhich + CALL numHalve CALL labEntryAt SETD.1 LabEntry @@ -169,16 +251,33 @@ labFindLoop: INCD.0 STB.0 + ; ---- sameText is already an ordering ---- + ; + ; Q is the difference at the first character that differed, so zero is a match - and the + ; Carry Flag from that same subtraction says which way round they were, and survives the + ; return because nothing puts the Status register back. A name that runs out while the + ; other carries on borrows against the other's character, which sorts the shorter first, + ; which is what anybody means by alphabetical. SETD.1 LabNamePointer LDD.0.1 SETD.1 LabSought LDD.1.1 CALL sameText BRQ labFindGot + BRC labFindAbove ; The one in the table is the smaller, so look above it. - SETD.0 LabWhich + SETD.0 LabHi + SETD.2 LabWhich + CALL numSet + BRI labFindStep + +labFindAbove: + SETD.0 LabLo + SETD.2 LabWhich + CALL numSet + SETD.0 LabLo CALL numStep - BRI labFindLoop + BRI labFindStep labFindGot: SETD.1 LabEntry @@ -285,6 +384,12 @@ LabEntry: 0x00 0x00 LabOffset: 0x00 0x00 +; Where a halving search has got to, and where a name that is not there would go. LabLo is +; what labAdd makes room at, which is the whole reason the search hands it back. +LabLo: + 0x00 0x00 +LabHi: + 0x00 0x00 LabWhich: 0x00 0x00 LabLength: diff --git a/Programs/CosmOS/Assembler/numbers.asm b/Programs/CosmOS/Assembler/numbers.asm index 660c4f6..a4f81f0 100644 --- a/Programs/CosmOS/Assembler/numbers.asm +++ b/Programs/CosmOS/Assembler/numbers.asm @@ -112,6 +112,45 @@ numCompare: numCompareDone: RET +; Takes one off the two byte number at DP0. The mirror of numStep, and wanted for the same +; reason: walking an index backwards is what moving a run of entries up needs. +numBack: + INCD.0 + LDA.0 + BNA numBackLow ; The low half has something to take, so the high half is safe. + DECD.0 + LDA.0 + DECA + STA.0 + INCD.0 + LDA.0 +numBackLow: + DECA + STA.0 + RET + +; The two byte number at DP0 becomes half of itself. +; +; A and B are a CIRCULAR sixteen bit register, so a rotate right brings bit nought back in at +; the top rather than dropping it - which is a halving only once that bit is taken off again. +; The numbers this is asked about are index positions, well under thirty two thousand, so the +; top bit was nought before the rotate and clearing it afterwards loses nothing. +numHalve: + LDA.0 + INCD.0 + LDB.0 + SHR + + ; The low half goes down FIRST, because taking the wrapped bit off the high half wants B + ; for the mask and there is nowhere else to keep it. + STB.0 + INIB 0x7F + AND + MVQA + DECD.0 + STA.0 + RET + ; Q is zero if the strings at DP0 and DP1 are the same, both ending in a zero byte. ; ; Down here rather than with the label table, where it started, because four separate