SPL or System Programmer's Language in reality is not a high level programming language, but an enriched assembly language programming system for writing protected mode programs for the XSM machine. This language is useful for implementation of an OS on top of the XSM machine. The language is minimalistic and consists only of very basic constructs. Programming using SPL requires an understanding of the underlying XSM architecture.
Each SPL program is considered as a module. A module consists of a maximum of 1024 words which includes both the space allocated for code and data. Particular class of modules called Interrupt Service Routines can be invoked from the application by the INT instruction. Other modules can only be invoked from the kernel. The SPL compiler translates an SPL source program to a target XSM assembly module. (See Loading modules into the disk architecture).
This specification defines the syntax and semantics of the basic constructs of the SPL language and also specifies the programming conventions recommended in using the langugage. The default SPL compiler provided as part of the eXpOS package is designed to follow the programming conventions specified in this document.
SPL allows only single line comments. Comments start with the character sequence // and stop at the end of the line.
White spaces in the program including tabs, newline and horizontal spaces are ignored.
The following are the reserved words in SPL and it cannot be used as identifiers.
alias define encrypt breakpoint inline halt goto call return ireturn backup restore readi read loadi load store do while endwhile break continue if then else endif
The following are the operators and delimiters in SPL
SPL allows the use of 25 registers (R0-R15, BP, SP, IP, PTBR, PTLR, EIP, EC, EPN, EMA) and 4 ports (P0-P3). P0 and P1 are used for standard input and standard output respectively.
Identifiers are used as symbolic names for constants and aliases for registers. Identifiers should start with an alphabet but may contain alphabets, digits and/or underscore ( _ ). No other special characters are allowed in identifiers.
Valid : var1, new_page
Invalid : 9blocks, $n etc.
Integer and String literals are permitted in SPL.
An integer literal is a sequence of digits representing an integer. Negative integers are represented with a negative sign preceding the sequence of digits. eg. 3, -512, 1024
A string literal is a sequence of characters which are enclosed within double quotes (" "). eg. "alice"
SPL supports labels which are used along with goto and call. Labels follow the same naming convention as that of the identifiers.
SPL provides a fixed set of registers and ports are provided. The register set in SPL contains 29 registers. There is a direct mapping between these registers and the machine registers in XSM. There are 4 ports supported.
|Program Registers||R0 - R15|
|Reserved Registers |
(For the use of SPL compiler)
|R16 - R19|
|Page Table Base Register||PTBR|
|Page Table Length Register||PTLR|
|Exception Instruction Pointer||EIP|
|Exception Page Number||EPN|
|Exception Memory Address||EMA|
|Unused Ports||P2, P3|
Any register/port can be referred to by using a different name. A name is assigned to a particular register/port using the alias keyword. Each register/port can be assigned to only one alias at any particular point of time. However, a register/port can be reassigned to a different alias at a later point. Aliasing can also be done inside the if and while block. However, an alias defined within the if and while blocks will only be valid within the block. No two registers/ports can have the same alias name simultaneously.
Symbolic names for values can be defined in an SPL module using the define keyword. They will be visible only inside the module. Unlike aliasing, two or more names can be assigned to the same value. A constant can only be defined once in a program.
SPL provides a set of predefined constants defined in the file splconstants.cfg.
The standard SPL implementation comes with a set of pre-defined constants included in the splconstants.cfg file specifically tuned for the implementation of eXpOS on the XSM architecture. These constants are mostly the starting addresses of various OS data structures/handlers in the memory which are specified in the implementation of the eXpOS.
Users can also define constants which are visible in all SPL modules by including the definition in the splconstants.cfg file. If a constant defined in the splconstants.cfg file is re-defined in a module, the local definition will override the global definition. These predefined constants' definitions can be over-ruled by assigning different values explicitly by the user using the define keyword.
An expression specifies the computation of a value by applying operators to operands. SPL supports arithmetic and logical expressions.
Registers, constants and two or more arithmetic expressions connected using arithmetic operators are categorized as arithmetic expressions. SPL provides five arithmetic operators, viz., +, -, *, / (Integer Division) and % (Modulo operator) through which arithmetic expressions may be combined. Expression syntax and semantics are similar to standard practice in programming languages and normal rules of precedence, associativity and paranthesization hold.
(5*R4) + 3
10 % 4
Logical expressions may be formed by combining arithmetic expressions using relational operators.
The relational operators supported by SPL are
>, <, >=, <=, !=, ==.
Standard meanings apply to these operators. A relational operator will take in two arguments and return 1 if the relation is valid and 0 otherwise.
The relational operators can also be applied to strings. <, >, <=, >= compares two strings lexicographically. != and == checks for equality in the case of strings. If one of the operands is a string, the other operand will also be considered as a string.
"adam" < "apple" // This returns 1
"hansel" == "gretel" // This returns 0
"3" == 3 // This returns 1, as 3 will be treated as "3"
Logical expressions themselves may be combined using logical operators, && (logical and) , || (logical or) and ! (not).
Memory of the machine can be directly accessed in an SPL program.
A word in the memory is accessed by specifying the addressing element, i.e. memory location within [ ].
This corresponds to the value stored in the given address. An arithmetic expression or an addressing expression can be used to specify the address.
, [R3], [R5+[R7]+128], [INODE_TABLE + R2] etc.
Statements control the execution of the program. All statements in SPL are terminated with a semicolon ( ; ) .
The define statement is used to define a symbolic name for a value. Define statements should be used before any other statement in an SPL program.
The keyword define is used to associate a literal to a symbolic name.
SYNTAX : define constant_name value;
define DISK_BLOCK 437;
An alias statement is used to associate a register/port with a name. Alias statements can be used anywhere in the program.
SYNTAX : alias alias_name register_name;
alias counter R0;
The breakpoint statement is used to debug the program. The program when run in the debug mode pauses the execution at this instruction.
SYNTAX :breakpoint; This statement translates to BRKP machine instruction.
The SPL assignment statement assigns the value of an expression/value stored in a memory address to a register/memory address.
= is the assignment operator used in SPL. The operand on the right hand side of the operator is assigned to the left hand side.
SYNTAX : Register / Alias / [Address] = Register / Port / Number / String / Expression / [Address];
R2 = P0;
[PTBR + 3] =  + 10;
R1 = "hello world";
If statement specifies the conditional execution of two branches according to the value of a logical expression.
If the expression evaluates to 1, the if branch is executed, otherwise the else branch is executed. The else part is optional.
if (logical expression) then
While statement iteratively executes a set of statements based on a condition.
The condition is defined using a logical expression.
The statements are iteratively executed as long as the condition is true.
while (logical expression) do
Break statement when used inside a while loop block, stops the execution of the loop in which it is used and passes the control of execution to the next statement after the loop.
This statement cannot be used anywhere else other than while loop.
SYNTAX : break;
Continue statement when used inside a while loop block, skips the current iteration of the loop and passes the control to the next iteration after checking the loop condition.
SYNTAX : continue;
ireturn statement or the Interrupt Return statement is used to pass control from a kernel mode interrupt service routine to the user mode program which invoked it.
SYNTAX : ireturn;
The ireturn is generally used at the end of an interrupt code. This statement translates to IRET machine instruction.
The read and print statements are used as standard input and output statements. The read statement initiates the transfer of a string from the console to the standard input port P0 using the IN machine instruction. The machine proceeds to execute the next instruction without waiting for the completion of the string transfer.
The print statement outputs value of a register or an integer/string literal or value of a memory location.
Note : String read or printed must not exceed 10 characters.
print Register / Number / String / Expression / [Address];
The readi statement reads a value from the standard input device and stores it in a register using the INI machine instruction (which can be used only in debug mode).
Note : String read must not exceed 10 characters.
Loading and storing between the disk and the memory of the XSM machine can be accomplished using load and store statements in SPL. The machine proceeds to execute the next instruction without waiting for the completion of the block transfer.
load statement loads the block specified by block_number from the disk to the the page specified by the page_number in the memory. store statement stores the page specified by page_number in the memory to the the block specified by the block_number in the disk. The page_number and block_number can be specified using arithmetic expressions.SYNTAX :
load (page_number, block_number);
store (page_number, block_number);
Loading from the disk to the memory of the XSM machine can also be accomplished using loadi statement in SPL. But here, the machine will continue execution of the next instruction only after the block transfer is completed.
loadi statement loads the block specified by block_number from the disk to the the page specified by the page_number in the memory. The page_number and block_number can be specified using arithmetic expressions.SYNTAX :
loadi (page_number, block_number);
Multipush statement is used to push a sequence of registers into the memory locations starting from the address pointed to by SP. The registers are pushed in the order in which they are specified in the statement.SYNTAX :
multipush (Register1, Register2, ...);
Multipop statement is used to pop a sequence of registers from the memory locations starting from the address pointed to by SP. The registers are popped in the reverse order in which they are specified in the statement.SYNTAX :
multipop (Register1, Register2, ...);
The backup statement is used to backup all the machine registers (except SP, IP, exception flag registers and ports) into the memory locations starting from the address pointed to by SP in the order : BP, PTBR, PTLR, R0 - R19. The value of SP gets incremented accordingly.
SYNTAX : backup;
This statement translates to the BACKUP machine instruction.
The restore statement is used to restore the backed up machine registers from the memory. The registers are restored from contiguous memory locations starting from the address pointed to by SP in the order : R19-R0, PTLR, PTBR, BP. The value of SP gets decremented accordingly.
SYNTAX : restore;
This statement translates to the RESTORE machine instruction.
The encrypt statement replaces the value in the register Ri with its encrypted value.
SYNTAX : encrypt Ri;
This statement translates to the ENCRYPT machine instruction.
The goto statement transfers control to the specified labelled statement.
SYNTAX : goto label / INT_n / MOD_n / constants ;
goto label1; goto INT_7; goto MOD_2; goto MEMORY_MANAGER
Note: label should be defined within the module.
The call statement saves procedure linking information on the stack and branches to the procedure specified by the argument.
SYNTAX : call label / INT_n / MOD_n / constants;
call swap_func; call INT_7; call MOD_2; call MEMORY_MANAGER
Note: label should be defined within the module.
Call statement translates to the CALL machine instruction.
The return statement is used to transfer the control from a subroutine to the calling program in the kernel mode and the return address is popped from the stack.
SYNTAX : return;
This statement translates to the RET machine instruction.
The halt statement is used to halt the machine.
SYNTAX : halt;
This statement translates to HALT machine instruction.
The inline statement is used to give XSM machine instructions directly within an SPL program.
inline "JMP 11776";
SPL does not provide explicit support for functions. However, a label can be defined at the beginning of the code for a function and the code can be invoked using the call instruction using the label. This allows use of functions inside a module. Labels defined in one module will not be visible in other modules.
If parameters are to be passed to a function, it has to be explictly passed either using agreed upon registers or using a stack.
A function loaded at a particular known location in memory (either specified directly by the memory address or using a pre-defined constant) can be invoked by a call to the corresponding memory address. However, it is recommended to follow the conventions discussed below in inter-module calls.
call swap; call MOD_4; call 511; /* Transfers control to the first page in memory */
Each SPL System Call Handler/Interrupt Handler/Module is designed to occupy a maximum of two pages of continuous memory in the XSM machine. (Sometimes, the generic term "module" is abused to indicate all types of routines of the above categories, though we avoid this usage here). They contain protected mode code that carries out certain functions as determined by the OS programmer. The following suggests certain programming conventions which are recommend while designing SPL modules and interrupt handlers.
These routines may be entered as a result of:
Case a: (Software Interrupts) In this case, the arguments to the module are passed through the application program stack. The return values are also passed through the same stack. The convention is that the application must save the state of its registers before making the call . (For instance, the eXpL compiler will save the caller context in the user stack before invoking a software interrupt). Thus, the interrupt routine need not concern itself about saving the context of the application and can use the registers R0-R15 without saving them. However, the application is not expected to save the SP register before the call, and the module must save it for future return.
In this case the kernel module must switch to the kernel stack and not use the application's stack. This is to avoid potential user level “hacks” into the interrupt modules through the stack.
Case b: (Hardware Interrupts) This case applies to the exception handler, timer interrupt routine, disk interrupt routine and the console interrupt routine. The difference here from Case a) is that the application does not have control over the transfer to the interrupt module, and hence would not have saved its context. Thus, in this case, the module must save the register context of the application in its own stack (or elsewhere in the memory) before using the registers and must restore the context before returning to the application.
In this case also the module is expected to allocate its own stack in the memory and not use the application's stack.
Case c: (Modules) In this case, since the caller and the callee are both executing in protected mode, the same stack can be used. Here, the recommended parameter passing convention is to use R1, R2... for argument_1, argument_2, argument_3... The return value of the module may be stored in R0. This convention is recommended instead of using the stack for passing arguments for improving efficiency. As in the previous cases, the caller must save the values of the registers in use into the stack before the call.
Note: The SPL compiler given here uses the constants
(given in splconstants.cfg file) MOD_0 to MOD_7 as starting address of eXpOS kernel modules.
eXpOS kernel loads these modules into various pre-defined memory pages of the XSM machine on startup.
In addition to these, all interrupt service routines can be programmed as SPL modules and loaded to the corresponding
interrupt service routine locations in memory.