Port shared core and platform services to native pointer widths

This commit is contained in:
Aaron committed 2026-11-08 09:02:33 +00:00
1 parent 7a6be7f66e
commit a27d9cb39d
59 files changed
+1904 -1307

No files matched your search

+2 -2
View File
@@ -205,7 +205,7 @@ Key constants:
- `DATA_OFFSET = $10000` — data area starts at 64 KB into each page
- `LARGE_ALLOC_MAGIC = $FFFFFFFE` — sentinel for klalloc'd multi-page allocations
Interrupt safety: `pushfd; cli; pop saved_flags` (stack-balanced) + `restore_if(saved_flags)`
Interrupt safety: `pushfd; cli; pop saved_flags` (stack-balanced) + `arch.platform.cpu.unlock(saved_flags)`
inline procedure that only restores IF via `sti`, avoiding dangerous EFLAGS restoration
(IOPL, NT, VM, AC bits that caused Bad-TSS on IRET).
@@ -256,7 +256,7 @@ list, fixes Search_Page, calls `free_page_at_address`). Skips Root_Page.
| Bug | Root Cause | Fix |
|-----|-----------|-----|
| Bad TSS exception during network processing | `pushfd`/`popfd` across separate asm blocks: FPC doesn't track ESP changes in asm, causing EFLAGS corruption (NT=1, IOPL wrong) → Bad TSS on IRET | Stack-balanced `pushfd; cli; pop local_var` + `restore_if()` that only does `sti` if IF was set |
| Bad TSS exception during network processing | `pushfd`/`popfd` across separate asm blocks: FPC doesn't track ESP changes in asm, causing EFLAGS corruption (NT=1, IOPL wrong) → Bad TSS on IRET | Stack-balanced `pushfd; cli; pop local_var` + `arch.platform.cpu.unlock()` that only does `sti` if IF was set |
| GPF from SSE MOVAPS in doublebuffer flush | `SIZE_PREFIX=4`→8 gave 8-byte alignment; `MOVAPS` needs 16-byte. `klalloc` returned `page+8` (not 16-aligned) | `SIZE_PREFIX=16` (4B size + 12B pad), klalloc header expanded to 16 bytes |
| Pages never freed (historic) | `free_page` extracted wrong block index from PDE Address field | Fixed extraction to `Address SHR 10` — page freeing works for the first time |
+2 -2
View File
@@ -19,7 +19,7 @@ Every allocation is preceded by a 16-byte prefix: 4 bytes for the unit count, 12
Allocations larger than a single heap page's capacity are handled by `klalloc`, which maps contiguous virtual pages and stores a magic sentinel (`$FFFFFFFE`) in the header so `kfree` can identify them and call `klfree` instead of the normal bitmap path.
`kalloc` and `kfree` disable interrupts for the duration of their critical sections using a stack-balanced `pushfd`/`cli`/`pop` sequence and restore the interrupt flag via `restore_if` on all exit paths.
`kalloc` and `kfree` disable interrupts for the duration of their critical sections using a stack-balanced `pushfd`/`cli`/`pop` sequence and restore the interrupt flag via `arch.platform.cpu.unlock` on all exit paths.
## Dependencies
@@ -132,7 +132,7 @@ Returns the number of heap pages currently in the linked list.
## Notes
The `restore_if` helper checks bit 9 (IF) of the saved EFLAGS word and issues `STI` only if interrupts were enabled on entry. It uses `STI` rather than `POPFD` to avoid restoring potentially dangerous EFLAGS bits (IOPL, NT, VM, AC) that could trigger a General Protection Fault or Bad-TSS on the next `IRET`.
The `arch.platform.cpu.unlock` helper checks bit 9 (IF) of the saved EFLAGS word and issues `STI` only if interrupts were enabled on entry. It uses `STI` rather than `POPFD` to avoid restoring potentially dangerous EFLAGS bits (IOPL, NT, VM, AC) that could trigger a General Protection Fault or Bad-TSS on the next `IRET`.
When `DEBUG_LMM` is defined, `kalloc` allocates one additional unit and writes `GUARD_MAGIC` at the end of the allocation. `kfree` verifies this sentinel before freeing; a mismatch triggers a GPF with a syslog error, catching buffer overruns at free time.
+34 -5
View File
@@ -12,34 +12,61 @@
{ Shared PC interrupt-controller programming. Call with interrupts disabled. }
unit arch.common.legacyirq;
interface
const
PIT_INPUT_HZ = 1193182;
PIT_DIVISOR = 149;
procedure remap(masterMask, slaveMask: uint8);
procedure masks(masterMask, slaveMask: uint8);
procedure enableLine(line: uint8);
procedure timerStart;
function realIRQ(line: uint8): boolean;
procedure acknowledge(line: uint8);
implementation
uses arch.platform.cpu;
procedure writeSlow(port: uint16; value: uint8);
begin
outb(port, value);
outb($80, 0);
end;
procedure masks(masterMask, slaveMask: uint8);
begin
writeSlow($21, masterMask); writeSlow($A1, slaveMask);
writeSlow($21, masterMask);
writeSlow($A1, slaveMask);
end;
procedure remap(masterMask, slaveMask: uint8);
begin
writeSlow($20, $11); writeSlow($A0, $11);
writeSlow($21, $20); writeSlow($A1, $28);
writeSlow($21, $04); writeSlow($A1, $02);
writeSlow($21, $01); writeSlow($A1, $01);
writeSlow($20, $11);
writeSlow($A0, $11);
writeSlow($21, $20);
writeSlow($A1, $28);
writeSlow($21, $04);
writeSlow($A1, $02);
writeSlow($21, $01);
writeSlow($A1, $01);
masks(masterMask, slaveMask);
end;
procedure enableLine(line: uint8);
var
flags: PtrUInt;
begin
if line >= 16 then exit;
flags := arch.platform.cpu.lock;
if line < 8 then
outb($21, inb($21) and not uint8(1 shl line))
else begin
outb($A1, inb($A1) and not uint8(1 shl (line - 8)));
outb($21, inb($21) and not uint8(4));
end;
arch.platform.cpu.unlock(flags);
end;
procedure timerStart;
begin
{ Channel 0, low/high byte, mode 2, binary. }
@@ -47,6 +74,7 @@ begin
outb($40, PIT_DIVISOR and $FF);
outb($40, PIT_DIVISOR shr 8);
end;
function realIRQ(line: uint8): boolean;
begin
realIRQ := line < 16;
@@ -60,6 +88,7 @@ begin
if not realIRQ then outb($20, $20);
end;
end;
procedure acknowledge(line: uint8);
begin
if line >= 16 then exit;
+10
View File
@@ -15,6 +15,16 @@ function lock: PtrUInt;
procedure unlock(flags: PtrUInt);
procedure outb(port: uint16; value: uint8);
function inb(port: uint16): uint8;
procedure outw(port: uint16; value: uint16);
function inw(port: uint16): uint16;
procedure copyBytes(source, dest: PtrUInt; bytes: SizeUInt);
procedure zero(dest: pointer; bytes: SizeUInt);
procedure idle;
procedure outl(port: uint16; value: uint32);
function inl(port: uint16): uint32;
type
TCPURegisters = record
eax, ebx, ecx, edx: uint32;
end;
procedure queryCPU(leaf, subleaf: uint32; var registers: TCPURegisters);
@@ -10,14 +10,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
unit arch.common.interrupts;
interface
function lock: PtrUInt;
procedure unlock(flags: PtrUInt);
implementation
uses arch.platform.cpu;
function lock: PtrUInt;
begin lock := arch.platform.cpu.lock; end;
procedure unlock(flags: PtrUInt);
begin arch.platform.cpu.unlock(flags); end;
end.
type
TIRQHandler = procedure;
TTimerHandler = procedure(data: void);
procedure registerIRQ(vector: uint8; handler: TIRQHandler);
procedure registerTimer(handler: TTimerHandler);
procedure routePCI(line, vector: uint8);
function timerTicks: uint64;
+4 -1
View File
@@ -15,4 +15,7 @@ function allocateHeapPage: PtrUInt;
function allocateLarge(pages: uint32): PtrUInt;
procedure release(address: PtrUInt; pages: uint32);
function identityReserve(address: PtrUInt): pointer;
function freeBacking: uint64;
function freePhysicalBytes: uint64;
function totalPhysicalBytes: uint64;
function physicalAddress(address: PtrUInt): uint64;
function mapDevice(address: uint64; bytes: SizeUInt): pointer;
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2021 Kieron Morris & Aaron Hance
// Copyright 2026 Aaron Hance
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
{ Shared dispatch for the table emitted by compile_initcalls.sh.
Initializers only register boot work or set globals; they cannot depend on
heap, hardware setup, or the alphabetical order of this generated table. }
var
unitsInitialized: boolean;
procedure initializeUnits;
var
i: longint;
entry: CodePointer;
begin
if unitsInitialized then exit;
unitsInitialized := true;
for i := 1 to InitFinalTable.TableCount do begin
entry := InitFinalTable.Procs[i].InitProc;
if entry <> nil then
TProcedure(entry)();
end;
end;
+3
View File
@@ -12,10 +12,13 @@
{ Commands extracted from io.stdio (Kieron Morris). }
unit arch.x86.console;
interface
procedure init;
implementation
uses io.stdio, core.version, driver.timer.rtc, arch.x86.util;
procedure cmd_version(params: PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf);
begin
bufWriteStr(stdout_buf, ' Asuro Version: ');
+1 -1
View File
@@ -38,7 +38,7 @@ type
Pmemory_map_t = boot.multiboot.Pmemory_map_t;
var
multibootinfo : Pmultiboot_info_t = nil;
multibootinfo : Pmultiboot_info_t absolute boot.multiboot.multibootinfo;
multibootmagic : uint32;
implementation
-24
View File
@@ -62,7 +62,6 @@ procedure resetSystem();
function getESP : uint32;
function RolDWord(AValue : uint32; Dist : uint8) : uint32;
function RorDWord(AValue : uint32; Dist : uint8) : uint32;
function MsSinceSystemBoot : uint64;
@@ -380,29 +379,6 @@ begin
getTSC:= (hi SHL 32) OR lo;
end;
function RolDWord(AValue : uint32; Dist : uint8) : uint32;
var
result : uint32;
i : uint8;
begin
result:= AValue;
asm
PUSH EAX
end;
for i:=0 to Dist-1 do begin
asm
MOV EAX, result
ROL EAX, 1
MOV result, EAX
end;
end;
asm
POP EAX
end;
RolDWord:= result;
end;
function RorDWord(AValue : uint32; Dist : uint8) : uint32;
var
result : uint32;
+71 -8
View File
@@ -12,29 +12,67 @@
{ Build-selected x86 adapter. }
unit arch.platform.cpu;
interface
{$i ../../common/platform.cpu.inc}
implementation
uses arch.x86.util, memory.raw;
function lock: PtrUInt; assembler; nostackframe;
asm pushfd; cli; pop eax end;
asm
pushfd;
cli;
pop eax
end;
procedure unlock(flags: PtrUInt);
begin if (flags and $200) <> 0 then asm sti end; end;
begin
if (flags and $200) <> 0 then asm
sti
end;
end;
procedure idle;
begin asm hlt end; end;
begin
asm
hlt
end;
end;
procedure outb(port: uint16; value: uint8);
begin arch.x86.util.outb(port, value); end;
begin
arch.x86.util.outb(port, value);
end;
function inb(port: uint16): uint8;
begin inb := arch.x86.util.inb(port); end;
begin
inb := arch.x86.util.inb(port);
end;
procedure copyBytes(source, dest: PtrUInt; bytes: SizeUInt);
begin
if bytes >= 16 then arch.x86.util.__REP_MOVSB_memcpy(source, dest, bytes)
else memory.raw.move(pointer(source), pointer(dest), bytes);
end;
procedure zero(dest: pointer; bytes: SizeUInt);
var d: pointer; b: SizeUInt;
procedure outw(port: uint16; value: uint16);
begin
d := dest; b := bytes;
arch.x86.util.outw(port, value);
end;
function inw(port: uint16): uint16;
begin
inw := arch.x86.util.inw(port);
end;
procedure zero(dest: pointer; bytes: SizeUInt);
var
d: pointer;
b: SizeUInt;
begin
d := dest;
b := bytes;
if b = 0 then exit;
asm
push edi
@@ -50,4 +88,29 @@ begin
pop edi
end;
end;
procedure outl(port: uint16; value: uint32);
begin
arch.x86.util.outl(port, value);
end;
function inl(port: uint16): uint32;
begin
inl := arch.x86.util.inl(port);
end;
procedure queryCPU(leaf, subleaf: uint32; var registers: TCPURegisters); assembler; nostackframe;
asm
push ebx
push edi
mov edi, ecx
mov ecx, edx
cpuid
mov [edi], eax
mov [edi + 4], ebx
mov [edi + 8], ecx
mov [edi + 12], edx
pop edi
pop ebx
end;
end.
@@ -12,22 +12,39 @@
{ Build-selected x86 adapter. }
unit arch.platform.diagnostics;
interface
{$i ../../common/platform.diagnostics.inc}
implementation
uses io.syslog, debug.tracer, core.panic;
procedure log(tag, message: pchar);
begin io.syslog.logln(tag, message); end;
begin
io.syslog.logln(tag, message);
end;
procedure push_trace(name: pchar);
begin debug.tracer.push_trace(name); end;
begin
debug.tracer.push_trace(name);
end;
procedure pop_trace;
begin debug.tracer.pop_trace; end;
begin
debug.tracer.pop_trace;
end;
procedure fatal(tag, message: pchar);
begin core.panic.panic(tag, message, nil); end;
begin
core.panic.panic(tag, message, nil);
end;
procedure testSummary(tag, passedKey, failedKey: pchar; passed, failed: uint32);
begin
io.syslog.log(tag, '');
io.syslog.writeint(passed); io.syslog.writestring(' passed, ');
io.syslog.writeint(failed); io.syslog.writestringln(' failed.');
io.syslog.writeint(passed);
io.syslog.writestring(' passed, ');
io.syslog.writeint(failed);
io.syslog.writestringln(' failed.');
end;
end.
@@ -0,0 +1,47 @@
// Copyright 2026 Aaron Hance
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
unit arch.platform.interrupts;
interface
{$i ../../common/platform.interrupts.inc}
implementation
uses arch.x86.isr.mgr, arch.x86.isr.tmr0, arch.x86.isr.ioapic,
arch.x86.bda, arch.platform.cpu;
function timerTicks: uint64;
var
flags: PtrUInt;
begin
flags := arch.platform.cpu.lock;
timerTicks := Counters.c64;
arch.platform.cpu.unlock(flags);
end;
procedure registerIRQ(vector: uint8; handler: TIRQHandler);
begin
arch.x86.isr.mgr.registerISR(vector, handler);
end;
procedure registerTimer(handler: TTimerHandler);
begin
arch.x86.isr.tmr0.hook(PtrUInt(handler));
end;
procedure routePCI(line, vector: uint8);
begin
arch.x86.isr.ioapic.init;
arch.x86.isr.ioapic.route_pci_irq(line, vector);
end;
end.
+55 -10
View File
@@ -12,25 +12,33 @@
{ Mapping policy extracted from memory.heap (Kieron Morris). }
unit arch.platform.memory;
interface
const MAX_HEAP_REGIONS = 256;
{$i ../../common/platform.memory.inc}
implementation
uses arch.x86.memory.virtual, arch.x86.memory.physical, arch.platform.diagnostics;
function allocateHeapPage: PtrUInt;
var i: uint32;
var
i: uint32;
begin
allocateHeapPage := 0;
i := KERNEL_PAGE_NUMBER + 4;
while i < 1024 do begin
if arch.x86.memory.virtual.new_page(i) then begin
allocateHeapPage := i shl 22; exit;
allocateHeapPage := i shl 22;
exit;
end;
inc(i);
end;
end;
function allocateLarge(pages: uint32): PtrUInt;
var curr_page, i: uint16; miss: boolean;
var
curr_page, i: uint16;
miss: boolean;
begin
allocateLarge := 0;
if (pages = 0) or (pages > MAX_HEAP_REGIONS) then exit;
@@ -39,7 +47,9 @@ begin
miss := false;
for i := 0 to pages - 1 do begin
if not page_mappable(curr_page + i) then begin
miss := true; curr_page := curr_page + i; break;
miss := true;
curr_page := curr_page + i;
break;
end;
end;
if not miss then begin
@@ -47,7 +57,8 @@ begin
while i < pages do begin
if not arch.x86.memory.virtual.new_page(curr_page + i) then begin
while i > 0 do begin
dec(i); arch.x86.memory.virtual.free_page(curr_page + i);
dec(i);
arch.x86.memory.virtual.free_page(curr_page + i);
end;
exit;
end;
@@ -59,23 +70,57 @@ begin
inc(curr_page);
end;
end;
procedure release(address: PtrUInt; pages: uint32);
var i: uint32; page_num: uint16;
var
i: uint32;
page_num: uint16;
begin
if (pages = 0) or (pages > MAX_HEAP_REGIONS) then begin
fatal('LMM', 'Invalid heap region release'); exit;
fatal('LMM', 'Invalid heap region release');
exit;
end;
page_num := address shr 22;
for i := 0 to pages - 1 do arch.x86.memory.virtual.free_page(page_num + uint16(i));
end;
function identityReserve(address: PtrUInt): pointer;
var block: uint16;
var
block: uint16;
begin
block := address shr 22;
force_alloc_block(block, 0);
map_page(block, block);
identityReserve := pointer(uint32(block) shl 22);
end;
function freeBacking: uint64;
begin freeBacking := pmm_free_blocks; end;
function freePhysicalBytes: uint64;
begin
freePhysicalBytes := uint64(pmm_free_blocks) * $400000;
end;
function totalPhysicalBytes: uint64;
begin
totalPhysicalBytes := uint64(pmm_total_blocks) * $400000;
end;
function physicalAddress(address: PtrUInt): uint64;
begin
physicalAddress := arch.x86.memory.virtual.vtop(address);
end;
function mapDevice(address: uint64; bytes: SizeUInt): pointer;
var
page, last: uint32;
begin
mapDevice := nil;
if (bytes = 0) or (address > $FFFFFFFF) or (bytes - 1 > $FFFFFFFF - address) then exit;
page := uint32(address) shr 22;
last := uint32(address + bytes - 1) shr 22;
while page <= last do begin
identityReserve(page shl 22);
inc(page);
end;
mapDevice := pointer(PtrUInt(address));
end;
end.
@@ -12,12 +12,20 @@
{ Build-selected x86 adapter. }
unit arch.platform.process;
interface
{$i ../../common/platform.process.inc}
implementation
uses arch.x86.proc.context, arch.x86.bda;
function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt;
begin prepare := arch.x86.proc.context.prepare(stack, bytes, entry); end;
begin
prepare := arch.x86.proc.context.prepare(stack, bytes, entry);
end;
function timerTicks: uint32;
begin timerTicks := PUInt32(@Counters.c32)^; end;
begin
timerTicks := PUInt32(@Counters.c32)^;
end;
end.
+6 -1
View File
@@ -12,12 +12,17 @@
{ i386 initial frame, extracted from proc.mgr (Kieron Morris). }
unit arch.x86.proc.context;
interface
function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt;
implementation
uses memory.raw;
function prepare(stack: pointer; bytes: SizeUInt; entry: pointer): PtrUInt;
var top, frame: PtrUInt; i: uint32;
var
top, frame: PtrUInt;
i: uint32;
begin
prepare := 0;
if (stack = nil) or (entry = nil) or (bytes < 64) or
+14 -35
View File
@@ -119,7 +119,6 @@ function Round(d: Double): int64;
implementation
{ ---- Raw driver.io.serial debug helpers (COM1 $3F8, no dependencies) ---- }
procedure serial_putch(ch: char); assembler;
asm
push edx
@@ -154,7 +153,8 @@ begin
end;
procedure serial_putstr(s: pchar);
var i: uint32;
var
i: uint32;
begin
i := 0;
while s[i] <> #0 do begin
@@ -196,20 +196,11 @@ begin
end;
end;
{$i ../common/system.init.inc}
procedure fpc_initializeunits; [public, alias: 'FPC_INITIALIZEUNITS']; compilerproc;
var
i : longint;
proc : CodePointer;
begin
{ Walk the compiler-generated INITFINAL table and call each unit's
initialization procedure (if non-nil). This runs before kmain, so
initialization sections must only do trivial work — write to static
globals, register boot entries, etc. No I/O or heap allocation. }
for i := 1 to InitFinalTable.TableCount do begin
proc := InitFinalTable.Procs[i].InitProc;
if proc <> nil then
TProcedure(proc)();
end;
initializeUnits;
end;
procedure fpc_do_exit; [public, alias: 'FPC_DO_EXIT']; compilerproc;
@@ -348,7 +339,8 @@ begin
end;
function fpc_mul_dword_to_qword(f1, f2: uint32): qword; [public, alias: 'FPC_MUL_DWORD_TO_QWORD']; compilerproc;
var res: qword;
var
res: qword;
begin
asm
MOV EAX, f1
@@ -368,8 +360,10 @@ procedure udiv64(dividendLo, dividendHi, divisorLo, divisorHi: uint32;
var
bit: longint;
begin
quotLo := 0; quotHi := 0;
remLo := 0; remHi := 0;
quotLo := 0;
quotHi := 0;
remLo := 0;
remHi := 0;
for bit := 63 downto 0 do begin
{ rem := rem shl 1 }
@@ -567,7 +561,6 @@ begin
end;
{ ---------- Float-to-integer intrinsics ---------- }
function Trunc(d: Double): int64; [public, alias: 'FPC_TRUNC'];
var
oldcw, newcw: word;
@@ -599,8 +592,8 @@ begin
end;
{ ---------- Memory allocation compilerprocs ---------- }
function kernel_kalloc(size: uint32): Pointer; external name 'kernel_kalloc';
procedure kernel_kfree(p: Pointer); external name 'kernel_kfree';
function fpc_getmem(size: PtrUInt): Pointer;
@@ -616,7 +609,6 @@ begin
end;
{ ---------- Exception handling compilerprocs ---------- }
procedure fpc_reraise; [public, alias: 'FPC_RERAISE']; compilerproc;
begin
{ In a bare-metal core.version re-raise is a fatal condition }
@@ -641,30 +633,17 @@ begin
end;
procedure init();
var
i : longint;
proc : CodePointer;
begin
ASURO_KERNEL_START := uint32(@AK_START);
ASURO_KERNEL_END := uint32(@AK_END);
ASURO_KERNEL_SIZE:= ASURO_KERNEL_END - ASURO_KERNEL_START;
ASURO_KERNEL_SIZE := ASURO_KERNEL_END - ASURO_KERNEL_START;
ExceptAddrStack := nil;
ErrorAddr := nil;
ErrorCode := 0;
ExitCode := 0;
ExitProc := nil;
{ Walk the INITFINAL table generated by compile_initcalls.sh and call
each unit's initialization section. FPC with -n does not insert this
call automatically, so we do it explicitly here — after basic system
globals are set up but before kmain proceeds.
Note: we inline the walk rather than calling fpc_initializeunits
because compilerproc procedures cannot be called from Pascal code. }
for i := 1 to InitFinalTable.TableCount do begin
proc := InitFinalTable.Procs[i].InitProc;
if proc <> nil then
TProcedure(proc)();
end;
initializeUnits;
end;
end.
File diff suppressed because it is too large. Load diff
+2
View File
@@ -11,8 +11,10 @@
// limitations under the License.
unit arch.x86_64.debug;
interface
procedure init;
implementation
uses debug.early;
{ The stub initializes COM1. Bounded polling also allows boot without a UART. }
File diff suppressed because it is too large. Load diff
Loaded 20 of 59 files, more files were not shown because too many files have changed in this diff. Show more