#drinc:libraries/dos.g #drinc:util.g #toy.g #externs.g /* * runTime.d: the routines that make up the 'toy' run-time system. * We load them with the compiler and emit them at the beginning of * each object file - this saves having to have them in a file at * some special place. */ /* Amiga Exec library offsets, etc. for the routines we need to call */ ulong ABS_EXEC_BASE = 4; int LVO_OPENLIBRARY = -552, LVO_CLOSELIBRARY = -414, LVO_READ = -42, LVO_WRITE = -48, LVO_INPUT = -54, LVO_OUTPUT = -60, LVO_EXIT = -144; /* opcodes and addressing modes, etc. we will need */ uint OP_MOVEL = 0x2000, OP_CLR = 0x4200, OP_JSR = 0x4e80, OP_MOVEQ = 0x7000, M_DDIR = 0o0, M_ADIR = 0o1, M_INDIR = 0o2, M_INC = 0o3, M_DEC = 0o4, M_DISP = 0o5, M_INDEX = 0o6, M_SPECIAL = 0o7, M_ABS_SHORT = 0o0, M_ABS_LONG = 0o1, M_PC_DISP = 0o2, M_PC_INDEX = 0o3, M_IMM = 0o4, M_SR = 0o4, S_BYTE = 0b00, S_WORD = 0b01, S_LONG = 0b10, R_D0 = 0, R_D1 = 1, R_D2 = 2, R_D3 = 3, R_A0 = 0, R_A1 = 1, R_A6 = 6, R_FP = 6, R_SP = 7; ulong INPUT_BUFFER_SIZE = 256; /* type used to indicate a reference within a run-time system routine to something outside that routine (usually a global run-time system variable). */ type Reloc_t = struct { RelocCode_t r_rc; /* what kind of thing the reference is to */ uint r_targetOffset; /* what offset in the target is needed */ uint r_refOffset; /* where in the source routine the ref is */ }; Handle_t StandardInput, StandardOutput; [INPUT_BUFFER_SIZE]char InputBuffer; *char InputBufferEnd, InputBufferPos; *byte Library; /* * FIRST, THE RUN-TIME ROUTINES THEMSELVES * Each has a name which starts with 'toy_', so that it does not conflict * with any other routine in the compiler. Each is followed by a comment * which lists the offsets within the routine where it references other * routines, a global variable or 'main'. */ /* * toy_main - the entry point for all 'toy' programs. * This is emitted as the first hunk in the final object file, so that * it ends up being the entry point to the whole program. */ proc toy_main()void: extern main()void; *char libName; /* call the Exec routine 'OpenLibrary' with name 'dos.library', and store the result into global run-time system variable 'Library' */ libName := "dos.library"; code( OP_MOVEL | R_A1 << 9 | M_ADIR << 6 | M_DISP << 3 | R_FP, libName, OP_CLR | S_LONG << 6 | M_DDIR << 3 | R_D0, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_ADIR << 3 | R_A6, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, ABS_EXEC_BASE, OP_JSR | M_DISP << 3 | R_A6, LVO_OPENLIBRARY, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | M_ABS_LONG << 9 | M_SPECIAL << 6 | M_DDIR << 3 | R_D0, Library ); /* make sure the previous call worked (unlikely that it won't) */ if Library ~= nil then /* call 'Input' to get a file handle for standard input, and call 'Output' to get a file handle for standard output. Store them in global run-time system variables 'StandardInput' and 'StandardOutput' */ code( OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_ADIR << 3 | R_A6, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_INPUT, OP_MOVEL | M_ABS_LONG << 9 | M_SPECIAL << 6 | M_DDIR << 3 | R_D0, StandardInput, OP_JSR | M_DISP << 3 | R_A6, LVO_OUTPUT, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | M_ABS_LONG << 9 | M_SPECIAL << 6 | M_DDIR << 3 | R_D0, StandardOutput ); /* call 'main'. When linking 'toy', this will resolve to the 'main' of the compiler, but when we emit the object file, we will make it a reference to 'main' in the user's toy program. */ main(); /* be a good boy and close dos.library */ code( OP_MOVEL | R_A1 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_ADIR << 3 | R_A6, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, ABS_EXEC_BASE, OP_JSR | M_DISP << 3 | R_A6, LVO_CLOSELIBRARY, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_INC << 3 | R_SP, ); fi; corp; /* size: 0x76 RELOC32 to hunk #0: target offset reference offset 0x000 0x3c 0x004 0x48 0x110 0x22, 0x28, 0x32, 0x54 RELOC32 to main at 0x4e */ /* * toy_printString - print a string. */ proc toy_printString(*char st)void: register *char p; ulong length; p := st; while p* ~= '\e' do p := p + sizeof(char); od; length := p - st; /* we now call 'Write', passing it the standard output file handle from the run-time system global variable 'StandardOutput', and the address passed to this routine, and the length computed here. This use of variables 'st' and 'length' is why they cannot be 'register'. Note that 'Write' (and 'Read') requires its arguments in regs D1/D2/D3, so we save D2 and D3 before the call, and restore them after. With the current version of 'toy', no registers will be in use when any of the run-time system routines are called, but in future they might be, so doing the save/restore now is preparation for then. */ code( OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_DDIR << 3 | R_D3, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_DDIR << 3 | R_D2, OP_MOVEL | R_D1 << 9 | M_DDIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, StandardOutput, OP_MOVEL | R_D2 << 9 | M_DDIR << 6 | M_DISP << 3 | R_FP, st, OP_MOVEL | R_D3 << 9 | M_DDIR << 6 | M_DISP << 3 | R_FP, length, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_ADIR << 3 | R_A6, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_WRITE, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | R_D2 << 9 | M_DDIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | R_D3 << 9 | M_DDIR << 6 | M_INC << 3 | R_SP ); corp; /* size: 0x4e RELOC32 to hunk #0: target offset reference offset 0x004 0x24 0x110 0x34 */ /* * toy_printInt - print an integer. */ proc toy_printInt(register int n)void: ulong length; *char p; [6] char buffer; bool isNegative; if n < 0 then n := -n; isNegative := true; else isNegative := false; fi; p := &buffer[5]; while p* := n % 10 + '0'; n := n / 10; n ~= 0 do p := p - sizeof(char); od; if isNegative then p := p - sizeof(char); p* := '-'; fi; length := &buffer[6] - p; /* this call to 'Write' is very similar to the previous one */ code( OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_DDIR << 3 | R_D3, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_DDIR << 3 | R_D2, OP_MOVEL | R_D1 << 9 | M_DDIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, StandardOutput, OP_MOVEL | R_D2 << 9 | M_DDIR << 6 | M_DISP << 3 | R_FP, p, OP_MOVEL | R_D3 << 9 | M_DDIR << 6 | M_DISP << 3 | R_FP, length, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_ADIR << 3 | R_A6, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_WRITE, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | R_D2 << 9 | M_DDIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | R_D3 << 9 | M_DDIR << 6 | M_INC << 3 | R_SP ); corp; /* size: 0x9e RELOC32 to hunk #0: target offset reference offset 0x004 0x74 0x110 0x84 */ /* * toy_readLine - read a line of input. */ proc toy_readLine()void: ulong length; code( OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_DDIR << 3 | R_D3, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_DDIR << 3 | R_D2, OP_MOVEL | R_D1 << 9 | M_DDIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, StandardInput, OP_MOVEL | R_D2 << 9 | M_DDIR << 6 | M_SPECIAL << 3 | M_IMM, InputBuffer, OP_MOVEL | R_D3 << 9 | M_DDIR << 6 | M_SPECIAL << 3 | M_IMM, INPUT_BUFFER_SIZE, OP_MOVEL | R_SP << 9 | M_DEC << 6 | M_ADIR << 3 | R_A6, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_READ, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | R_FP << 9 | M_DISP << 6 | M_DDIR << 3 | R_D0, length, OP_MOVEL | R_D2 << 9 | M_DDIR << 6 | M_INC << 3 | R_SP, OP_MOVEL | R_D3 << 9 | M_DDIR << 6 | M_INC << 3 | R_SP ); /* 'Read' returns in D0 the number of bytes read. We use this to setup global run-time system variable 'InputBufferEnd', which marks the end of the current input line in the input buffer. */ InputBufferPos := &InputBuffer[0]; InputBufferEnd := &InputBuffer[length]; corp; /* size: 0x58 RELOC32 to hunk #0: target offset reference offset 0x000 0x0c 0x008 0x12, 0x34, 0x44 0x108 0x4e 0x10c 0x3a 0x110 0x20 */ /* * toy_readInt - read an integer. * If there is anything wrong with the input data (i.e. not enough, or * it isn't digits and a sign), we use 'toy_printString' to print an * error message and then call DOS's 'Exit' routine with an error code * indicating an error in the program. Note that we cannot, of course, * close 'dos.library', since 'Exit' does not return. (We can't close * the library first, since 'Exit' is part of that library.) */ proc toy_readInt()int: uint RETURN_ERROR_WORD = RETURN_ERROR; register *char p, end; register int n; bool isNegative; p := InputBufferPos; end := InputBufferEnd; /* skip past any leading blanks or tabs */ while p ~= end and (p* = ' ' or p* = '\t') do p := p + sizeof(char); od; if p = end then toy_printString("*** toy: insufficient data for input - aborting\n"); code( OP_MOVEQ | R_D1 << 9 | RETURN_ERROR_WORD, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_EXIT ); fi; /* handle a + or - sign */ isNegative := false; if p* = '-' then isNegative := true; p := p + sizeof(char); elif p* = '+' then p := p + sizeof(char); fi; if p = end then toy_printString("*** toy: insufficient data for input - aborting\n"); code( OP_MOVEQ | R_D1 << 9 | RETURN_ERROR_WORD, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_EXIT ); fi; if p* < '0' or p* > '9' then toy_printString("*** toy: invalid data for input - aborting\n"); code( OP_MOVEQ | R_D1 << 9 | RETURN_ERROR_WORD, OP_MOVEL | R_A6 << 9 | M_ADIR << 6 | M_SPECIAL << 3 | M_ABS_LONG, Library, OP_JSR | M_DISP << 3 | R_A6, LVO_EXIT ); fi; /* decode the number itself */ n := 0; while p ~= end and p* >= '0' and p* <= '9' do n := n * 10 + (p* - '0'); p := p + sizeof(char); od; InputBufferPos := p; if isNegative then -n else n fi corp; /* size: 0x140 RELOC32 to hunk #0: target offset reference offset 0x108 0x10 0x10c 0x0a, 0xc6 0x110 0x3a, 0x70, 0x92 RELOC32 to toy_printString @ 0x32, 0x68, 0x8a */ /* ROUTINES USED BY THE COMPILER TO EMIT THE RUN-TIME SYSTEM */ /* * runtime - output a single run-time routine. */ proc runTime(*byte codePtr; uint codeLength; register *Reloc_t r)void: register *byte codeCopy; /* we are about to emit one of the above routines into the output code file (actually it will be buffered in 'system.d' before that). This requires changing it slightly to meet the AmigaDOS format for a relocatable object file. The changes are always the same, and the compiler itself never calls any of the above routines, so we could do the changes directly on the loaded versions. Modifying code is not a good idea, however, and may be impossible on systems with virtual memory and/or an MMU, so we make a copy of the code, modify that copy, pass it to 'system.d', then free the copy. */ codeCopy := memAlloc(codeLength); BlockCopy(codeCopy, codePtr, codeLength); codeStartProc(nil); while r*.r_rc ~= rc_none do (codeCopy + r*.r_refOffset + 0)* := 0; (codeCopy + r*.r_refOffset + 1)* := 0; if r*.r_rc = rc_globalVariable then (codeCopy + r*.r_refOffset + 2)* := r*.r_targetOffset >> 8; (codeCopy + r*.r_refOffset + 3)* := r*.r_targetOffset; codeReference(rc_globalVariable, nil, r*.r_refOffset); else (codeCopy + r*.r_refOffset + 2)* := 0; (codeCopy + r*.r_refOffset + 3)* := 0; codeReference(r*.r_rc, nil, r*.r_refOffset); fi; r := r + sizeof(Reloc_t); od; codeEndProc(nil, codeCopy, codeLength); memFree(codeCopy, codeLength); corp; /* * outputRunTime - output all of the run-time routines. */ proc outputRunTime()void: type REF_2 = [2 + 1]Reloc_t, REF_7 = [7 + 1]Reloc_t, REF_9 = [9 + 1]Reloc_t; runTime(pretend(toy_main, *byte), 0x76, &REF_7( (rc_main , 0x000, 0x4e), (rc_globalVariable, 0x000, 0x3c), (rc_globalVariable, 0x004, 0x48), (rc_globalVariable, 0x110, 0x22), (rc_globalVariable, 0x110, 0x28), (rc_globalVariable, 0x110, 0x32), (rc_globalVariable, 0x110, 0x54), (rc_none, 0, 0) )[0] ); runTime(pretend(toy_printString, *byte), 0x4e, &REF_2( (rc_globalVariable, 0x004, 0x24), (rc_globalVariable, 0x110, 0x34), (rc_none, 0, 0) )[0] ); runTime(pretend(toy_printInt, *byte), 0xa2, &REF_2( (rc_globalVariable, 0x004, 0x74), (rc_globalVariable, 0x110, 0x84), (rc_none, 0, 0) )[0] ); runTime(pretend(toy_readLine, *byte), 0x5c, &REF_7( (rc_globalVariable, 0x000, 0x0c), (rc_globalVariable, 0x008, 0x12), (rc_globalVariable, 0x008, 0x34), (rc_globalVariable, 0x008, 0x44), (rc_globalVariable, 0x108, 0x4e), (rc_globalVariable, 0x10c, 0x3a), (rc_globalVariable, 0x110, 0x20), (rc_none, 0, 0) )[0] ); runTime(pretend(toy_readInt, *byte), 0x140, &REF_9( (rc_globalVariable, 0x108, 0x10), (rc_globalVariable, 0x10c, 0x0a), (rc_globalVariable, 0x10c, 0xc6), (rc_globalVariable, 0x110, 0x3a), (rc_globalVariable, 0x110, 0x70), (rc_globalVariable, 0x110, 0x92), (rc_printString , 0x000, 0x32), (rc_printString , 0x000, 0x68), (rc_printString , 0x000, 0x8a), (rc_none, 0, 0) )[0] ); corp;