Sign in Join
Wiki copytemporary

Assembler and ABI Resources: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## The Assembler
22
33The FPC Pascal Compiler translates Pascal source code into assembly language which is then processed by an assembler running as a separate backend. Some other Pascal compilers directly generate object modules or executable programs directly, i.e. they do not require a separate assembler.
44
55An assembler is itself an executable program that translates assembly language into an object module. In most cases the object modules are passed to a linker which then produces an executable program, although in some there are additional stages (code signing for secure operating systems, conversion to a binary for embedded systems and so on).
66
77## The ABI
88
99The interface between an executable program and the underlying operating system is referred to as the Application Binary Interface or ABI. This includes the CPU's operating mode (e.g. whether word and address sizes default to 32 or 64 bits), operand alignment, function calling conventions, system call numbers, and a selection of constants (e.g. file open modes) and structures (e.g. as returned by the stat() function). It is also usually considered to include the format of the object modules, executable and library files.
1010
1111Obviously the ABI is grossly different between operating systems: in general a program compiled for Windows will not run on Linux and vice versa. In addition, however, there is a significant amount of variation between different "flavours" of related operating systems, for example not only are the system call numbers different between SPARC Solaris and SPARC Linux but they are different between SPARC Linux and x86 Linux.
1212
1313## Purpose of this note
1414
1515In most cases FPC uses the GNU assembler ("as" or "gas") as its backend. However, the assembly language syntax expected by this is different for each target CPU, sections below give examples of this. The original incentive for this was because the author (MarkMLl) found that he needed to write an assembler reader for the MIPS processor, and that there was no straightforward comparison of existing formats on which he could base new code.
1616
1717In addition, in some cases the details of the assembly language format or the ABI specification are only available to users registered with the relevant manufacturer, where possible links to unofficial mirrors are given below for casual reference.
1818
1919## Assembler source formats
2020
2121Assembler source emitted by the compiler's code generator has to be (a subset of what is) acceptable to the assembler for the relevant target CPU. In addition, small portions of the RTL (e.g. prt0.as) are of necessity written in assembler, and some Pascal source files (e.g. syscall.inc) contain inline assembler which the compiler has to be able to parse before it is passed to the backend.
2222
2323The list of CPUs below is taken from the compiler as of late 2011. Some of these are no longer supported, or exist merely as minimal stubs.
2424
2525### Alpha
2626
2727This compiler exists only as a minimal stub.
2828
2929### ARM
3030
3131This fragment is from FpSysCall alias FPC_SYSCALL6 in FPC's ./rtl/linux/arm/syscall.inc:
3232
33-`asm`\
34-`  stmfd sp!,{r4,r5,r6}`\
35-`  ldr  r4,param4`\
36-`  ldr  r5,param5`\
37-`  ldr  r6,param6`\
38-`  bl FPC_SYSCALL`\
39-`  ldmfd sp!,{r4,r5,r6}`\
40-`end;`
33+``` pascal
34+asm
35+ stmfd sp!,{r4,r5,r6}
36+ ldr r4,param4
37+ ldr r5,param5
38+ ldr r6,param6
39+ bl FPC_SYSCALL
40+ ldmfd sp!,{r4,r5,r6}
41+end;
42+```
4143
4244This fragment is from ret_from_fork in Linux's ./arch/arm/kernel/entry-common.S:
4345
44-`ENTRY(ret_from_fork)`\
45-`        bl      schedule_tail`\
46-`        get_thread_info tsk`\
47-`        ldr     r1, [tsk, #TI_FLAGS]            @ check for syscall tracing`\
48-`        mov     why, #1`\
49-`        tst     r1, #_TIF_SYSCALL_TRACE         @ are we tracing syscalls?`\
50-`        beq     ret_slow_syscall`\
51-`        mov     r1, sp`\
52-`        mov     r0, #1                          @ trace exit [IP = 1]`\
53-`        bl      syscall_trace`\
54-`        b       ret_slow_syscall`\
55-`ENDPROC(ret_from_fork)`
46+```
47+ENTRY(ret_from_fork)
48+ bl schedule_tail
49+ get_thread_info tsk
50+ ldr r1, [tsk, #TI_FLAGS] @ check for syscall tracing
51+ mov why, #1
52+ tst r1, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?
53+ beq ret_slow_syscall
54+ mov r1, sp
55+ mov r0, #1 @ trace exit [IP = 1]
56+ bl syscall_trace
57+ b ret_slow_syscall
58+ENDPROC(ret_from_fork)
59+```
5660
5761Note that register names are r0, r1 etc. without a sigil, and that register assignment is right-to-left.
5862
5963### AVR
6064
6165This fragment is from part of fpc's startup code in /rtl/embedded/avr/start.inc:
6266
63-`   _start:`\
64-` {$ifdef CPUAVR_16_REGS}`\
65-`       clr r17`\
66-` {$else CPUAVR_16_REGS}`\
67-`       clr r1`\
68-` {$endif CPUAVR_16_REGS}`\
67+``` pascal
68+ _start:
69+ {$ifdef CPUAVR_16_REGS}
70+ clr r17
71+ {$else CPUAVR_16_REGS}
72+ clr r1
73+ {$endif CPUAVR_16_REGS}
6974
70-`       // load stack pointer`\
71-`       ldi r30,lo8(_stack_top)`\
72-`       out 0x3d,r30`\
73-`       ldi r30,hi8(_stack_top)`\
74-`       out 0x3e,r30`\
75+ // load stack pointer
76+ ldi r30,lo8(_stack_top)
77+ out 0x3d,r30
78+ ldi r30,hi8(_stack_top)
79+ out 0x3e,r30
7580
76-`       // Initialize .data section`\
77-`       ldi XL,lo8(_data)`\
78-`       ldi XH,hi8(_data)`\
79-`       ldi YH,hi8(_edata)`\
80-`       ldi ZL,lo8(_etext)`\
81-` {$ifdef CPUAVR_16_REGS}`\
82-`       ldi ZH,hi8(_etext)+(0x40) // program memory mapped to $4000 in data space`\
83-` {$else CPUAVR_16_REGS}`\
84-`       ldi ZH,hi8(_etext)`\
85-` {$endif CPUAVR_16_REGS}`
81+ // Initialize .data section
82+ ldi XL,lo8(_data)
83+ ldi XH,hi8(_data)
84+ ldi YH,hi8(_edata)
85+ ldi ZL,lo8(_etext)
86+ {$ifdef CPUAVR_16_REGS}
87+ ldi ZH,hi8(_etext)+(0x40) // program memory mapped to $4000 in data space
88+ {$else CPUAVR_16_REGS}
89+ ldi ZH,hi8(_etext)
90+ {$endif CPUAVR_16_REGS}
91+```
8692
8793Note that register names are r0, r1 etc. without a sigil, and that register assignment is right-to-left.
8894
8995### i386
9096
9197This fragment is from FpSysCall alias FPC_SYSCALL6 in FPC's ./rtl/linux/i386/syscall.inc:
9298
93-`asm`\
94-`        push  %ebx`\
95-`        push  %edx`\
96-`        push  %esi`\
97-`        push  %edi`\
98-`        push  %ebp`\
99-`        push  %ecx`\
100-`        cmp   $0, sysenter_supported`\
101-`        jne   .LSysEnter`\
102-`        movl  %edx,%ebx         // param1`\
103-`        pop   %ecx              // param2`\
104-`        movl  param3,%edx       // param3`\
105-`        movl  param4,%esi       // param4`\
106-`        movl  param5,%edi       // param5`\
107-`        movl  param6,%ebp       // param6`\
108-`        int   $0x80`\
109-`        jmp   .LTail`\
110-`  .LSysEnter:`\
111-`        movl  %edx,%ebx         // param1`\
112-`        pop   %ecx              // param2`\
113-`        movl  param3,%edx       // param3`\
114-`        movl  param4,%esi       // param4`\
115-`        movl  param5,%edi       // param5`\
116-`        movl  param6,%ebp       // param6`\
117-`        call psysinfo`\
118-`  .LTail:`\
119-`        pop   %ebp`\
120-`        pop   %edi`\
121-`        pop   %esi`\
122-`        pop   %edx`\
123-`        pop   %ebx`\
124-`        cmpl  $-4095,%eax`\
125-`        jb    .LSyscOK`\
126-`        negl  %eax`\
127-`        call  seterrno`\
128-`        movl  $-1,%eax`\
129-`  .LSyscOK:`\
130-`end;`
99+``` pascal
100+asm
101+ push %ebx
102+ push %edx
103+ push %esi
104+ push %edi
105+ push %ebp
106+ push %ecx
107+ cmp $0, sysenter_supported
108+ jne .LSysEnter
109+ movl %edx,%ebx // param1
110+ pop %ecx // param2
111+ movl param3,%edx // param3
112+ movl param4,%esi // param4
113+ movl param5,%edi // param5
114+ movl param6,%ebp // param6
115+ int $0x80
116+ jmp .LTail
117+ .LSysEnter:
118+ movl %edx,%ebx // param1
119+ pop %ecx // param2
120+ movl param3,%edx // param3
121+ movl param4,%esi // param4
122+ movl param5,%edi // param5
123+ movl param6,%ebp // param6
124+ call psysinfo
125+ .LTail:
126+ pop %ebp
127+ pop %edi
128+ pop %esi
129+ pop %edx
130+ pop %ebx
131+ cmpl $-4095,%eax
132+ jb .LSyscOK
133+ negl %eax
134+ call seterrno
135+ movl $-1,%eax
136+ .LSyscOK:
137+end;
138+```
131139
132140This fragment is from ret_from_fork in Linux's ./arch/x86/kernel/entry_32.S:
133141
134-`ENTRY(ret_from_fork)`\
135-`        CFI_STARTPROC`\
136-`        pushl %eax`\
137-`        CFI_ADJUST_CFA_OFFSET 4`\
138-`        call schedule_tail`\
139-`        GET_THREAD_INFO(%ebp)`\
140-`        popl %eax`\
141-`        CFI_ADJUST_CFA_OFFSET -4`\
142-`        pushl $0x0202                   # Reset kernel eflags`\
143-`        CFI_ADJUST_CFA_OFFSET 4`\
144-`        popfl`\
145-`        CFI_ADJUST_CFA_OFFSET -4`\
146-`        jmp syscall_exit`\
147-`        CFI_ENDPROC`\
148-`END(ret_from_fork)`
142+``` pascal
143+ENTRY(ret_from_fork)
144+ CFI_STARTPROC
145+ pushl %eax
146+ CFI_ADJUST_CFA_OFFSET 4
147+ call schedule_tail
148+ GET_THREAD_INFO(%ebp)
149+ popl %eax
150+ CFI_ADJUST_CFA_OFFSET -4
151+ pushl $0x0202 # Reset kernel eflags
152+ CFI_ADJUST_CFA_OFFSET 4
153+ popfl
154+ CFI_ADJUST_CFA_OFFSET -4
155+ jmp syscall_exit
156+ CFI_ENDPROC
157+END(ret_from_fork)
158+```
149159
150160Note that register names are eax, ebx etc. with % as a mandatory sigil, and that register assignment is left-to-right.
151161
152162### IA-64
153163
154164This compiler exists only as a minimal stub.
155165
156166### M68K
157167
158168This compiler exists in FPC v1 and has lain dormant for a while but has since inclusion in FPC 2.x in the end of 2013.
159169
160170### MIPS
161171
162172This fragment is from FpSysCall alias FPC_SYSCALL6 in FPC's ./rtl/linux/mips/syscall.inc:
163173
164-`asm`\
165-`  sw  $4,0($23)`\
166-`  sw  $5,-4($23)`\
167-`  sw  $6,-8($23)`\
168-`  sw  $7,-12($23)`\
169-`  sw  $8,-16($23)`\
170-`  sw  $9,-20($23)`\
171-`  sw  $10,-24($23)`\
172-`  sw  $11,-28($23)`\
173-`  sw  $12,-32($23)`\
174-`  sw  $13,-36($23)`\
175-`  sw  $14,-40($23)`\
176-`  addiu  $23,$23,-44`\
174+``` pascal
175+asm
176+ sw $4,0($23)
177+ sw $5,-4($23)
178+ sw $6,-8($23)
179+ sw $7,-12($23)
180+ sw $8,-16($23)
181+ sw $9,-20($23)
182+ sw $10,-24($23)
183+ sw $11,-28($23)
184+ sw $12,-32($23)
185+ sw $13,-36($23)
186+ sw $14,-40($23)
187+ addiu $23,$23,-44
177188
178-`  move  $2,$4`\
179-`  move  $4,$5`\
180-`  move  $5,$6`\
181-`  move  $6,$7`\
182-`  move  $7,$8`\
183-`  move  $8,$9`\
184-`  lw  $9,0($fp)`\
189+ move $2,$4
190+ move $4,$5
191+ move $5,$6
192+ move $6,$7
193+ move $7,$8
194+ move $8,$9
195+ lw $9,0($fp)
185196
186-`  subu  $29,32`\
187-`  sw    $8, 16($29)`\
188-`  sw    $9, 20($29)`\
189-`  syscall`\
190-`  nop`\
191-`  addiu $29,32`\
197+ subu $29,32
198+ sw $8, 16($29)
199+ sw $9, 20($29)
200+ syscall
201+ nop
202+ addiu $29,32
192203
193-`  beq $7,$0,.LDone`\
194-`  nop`\
195-`  lui   $8,%hi(fpc_threadvar_relocate_proc)`\
196-`  addiu   $8,%lo(fpc_threadvar_relocate_proc)`\
197-`  lw   $8,0($8)`\
198-`  bne  $8,$0,.LThreaded`\
199-`  nop`\
200-`  lui   $4,%hi(Errno+4)`\
201-`  addiu   $4,%lo(Errno+4)`\
202-`  sw    $2,0($4)`\
203-`  b     .LFailed`\
204-`  nop`\
205-`.LThreaded:`\
206-`  sw   $2,-4($fp)#temp#sw $4`\
207-`  lui   $4,%hi(errno)`\
208-`  addiu   $4,$4,%lo(errno)`\
209-`  jal   $8`\
210-`  nop`\
211-`  lw  $8,-4($fp)`\
212-`  sw  $8,0($2)`\
213-`.LFailed:`\
214-`  li    $2,-1`\
215-`.LDone:`\
204+ beq $7,$0,.LDone
205+ nop
206+ lui $8,%hi(fpc_threadvar_relocate_proc)
207+ addiu $8,%lo(fpc_threadvar_relocate_proc)
208+ lw $8,0($8)
209+ bne $8,$0,.LThreaded
210+ nop
211+ lui $4,%hi(Errno+4)
212+ addiu $4,%lo(Errno+4)
213+ sw $2,0($4)
214+ b .LFailed
215+ nop
216+.LThreaded:
217+ sw $2,-4($fp)#temp#sw $4
218+ lui $4,%hi(errno)
219+ addiu $4,$4,%lo(errno)
220+ jal $8
221+ nop
222+ lw $8,-4($fp)
223+ sw $8,0($2)
224+.LFailed:
225+ li $2,-1
226+.LDone:
216227
217-`  addiu  $23,$23,44`\
218-`  lw  $4,0($23)`\
219-`  lw  $5,-4($23)`\
220-`  lw  $6,-8($23)`\
221-`  lw  $7,-12($23)`\
222-`  lw  $8,-16($23)`\
223-`  lw  $9,-20($23)`\
224-`  lw  $10,-24($23)`\
225-`  lw  $11,-28($23)`\
226-`  lw  $12,-32($23)`\
227-`  lw  $13,-36($23)`\
228-`  lw  $14,-40($23)`\
228+ addiu $23,$23,44
229+ lw $4,0($23)
230+ lw $5,-4($23)
231+ lw $6,-8($23)
232+ lw $7,-12($23)
233+ lw $8,-16($23)
234+ lw $9,-20($23)
235+ lw $10,-24($23)
236+ lw $11,-28($23)
237+ lw $12,-32($23)
238+ lw $13,-36($23)
239+ lw $14,-40($23)
229240
230-`end;`
241+end;
242+```
231243
232244This fragment is from ret_from_fork in Linux's ./arch/mips/kernel/entry.S:
233245
234-`FEXPORT(ret_from_fork)`\
235-`        jal     schedule_tail           # a0 = struct task_struct *prev`\
246+```
247+FEXPORT(ret_from_fork)
248+ jal schedule_tail # a0 = struct task_struct *prev
236249
237-`FEXPORT(syscall_exit)`\
238-`        local_irq_disable               # make sure need_resched and`\
239-`                                        # signals dont change between`\
240-`                                        # sampling and return`\
241-`        LONG_L  a2, TI_FLAGS($28)       # current->work`\
242-`        li      t0, _TIF_ALLWORK_MASK`\
243-`        and     t0, a2, t0`\
244-`        bnez    t0, syscall_exit_work`\
250+FEXPORT(syscall_exit)
251+ local_irq_disable # make sure need_resched and
252+ # signals dont change between
253+ # sampling and return
254+ LONG_L a2, TI_FLAGS($28) # current->work
255+ li t0, _TIF_ALLWORK_MASK
256+ and t0, a2, t0
257+ bnez t0, syscall_exit_work
245258
246-`FEXPORT(restore_all)                    # restore full frame`\
247-`        .set    noat`\
248-`        RESTORE_TEMP`\
249-`        RESTORE_AT`\
250-`        RESTORE_STATIC`\
251-`FEXPORT(restore_partial)                # restore partial frame`\
252-`        RESTORE_SOME`\
253-`        RESTORE_SP_AND_RET`\
254-`        .set    at`
259+FEXPORT(restore_all) # restore full frame
260+ .set noat
261+ RESTORE_TEMP
262+ RESTORE_AT
263+ RESTORE_STATIC
264+FEXPORT(restore_partial) # restore partial frame
265+ RESTORE_SOME
266+ RESTORE_SP_AND_RET
267+ .set at
268+```
255269
256270Note that register names are 0, 1 etc. with \$ as a mandatory sigil, and that register assignment is right-to-left; versions of GNU as from 2.18 onwards also support symbolic register names a0, a1 etc. There is a delay slot after branch etc. instructions.
257271
258272### PowerPC
259273
260274This fragment is from FpSysCall alias FPC_SYSCALL6 in FPC's ./rtl/linux/powerpc/syscall.inc:
261275
262-`asm`\
263-`  mr  r0,r3`\
264-`  mr  r3,r4`\
265-`  mr  r4,r5`\
266-`  mr  r5,r6`\
267-`  mr  r6,r7`\
268-`  mr  r7,r8`\
269-`  mr  r8,r9`\
270-`  sc`\
271-`  bns   .LDone`\
272-`  lis   r10,(fpc_threadvar_relocate_proc)@ha`\
273-`  lwz   r10,(fpc_threadvar_relocate_proc)@l(r10)`\
274-`  cmpwi r10,0`\
275-`  bne   .LThreaded`\
276-`  lis   r4,(Errno+4)@ha`\
277-`  stw   r3,(Errno+4)@l(r4)`\
278-`  b     .LFailed`\
279-`.LThreaded:`\
280-`  stw   r3,temp`\
281-`  mflr  r3`\
282-`  mtctr r10`\
283-`  lis   r4,(errno)@ha`\
284-`  stw   r3,retaddress`\
285-`  lwz   r3,(errno)@l(r4)`\
286-`  bctrl`\
287-`  lwz   r4,temp`\
288-`  lwz   r5,retaddress`\
289-`  stw   r4,0(r3)`\
290-`  mtlr  r5`\
291-`.LFailed:`\
292-` li    r3,-1`\
293-`.LDone:`\
294-`end;`
276+``` pascal
277+asm
278+ mr r0,r3
279+ mr r3,r4
280+ mr r4,r5
281+ mr r5,r6
282+ mr r6,r7
283+ mr r7,r8
284+ mr r8,r9
285+ sc
286+ bns .LDone
287+ lis r10,(fpc_threadvar_relocate_proc)@ha
288+ lwz r10,(fpc_threadvar_relocate_proc)@l(r10)
289+ cmpwi r10,0
290+ bne .LThreaded
291+ lis r4,(Errno+4)@ha
292+ stw r3,(Errno+4)@l(r4)
293+ b .LFailed
294+.LThreaded:
295+ stw r3,temp
296+ mflr r3
297+ mtctr r10
298+ lis r4,(errno)@ha
299+ stw r3,retaddress
300+ lwz r3,(errno)@l(r4)
301+ bctrl
302+ lwz r4,temp
303+ lwz r5,retaddress
304+ stw r4,0(r3)
305+ mtlr r5
306+.LFailed:
307+ li r3,-1
308+.LDone:
309+end;
310+```
295311
296312This fragment is from ret_from_fork in Linux's ./arch/powerpc/kernel/entry_32.S:
297313
298-`ret_from_syscall:`\
299-`        mr      r6,r3`\
300-`        rlwinm  r12,r1,0,0,(31-THREAD_SHIFT)    /* current_thread_info() */`\
301-`        /* disable interrupts so current_thread_info()->flags can't change */`\
302-`        LOAD_MSR_KERNEL(r10,MSR_KERNEL) /* doesn't include MSR_EE */`\
303-`        /* Note: We don't bother telling lockdep about it */`\
304-`        SYNC`\
305-`        MTMSRD(r10)`\
306-`        lwz     r9,TI_FLAGS(r12)`\
307-`        li      r8,-_LAST_ERRNO`\
308-`        andi.   r0,r9,(_TIF_SYSCALL_T_OR_A|_TIF_SINGLESTEP|_TIF_USER_WORK_MASK|_TIF_PERSYSCALL_MASK)`\
309-`        bne-    syscall_exit_work`\
310-`        cmplw   0,r3,r8`\
311-`        blt+    syscall_exit_cont`\
312-`        lwz     r11,_CCR(r1)                    /* Load CR */`\
313-`        neg     r3,r3`\
314-`        oris    r11,r11,0x1000  /* Set SO bit in CR */`\
315-`        stw     r11,_CCR(r1)`\
316-`syscall_exit_cont:`\
317-`        lwz     r8,_MSR(r1)`\
318-`BEGIN_FTR_SECTION`\
319-`        lwarx   r7,0,r1`\
320-`END_FTR_SECTION_IFSET(CPU_FTR_NEED_PAIRED_STWCX)`\
321-`        stwcx.  r0,0,r1                 /* to clear the reservation */`\
322-`        lwz     r4,_LINK(r1)`\
323-`        lwz     r5,_CCR(r1)`\
324-`        mtlr    r4`\
325-`        mtcr    r5`\
326-`        lwz     r7,_NIP(r1)`\
327-`        FIX_SRR1(r8, r0)`\
328-`        lwz     r2,GPR2(r1)`\
329-`        lwz     r1,GPR1(r1)`\
330-`        mtspr   SPRN_SRR0,r7`\
331-`        mtspr   SPRN_SRR1,r8`\
332-`        SYNC`\
333-`        RFI`\
314+```
315+ret_from_syscall:
316+ mr r6,r3
317+ rlwinm r12,r1,0,0,(31-THREAD_SHIFT) /* current_thread_info() */
318+ /* disable interrupts so current_thread_info()->flags can't change */
319+ LOAD_MSR_KERNEL(r10,MSR_KERNEL) /* doesn't include MSR_EE */
320+ /* Note: We don't bother telling lockdep about it */
321+ SYNC
322+ MTMSRD(r10)
323+ lwz r9,TI_FLAGS(r12)
324+ li r8,-_LAST_ERRNO
325+ andi. r0,r9,(_TIF_SYSCALL_T_OR_A|_TIF_SINGLESTEP|_TIF_USER_WORK_MASK|_TIF_PERSYSCALL_MASK)
326+ bne- syscall_exit_work
327+ cmplw 0,r3,r8
328+ blt+ syscall_exit_cont
329+ lwz r11,_CCR(r1) /* Load CR */
330+ neg r3,r3
331+ oris r11,r11,0x1000 /* Set SO bit in CR */
332+ stw r11,_CCR(r1)
333+syscall_exit_cont:
334+ lwz r8,_MSR(r1)
335+BEGIN_FTR_SECTION
336+ lwarx r7,0,r1
337+END_FTR_SECTION_IFSET(CPU_FTR_NEED_PAIRED_STWCX)
338+ stwcx. r0,0,r1 /* to clear the reservation */
339+ lwz r4,_LINK(r1)
340+ lwz r5,_CCR(r1)
341+ mtlr r4
342+ mtcr r5
343+ lwz r7,_NIP(r1)
344+ FIX_SRR1(r8, r0)
345+ lwz r2,GPR2(r1)
346+ lwz r1,GPR1(r1)
347+ mtspr SPRN_SRR0,r7
348+ mtspr SPRN_SRR1,r8
349+ SYNC
350+ RFI
334351
335-`66:     li      r3,-ENOSYS`\
336-`        b       ret_from_syscall`\
352+66: li r3,-ENOSYS
353+ b ret_from_syscall
337354
338-`ret_from_fork:`\
339-`        REST_NVGPRS(r1)`\
340-`        bl      schedule_tail`\
341-`        li      r3,0`\
342-`        b       ret_from_syscall`
355+ret_from_fork:
356+ REST_NVGPRS(r1)
357+ bl schedule_tail
358+ li r3,0
359+ b ret_from_syscall
360+```
343361
344362Note that register names are r0, r1 etc. without a sigil, and that register assignment is right-to-left.
345363
346364### PowerPC-64
347365
348366Similar to PowerPC above.
349367
350368### SPARC
351369
352370This fragment is from FpSysCall alias FPC_SYSCALL6 in FPC's ./rtl/linux/sparc/syscall.inc:
353371
354-`asm`\
355-`        mov     %i0,%g1`\
356-`        mov     %i1,%o0`\
357-`        mov     %i2,%o1`\
358-`        mov     %i3,%o2`\
359-`        mov     %i4,%o3`\
360-`        ld      [%i6+92],%o5`\
361-`        mov     %i5,%o4`\
362-`        ta      0x10`\
363-`        bcc     .LSyscOK`\
364-`        nop`\
365-`        mov     %o0,%l0`\
366-`        sethi   %hi(fpc_threadvar_relocate_proc),%o2`\
367-`        or      %o2,%lo(fpc_threadvar_relocate_proc),%o2`\
368-`        ld      [%o2],%o3`\
369-`        subcc   %o3,%g0,%g0`\
370-`        bne     .LThread`\
371-`        nop`\
372-`        sethi   %hi(Errno+4),%o0`\
373-`        ba      .LNoThread`\
374-`        or      %o0,%lo(Errno+4),%o0`\
375-`.LThread:`\
376-`        sethi   %hi(Errno),%o0`\
377-`        or      %o0,%lo(Errno),%o0`\
378-`        call    %o3`\
379-`        ld      [%o0],%o0`\
380-`.LNoThread:`\
381-`        st      %l0,[%o0]`\
382-`        mov     -1,%o0`\
383-`.LSyscOK:`\
384-`        mov     %o0,%i0`\
385-`end;`
372+``` pascal
373+asm
374+ mov %i0,%g1
375+ mov %i1,%o0
376+ mov %i2,%o1
377+ mov %i3,%o2
378+ mov %i4,%o3
379+ ld [%i6+92],%o5
380+ mov %i5,%o4
381+ ta 0x10
382+ bcc .LSyscOK
383+ nop
384+ mov %o0,%l0
385+ sethi %hi(fpc_threadvar_relocate_proc),%o2
386+ or %o2,%lo(fpc_threadvar_relocate_proc),%o2
387+ ld [%o2],%o3
388+ subcc %o3,%g0,%g0
389+ bne .LThread
390+ nop
391+ sethi %hi(Errno+4),%o0
392+ ba .LNoThread
393+ or %o0,%lo(Errno+4),%o0
394+.LThread:
395+ sethi %hi(Errno),%o0
396+ or %o0,%lo(Errno),%o0
397+ call %o3
398+ ld [%o0],%o0
399+.LNoThread:
400+ st %l0,[%o0]
401+ mov -1,%o0
402+.LSyscOK:
403+ mov %o0,%i0
404+end;
405+```
386406
387407This fragment is from ret_from_fork in Linux's ./arch/sparc/kernel/entry.S:
388408
389-`linux_fast_syscall:`\
390-`        andn    %l7, 3, %l7`\
391-`        mov     %i0, %o0`\
392-`        mov     %i1, %o1`\
393-`        mov     %i2, %o2`\
394-`        jmpl    %l7 + %g0, %g0`\
395-`         mov    %i3, %o3`\
409+```
410+linux_fast_syscall:
411+ andn %l7, 3, %l7
412+ mov %i0, %o0
413+ mov %i1, %o1
414+ mov %i2, %o2
415+ jmpl %l7 + %g0, %g0
416+ mov %i3, %o3
396417
397-`linux_syscall_trace:`\
398-`        add     %sp, STACKFRAME_SZ, %o0`\
399-`        call    syscall_trace`\
400-`         mov    0, %o1`\
401-`        cmp     %o0, 0`\
402-`        bne     3f`\
403-`         mov    -ENOSYS, %o0`\
404-`        mov     %i0, %o0`\
405-`        mov     %i1, %o1`\
406-`        mov     %i2, %o2`\
407-`        mov     %i3, %o3`\
408-`        b       2f`\
409-`         mov    %i4, %o4`\
418+linux_syscall_trace:
419+ add %sp, STACKFRAME_SZ, %o0
420+ call syscall_trace
421+ mov 0, %o1
422+ cmp %o0, 0
423+ bne 3f
424+ mov -ENOSYS, %o0
425+ mov %i0, %o0
426+ mov %i1, %o1
427+ mov %i2, %o2
428+ mov %i3, %o3
429+ b 2f
430+ mov %i4, %o4
410431
411-`        .globl  ret_from_fork`\
412-`ret_from_fork:`\
413-`        call    schedule_tail`\
414-`         mov    %g3, %o0`\
415-`        b       ret_sys_call`\
416-`         ld     [%sp + STACKFRAME_SZ + PT_I0], %o0`\
432+ .globl ret_from_fork
433+ret_from_fork:
434+ call schedule_tail
435+ mov %g3, %o0
436+ b ret_sys_call
437+ ld [%sp + STACKFRAME_SZ + PT_I0], %o0
417438
418-`        /* Linux native system calls enter here... */`\
419-`        .align  4`\
420-`        .globl  linux_sparc_syscall`\
421-`linux_sparc_syscall:`\
422-`        sethi   %hi(PSR_SYSCALL), %l4`\
423-`        or      %l0, %l4, %l0`\
424-`        /* Direct access to user regs, must faster. */`\
425-`        cmp     %g1, NR_SYSCALLS`\
426-`        bgeu    linux_sparc_ni_syscall`\
427-`         sll    %g1, 2, %l4`\
428-`        ld      [%l7 + %l4], %l7`\
429-`        andcc   %l7, 1, %g0`\
430-`        bne     linux_fast_syscall`\
431-`         /* Just do first insn from SAVE_ALL in the delay slot */`
439+ /* Linux native system calls enter here... */
440+ .align 4
441+ .globl linux_sparc_syscall
442+linux_sparc_syscall:
443+ sethi %hi(PSR_SYSCALL), %l4
444+ or %l0, %l4, %l0
445+ /* Direct access to user regs, must faster. */
446+ cmp %g1, NR_SYSCALLS
447+ bgeu linux_sparc_ni_syscall
448+ sll %g1, 2, %l4
449+ ld [%l7 + %l4], %l7
450+ andcc %l7, 1, %g0
451+ bne linux_fast_syscall
452+ /* Just do first insn from SAVE_ALL in the delay slot */
453+```
432454
433455Note that register names are i0, i1 etc. with % as a mandatory sigil, and that register assignment is left-to-right. Registers are windowed, and there is a delay slot after branch etc. instructions.
434456
435457### VIS
436458
437459This compiler exists only as a minimal stub.
438460
439461### x86
440462
441463Refer to i386 above.
442464
443465### x86-64
444466
445467This fragment is from FpSysCall alias FPC_SYSCALL6 in FPC's ./rtl/linux/x86_64/syscall.inc:
446468
447-`asm`\
448-`  movq sysnr, %rax        { Syscall number -> rax.  }`\
449-`  movq param1, %rdi         { shift arg1 - arg5. }`\
450-`  movq param2, %rsi`\
451-`  movq param3, %rdx`\
452-`  movq param4, %r10`\
453-`  movq param5, %r8`\
454-`  movq param6, %r9`\
455-`  syscall                 { Do the system call. }`\
456-`  cmpq $-4095, %rax       { Check %rax for error.  }`\
457-`  jnae .LSyscOK           { Jump to error handler if error.  }`\
458-`  negq  %rax`\
459-`  movq  %rax,%rdx`\
460-`  movq  fpc_threadvar_relocate_proc,%rax`\
461-`  leaq  Errno,%r11`\
462-`  testq %rax,%rax`\
463-`  jne   .LThread`\
464-`  movl  %edx,8(%r11)`\
465-`  jmp   .LNoThread`\
466-`.LThread:`\
467-`  pushq %rdx`\
468-`  movq  (%r11),%rdi`\
469-`  call  *%rax`\
470-`  popq  %rdx`\
471-`  movl  %edx,(%rax)`\
472-`.LNoThread:`\
473-`  movq  $-1,%rax`\
474-`.LSyscOK:`\
475-`end;`
469+``` pascal
470+asm
471+ movq sysnr, %rax { Syscall number -> rax. }
472+ movq param1, %rdi { shift arg1 - arg5. }
473+ movq param2, %rsi
474+ movq param3, %rdx
475+ movq param4, %r10
476+ movq param5, %r8
477+ movq param6, %r9
478+ syscall { Do the system call. }
479+ cmpq $-4095, %rax { Check %rax for error. }
480+ jnae .LSyscOK { Jump to error handler if error. }
481+ negq %rax
482+ movq %rax,%rdx
483+ movq fpc_threadvar_relocate_proc,%rax
484+ leaq Errno,%r11
485+ testq %rax,%rax
486+ jne .LThread
487+ movl %edx,8(%r11)
488+ jmp .LNoThread
489+.LThread:
490+ pushq %rdx
491+ movq (%r11),%rdi
492+ call *%rax
493+ popq %rdx
494+ movl %edx,(%rax)
495+.LNoThread:
496+ movq $-1,%rax
497+.LSyscOK:
498+end;
499+```
476500
477501This fragment is from ret_from_fork in Linux's ./arch/x86/kernel/entry_64.S:
478502
479-`ENTRY(ret_from_fork)`\
480-`        DEFAULT_FRAME`\
503+``` pascal
504+ENTRY(ret_from_fork)
505+ DEFAULT_FRAME
481506
482-`        LOCK ; btr $TIF_FORK,TI_flags(%r8)`\
507+ LOCK ; btr $TIF_FORK,TI_flags(%r8)
483508
484-`        push kernel_eflags(%rip)`\
485-`        CFI_ADJUST_CFA_OFFSET 8`\
486-`        popf                                    # reset kernel eflags`\
487-`        CFI_ADJUST_CFA_OFFSET -8`\
509+ push kernel_eflags(%rip)
510+ CFI_ADJUST_CFA_OFFSET 8
511+ popf # reset kernel eflags
512+ CFI_ADJUST_CFA_OFFSET -8
488513
489-`        call schedule_tail                      # rdi: 'prev' task parameter`\
514+ call schedule_tail # rdi: 'prev' task parameter
490515
491-`        GET_THREAD_INFO(%rcx)`\
516+ GET_THREAD_INFO(%rcx)
492517
493-`        RESTORE_REST`\
518+ RESTORE_REST
494519
495-`        testl $3, CS-ARGOFFSET(%rsp)            # from kernel_thread?`\
496-`        je   int_ret_from_sys_call`\
520+ testl $3, CS-ARGOFFSET(%rsp) # from kernel_thread?
521+ je int_ret_from_sys_call
497522
498-`        testl $_TIF_IA32, TI_flags(%rcx)        # 32-bit compat task needs IRET`\
499-`        jnz  int_ret_from_sys_call`\
523+ testl $_TIF_IA32, TI_flags(%rcx) # 32-bit compat task needs IRET
524+ jnz int_ret_from_sys_call
500525
501-`        RESTORE_TOP_OF_STACK %rdi, -ARGOFFSET`\
502-`        jmp ret_from_sys_call                   # go to the SYSRET fastpath`\
526+ RESTORE_TOP_OF_STACK %rdi, -ARGOFFSET
527+ jmp ret_from_sys_call # go to the SYSRET fastpath
503528
504-`        CFI_ENDPROC`\
505-`END(ret_from_fork)`
529+ CFI_ENDPROC
530+END(ret_from_fork)
531+```
506532
507533Note that register names are rax, rbx etc. with % as a mandatory sigil, and that register assignment is left-to-right.
508534
509535### zSeries (S/390)
510536
511537As of early 2012, IBM's [[ZSeries|zSeries]] (formerly System/390) is not supported by the compiler, refer to [[ZSeries/Part 1]] for the current implementation status. Note that Linux 2.6 probably won't run on anything older than an S/390 G5.
512538
513539#### For the GNU assembler (gas):
514540
515541This fragment is from ret_from_fork in Linux's ./arch/s390/kernel/entry.S:
516542
517-`#define BASED(name) name-system_call(%r13)`\
543+```
544+#define BASED(name) name-system_call(%r13)
518545
519-`        .globl  ret_from_fork`\
520-`ret_from_fork:`\
521-`        l       %r13,__LC_SVC_NEW_PSW+4`\
522-`        l       %r9,__LC_THREAD_INFO    # load pointer to thread_info struct`\
523-`        tm      SP_PSW+1(%r15),0x01     # forking a kernel thread ?`\
524-`        bo      BASED(0f)`\
525-`        st      %r15,SP_R15(%r15)       # store stack pointer for new kthread`\
526-`0:      l       %r1,BASED(.Lschedtail)`\
527-`        basr    %r14,%r1`\
528-`        TRACE_IRQS_ON`\
529-`        stosm   __SF_EMPTY(%r15),0x03   # reenable interrupts`\
530-`        b       BASED(sysc_tracenogo)`\
546+ .globl ret_from_fork
547+ret_from_fork:
548+ l %r13,__LC_SVC_NEW_PSW+4
549+ l %r9,__LC_THREAD_INFO # load pointer to thread_info struct
550+ tm SP_PSW+1(%r15),0x01 # forking a kernel thread ?
551+ bo BASED(0f)
552+ st %r15,SP_R15(%r15) # store stack pointer for new kthread
553+0: l %r1,BASED(.Lschedtail)
554+ basr %r14,%r1
555+ TRACE_IRQS_ON
556+ stosm __SF_EMPTY(%r15),0x03 # reenable interrupts
557+ b BASED(sysc_tracenogo)
531558
532-`sysc_tracenogo:`\
533-`        tm      __TI_flags+2(%r9),_TIF_SYSCALL`\
534-`        bz      BASED(sysc_return)`\
535-`        l       %r1,BASED(.Ltrace_exit)`\
536-`        la      %r2,SP_PTREGS(%r15)     # load pt_regs`\
537-`        la      %r14,BASED(sysc_return)`\
538-`        br      %r1`
559+sysc_tracenogo:
560+ tm __TI_flags+2(%r9),_TIF_SYSCALL
561+ bz BASED(sysc_return)
562+ l %r1,BASED(.Ltrace_exit)
563+ la %r2,SP_PTREGS(%r15) # load pt_regs
564+ la %r14,BASED(sysc_return)
565+ br %r1
566+```
539567
540568Note that register names are r0, r1 etc. with % as a mandatory sigil. Older assemblers (below) arranged the operands as ordered in the binary opcode (i.e. register assignment order varies by operation), this might have changed for the GNU assembler resulting in obvious compatibility problems.
541569
542570#### For the IBM assembler:
543571
544572For the purpose of comparison and with minimal comment, here are "Hello, World!" programs compiled with GCC for different targets.
545573
546-`GCCMVS on MUSIC/SP hosted by Sim/390 using the command`\
574+``` pascal
575+GCCMVS on MUSIC/SP hosted by Sim/390 using the command
547576
548-`gccmvs_v30 -S -o hello.asm ... hello.c`\
577+gccmvs_v30 -S -o hello.asm ... hello.c
549578
550-`generates this:`\
579+generates this:
551580
552-`LC0     EQU   *`\
553-`        DC    C'Hello, MUSIC/SP!'`\
554-`        DC    X'15'`\
555-`        DC    X'0'`\
556-`        DS    0F`\
557-`        EXTRN @@CRT0`\
558-`* Function main prologue`\
559-`MAIN    PDPPRLG CINDEX=0,FRAME=92,BASER=12,ENTRY=YES`\
560-`        B     FEN0`\
561-`        LTORG`\
562-`FEN0    EQU   *`\
563-`        DROP  12`\
564-`        BALR  12,0`\
565-`        USING *,12`\
566-`PG0     EQU   *`\
567-`        LR    11,1`\
568-`        L     10,=A(PGT0)`\
569-`* Function main code`\
570-`        MVC   88(4,13),=A(LC0)`\
571-`        LA    1,88(,13)`\
572-`        L     15,=V(PRINTF)`\
573-`        BALR  14,15`\
574-`        SLR   2,2`\
575-`        LR    15,2`
581+LC0 EQU *
582+ DC C'Hello, MUSIC/SP!'
583+ DC X'15'
584+ DC X'0'
585+ DS 0F
586+ EXTRN @@CRT0
587+* Function main prologue
588+MAIN PDPPRLG CINDEX=0,FRAME=92,BASER=12,ENTRY=YES
589+ B FEN0
590+ LTORG
591+FEN0 EQU *
592+ DROP 12
593+ BALR 12,0
594+ USING *,12
595+PG0 EQU *
596+ LR 11,1
597+ L 10,=A(PGT0)
598+* Function main code
599+ MVC 88(4,13),=A(LC0)
600+ LA 1,88(,13)
601+ L 15,=V(PRINTF)
602+ BALR 14,15
603+ SLR 2,2
604+ LR 15,2
605+```
576606
577607In the example above, note that registers are identified numerically (e.g. BALR 12,0); it is common to load a macro package which introduces a leading R as a sigil (i.e. BALR R12,R0). The operands are ordered as in the binary opcode (i.e. register assignment order varies by operation), this might have changed for the GNU assembler (see section above) resulting in obvious compatibility problems.
578608
579-`GCC on zSeries Linux hosted by Hercules using the command`\
609+``` pascal
610+GCC on zSeries Linux hosted by Hercules using the command
580611
581-`gcc -S -o hello.asm hello.c`\
612+gcc -S -o hello.asm hello.c
582613
583-`apparently assuming -march=g5, generates this:`\
614+apparently assuming -march=g5, generates this:
584615
585-`.LC0:`\
586-`       .string "Hello, Linux!"`\
587-`.text`\
588-`       .align  4`\
589-`.globl main`\
590-`       .type   main, @function`\
591-`main:`\
592-`.LFB2:`\
593-`       stm     %r11,%r15,44(%r15)`\
594-`.LCFI0:`\
595-`       basr    %r13,0`\
596-`.L3:`\
597-`       ahi     %r15,-96`\
598-`.LCFI1:`\
599-`       lr      %r11,%r15`\
600-`.LCFI2:`\
601-`       l       %r1,.L4-.L3(%r13)`\
602-`       lr      %r2,%r1`\
603-`       l       %r1,.L5-.L3(%r13)`\
604-`       basr    %r14,%r1`\
605-`       lhi     %r1,0`\
606-`       lr      %r2,%r1`\
607-`       l       %r4,152(%r11)`\
608-`       lm      %r11,%r15,140(%r11)`\
609-`       br      %r4`\
610-`       .align  4`\
611-`.L5:`\
612-`       .long   puts`\
613-`.L4:`\
614-`       .long   .LC0`\
616+.LC0:
617+ .string "Hello, Linux!"
618+.text
619+ .align 4
620+.globl main
621+ .type main, @function
622+main:
623+.LFB2:
624+ stm %r11,%r15,44(%r15)
625+.LCFI0:
626+ basr %r13,0
627+.L3:
628+ ahi %r15,-96
629+.LCFI1:
630+ lr %r11,%r15
631+.LCFI2:
632+ l %r1,.L4-.L3(%r13)
633+ lr %r2,%r1
634+ l %r1,.L5-.L3(%r13)
635+ basr %r14,%r1
636+ lhi %r1,0
637+ lr %r2,%r1
638+ l %r4,152(%r11)
639+ lm %r11,%r15,140(%r11)
640+ br %r4
641+ .align 4
642+.L5:
643+ .long puts
644+.L4:
645+ .long .LC0
615646
616-`I think that's still effectively generating code similar to traditional`\
617-`assembler's "using".`
647+I think that's still effectively generating code similar to traditional
648+assembler's "using".
649+```
618650
619-`With the explicit instruction to use a later CPU:`\
651+``` pascal
652+With the explicit instruction to use a later CPU:
620653
621-`gcc -march=z900 -S -o hello.asm hello.c`\
654+gcc -march=z900 -S -o hello.asm hello.c
622655
623-`it generates this:`\
656+it generates this:
624657
625-`.LC0:`\
626-`       .string "Hello, Linux!"`\
627-`.text`\
628-`       .align  4`\
629-`.globl main`\
630-`       .type   main, @function`\
631-`main:`\
632-`.LFB2:`\
633-`       stm     %r11,%r15,44(%r15)`\
634-`.LCFI0:`\
635-`       ahi     %r15,-96`\
636-`.LCFI1:`\
637-`       lr      %r11,%r15`\
638-`.LCFI2:`\
639-`       larl    %r2,.LC0`\
640-`       brasl   %r14,puts`\
641-`       lhi     %r1,0`\
642-`       lr      %r2,%r1`\
643-`       l       %r4,152(%r11)`\
644-`       lm      %r11,%r15,140(%r11)`\
645-`       br      %r4`\
658+.LC0:
659+ .string "Hello, Linux!"
660+.text
661+ .align 4
662+.globl main
663+ .type main, @function
664+main:
665+.LFB2:
666+ stm %r11,%r15,44(%r15)
667+.LCFI0:
668+ ahi %r15,-96
669+.LCFI1:
670+ lr %r11,%r15
671+.LCFI2:
672+ larl %r2,.LC0
673+ brasl %r14,puts
674+ lhi %r1,0
675+ lr %r2,%r1
676+ l %r4,152(%r11)
677+ lm %r11,%r15,140(%r11)
678+ br %r4
646679
647-`If I'm reading that correctly, it's referring to the string directly`\
648-`without using a register.`
680+If I'm reading that correctly, it's referring to the string directly
681+without using a register.
682+```
649683
650684## ABI references
651685
652686The list of CPUs etc. is based on those found in the compiler (see above).
653687
654688### Alpha
655689
656690### ARM
657691
658692### AVR
659693
660694Currently only the embedded target is supported by FPC. The compiler follows the register layout [1](https://gcc.gnu.org/wiki/avr-gcc#Register_Layout), frame layout [2](https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout) and calling convention [3](https://gcc.gnu.org/wiki/avr-gcc#Calling_Convention) of the avr-gcc ABI.
661695
662696### i386
663697
664698### IA-64
665699
666700### M68K
667701
668702### MIPS
669703
670704MIPS has several kinds of ABI, including O32, EABI, n32, n64, nowadays the de facto one should be O32. Some ABI document for MIPS O32 ABI can be found in the internet, unfortunately due to the copyright limit, we cannot provide it here. However, this page [4](http://www.linux-mips.org/wiki/MIPSABIHistory) has some useful links.
671705
672706### PowerPC
673707
674708### PowerPC-64
675709
676710### SPARC
677711
678712SPARC is an open architecture. All design documents are freely available (no signup required) from sparc.org and opensparc.net. sparc.org has documents on the 32 bit SPARC version V8 and the 64 bit SPARC version V9. Earlier versions of the architecture, specifically V7 are available on the net (search for Solbourne).
679713
680714You can download the SPARC V8 Architecture Manual here: [5](http://sparc.org/standards/V8.pdf) The SPARC V8 ABI for UNIX is here: [6](http://sparc.org/standards/psABI3rd.pdf)
681715
682716You can download the SPARC V9 Architecture Manual here: [7](http://sparc.org/standards/SPARCV9.pdf) The SPARC V9 ABI for UNIX is here: [8](http://sparc.org/standards/SCD.2.4.1.ps.Z) Newer versions than V9 are available on opensparc.net.
683717
684718### VIS
685719
686720### x86
687721
688722### x86-64
689723
690724### zSeries (S/390)
691725
692726The classic IBM operating systems, particularly when running on older hardware (i.e. preceding the S/390 G5) historically adopt a programming style which passes function parameters in registers and blocks of memory rather than using a stack.
693727
694728For Linux on a 32-bit S/390, "The first 5 integer (char, short, int, long, long long) and pointer parameters are passed in registers R2, R3, R4, R5 and R6. ... long long parameters are passed in register pairs. Structures of 1, 2, 4 or 8 bytes are passed as integers. ... the first 2 floating point parameters are passed in F0 and F2. ... All other parameters are passed on the stack. If the return value is not an integer, pointer, float, double or 1, 2, 4 or 9-byte structure, a "hidden" parameter in R2 will contain the address of the return area." [9](http://linuxvm.org/present/SHARE99/S8139db.pdf)
695729
696730## Other resources
697731
698732As a general point, there's some useful thoughts on binary disassembly at <http://chdk.wikia.com/wiki/GPL_Disassembling> for situations where IDA or equivalent aren't available.
699733
700734---
701735*From the Free Pascal wiki, page "Assembler and ABI Resources", as it stood on 2022-06-18 after a change by Alextp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
702736Category: FPC internals

Back to history