~eliasnaur/unik

cd0f505f7c2c06bac9c3655ab38b7bbe2d1761f3 — Elias Naur 7 months ago
initial import
A  => LICENSE +63 -0
@@ 1,63 @@
This project is dual-licensed under the UNLICENSE or
the MIT license with the SPDX identifier:

SPDX-License-Identifier: Unlicense OR MIT

You may use the project under the terms of either license.

Both licenses are reproduced below.

----
The MIT License (MIT)

Copyright (c) 2019 The Gio authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---



---
The UNLICENSE

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org/>
---

A  => README.md +33 -0
@@ 1,33 @@
# Unik

Unik is a Go module for running Go programs as unikernels, without an
underlying operating system. The included demo is a functional
[Gio](https://gioui.org) GUI program that demonstrates the
[virtio](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html)
GPU and tablet drivers.

# Requirements

- Linux
- Qemu >= 4.2.0
- mtools (for creating FAT images)
- OVMF UEFI firmware (`dnf install edk2-ovmf` on Fedora)
- (Optional) gnu-efi (for building the UEFI boot loader)

# Building

The `build.sh` script takes a `go` package or file list, builds the Go
program and a bootable FAT image with the bootloader and program. To
build the demo, run

	$ ./build.sh ./cmd/demo

# Executing

The `qemu.sh` script runs the bootable image inside Qemu, with the
virtio GPU and tablet devices enabled. If everything goes well,

	$ ./qemu.sh

should give you a functional GUI program with mouse support. There is
not yet support for the keyboard input.

A  => build.sh +13 -0
@@ 1,13 @@
#!/bin/sh

set -e

mkdir -p bootdrive/EFI/BOOT
go build -ldflags="-E eliasnaur.com/unik/kernel.rt0 -T 0x1700000" -o bootdrive/KERNEL.ELF $@
cp uefi/loader.efi bootdrive/EFI/BOOT/BOOTX64.EFI

# Create disk image
rm -f boot.img
dd if=/dev/zero of=boot.img bs=1M count=20
mformat -i boot.img ::/
mcopy -i boot.img -s bootdrive/* ::/

A  => cmd/demo/cursor.go +3 -0
@@ 1,3 @@
package main

var cursor = []byte{0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x6, 0x0, 0x0, 0x0, 0xaa, 0x69, 0x71, 0xde, 0x0, 0x0, 0x2, 0xeb, 0x7a, 0x54, 0x58, 0x74, 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x78, 0x69, 0x66, 0x0, 0x0, 0x78, 0xda, 0xed, 0x97, 0x6d, 0x92, 0xdc, 0x28, 0xc, 0x86, 0xff, 0x73, 0x8a, 0x1c, 0x1, 0x49, 0x8, 0x89, 0xe3, 0x60, 0x3e, 0xaa, 0x72, 0x83, 0x3d, 0x7e, 0x5e, 0xb0, 0xdb, 0xd3, 0x3d, 0x93, 0xdd, 0x24, 0xb5, 0xfb, 0x6b, 0xab, 0x4d, 0xd9, 0x60, 0x81, 0x25, 0xf9, 0x7d, 0x64, 0x7a, 0x26, 0x8c, 0xbf, 0xbe, 0xcf, 0xf0, 0xd, 0x7, 0x95, 0xcc, 0x21, 0xa9, 0x79, 0x2e, 0x39, 0x47, 0x1c, 0xa9, 0xa4, 0xc2, 0x15, 0x3, 0x8f, 0xe7, 0x51, 0xf6, 0x95, 0x62, 0xda, 0xd7, 0x7d, 0xa4, 0x6b, 0xa, 0xf7, 0x2f, 0xf6, 0x70, 0x4f, 0x30, 0x4c, 0x82, 0x5e, 0xce, 0x5b, 0xab, 0xd7, 0xfa, 0xa, 0xbb, 0x7e, 0x3c, 0xf0, 0x88, 0x41, 0xc7, 0xab, 0x3d, 0xf8, 0x35, 0xc3, 0x7e, 0x39, 0xa2, 0xdb, 0xf1, 0x3e, 0x64, 0x45, 0x5e, 0xe3, 0xfe, 0x9c, 0x24, 0xec, 0x7c, 0xda, 0xe9, 0xca, 0x24, 0x94, 0x71, 0xe, 0x72, 0x71, 0x7b, 0x4e, 0xf5, 0xb8, 0x1c, 0xb5, 0x47, 0xca, 0xfe, 0x71, 0xa6, 0x3b, 0xad, 0xeb, 0x75, 0x71, 0x1f, 0x5e, 0xc, 0x6, 0x95, 0xba, 0x22, 0x90, 0x30, 0xf, 0x21, 0x89, 0xfb, 0xea, 0x67, 0x6, 0x72, 0x9e, 0x15, 0x67, 0xc2, 0x95, 0x84, 0xb0, 0x8e, 0xa4, 0xec, 0x71, 0xc, 0xe8, 0x92, 0x3c, 0x32, 0x81, 0x20, 0x2f, 0xaf, 0xf7, 0xe8, 0x63, 0x7c, 0x16, 0xe8, 0x45, 0xe4, 0xc7, 0x28, 0x7c, 0x56, 0xff, 0x1e, 0x7d, 0x12, 0x9f, 0xeb, 0x65, 0x97, 0x4f, 0x5a, 0xe6, 0x4b, 0x23, 0xc, 0x7e, 0x3a, 0x41, 0xfa, 0xc9, 0x2e, 0x77, 0x18, 0x7e, 0xe, 0x2c, 0x77, 0x46, 0xfc, 0x3a, 0x61, 0xf2, 0x70, 0xf5, 0x55, 0xe4, 0x39, 0xbb, 0xcf, 0x39, 0xce, 0xb7, 0xab, 0x29, 0x43, 0xd1, 0x7c, 0x55, 0xd4, 0x16, 0x9b, 0x1e, 0x6e, 0xb0, 0xf0, 0x80, 0xe4, 0xb2, 0x1f, 0xcb, 0x68, 0x86, 0x53, 0x31, 0xb6, 0xdd, 0xa, 0x9a, 0xc7, 0x1a, 0x1b, 0x90, 0xf7, 0xd8, 0xe2, 0x81, 0xd6, 0xa8, 0x10, 0x43, 0xeb, 0x19, 0x28, 0x51, 0xa7, 0x4a, 0x93, 0xc6, 0xee, 0x1b, 0x35, 0xa4, 0x98, 0x78, 0xb0, 0xa1, 0x67, 0x6e, 0x2c, 0xdb, 0xe6, 0x62, 0x5c, 0xb8, 0xc9, 0xe2, 0x94, 0x56, 0xa3, 0xc9, 0x6, 0x62, 0x5d, 0x1c, 0x2c, 0x1b, 0x8f, 0x20, 0x2, 0x33, 0xdf, 0xb9, 0xd0, 0x8e, 0x5b, 0x76, 0xbc, 0x46, 0x8e, 0xc8, 0x9d, 0xb0, 0x94, 0x9, 0xce, 0x8, 0x8f, 0xfc, 0x6d, 0xb, 0xff, 0x34, 0xf9, 0x27, 0x2d, 0xcc, 0xd9, 0x96, 0x44, 0x14, 0xfd, 0xd6, 0xa, 0x79, 0xf1, 0xaa, 0x6b, 0xa4, 0xb1, 0xc8, 0xad, 0x2b, 0x56, 0x1, 0x8, 0xcd, 0x8b, 0x9b, 0x6e, 0x81, 0x1f, 0xed, 0xc2, 0x1f, 0x9f, 0xea, 0x7, 0xa5, 0xa, 0x82, 0xba, 0x65, 0x76, 0xbc, 0x60, 0x8d, 0xc7, 0xe9, 0xe2, 0x50, 0xfa, 0xa8, 0x2d, 0xd9, 0x9c, 0x5, 0xeb, 0x14, 0xfd, 0xf9, 0x9, 0x51, 0xb0, 0x7e, 0x39, 0x80, 0x44, 0x88, 0xad, 0x48, 0x6, 0xc5, 0x9f, 0x28, 0x66, 0x12, 0xa5, 0x4c, 0xd1, 0x98, 0x8d, 0x8, 0x3a, 0x3a, 0x0, 0x55, 0x64, 0xce, 0x92, 0xf8, 0x0, 0x1, 0x52, 0xe5, 0x8e, 0x24, 0x39, 0x89, 0x60, 0x3f, 0x32, 0x76, 0x5e, 0xb1, 0xf1, 0x8c, 0xd1, 0x5e, 0xcb, 0xca, 0x99, 0x97, 0x19, 0x7b, 0x13, 0x40, 0xa8, 0x64, 0x31, 0xb0, 0xc1, 0x37, 0x5, 0x58, 0x29, 0x29, 0xea, 0xc7, 0x92, 0xa3, 0x86, 0xaa, 0x8a, 0x26, 0x55, 0xcd, 0x6a, 0xea, 0x41, 0x8b, 0xd6, 0x2c, 0x39, 0x65, 0xcd, 0x39, 0x5b, 0x5e, 0x9b, 0x5c, 0x35, 0xb1, 0x64, 0x6a, 0xd9, 0xcc, 0xdc, 0x8a, 0x55, 0x17, 0x4f, 0xae, 0x9e, 0xdd, 0xdc, 0xbd, 0x78, 0x2d, 0x5c, 0x4, 0x7b, 0xa0, 0x96, 0x5c, 0xac, 0x78, 0x29, 0xa5, 0x56, 0xe, 0x15, 0x81, 0x2a, 0x7c, 0x55, 0xac, 0xaf, 0xb0, 0x1c, 0x7c, 0xc8, 0x91, 0xe, 0x3d, 0xf2, 0x61, 0x87, 0x1f, 0xe5, 0xa8, 0xd, 0xe5, 0xd3, 0x52, 0xd3, 0x96, 0x9b, 0x35, 0x6f, 0xa5, 0xd5, 0xce, 0x5d, 0x3a, 0xb6, 0x89, 0x9e, 0xbb, 0x75, 0xef, 0xa5, 0xd7, 0x41, 0x61, 0x60, 0xa7, 0x18, 0x69, 0xe8, 0xc8, 0xc3, 0x86, 0x8f, 0x32, 0xea, 0x44, 0xad, 0x4d, 0x99, 0x69, 0xea, 0xcc, 0xd3, 0xa6, 0xcf, 0x32, 0xeb, 0x4d, 0xed, 0xa2, 0xfa, 0xa5, 0xfd, 0x1, 0x35, 0xba, 0xa8, 0xf1, 0x26, 0xb5, 0xd6, 0xd9, 0x4d, 0xd, 0xd6, 0x60, 0xf6, 0x70, 0x41, 0x6b, 0x3b, 0xd1, 0xc5, 0xc, 0xc4, 0x38, 0x11, 0x88, 0xdb, 0x22, 0x80, 0x82, 0xe6, 0xc5, 0x2c, 0x3a, 0xa5, 0xc4, 0x8b, 0xdc, 0x62, 0x16, 0xb, 0xe3, 0xa3, 0x50, 0x46, 0x92, 0xba, 0xd8, 0x84, 0x4e, 0x8b, 0x18, 0x10, 0xa6, 0x41, 0xac, 0x93, 0x6e, 0x76, 0x1f, 0xe4, 0x7e, 0x8b, 0x5b, 0x50, 0xff, 0x2d, 0x6e, 0xfc, 0x2b, 0x72, 0x61, 0xa1, 0xfb, 0x2f, 0xc8, 0x5, 0xa0, 0xfb, 0xca, 0xed, 0x27, 0xd4, 0xfa, 0xfa, 0x9d, 0x6b, 0x9b, 0xd8, 0xf9, 0x15, 0x2e, 0x4d, 0xa3, 0xe0, 0xeb, 0xc3, 0xfc, 0xf0, 0x1a, 0xd8, 0xeb, 0xfa, 0x51, 0xab, 0xff, 0xb6, 0x7f, 0x3b, 0x7a, 0x3b, 0x7a, 0x3b, 0x7a, 0x3b, 0x7a, 0x3b, 0x7a, 0x3b, 0x7a, 0x3b, 0xfa, 0x1f, 0x38, 0x9a, 0xf8, 0xe3, 0x1, 0xff, 0xc4, 0x86, 0x1f, 0xe1, 0x76, 0x9d, 0x73, 0xe0, 0xa8, 0xf2, 0x49, 0x0, 0x0, 0x1, 0x84, 0x69, 0x43, 0x43, 0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x0, 0x0, 0x78, 0x9c, 0x7d, 0x91, 0x3d, 0x48, 0xc3, 0x40, 0x1c, 0xc5, 0x5f, 0x3f, 0x44, 0xd1, 0x8a, 0x5, 0x3b, 0x88, 0x38, 0x64, 0xa8, 0x4e, 0x16, 0x8a, 0x8a, 0x38, 0x6a, 0x15, 0x8a, 0x50, 0x21, 0xd4, 0xa, 0xad, 0x3a, 0x98, 0x5c, 0xfa, 0x5, 0x4d, 0x1a, 0x92, 0x14, 0x17, 0x47, 0xc1, 0xb5, 0xe0, 0xe0, 0xc7, 0x62, 0xd5, 0xc1, 0xc5, 0x59, 0x57, 0x7, 0x57, 0x41, 0x10, 0xfc, 0x0, 0x71, 0x72, 0x74, 0x52, 0x74, 0x91, 0x12, 0xff, 0x97, 0x14, 0x5a, 0xc4, 0x78, 0x70, 0xdc, 0x8f, 0x77, 0xf7, 0x1e, 0x77, 0xef, 0x0, 0x7f, 0xa3, 0xc2, 0x54, 0x33, 0x18, 0x7, 0x54, 0xcd, 0x32, 0xd2, 0xc9, 0x84, 0x90, 0xcd, 0xad, 0xa, 0xdd, 0xaf, 0x8, 0xa2, 0xf, 0x61, 0xc4, 0x11, 0x96, 0x98, 0xa9, 0xcf, 0x89, 0x62, 0xa, 0x9e, 0xe3, 0xeb, 0x1e, 0x3e, 0xbe, 0xde, 0xc5, 0x78, 0x96, 0xf7, 0xb9, 0x3f, 0x47, 0xbf, 0x92, 0x37, 0x19, 0xe0, 0x13, 0x88, 0x67, 0x99, 0x6e, 0x58, 0xc4, 0x1b, 0xc4, 0xd3, 0x9b, 0x96, 0xce, 0x79, 0x9f, 0x38, 0xc2, 0x4a, 0x92, 0x42, 0x7c, 0x4e, 0x3c, 0x6e, 0xd0, 0x5, 0x89, 0x1f, 0xb9, 0x2e, 0xbb, 0xfc, 0xc6, 0xb9, 0xe8, 0xb0, 0x9f, 0x67, 0x46, 0x8c, 0x4c, 0x7a, 0x9e, 0x38, 0x42, 0x2c, 0x14, 0x3b, 0x58, 0xee, 0x60, 0x56, 0x32, 0x54, 0xe2, 0x29, 0xe2, 0xa8, 0xa2, 0x6a, 0x94, 0xef, 0xcf, 0xba, 0xac, 0x70, 0xde, 0xe2, 0xac, 0x56, 0x6a, 0xac, 0x75, 0x4f, 0xfe, 0xc2, 0x50, 0x5e, 0x5b, 0x59, 0xe6, 0x3a, 0xcd, 0x11, 0x24, 0xb1, 0x88, 0x25, 0x88, 0x10, 0x20, 0xa3, 0x86, 0x32, 0x2a, 0xb0, 0x10, 0xa3, 0x55, 0x23, 0xc5, 0x44, 0x9a, 0xf6, 0x13, 0x1e, 0xfe, 0x61, 0xc7, 0x2f, 0x92, 0x4b, 0x26, 0x57, 0x19, 0x8c, 0x1c, 0xb, 0xa8, 0x42, 0x85, 0xe4, 0xf8, 0xc1, 0xff, 0xe0, 0x77, 0xb7, 0x66, 0x61, 0x72, 0xc2, 0x4d, 0xa, 0x25, 0x80, 0xae, 0x17, 0xdb, 0xfe, 0x18, 0x5, 0xba, 0x77, 0x81, 0x66, 0xdd, 0xb6, 0xbf, 0x8f, 0x6d, 0xbb, 0x79, 0x2, 0x4, 0x9e, 0x81, 0x2b, 0xad, 0xed, 0xaf, 0x36, 0x80, 0x99, 0x4f, 0xd2, 0xeb, 0x6d, 0x2d, 0x7a, 0x4, 0xc, 0x6c, 0x3, 0x17, 0xd7, 0x6d, 0x4d, 0xde, 0x3, 0x2e, 0x77, 0x80, 0xa1, 0x27, 0x5d, 0x32, 0x24, 0x47, 0xa, 0xd0, 0xf4, 0x17, 0xa, 0xc0, 0xfb, 0x19, 0x7d, 0x53, 0xe, 0x18, 0xbc, 0x5, 0x7a, 0xd7, 0xdc, 0xde, 0x5a, 0xfb, 0x38, 0x7d, 0x0, 0x32, 0xd4, 0x55, 0xea, 0x6, 0x38, 0x38, 0x4, 0xc6, 0x8a, 0x94, 0xbd, 0xee, 0xf1, 0xee, 0x9e, 0xce, 0xde, 0xfe, 0x3d, 0xd3, 0xea, 0xef, 0x7, 0x57, 0x84, 0x72, 0x9c, 0x83, 0xaf, 0xe1, 0x29, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe4, 0x4, 0xb, 0x11, 0xa, 0x2c, 0xb6, 0x53, 0x9f, 0xdb, 0x0, 0x0, 0x1, 0xbc, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x98, 0x3b, 0x2f, 0x44, 0x41, 0x14, 0x80, 0xbf, 0x65, 0x57, 0x54, 0x44, 0xfc, 0x0, 0xd5, 0x26, 0x2a, 0x89, 0x56, 0x25, 0xfc, 0x1, 0xbf, 0x41, 0x21, 0xa1, 0x53, 0x8, 0x4a, 0x9d, 0x4a, 0xa2, 0x42, 0x8d, 0x90, 0x58, 0x14, 0x58, 0x85, 0x47, 0x22, 0x91, 0x6c, 0x88, 0x4, 0x9d, 0x57, 0x34, 0x1e, 0xd, 0x21, 0x8b, 0x66, 0x25, 0x5c, 0xcd, 0x99, 0x64, 0x72, 0xb3, 0x76, 0x37, 0xd1, 0xb8, 0x73, 0xce, 0x97, 0x4c, 0x31, 0x99, 0xd3, 0x9c, 0x6f, 0xce, 0xdc, 0x33, 0x73, 0x1, 0x46, 0x50, 0xce, 0x23, 0xb0, 0xac, 0x59, 0xc0, 0x3d, 0x10, 0x1, 0x39, 0xed, 0x2, 0x22, 0x60, 0x41, 0xbb, 0x80, 0x8, 0xd8, 0xd2, 0x2e, 0x20, 0x2, 0x56, 0x81, 0x94, 0x36, 0x1, 0x45, 0xe0, 0xd4, 0x93, 0x30, 0xaf, 0x4d, 0xc0, 0x93, 0xcc, 0x77, 0xb5, 0x1d, 0x87, 0xb8, 0x80, 0xc, 0x70, 0xe4, 0x49, 0x58, 0xc, 0xfd, 0x38, 0xc4, 0x5, 0x38, 0x7c, 0x9, 0x2b, 0x1a, 0x5, 0xa4, 0x81, 0xbc, 0x27, 0x61, 0x5b, 0x9b, 0x0, 0x80, 0x7a, 0xe0, 0x38, 0x76, 0x4f, 0x48, 0x69, 0x12, 0xe0, 0xd8, 0xf7, 0x24, 0x6c, 0x6a, 0x14, 0x90, 0x96, 0x8e, 0xe0, 0x24, 0xe4, 0x43, 0xaa, 0x84, 0x5a, 0x4, 0x20, 0x9, 0xfb, 0x1f, 0xc6, 0x25, 0x6d, 0x2, 0x1c, 0x9b, 0x9e, 0x84, 0x5d, 0x8d, 0x2, 0xea, 0x63, 0xdd, 0x61, 0x3, 0xa8, 0xd3, 0x24, 0xc0, 0x71, 0xe8, 0x49, 0xc8, 0x69, 0x14, 0x80, 0x5c, 0x90, 0x9c, 0x84, 0x3, 0x8d, 0x2, 0x0, 0xd6, 0x3c, 0x9, 0xeb, 0x49, 0xec, 0xe, 0xb5, 0x8, 0xa8, 0x3, 0x1a, 0x81, 0x26, 0xa0, 0xb, 0x18, 0x4, 0x66, 0x80, 0x13, 0xe0, 0x33, 0xe9, 0xff, 0x13, 0x7e, 0x13, 0x90, 0xf2, 0x76, 0xb3, 0xa5, 0xcc, 0x3f, 0x83, 0x4a, 0xe3, 0xc, 0x68, 0x48, 0x42, 0xf2, 0xe9, 0xa, 0x6b, 0x6d, 0x40, 0x1, 0x98, 0x0, 0x66, 0x81, 0x49, 0x60, 0x4c, 0xd6, 0x8a, 0x22, 0xee, 0x15, 0x78, 0x6, 0x6e, 0x80, 0xb, 0x19, 0x57, 0x7f, 0x38, 0x4e, 0xff, 0xa6, 0x2, 0xb2, 0x40, 0xc9, 0xdb, 0xd1, 0x66, 0xa0, 0x15, 0x78, 0x93, 0xf9, 0x75, 0xc8, 0x57, 0xe1, 0xa1, 0x32, 0x25, 0x3d, 0x2e, 0x6b, 0x73, 0x32, 0xff, 0x6, 0x3a, 0x42, 0x13, 0xf0, 0x2, 0x4c, 0x79, 0x49, 0xbf, 0xcb, 0x4e, 0x47, 0xb2, 0xf3, 0xe, 0x57, 0x19, 0x7b, 0xa1, 0x9, 0x88, 0x8f, 0x2c, 0xd0, 0x9, 0x7c, 0xc9, 0x7c, 0x5a, 0xe2, 0x47, 0xbd, 0x98, 0xde, 0x10, 0x5, 0x9c, 0xcb, 0x99, 0x77, 0x5c, 0x7a, 0x15, 0x91, 0x2, 0x6, 0xbc, 0xd8, 0x42, 0x8, 0xaf, 0xc2, 0xfb, 0x2a, 0x3d, 0xbc, 0xdd, 0x5b, 0xff, 0x88, 0xc9, 0x7a, 0x90, 0xb7, 0x41, 0x10, 0x2, 0x86, 0x2b, 0xc4, 0xec, 0xc4, 0x12, 0x2f, 0x1, 0x3d, 0x49, 0xe9, 0xf5, 0xd5, 0xb8, 0x3, 0xfa, 0xaa, 0xc4, 0x74, 0x4b, 0xe2, 0xb7, 0xd2, 0x25, 0x82, 0x22, 0x53, 0x63, 0x5c, 0x3f, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x9, 0xe4, 0x7, 0xc1, 0xdd, 0xd8, 0x1d, 0x58, 0xff, 0xf9, 0xa2, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}

A  => cmd/demo/kitchen.go +233 -0
@@ 1,233 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

// A Gio program that demonstrates Gio widgets. See https://gioui.org for more information.

import (
	"image"
	"image/color"
	"log"
	"math"
	"time"

	"gioui.org/layout"
	"gioui.org/text"
	"gioui.org/unit"
	"gioui.org/widget"
	"gioui.org/widget/material"
	"golang.org/x/exp/shiny/materialdesign/icons"
)

type scaledConfig struct {
	Scale float32
}

type iconAndTextButton struct {
	theme *material.Theme
}

var (
	editor     = new(widget.Editor)
	lineEditor = &widget.Editor{
		SingleLine: true,
		Submit:     true,
	}
	button            = new(widget.Button)
	greenButton       = new(widget.Button)
	iconTextButton    = new(widget.Button)
	iconButton        = new(widget.Button)
	radioButtonsGroup = new(widget.Enum)
	list              = &layout.List{
		Axis: layout.Vertical,
	}
	progress            = 0
	progressIncrementer chan int
	green               = true
	topLabel            = "Hello, Gio"
	icon                *material.Icon
	checkbox            = new(widget.CheckBox)
)

func init() {
	editor.SetText(longText)
	ic, err := material.NewIcon(icons.ContentAdd)
	if err != nil {
		log.Fatal(err)
	}
	icon = ic
}

func (b iconAndTextButton) Layout(gtx *layout.Context, button *widget.Button, icon *material.Icon, word string) {
	b.theme.ButtonLayout().Layout(gtx, iconTextButton, func() {
		iconAndLabel := layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}
		textIconSpacer := unit.Dp(5)

		layIcon := layout.Rigid(func() {
			layout.Inset{Right: textIconSpacer}.Layout(gtx, func() {
				size := gtx.Px(unit.Dp(56)) - 2*gtx.Px(unit.Dp(16))
				if icon != nil {
					icon.Layout(gtx, unit.Px(float32(size)))
					gtx.Dimensions = layout.Dimensions{
						Size: image.Point{X: size, Y: size},
					}
				}
			})
		})

		layLabel := layout.Rigid(func() {
			layout.Inset{Left: textIconSpacer}.Layout(gtx, func() {
				widget.Label{}.Layout(gtx, b.theme.Shaper, text.Font{}, b.theme.TextSize, word)
			})
		})

		iconAndLabel.Layout(gtx, layIcon, layLabel)
	})
}

func kitchen(gtx *layout.Context, th *material.Theme) {
	widgets := []func(){
		func() {
			th.H3(topLabel).Layout(gtx)
		},
		func() {
			gtx.Constraints.Height.Max = gtx.Px(unit.Dp(200))
			th.Editor("Hint").Layout(gtx, editor)
		},
		func() {
			e := th.Editor("Hint")
			e.Font.Style = text.Italic
			e.Layout(gtx, lineEditor)
			for _, e := range lineEditor.Events(gtx) {
				if e, ok := e.(widget.SubmitEvent); ok {
					topLabel = e.Text
					lineEditor.SetText("")
				}
			}
		},
		func() {
			in := layout.UniformInset(unit.Dp(8))
			layout.Flex{Alignment: layout.Middle}.Layout(gtx,
				layout.Rigid(func() {
					in.Layout(gtx, func() {
						th.IconButton(icon).Layout(gtx, iconButton)
					})
				}),
				layout.Rigid(func() {
					in.Layout(gtx, func() {
						iconAndTextButton{th}.Layout(gtx, iconTextButton, icon, "Horizontal button")
					})
				}),
				layout.Rigid(func() {
					in.Layout(gtx, func() {
						for button.Clicked(gtx) {
							green = !green
						}
						th.Button("Click me!").Layout(gtx, button)
					})
				}),
				layout.Rigid(func() {
					in.Layout(gtx, func() {
						var btn material.Button
						btn = th.Button("Green button")
						if green {
							btn.Background = color.RGBA{A: 0xff, R: 0x9e, G: 0x9d, B: 0x24}
						}
						btn.Layout(gtx, greenButton)
					})
				}),
			)
		},
		func() {
			th.ProgressBar().Layout(gtx, progress)
		},
		func() {
			th.CheckBox("Checkbox").Layout(gtx, checkbox)
		},
		func() {
			layout.Flex{}.Layout(gtx,
				layout.Rigid(func() {
					th.RadioButton("r1", "RadioButton1").Layout(gtx, radioButtonsGroup)
				}),
				layout.Rigid(func() {
					th.RadioButton("r2", "RadioButton2").Layout(gtx, radioButtonsGroup)
				}),
				layout.Rigid(func() {
					th.RadioButton("r3", "RadioButton3").Layout(gtx, radioButtonsGroup)
				}),
			)
		},
	}

	list.Layout(gtx, len(widgets), func(i int) {
		layout.UniformInset(unit.Dp(16)).Layout(gtx, widgets[i])
	})
}

func (s *scaledConfig) Now() time.Time {
	return time.Now()
}

func (s *scaledConfig) Px(v unit.Value) int {
	scale := s.Scale
	if v.U == unit.UnitPx {
		scale = 1
	}
	return int(math.Round(float64(scale * v.V)))
}

const longText = `1. I learned from my grandfather, Verus, to use good manners, and to
put restraint on anger. 2. In the famous memory of my father I had a
pattern of modesty and manliness. 3. Of my mother I learned to be
pious and generous; to keep myself not only from evil deeds, but even
from evil thoughts; and to live with a simplicity which is far from
customary among the rich. 4. I owe it to my great-grandfather that I
did not attend public lectures and discussions, but had good and able
teachers at home; and I owe him also the knowledge that for things of
this nature a man should count no expense too great.

5. My tutor taught me not to favour either green or blue at the
chariot races, nor, in the contests of gladiators, to be a supporter
either of light or heavy armed. He taught me also to endure labour;
not to need many things; to serve myself without troubling others; not
to intermeddle in the affairs of others, and not easily to listen to
slanders against them.

6. Of Diognetus I had the lesson not to busy myself about vain things;
not to credit the great professions of such as pretend to work
wonders, or of sorcerers about their charms, and their expelling of
Demons and the like; not to keep quails (for fighting or divination),
nor to run after such things; to suffer freedom of speech in others,
and to apply myself heartily to philosophy. Him also I must thank for
my hearing first Bacchius, then Tandasis and Marcianus; that I wrote
dialogues in my youth, and took a liking to the philosopher's pallet
and skins, and to the other things which, by the Grecian discipline,
belong to that profession.

7. To Rusticus I owe my first apprehensions that my nature needed
reform and cure; and that I did not fall into the ambition of the
common Sophists, either by composing speculative writings or by
declaiming harangues of exhortation in public; further, that I never
strove to be admired by ostentation of great patience in an ascetic
life, or by display of activity and application; that I gave over the
study of rhetoric, poetry, and the graces of language; and that I did
not pace my house in my senatorial robes, or practise any similar
affectation. I observed also the simplicity of style in his letters,
particularly in that which he wrote to my mother from Sinuessa. I
learned from him to be easily appeased, and to be readily reconciled
with those who had displeased me or given cause of offence, so soon as
they inclined to make their peace; to read with care; not to rest
satisfied with a slight and superficial knowledge; nor quickly to
assent to great talkers. I have him to thank that I met with the
discourses of Epictetus, which he furnished me from his own library.

8. From Apollonius I learned true liberty, and tenacity of purpose; to
regard nothing else, even in the smallest degree, but reason always;
and always to remain unaltered in the agonies of pain, in the losses
of children, or in long diseases. He afforded me a living example of
how the same man can, upon occasion, be most yielding and most
inflexible. He was patient in exposition; and, as might well be seen,
esteemed his fine skill and ability in teaching others the principles
of philosophy as the least of his endowments. It was from him that I
learned how to receive from friends what are thought favours without
seeming humbled by the giver or insensible to the gift.`

A  => cmd/demo/main.go +987 -0
@@ 1,987 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

import (
	"bytes"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"log"
	"math"
	"reflect"
	"time"
	"unsafe"

	_ "eliasnaur.com/unik/kernel"
	virtgpu "eliasnaur.com/unik/virtio/gpu"
	"eliasnaur.com/unik/virtio/input"
	"gioui.org/f32"
	"gioui.org/font/gofont"
	"gioui.org/gpu"
	"gioui.org/gpu/backend"
	"gioui.org/gpu/gl"
	"gioui.org/io/pointer"
	"gioui.org/io/router"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/unit"
	"gioui.org/widget/material"
)

type virtBackend struct {
	dev           *virtgpu.Device
	fb            *framebuffer
	texUnits      [maxSamplerUnits]*texture
	samplerViews  [maxSamplerUnits]virtgpu.Handle
	samplerStates [maxSamplerUnits]virtgpu.Handle
	buffers       [1]virtgpu.VertexBuffer
	depth         depthState
	depthCache    map[depthState]virtgpu.Handle
	blend         blendState
	blendCache    map[blendState]virtgpu.Handle
	prog          *program
}

type depthState struct {
	fun    uint32
	enable bool
	mask   bool
}

type blendState struct {
	sfactor, dfactor uint32
	enable           bool
}

type framebuffer struct {
	dev                  *virtgpu.Device
	colorRes, depthRes   virtgpu.Resource
	colorSurf, depthSurf virtgpu.Handle
}

type buffer struct {
	dev    *virtgpu.Device
	res    virtgpu.Resource
	length int
}

type texture struct {
	dev      *virtgpu.Device
	res      virtgpu.Resource
	view     virtgpu.Handle
	state    virtgpu.Handle
	width    int
	height   int
	format   uint32
	released bool
}

type program struct {
	dev           *virtgpu.Device
	minSamplerIdx int
	texUnits      int
	vert          struct {
		shader   virtgpu.Handle
		uniforms *buffer
	}
	frag struct {
		shader   virtgpu.Handle
		uniforms *buffer
	}
}

type inputLayout struct {
	dev         *virtgpu.Device
	vertexElems virtgpu.Handle
	inputs      []backend.InputLocation
	layout      []backend.InputDesc
}

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}

const maxSamplerUnits = 2

func run() error {
	d, err := virtgpu.New()
	if err != nil {
		return err
	}
	var width, height int
	var displayFB *framebuffer
	var colorRes virtgpu.Resource
	var g *gpu.GPU

	inputDev, err := input.New()
	if err != nil {
		return err
	}
	events := make(chan input.Event, 100)
	go func() {
		buf := make([]input.Event, cap(events))
		for {
			n, err := inputDev.Read(buf)
			for i := 0; i < n; i++ {
				events <- buf[i]
			}
			if err != nil {
				panic(err)
			}
		}
	}()
	gofont.Register()
	var queue router.Router
	imap := newInputMapper(inputDev)
	gtx := layout.NewContext(&queue)
	th := material.NewTheme()
	timer := time.NewTimer(0)
	cursor, err := newCursor(d)
	if err != nil {
		return err
	}
	for {
		select {
		case e := <-events:
			imap.event(&queue, e)
		loop:
			for {
				select {
				case e := <-events:
					imap.event(&queue, e)
				default:
					break loop
				}
			}
			d.MoveCursor(cursor, uint32(imap.x+.5), uint32(imap.y+.5))
		case <-d.ConfigNotify():
			if g != nil {
				g.Release()
				displayFB.Release()
				d.CmdCtxDetachResource(colorRes)
				d.CmdResourceUnref(colorRes)
				g = nil
			}
		case <-timer.C:
		}
		if g == nil {
			width, height, err = d.QueryScanout()
			if err != nil {
				return err
			}
			imap.width, imap.height = width, height
			colorRes = createDisplayBuffer(d, width, height)
			fb := newFramebuffer(d, colorRes, virtgpu.VIRGL_FORMAT_B8G8R8A8_SRGB, width, height, 16)
			displayFB = fb
			d.CmdSetScanout(fb.colorRes)
			backend, err := newBackend(d, displayFB)
			if err != nil {
				return err
			}
			backend.BindFramebuffer(displayFB)
			g, err = gpu.New(backend)
			if err != nil {
				return err
			}
		}
		if err := d.Flush3D(); err != nil {
			return err
		}
		sz := image.Point{X: width, Y: height}
		gtx.Reset(&config{1.5}, sz)
		kitchen(gtx, th)
		g.Collect(sz, gtx.Ops)
		g.BeginFrame()
		queue.Frame(gtx.Ops)
		g.EndFrame()
		d.CmdResourceFlush(displayFB.colorRes)
		if t, ok := queue.WakeupTime(); ok {
			timer.Reset(time.Until(t))
		}
	}
}

func newCursor(d *virtgpu.Device) (virtgpu.Resource, error) {
	cursorImg, _, err := image.Decode(bytes.NewBuffer(cursor))
	if err != nil {
		return 0, err
	}
	rgba := image.NewRGBA(cursorImg.Bounds())
	draw.Draw(rgba, rgba.Bounds(), cursorImg, cursorImg.Bounds().Min, draw.Src)
	return d.NewCursor(rgba, image.Point{})
}

type inputMapper struct {
	width, height int
	begun         bool
	xinf          input.AbsInfo
	yinf          input.AbsInfo
	x, y          float32
	buttons       pointer.Buttons
}

func newInputMapper(d *input.Device) *inputMapper {
	m := new(inputMapper)
	m.xinf, _ = d.AbsInfo(input.ABS_X)
	m.yinf, _ = d.AbsInfo(input.ABS_Y)
	return m
}

func (m *inputMapper) event(q *router.Router, e input.Event) {
	switch e.Type {
	case input.EV_SYN:
		if m.begun {
			q.Add(pointer.Event{
				Type:     pointer.Move,
				Source:   pointer.Mouse,
				Position: f32.Point{X: m.x, Y: m.y},
				Buttons:  m.buttons,
			})
			m.begun = false
		}
	case input.EV_REL:
		switch e.Code {
		case input.REL_WHEEL:
			amount := -int32(e.Value)
			q.Add(pointer.Event{
				Type:     pointer.Move,
				Source:   pointer.Mouse,
				Position: f32.Point{X: m.x, Y: m.y},
				Scroll:   f32.Point{Y: float32(amount) * 120},
				Buttons:  m.buttons,
			})
		case input.REL_HWHEEL:
			amount := -int32(e.Value)
			q.Add(pointer.Event{
				Type:     pointer.Move,
				Source:   pointer.Mouse,
				Position: f32.Point{X: m.x, Y: m.y},
				Scroll:   f32.Point{X: float32(amount) * 120},
				Buttons:  m.buttons,
			})
		}
	case input.EV_ABS:
		val := int(e.Value)
		switch e.Code {
		case input.ABS_X:
			m.begun = true
			m.x = m.mapAxis(m.xinf, m.width, val)
		case input.ABS_Y:
			m.begun = true
			m.y = m.mapAxis(m.yinf, m.height, val)
		}
	case input.EV_KEY:
		var button pointer.Buttons
		switch e.Code {
		case input.BTN_LEFT:
			button = pointer.ButtonLeft
		case input.BTN_RIGHT:
			button = pointer.ButtonRight
		case input.BTN_MIDDLE:
			button = pointer.ButtonMiddle
		default:
			return
		}
		var t pointer.Type
		if e.Value != 0 {
			t = pointer.Press
			m.buttons |= button
		} else {
			t = pointer.Release
			m.buttons &^= button
		}
		m.begun = false
		q.Add(pointer.Event{
			Type:     t,
			Source:   pointer.Mouse,
			Position: f32.Point{X: m.x, Y: m.y},
			Buttons:  m.buttons,
		})
	}
}

func (m *inputMapper) mapAxis(inf input.AbsInfo, dim, val int) float32 {
	d := inf.Max - inf.Min
	if d <= 0 {
		return 0
	}
	return float32((val-int(inf.Min))*dim) / float32(d)
}

func createDisplayBuffer(d *virtgpu.Device, width, height int) virtgpu.Resource {
	res := d.CmdResourceCreate3D(virtgpu.ResourceCreate3DReq{
		Format:     virtgpu.VIRGL_FORMAT_B8G8R8A8_SRGB,
		Width:      uint32(width),
		Height:     uint32(height),
		Depth:      1,
		Array_size: 1,
		Flags:      virtgpu.VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
		Bind:       virtgpu.VIRGL_BIND_RENDER_TARGET,
		Target:     virtgpu.PIPE_TEXTURE_2D,
	})
	d.CmdCtxAttachResource(res)
	return res
}

func drawShapes(gtx *layout.Context) {
	blue := color.RGBA{B: 0xFF, A: 0xFF}
	paint.ColorOp{Color: blue}.Add(gtx.Ops)
	paint.PaintOp{Rect: f32.Rectangle{
		Max: f32.Point{X: 50, Y: 100},
	}}.Add(gtx.Ops)

	red := color.RGBA{R: 0xFF, A: 0xFF}
	paint.ColorOp{Color: red}.Add(gtx.Ops)
	paint.PaintOp{Rect: f32.Rectangle{
		Max: f32.Point{X: 100, Y: 50},
	}}.Add(gtx.Ops)

	var stack op.StackOp
	stack.Push(gtx.Ops)
	op.TransformOp{}.Offset(f32.Point{X: 100, Y: 100}).Add(gtx.Ops)
	col := color.RGBA{A: 0xff, R: 0xca, G: 0xfe, B: 0x00}
	col2 := color.RGBA{A: 0xff, R: 0x00, G: 0xfe, B: 0x00}
	pop := paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{
		X: 500,
		Y: 500,
	}}}
	paint.ColorOp{Color: col}.Add(gtx.Ops)
	clip.Rect{
		Rect: f32.Rectangle{
			Min: f32.Point{X: 50, Y: 50},
			Max: f32.Point{X: 250, Y: 250},
		},
		SE: 15,
	}.Op(gtx.Ops).Add(gtx.Ops)
	pop.Add(gtx.Ops)

	paint.ColorOp{Color: col2}.Add(gtx.Ops)
	clip.Rect{
		Rect: f32.Rectangle{
			Min: f32.Point{X: 100, Y: 100},
			Max: f32.Point{X: 350, Y: 350},
		},
		NW: 25, SE: 115, NE: 35,
	}.Op(gtx.Ops).Add(gtx.Ops)
	pop.Add(gtx.Ops)
	stack.Pop()
}

func newBackend(d *virtgpu.Device, fb *framebuffer) (*virtBackend, error) {
	b := &virtBackend{
		dev:        d,
		fb:         fb,
		blendCache: make(map[blendState]virtgpu.Handle),
		depthCache: make(map[depthState]virtgpu.Handle),
	}
	// Depth mask is on by default.
	b.depth.mask = true
	return b, nil
}

type config struct {
	Scale float32
}

func (s *config) Now() time.Time {
	return time.Now()
}

func (s *config) Px(v unit.Value) int {
	scale := s.Scale
	if v.U == unit.UnitPx {
		scale = 1
	}
	return int(math.Round(float64(scale * v.V)))
}

func (b *virtBackend) BeginFrame() {}

func (b *virtBackend) EndFrame() {
}

func (b *virtBackend) Caps() backend.Caps {
	return backend.Caps{
		MaxTextureSize: 4096, // TODO
	}
}

func (b *virtBackend) NewTimer() backend.Timer {
	panic("timers not implemented")
}

func (b *virtBackend) IsTimeContinuous() bool {
	panic("timers not implemented")
}

func (b *virtBackend) NewFramebuffer(tex backend.Texture, depthBits int) (backend.Framebuffer, error) {
	t := tex.(*texture)
	return newFramebuffer(b.dev, t.res, t.format, t.width, t.height, depthBits), nil
}

func newFramebuffer(d *virtgpu.Device, colorRes virtgpu.Resource, format uint32, width, height, depthBits int) *framebuffer {
	surf := d.CreateSurface(colorRes, format)
	fb := &framebuffer{dev: d, colorRes: colorRes, colorSurf: surf}
	if depthBits > 0 {
		depthRes, depthSurf := createZBuffer(d, width, height)
		fb.depthRes = depthRes
		fb.depthSurf = depthSurf
	}
	return fb
}

func createZBuffer(d *virtgpu.Device, width, height int) (virtgpu.Resource, virtgpu.Handle) {
	res := d.CmdResourceCreate3D(virtgpu.ResourceCreate3DReq{
		Format:     virtgpu.VIRGL_FORMAT_Z24X8_UNORM,
		Width:      uint32(width),
		Height:     uint32(height),
		Depth:      1,
		Array_size: 1,
		Bind:       virtgpu.VIRGL_BIND_DEPTH_STENCIL,
		Target:     virtgpu.PIPE_TEXTURE_2D,
	})
	d.CmdCtxAttachResource(res)
	surf := d.CreateSurface(res, virtgpu.VIRGL_FORMAT_Z24X8_UNORM)
	return res, surf
}

func (b *virtBackend) CurrentFramebuffer() backend.Framebuffer {
	return b.fb
}

func (b *virtBackend) NewTexture(format backend.TextureFormat, width, height int, minFilter, magFilter backend.TextureFilter, binding backend.BufferBinding) (backend.Texture, error) {
	var bfmt uint32
	switch format {
	case backend.TextureFormatSRGB:
		bfmt = virtgpu.VIRGL_FORMAT_B8G8R8A8_SRGB
	case backend.TextureFormatFloat:
		bfmt = virtgpu.VIRGL_FORMAT_R16_FLOAT
	default:
		return nil, fmt.Errorf("gpu: unsupported texture format: %v", format)
	}
	var bind uint32
	if binding&backend.BufferBindingTexture != 0 {
		bind |= virtgpu.VIRGL_BIND_SAMPLER_VIEW
	}
	if binding&backend.BufferBindingFramebuffer != 0 {
		bind |= virtgpu.VIRGL_BIND_RENDER_TARGET
	}
	tex := b.dev.CmdResourceCreate3D(virtgpu.ResourceCreate3DReq{
		Format:     bfmt,
		Width:      uint32(width),
		Height:     uint32(height),
		Depth:      1,
		Array_size: 1,
		//Flags:      virtgpu.VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
		Bind:   bind,
		Target: virtgpu.PIPE_TEXTURE_2D,
	})
	b.dev.CmdCtxAttachResource(tex)
	const swizzle = virtgpu.PIPE_SWIZZLE_ALPHA<<9 | virtgpu.PIPE_SWIZZLE_BLUE<<6 | virtgpu.PIPE_SWIZZLE_GREEN<<3 | virtgpu.PIPE_SWIZZLE_RED
	view := b.dev.CreateSamplerView(tex, bfmt, virtgpu.PIPE_TEXTURE_2D, swizzle)
	minImg := convertTextureFilter(minFilter)
	magImg := convertTextureFilter(magFilter)

	state := b.dev.CreateSamplerState(
		virtgpu.PIPE_TEX_WRAP_CLAMP_TO_EDGE,
		virtgpu.PIPE_TEX_WRAP_CLAMP_TO_EDGE,
		minImg, virtgpu.PIPE_TEX_MIPFILTER_NONE,
		magImg,
	)
	return &texture{dev: b.dev, res: tex, view: view, state: state, width: width, height: height, format: bfmt}, nil
}

func convertTextureFilter(f backend.TextureFilter) uint32 {
	switch f {
	case backend.FilterLinear:
		return virtgpu.PIPE_TEX_FILTER_LINEAR
	case backend.FilterNearest:
		return virtgpu.PIPE_TEX_FILTER_NEAREST
	default:
		panic("unknown texture filter")
	}
}

func (b *virtBackend) NewBuffer(typ backend.BufferBinding, size int) (backend.Buffer, error) {
	return b.newBuffer(typ, size)
}

func (b *virtBackend) newBuffer(typ backend.BufferBinding, length int) (*buffer, error) {
	bind, err := convBufferBinding(typ)
	if err != nil {
		return nil, err
	}
	res := b.dev.CmdResourceCreate3D(virtgpu.ResourceCreate3DReq{
		Width:      uint32(length),
		Height:     1,
		Depth:      1,
		Array_size: 1,
		Bind:       bind,
		Target:     virtgpu.PIPE_BUFFER,
	})
	b.dev.CmdCtxAttachResource(res)
	return &buffer{dev: b.dev, res: res, length: length}, nil
}

func (b *virtBackend) NewImmutableBuffer(typ backend.BufferBinding, data []byte) (backend.Buffer, error) {
	buf, err := b.newBuffer(typ, len(data))
	if err != nil {
		return nil, err
	}
	return buf, buf.upload(data)
}

func (b *virtBackend) NewInputLayout(vs backend.ShaderSources, layout []backend.InputDesc) (backend.InputLayout, error) {
	elems := make([]virtgpu.VertexElement, len(vs.Inputs))
	for i, input := range vs.Inputs {
		vfmt, err := convertVertexFormat(input.Type, input.Size)
		if err != nil {
			return nil, err
		}
		l := layout[i]
		elems[i] = virtgpu.VertexElement{
			Format:      vfmt,
			Offset:      uint32(l.Offset),
			Divisor:     0,
			BufferIndex: 0, // Only one vertex buffer is supported.
		}
	}
	ve, err := b.dev.CreateVertexElements(elems)
	if err != nil {
		return nil, err
	}
	return &inputLayout{
		dev:         b.dev,
		vertexElems: ve,
		inputs:      vs.Inputs,
		layout:      layout,
	}, nil
}

func convertVertexFormat(dataType backend.DataType, size int) (uint32, error) {
	var f uint32
	switch dataType {
	case backend.DataTypeFloat:
		switch size {
		case 1:
			f = virtgpu.VIRGL_FORMAT_R32_FLOAT
		case 2:
			f = virtgpu.VIRGL_FORMAT_R32G32_FLOAT
		case 3:
			f = virtgpu.VIRGL_FORMAT_R32G32B32_FLOAT
		case 4:
			f = virtgpu.VIRGL_FORMAT_R32G32B32A32_FLOAT
		}
	default:
		return 0, fmt.Errorf("gpu: invalid data type %v, size %d", dataType, size)
	}
	return f, nil
}

func (b *virtBackend) NewProgram(vertShader, fragShader backend.ShaderSources) (backend.Program, error) {
	tgsi, exist := shaders[[2]string{fragShader.GLSL100ES, vertShader.GLSL100ES}]
	if !exist {
		return nil, fmt.Errorf("gpu: unrecognized vertex shader")
	}
	fsrc, vsrc := tgsi[0], tgsi[1]
	vh := b.dev.CreateShader(virtgpu.PIPE_SHADER_VERTEX, vsrc)
	fh := b.dev.CreateShader(virtgpu.PIPE_SHADER_FRAGMENT, fsrc)
	minSamplerIdx := maxSamplerUnits
	for _, t := range fragShader.Textures {
		if t.Binding < minSamplerIdx {
			minSamplerIdx = t.Binding
		}
	}
	p := &program{dev: b.dev, minSamplerIdx: minSamplerIdx, texUnits: len(fragShader.Textures)}
	p.vert.shader = vh
	p.frag.shader = fh
	return p, nil
}

func (b *virtBackend) SetDepthTest(enable bool) {
	b.depth.enable = enable
}

func (b *virtBackend) DepthMask(mask bool) {
	b.depth.mask = mask
}

func (b *virtBackend) DepthFunc(fun backend.DepthFunc) {
	var f uint32
	switch fun {
	case backend.DepthFuncGreater:
		f = virtgpu.DepthFuncGreater
	default:
		panic("unsupported depth func")
	}
	b.depth.fun = f
}

func (b *virtBackend) BlendFunc(sfactor, dfactor backend.BlendFactor) {
	b.blend.sfactor = convertBlendFactor(sfactor)
	b.blend.dfactor = convertBlendFactor(dfactor)
}

func convertBlendFactor(f backend.BlendFactor) uint32 {
	switch f {
	case backend.BlendFactorOne:
		return virtgpu.PIPE_BLENDFACTOR_ONE
	case backend.BlendFactorOneMinusSrcAlpha:
		return virtgpu.PIPE_BLENDFACTOR_INV_SRC_ALPHA
	case backend.BlendFactorZero:
		return virtgpu.PIPE_BLENDFACTOR_ZERO
	case backend.BlendFactorDstColor:
		return virtgpu.PIPE_BLENDFACTOR_DST_COLOR
	default:
		panic("unsupported blend factor")
	}
}

func (b *virtBackend) SetBlend(enable bool) {
	b.blend.enable = enable
}

func (b *virtBackend) DrawElements(mode backend.DrawMode, off, count int) {
	b.prepareDraw()
	m := convertDrawMode(mode)
	b.dev.Draw(true, m, uint32(off), uint32(count))
}

func (b *virtBackend) DrawArrays(mode backend.DrawMode, off, count int) {
	b.prepareDraw()
	m := convertDrawMode(mode)
	b.dev.Draw(false, m, uint32(off), uint32(count))
}

func convertDrawMode(mode backend.DrawMode) uint32 {
	switch mode {
	case backend.DrawModeTriangles:
		return gl.TRIANGLES
	case backend.DrawModeTriangleStrip:
		return gl.TRIANGLE_STRIP
	default:
		panic("unsupported draw mode")
	}
}

func (b *virtBackend) prepareDraw() {
	if p := b.prog; p != nil {
		if u := p.vert.uniforms; u != nil {
			b.dev.SetUniformBuffer(virtgpu.PIPE_SHADER_VERTEX, 1, 0, uint32(u.length), u.res)
		}
		if u := p.frag.uniforms; u != nil {
			b.dev.SetUniformBuffer(virtgpu.PIPE_SHADER_FRAGMENT, 1, 0, uint32(u.length), u.res)
		}
		for i := 0; i < p.texUnits; i++ {
			u := i + p.minSamplerIdx
			if t := b.texUnits[u]; t != nil && !t.released {
				b.samplerStates[i] = t.state
				b.samplerViews[i] = t.view
			} else {
				b.samplerStates[i] = 0
				b.samplerViews[i] = 0
			}
		}
		b.dev.SetSamplerStates(virtgpu.PIPE_SHADER_FRAGMENT, b.samplerStates[:p.texUnits])
		b.dev.SetSamplerViews(virtgpu.PIPE_SHADER_FRAGMENT, b.samplerViews[:p.texUnits])
	}
	bstate, exists := b.blendCache[b.blend]
	if !exists {
		bstate = b.dev.CreateBlend(b.blend.enable, virtgpu.PIPE_BLEND_ADD, b.blend.sfactor, b.blend.dfactor)
		b.blendCache[b.blend] = bstate
	}
	b.dev.BindObject(virtgpu.VIRGL_OBJECT_BLEND, bstate)
	dstate, exists := b.depthCache[b.depth]
	if !exists {
		dstate = b.dev.CreateDepthState(b.depth.enable, b.depth.mask, b.depth.fun)
		b.depthCache[b.depth] = dstate
	}
	b.dev.BindObject(virtgpu.VIRGL_OBJECT_DSA, dstate)
}

func (b *virtBackend) Viewport(x, y, width, height int) {
	b.dev.Viewport(x, y, width, height)
}

func (b *virtBackend) ClearDepth(d float32) {
	b.dev.Clear(virtgpu.PIPE_CLEAR_DEPTH, [4]float32{}, d)
}

func (b *virtBackend) Clear(colR, colG, colB, colA float32) {
	b.dev.Clear(virtgpu.PIPE_CLEAR_COLOR0, [4]float32{colR, colB, colG, colA}, 0)
}

func (b *virtBackend) BindProgram(prog backend.Program) {
	p := prog.(*program)
	b.dev.BindShader(virtgpu.PIPE_SHADER_VERTEX, p.vert.shader)
	b.dev.BindShader(virtgpu.PIPE_SHADER_FRAGMENT, p.frag.shader)
	b.prog = p
}

func (b *virtBackend) BindVertexBuffer(buf backend.Buffer, stride, offset int) {
	res := buf.(*buffer).res
	b.buffers[0].Stride = uint32(stride)
	b.buffers[0].Offset = uint32(offset)
	b.buffers[0].Buffer = res
	b.dev.SetVertexBuffers(b.buffers[:])
}

func (b *virtBackend) BindIndexBuffer(buf backend.Buffer) {
	const uint16Size = 2
	b.dev.SetIndexBuffer(buf.(*buffer).res, uint16Size, 0)
}

func (b *virtBackend) BindFramebuffer(fbo backend.Framebuffer) {
	f := fbo.(*framebuffer)
	b.dev.SetFramebufferState(f.colorSurf, f.depthSurf)
}

func (b *virtBackend) BindTexture(unit int, tex backend.Texture) {
	t := tex.(*texture)
	b.texUnits[unit] = t
}

func (b *virtBackend) BindInputLayout(layout backend.InputLayout) {
	l := layout.(*inputLayout)
	b.dev.BindObject(virtgpu.VIRGL_OBJECT_VERTEX_ELEMENTS, l.vertexElems)
}

func (b *virtBackend) Release() {
	for _, state := range b.blendCache {
		b.dev.DestroyObject(state)
	}
	for _, state := range b.depthCache {
		b.dev.DestroyObject(state)
	}
}

func (f *framebuffer) Invalidate() {}

func (f *framebuffer) ReadPixels(rect image.Rectangle, pix []byte) error {
	panic("TODO")
}

func (f *framebuffer) Release() {
	if f.colorSurf != 0 {
		f.dev.DestroyObject(f.colorSurf)
	}
	if f.depthSurf != 0 {
		f.dev.DestroyObject(f.depthSurf)
	}
	if f.depthRes != 0 {
		f.dev.CmdCtxDetachResource(f.depthRes)
		f.dev.CmdResourceUnref(f.depthRes)
	}
}

func (b *buffer) Release() {
	b.dev.CmdCtxDetachResource(b.res)
	b.dev.CmdResourceUnref(b.res)
}

func (t *texture) Release() {
	t.dev.DestroyObject(t.state)
	t.dev.DestroyObject(t.view)
	t.dev.CmdCtxDetachResource(t.res)
	t.dev.CmdResourceUnref(t.res)
	t.released = true
}

func (p *program) SetVertexUniforms(uniforms backend.Buffer) {
	p.vert.uniforms = uniforms.(*buffer)
}

func (p *program) SetFragmentUniforms(uniforms backend.Buffer) {
	p.frag.uniforms = uniforms.(*buffer)
}

func (p *program) Release() {
	p.dev.DestroyObject(p.frag.shader)
	p.dev.DestroyObject(p.vert.shader)
}

func (b *buffer) upload(data []byte) error {
	b.dev.Copy(b.res, data, len(data), 1)
	return nil
}

func (b *buffer) Upload(data []byte) {
	if err := b.upload(data); err != nil {
		panic(err)
	}
}

func (t *texture) upload(data []byte, width, height int) error {
	t.dev.Copy(t.res, data, width, height)
	return nil
}

func (t *texture) Upload(img *image.RGBA) {
	var pixels []byte
	b := img.Bounds()
	w, h := b.Dx(), b.Dy()
	if img.Stride != w*4 {
		panic("unsupported stride")
	}
	start := (b.Min.X + b.Min.Y*w) * 4
	end := (b.Max.X + (b.Max.Y-1)*w) * 4
	pixels = img.Pix[start:end]
	if err := t.upload(pixels, w, h); err != nil {
		panic(err)
	}
}

func (i *inputLayout) Release() {
	i.dev.DestroyObject(i.vertexElems)
}

func convBufferBinding(typ backend.BufferBinding) (uint32, error) {
	var res uint32
	switch typ {
	case backend.BufferBindingIndices:
		res = virtgpu.VIRGL_BIND_INDEX_BUFFER
	case backend.BufferBindingVertices:
		res = virtgpu.VIRGL_BIND_VERTEX_BUFFER
	case backend.BufferBindingUniforms:
		res = virtgpu.VIRGL_BIND_CONSTANT_BUFFER
	default:
		return 0, fmt.Errorf("gpu: unsupported BufferBinding: %v", typ)
	}
	return res, nil
}

func testSimpleShader(b backend.Device) error {
	p, err := b.NewProgram(shader_simple_vert, shader_simple_frag)
	if err != nil {
		return err
	}
	defer p.Release()
	b.BindProgram(p)
	layout, err := b.NewInputLayout(shader_simple_vert, nil)
	if err != nil {
		return err
	}
	defer layout.Release()
	b.BindInputLayout(layout)
	b.DrawArrays(backend.DrawModeTriangles, 0, 3)
	return nil
}

func testInputShader(b backend.Device) error {
	p, err := b.NewProgram(shader_input_vert, shader_simple_frag)
	if err != nil {
		return err
	}
	defer p.Release()
	b.BindProgram(p)
	buf, err := b.NewImmutableBuffer(backend.BufferBindingVertices,
		BytesView([]float32{
			0, .5, .5, 1,
			-.5, -.5, .5, 1,
			.5, -.5, .5, 1,
		}),
	)
	if err != nil {
		return err
	}
	defer buf.Release()
	b.BindVertexBuffer(buf, 4*4, 0)
	layout, err := b.NewInputLayout(shader_input_vert, []backend.InputDesc{
		{
			Type:   backend.DataTypeFloat,
			Size:   4,
			Offset: 0,
		},
	})
	if err != nil {
		return err
	}
	defer layout.Release()
	b.BindInputLayout(layout)
	b.DrawArrays(backend.DrawModeTriangles, 0, 3)
	return nil
}

// BytesView returns a byte slice view of a slice.
func BytesView(s interface{}) []byte {
	v := reflect.ValueOf(s)
	first := v.Index(0)
	sz := int(first.Type().Size())
	return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))),
		Len:  v.Len() * sz,
		Cap:  v.Cap() * sz,
	}))
}

func setupFBO(b backend.Device, size image.Point) (backend.Texture, backend.Framebuffer, error) {
	tex, fbo, err := newFBO(b, size)
	if err != nil {
		return nil, nil, err
	}
	b.BindFramebuffer(fbo)
	// ClearColor accepts linear RGBA colors, while 8-bit colors
	// are in the sRGB color space.
	var clearCol = color.RGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
	col := RGBAFromSRGB(clearCol)
	b.Clear(col.Float32())
	b.ClearDepth(0.0)
	b.Viewport(0, 0, size.X, size.Y)
	return tex, fbo, nil
}

func newFBO(b backend.Device, size image.Point) (backend.Texture, backend.Framebuffer, error) {
	fboTex, err := b.NewTexture(
		backend.TextureFormatSRGB,
		size.X, size.Y,
		backend.FilterNearest, backend.FilterNearest,
		backend.BufferBindingFramebuffer,
	)
	if err != nil {
		return nil, nil, err
	}
	const depthBits = 16
	fbo, err := b.NewFramebuffer(fboTex, depthBits)
	if err != nil {
		fboTex.Release()
		return nil, nil, err
	}
	return fboTex, fbo, nil
}

// RGBAFromSRGB converts color.Color to RGBA.
func RGBAFromSRGB(col color.Color) RGBA {
	r, g, b, a := col.RGBA()
	return RGBA{
		R: sRGBToLinear(float32(r) / 0xffff),
		G: sRGBToLinear(float32(g) / 0xffff),
		B: sRGBToLinear(float32(b) / 0xffff),
		A: float32(a) / 0xFFFF,
	}
}

// RGBA is a 32 bit floating point linear space color.
type RGBA struct {
	R, G, B, A float32
}

// sRGBToLinear transforms color value from sRGB to linear.
func sRGBToLinear(c float32) float32 {
	// Formula from EXT_sRGB.
	if c <= 0.04045 {
		return c / 12.92
	} else {
		return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
	}
}

// Float32 returns r, g, b, a values.
func (col RGBA) Float32() (r, g, b, a float32) {
	return col.R, col.G, col.B, col.A
}

A  => cmd/demo/shaders.go +308 -0
@@ 1,308 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

var shaders = map[[2]string][2]string{
	{"precision mediump float;\nprecision highp int;\n\nstruct Color\n{\n    vec4 _color;\n};\n\nuniform Color _12;\n\nvarying vec2 vUV;\n\nvoid main()\n{\n    gl_FragData[0] = _12._color;\n}\n\n", "\nstruct Block\n{\n    vec4 transform;\n    vec4 uvTransform;\n    float z;\n};\n\nuniform Block _24;\n\nattribute vec2 pos;\nvarying vec2 vUV;\nattribute vec2 uv;\n\nvec4 toClipSpace(vec4 pos_1)\n{\n    return pos_1;\n}\n\nvoid main()\n{\n    vec2 p = (pos * _24.transform.xy) + _24.transform.zw;\n    vec4 param = vec4(p, _24.z, 1.0);\n    gl_Position = toClipSpace(param);\n    vUV = (uv * _24.uvTransform.xy) + _24.uvTransform.zw;\n}\n\n"}: {`FRAG
DCL OUT[0], COLOR
DCL CONST[1][0]
IMM[0] UINT32 {0, 0, 0, 0}
  0: MOV OUT[0], CONST[1][0]
  1: END`, `VERT
DCL IN[0]
DCL OUT[0], POSITION
DCL CONST[1][0..2]
DCL TEMP[0], LOCAL
IMM[0] FLT32 {    1.0000,     0.0000,     0.0000,     0.0000}
IMM[1] UINT32 {0, 32, 0, 0}
  0: MOV TEMP[0].w, IMM[0].xxxx
  1: MAD TEMP[0].xy, IN[0].xyyy, CONST[1][0].xyyy, CONST[1][0].zwww
  2: MOV TEMP[0].z, CONST[1][2].xxxx
  3: MOV OUT[0], TEMP[0]
  4: END`},
	{"precision mediump float;\nprecision highp int;\n\nuniform mediump sampler2D tex;\n\nvarying vec2 vUV;\n\nvoid main()\n{\n    gl_FragData[0] = texture2D(tex, vUV);\n}\n\n", "\nstruct Block\n{\n    vec4 transform;\n    vec4 uvTransform;\n    float z;\n};\n\nuniform Block _24;\n\nattribute vec2 pos;\nvarying vec2 vUV;\nattribute vec2 uv;\n\nvec4 toClipSpace(vec4 pos_1)\n{\n    return pos_1;\n}\n\nvoid main()\n{\n    vec2 p = (pos * _24.transform.xy) + _24.transform.zw;\n    vec4 param = vec4(p, _24.z, 1.0);\n    gl_Position = toClipSpace(param);\n    vUV = (uv * _24.uvTransform.xy) + _24.uvTransform.zw;\n}\n\n"}: {`FRAG
DCL IN[0].xy, GENERIC[9], PERSPECTIVE
DCL OUT[0], COLOR
DCL SAMP[0]
DCL SVIEW[0], 2D, FLOAT
DCL TEMP[0], LOCAL
  0: MOV TEMP[0].xy, IN[0].xyyy
  1: TEX TEMP[0], TEMP[0], SAMP[0], 2D
  2: MOV OUT[0], TEMP[0]
  3: END`, `VERT
DCL IN[0]
DCL IN[1]
DCL OUT[0], POSITION
DCL OUT[1].xy, GENERIC[9]
DCL CONST[1][0..2]
DCL TEMP[0..1], LOCAL
IMM[0] FLT32 {    1.0000,     0.0000,     0.0000,     0.0000}
IMM[1] UINT32 {0, 32, 16, 0}
  0: MOV TEMP[0].w, IMM[0].xxxx
  1: MAD TEMP[0].xy, IN[0].xyyy, CONST[1][0].xyyy, CONST[1][0].zwww
  2: MOV TEMP[0].z, CONST[1][2].xxxx
  3: MAD TEMP[1].xy, IN[1].xyyy, CONST[1][1].xyyy, CONST[1][1].zwww
  4: MOV OUT[0], TEMP[0]
  5: MOV OUT[1].xy, TEMP[1].xyxx
  6: END`},
	{"precision mediump float;\nprecision highp int;\n\nstruct Color\n{\n    vec4 _color;\n};\n\nuniform Color _12;\n\nuniform mediump sampler2D cover;\n\nvarying highp vec2 vCoverUV;\nvarying vec2 vUV;\n\nvoid main()\n{\n    gl_FragData[0] = _12._color;\n    float cover_1 = abs(texture2D(cover, vCoverUV).x);\n    gl_FragData[0] *= cover_1;\n}\n\n", "\nstruct Block\n{\n    vec4 transform;\n    vec4 uvCoverTransform;\n    vec4 uvTransform;\n    float z;\n};\n\nuniform Block _66;\n\nattribute vec2 pos;\nvarying vec2 vUV;\nattribute vec2 uv;\nvarying vec2 vCoverUV;\n\nvec4 toClipSpace(vec4 pos_1)\n{\n    return pos_1;\n}\n\nvec3[2] fboTextureTransform()\n{\n    vec3 t[2];\n    t[0] = vec3(1.0, 0.0, 0.0);\n    t[1] = vec3(0.0, 1.0, 0.0);\n    return t;\n}\n\nvec3 transform3x2(vec3 t[2], vec3 v)\n{\n    return vec3(dot(t[0], v), dot(t[1], v), dot(vec3(0.0, 0.0, 1.0), v));\n}\n\nvoid main()\n{\n    vec4 param = vec4((pos * _66.transform.xy) + _66.transform.zw, _66.z, 1.0);\n    gl_Position = toClipSpace(param);\n    vUV = (uv * _66.uvTransform.xy) + _66.uvTransform.zw;\n    vec3 fboTrans[2] = fboTextureTransform();\n    vec3 param_1[2] = fboTrans;\n    vec3 param_2 = vec3(uv, 1.0);\n    vec3 uv3 = transform3x2(param_1, param_2);\n    vCoverUV = ((uv3 * vec3(_66.uvCoverTransform.xy, 1.0)) + vec3(_66.uvCoverTransform.zw, 0.0)).xy;\n}\n\n"}: {`FRAG
DCL IN[0].xy, GENERIC[9], PERSPECTIVE
DCL OUT[0], COLOR
DCL SAMP[0]
DCL SVIEW[0], 2D, FLOAT
DCL CONST[1][0]
DCL TEMP[0], LOCAL
IMM[0] UINT32 {0, 0, 0, 0}
  0: MOV TEMP[0].xy, IN[0].xyyy
  1: TEX TEMP[0].x, TEMP[0], SAMP[0], 2D
  2: MOV TEMP[0].x, |TEMP[0].xxxx|
  3: MUL TEMP[0], CONST[1][0], TEMP[0].xxxx
  4: MOV OUT[0], TEMP[0]
  5: END`, `VERT
DCL IN[0]
DCL IN[1]
DCL OUT[0], POSITION
DCL OUT[1].xy, GENERIC[9]
DCL CONST[1][0..3]
DCL TEMP[0..3], LOCAL
IMM[0] FLT32 {    1.0000,     0.0000,     0.0000,     0.0000}
IMM[1] UINT32 {0, 48, 16, 0}
  0: MOV TEMP[0].w, IMM[0].xxxx
  1: MAD TEMP[0].xy, IN[0].xyyy, CONST[1][0].xyyy, CONST[1][0].zwww
  2: MOV TEMP[0].z, CONST[1][3].xxxx
  3: MOV TEMP[1].z, IMM[0].xxxx
  4: MOV TEMP[1].xy, IN[1].xyxx
  5: DP3 TEMP[2].x, IMM[0].xyyy, TEMP[1].xyzz
  6: DP3 TEMP[3].x, IMM[0].yxyy, TEMP[1].xyzz
  7: MOV TEMP[2].y, TEMP[3].xxxx
  8: DP3 TEMP[1].x, IMM[0].yyxx, TEMP[1].xyzz
  9: MOV TEMP[2].z, TEMP[1].xxxx
 10: MOV TEMP[1].z, IMM[0].xxxx
 11: MOV TEMP[1].xy, CONST[1][1].xyxx
 12: MOV TEMP[3].z, IMM[0].yyyy
 13: MOV TEMP[3].xy, CONST[1][1].zwzz
 14: MAD TEMP[1].xy, TEMP[2].xyzz, TEMP[1].xyzz, TEMP[3].xyzz
 15: MOV OUT[1].xy, TEMP[1].xyxx
 16: MOV OUT[0], TEMP[0]
 17: END`},
	{"precision mediump float;\nprecision highp int;\n\nuniform mediump sampler2D tex;\nuniform mediump sampler2D cover;\n\nvarying vec2 vUV;\nvarying highp vec2 vCoverUV;\n\nvoid main()\n{\n    gl_FragData[0] = texture2D(tex, vUV);\n    float cover_1 = abs(texture2D(cover, vCoverUV).x);\n    gl_FragData[0] *= cover_1;\n}\n\n", "\nstruct Block\n{\n    vec4 transform;\n    vec4 uvCoverTransform;\n    vec4 uvTransform;\n    float z;\n};\n\nuniform Block _66;\n\nattribute vec2 pos;\nvarying vec2 vUV;\nattribute vec2 uv;\nvarying vec2 vCoverUV;\n\nvec4 toClipSpace(vec4 pos_1)\n{\n    return pos_1;\n}\n\nvec3[2] fboTextureTransform()\n{\n    vec3 t[2];\n    t[0] = vec3(1.0, 0.0, 0.0);\n    t[1] = vec3(0.0, 1.0, 0.0);\n    return t;\n}\n\nvec3 transform3x2(vec3 t[2], vec3 v)\n{\n    return vec3(dot(t[0], v), dot(t[1], v), dot(vec3(0.0, 0.0, 1.0), v));\n}\n\nvoid main()\n{\n    vec4 param = vec4((pos * _66.transform.xy) + _66.transform.zw, _66.z, 1.0);\n    gl_Position = toClipSpace(param);\n    vUV = (uv * _66.uvTransform.xy) + _66.uvTransform.zw;\n    vec3 fboTrans[2] = fboTextureTransform();\n    vec3 param_1[2] = fboTrans;\n    vec3 param_2 = vec3(uv, 1.0);\n    vec3 uv3 = transform3x2(param_1, param_2);\n    vCoverUV = ((uv3 * vec3(_66.uvCoverTransform.xy, 1.0)) + vec3(_66.uvCoverTransform.zw, 0.0)).xy;\n}\n\n"}: {`FRAG
DCL IN[0], GENERIC[9], PERSPECTIVE
DCL OUT[0], COLOR
DCL SAMP[0]
DCL SAMP[1]
DCL SVIEW[0], 2D, FLOAT
DCL SVIEW[1], 2D, FLOAT
DCL TEMP[0..1], LOCAL
  0: MOV TEMP[0].xy, IN[0].zwww
  1: TEX TEMP[0], TEMP[0], SAMP[0], 2D
  2: MOV TEMP[1].xy, IN[0].xyyy
  3: TEX TEMP[1].x, TEMP[1], SAMP[1], 2D
  4: MOV TEMP[1].x, |TEMP[1].xxxx|
  5: MUL TEMP[0], TEMP[0], TEMP[1].xxxx
  6: MOV OUT[0], TEMP[0]
  7: END`, `VERT
DCL IN[0]
DCL IN[1]
DCL OUT[0], POSITION
DCL OUT[1], GENERIC[9]
DCL CONST[1][0..3]
DCL TEMP[0..4], LOCAL
IMM[0] FLT32 {    1.0000,     0.0000,     0.0000,     0.0000}
IMM[1] UINT32 {0, 48, 32, 16}
  0: MOV TEMP[0].w, IMM[0].xxxx
  1: MAD TEMP[0].xy, IN[0].xyyy, CONST[1][0].xyyy, CONST[1][0].zwww
  2: MOV TEMP[0].z, CONST[1][3].xxxx
  3: MAD TEMP[1].xy, IN[1].xyyy, CONST[1][2].xyyy, CONST[1][2].zwww
  4: MOV TEMP[2].z, IMM[0].xxxx
  5: MOV TEMP[2].xy, IN[1].xyxx
  6: DP3 TEMP[3].x, IMM[0].xyyy, TEMP[2].xyzz
  7: DP3 TEMP[4].x, IMM[0].yxyy, TEMP[2].xyzz
  8: MOV TEMP[3].y, TEMP[4].xxxx
  9: DP3 TEMP[2].x, IMM[0].yyxx, TEMP[2].xyzz
 10: MOV TEMP[3].z, TEMP[2].xxxx
 11: MOV TEMP[2].z, IMM[0].xxxx
 12: MOV TEMP[2].xy, CONST[1][1].xyxx
 13: MOV TEMP[4].z, IMM[0].yyyy
 14: MOV TEMP[4].xy, CONST[1][1].zwzz
 15: MAD TEMP[2].xy, TEMP[3].xyzz, TEMP[2].xyzz, TEMP[4].xyzz
 16: MOV OUT[1].xy, TEMP[2].xyxx
 17: MOV OUT[0], TEMP[0]
 18: MOV OUT[1].zw, TEMP[1].xxxy
 19: END`},
	{"precision mediump float;\nprecision highp int;\n\nuniform mediump sampler2D cover;\n\nvarying highp vec2 vUV;\n\nvoid main()\n{\n    float cover_1 = abs(texture2D(cover, vUV).x);\n    gl_FragData[0].x = cover_1;\n}\n\n", "\nstruct Block\n{\n    vec4 uvTransform;\n    vec4 subUVTransform;\n};\n\nuniform Block _101;\n\nattribute vec2 pos;\nattribute vec2 uv;\nvarying vec2 vUV;\n\nvec3[2] fboTransform()\n{\n    vec3 t[2];\n    t[0] = vec3(1.0, 0.0, 0.0);\n    t[1] = vec3(0.0, -1.0, 0.0);\n    return t;\n}\n\nvec3 transform3x2(vec3 t[2], vec3 v)\n{\n    return vec3(dot(t[0], v), dot(t[1], v), dot(vec3(0.0, 0.0, 1.0), v));\n}\n\nvec3[2] fboTextureTransform()\n{\n    vec3 t[2];\n    t[0] = vec3(1.0, 0.0, 0.0);\n    t[1] = vec3(0.0, 1.0, 0.0);\n    return t;\n}\n\nvoid main()\n{\n    vec3 fboTrans[2] = fboTransform();\n    vec3 param[2] = fboTrans;\n    vec3 param_1 = vec3(pos, 1.0);\n    vec3 p = transform3x2(param, param_1);\n    gl_Position = vec4(p, 1.0);\n    vec3 fboTexTrans[2] = fboTextureTransform();\n    vec3 param_2[2] = fboTexTrans;\n    vec3 param_3 = vec3(uv, 1.0);\n    vec3 uv3 = transform3x2(param_2, param_3);\n    vUV = (uv3.xy * _101.subUVTransform.xy) + _101.subUVTransform.zw;\n    vec3 param_4[2] = fboTexTrans;\n    vec3 param_5 = vec3(vUV, 1.0);\n    vUV = transform3x2(param_4, param_5).xy;\n    vUV = (vUV * _101.uvTransform.xy) + _101.uvTransform.zw;\n}\n\n"}: {`FRAG
DCL IN[0].xy, GENERIC[9], PERSPECTIVE
DCL OUT[0], COLOR
DCL SAMP[0]
DCL SVIEW[0], 2D, FLOAT
DCL TEMP[0], LOCAL
  0: MOV TEMP[0].xy, IN[0].xyyy
  1: TEX TEMP[0].x, TEMP[0], SAMP[0], 2D
  2: MOV TEMP[0].x, |TEMP[0].xxxx|
  3: MOV OUT[0], TEMP[0]
  4: END`, `VERT
DCL IN[0]
DCL IN[1]
DCL OUT[0], POSITION
DCL OUT[1].xy, GENERIC[9]
DCL CONST[1][0..1]
DCL TEMP[0..3], LOCAL
IMM[0] FLT32 {    1.0000,     0.0000,    -1.0000,     0.0000}
IMM[1] UINT32 {0, 16, 0, 0}
  0: MOV TEMP[0].z, IMM[0].xxxx
  1: MOV TEMP[0].xy, IN[0].xyxx
  2: DP3 TEMP[1].x, IMM[0].xyyy, TEMP[0].xyzz
  3: DP3 TEMP[2].x, IMM[0].yzyy, TEMP[0].xyzz
  4: MOV TEMP[1].y, TEMP[2].xxxx
  5: DP3 TEMP[0].x, IMM[0].yyxx, TEMP[0].xyzz
  6: MOV TEMP[1].z, TEMP[0].xxxx
  7: MOV TEMP[0].w, IMM[0].xxxx
  8: MOV TEMP[0].xyz, TEMP[1].xyzx
  9: MOV TEMP[1].z, IMM[0].xxxx
 10: MOV TEMP[1].xy, IN[1].xyxx
 11: DP3 TEMP[2].x, IMM[0].xyyy, TEMP[1].xyzz
 12: DP3 TEMP[1].x, IMM[0].yxyy, TEMP[1].xyzz
 13: MOV TEMP[2].y, TEMP[1].xxxx
 14: MAD TEMP[1].xy, TEMP[2].xyyy, CONST[1][1].xyyy, CONST[1][1].zwww
 15: MOV TEMP[2].z, IMM[0].xxxx
 16: MOV TEMP[2].xy, TEMP[1].xyxx
 17: DP3 TEMP[3].x, IMM[0].xyyy, TEMP[2].xyzz
 18: DP3 TEMP[2].x, IMM[0].yxyy, TEMP[2].xyzz
 19: MOV TEMP[3].y, TEMP[2].xxxx
 20: MAD TEMP[1].xy, TEMP[3].xyyy, CONST[1][0].xyyy, CONST[1][0].zwww
 21: MOV OUT[0], TEMP[0]
 22: MOV OUT[1].xy, TEMP[1].xyxx
 23: END`},
	{"precision mediump float;\nprecision highp int;\n\nvarying vec2 vTo;\nvarying vec2 vFrom;\nvarying vec2 vCtrl;\n\nvoid main()\n{\n    float dx = vTo.x - vFrom.x;\n    bool increasing = vTo.x >= vFrom.x;\n    bvec2 _35 = bvec2(increasing);\n    vec2 left = vec2(_35.x ? vFrom.x : vTo.x, _35.y ? vFrom.y : vTo.y);\n    bvec2 _41 = bvec2(increasing);\n    vec2 right = vec2(_41.x ? vTo.x : vFrom.x, _41.y ? vTo.y : vFrom.y);\n    vec2 extent = clamp(vec2(vFrom.x, vTo.x), vec2(-0.5), vec2(0.5));\n    float midx = mix(extent.x, extent.y, 0.5);\n    float x0 = midx - left.x;\n    vec2 p1 = vCtrl - left;\n    vec2 v = right - vCtrl;\n    float t = x0 / (p1.x + sqrt((p1.x * p1.x) + ((v.x - p1.x) * x0)));\n    float y = mix(mix(left.y, vCtrl.y, t), mix(vCtrl.y, right.y, t), t);\n    vec2 d_half = mix(p1, v, vec2(t));\n    float dy = d_half.y / d_half.x;\n    float width = extent.y - extent.x;\n    dy = abs(dy * width);\n    vec4 sides = vec4((dy * 0.5) + y, (dy * (-0.5)) + y, (0.5 - y) / dy, ((-0.5) - y) / dy);\n    sides = clamp(sides + vec4(0.5), vec4(0.0), vec4(1.0));\n    float area = 0.5 * ((((sides.z - (sides.z * sides.y)) + 1.0) - sides.x) + (sides.x * sides.w));\n    area *= width;\n    if (width == 0.0)\n    {\n        area = 0.0;\n    }\n    gl_FragData[0].x = area;\n}\n\n", "\nstruct Block\n{\n    vec4 transform;\n    vec2 pathOffset;\n};\n\nuniform Block _16;\n\nattribute vec2 from;\nattribute vec2 ctrl;\nattribute vec2 to;\nattribute float maxy;\nattribute float corner;\nvarying vec2 vFrom;\nvarying vec2 vCtrl;\nvarying vec2 vTo;\n\nvoid main()\n{\n    vec2 from_1 = from + _16.pathOffset;\n    vec2 ctrl_1 = ctrl + _16.pathOffset;\n    vec2 to_1 = to + _16.pathOffset;\n    float maxy_1 = maxy + _16.pathOffset.y;\n    float c = corner;\n    vec2 pos;\n    if (c >= 0.375)\n    {\n        c -= 0.5;\n        pos.y = maxy_1 + 1.0;\n    }\n    else\n    {\n        pos.y = min(min(from_1.y, ctrl_1.y), to_1.y) - 1.0;\n    }\n    if (c >= 0.125)\n    {\n        pos.x = max(max(from_1.x, ctrl_1.x), to_1.x) + 1.0;\n    }\n    else\n    {\n        pos.x = min(min(from_1.x, ctrl_1.x), to_1.x) - 1.0;\n    }\n    vFrom = from_1 - pos;\n    vCtrl = ctrl_1 - pos;\n    vTo = to_1 - pos;\n    pos = (pos * _16.transform.xy) + _16.transform.zw;\n    gl_Position = vec4(pos, 1.0, 1.0);\n}\n\n"}: {`FRAG
DCL IN[0], GENERIC[9], PERSPECTIVE
DCL IN[1].xy, GENERIC[10], PERSPECTIVE
DCL OUT[0], COLOR
DCL TEMP[0..7], LOCAL
IMM[0] FLT32 {   -0.5000,     0.5000,     1.0000,     0.0000}
  0: FSGE TEMP[0].x, IN[1].xxxx, IN[0].zzzz
  1: UIF TEMP[0].xxxx
  2:   MOV TEMP[1].x, IN[0].zzzz
  3: ELSE
  4:   MOV TEMP[1].x, IN[1].xxxx
  5: ENDIF
  6: UIF TEMP[0].xxxx
  7:   MOV TEMP[2].x, IN[0].wwww
  8: ELSE
  9:   MOV TEMP[2].x, IN[1].yyyy
 10: ENDIF
 11: MOV TEMP[3].x, TEMP[1].xxxx
 12: MOV TEMP[3].y, TEMP[2].xxxx
 13: UIF TEMP[0].xxxx
 14:   MOV TEMP[4].x, IN[1].xxxx
 15: ELSE
 16:   MOV TEMP[4].x, IN[0].zzzz
 17: ENDIF
 18: UIF TEMP[0].xxxx
 19:   MOV TEMP[0].x, IN[1].yyyy
 20: ELSE
 21:   MOV TEMP[0].x, IN[0].wwww
 22: ENDIF
 23: MOV TEMP[4].x, TEMP[4].xxxx
 24: MOV TEMP[4].y, TEMP[0].xxxx
 25: MOV TEMP[5].x, IN[0].zzzz
 26: MOV TEMP[5].y, IN[1].xxxx
 27: MAX TEMP[5].xy, TEMP[5].xyyy, IMM[0].xxxx
 28: MIN TEMP[5].xy, TEMP[5].xyyy, IMM[0].yyyy
 29: LRP TEMP[6].x, IMM[0].yyyy, TEMP[5].yyyy, TEMP[5].xxxx
 30: ADD TEMP[1].x, TEMP[6].xxxx, -TEMP[1].xxxx
 31: ADD TEMP[3].xy, IN[0].xyyy, -TEMP[3].xyyy
 32: ADD TEMP[4].xy, TEMP[4].xyyy, -IN[0].xyyy
 33: ADD TEMP[6].x, TEMP[4].xxxx, -TEMP[3].xxxx
 34: MUL TEMP[7].x, TEMP[3].xxxx, TEMP[3].xxxx
 35: MAD TEMP[6].x, TEMP[6].xxxx, TEMP[1].xxxx, TEMP[7].xxxx
 36: RSQ TEMP[6].x, |TEMP[6].xxxx|
 37: RCP TEMP[6].x, TEMP[6].xxxx
 38: ADD TEMP[6].x, TEMP[3].xxxx, TEMP[6].xxxx
 39: RCP TEMP[6].x, TEMP[6].xxxx
 40: MUL TEMP[1].x, TEMP[1].xxxx, TEMP[6].xxxx
 41: LRP TEMP[2].x, TEMP[1].xxxx, IN[0].yyyy, TEMP[2].xxxx
 42: LRP TEMP[0].x, TEMP[1].xxxx, TEMP[0].xxxx, IN[0].yyyy
 43: LRP TEMP[0].x, TEMP[1].xxxx, TEMP[0].xxxx, TEMP[2].xxxx
 44: LRP TEMP[1].xy, TEMP[1].xxxx, TEMP[4].xyyy, TEMP[3].xyyy
 45: ADD TEMP[2].x, TEMP[5].yyyy, -TEMP[5].xxxx
 46: RCP TEMP[3].x, TEMP[1].xxxx
 47: MUL TEMP[1].x, TEMP[1].yyyy, TEMP[3].xxxx
 48: MUL TEMP[1].x, TEMP[1].xxxx, TEMP[2].xxxx
 49: MOV TEMP[1].x, |TEMP[1].xxxx|
 50: MAD TEMP[3].x, TEMP[1].xxxx, IMM[0].yyyy, TEMP[0].xxxx
 51: MAD TEMP[4].x, TEMP[1].xxxx, IMM[0].xxxx, TEMP[0].xxxx
 52: MOV TEMP[3].y, TEMP[4].xxxx
 53: ADD TEMP[4].x, IMM[0].yyyy, -TEMP[0].xxxx
 54: RCP TEMP[5].x, TEMP[1].xxxx
 55: MUL TEMP[4].x, TEMP[4].xxxx, TEMP[5].xxxx
 56: MOV TEMP[3].z, TEMP[4].xxxx
 57: ADD TEMP[0].x, IMM[0].xxxx, -TEMP[0].xxxx
 58: RCP TEMP[1].x, TEMP[1].xxxx
 59: MUL TEMP[0].x, TEMP[0].xxxx, TEMP[1].xxxx
 60: MOV TEMP[3].w, TEMP[0].xxxx
 61: ADD TEMP[0], TEMP[3], IMM[0].yyyy
 62: MOV_SAT TEMP[0], TEMP[0]
 63: MUL TEMP[1].x, TEMP[0].zzzz, TEMP[0].yyyy
 64: ADD TEMP[1].x, TEMP[0].zzzz, -TEMP[1].xxxx
 65: ADD TEMP[1].x, TEMP[1].xxxx, IMM[0].zzzz
 66: ADD TEMP[1].x, TEMP[1].xxxx, -TEMP[0].xxxx
 67: MAD TEMP[0].x, TEMP[0].xxxx, TEMP[0].wwww, TEMP[1].xxxx
 68: MUL TEMP[0].x, IMM[0].yyyy, TEMP[0].xxxx
 69: MUL TEMP[0].x, TEMP[0].xxxx, TEMP[2].xxxx
 70: FSEQ TEMP[1].x, TEMP[2].xxxx, IMM[0].wwww
 71: UIF TEMP[1].xxxx
 72:   MOV TEMP[0].x, IMM[0].wwww
 73: ENDIF
 74: MOV TEMP[0].x, TEMP[0].xxxx
 75: MOV OUT[0], TEMP[0]
 76: END`, `VERT
DCL IN[0]
DCL IN[1]
DCL IN[2]
DCL IN[3]
DCL IN[4]
DCL OUT[0], POSITION
DCL OUT[1], GENERIC[9]
DCL OUT[2].xy, GENERIC[10]
DCL CONST[1][0..1]
DCL TEMP[0..5], LOCAL
IMM[0] UINT32 {0, 16, 0, 0}
IMM[1] FLT32 {    0.3750,    -0.5000,     1.0000,    -1.0000}
IMM[2] FLT32 {    0.1250,     0.0000,     0.0000,     0.0000}
  0: ADD TEMP[0].xy, IN[2].xyyy, CONST[1][1].xyyy
  1: ADD TEMP[1].xy, IN[3].xyyy, CONST[1][1].xyyy
  2: ADD TEMP[2].xy, IN[4].xyyy, CONST[1][1].xyyy
  3: ADD TEMP[3].x, IN[1].xxxx, CONST[1][1].yyyy
  4: MOV TEMP[4].x, IN[0].xxxx
  5: FSGE TEMP[5].x, IN[0].xxxx, IMM[1].xxxx
  6: UIF TEMP[5].xxxx
  7:   ADD TEMP[4].x, IN[0].xxxx, IMM[1].yyyy
  8:   ADD TEMP[3].x, TEMP[3].xxxx, IMM[1].zzzz
  9:   MOV TEMP[3].y, TEMP[3].xxxx
 10: ELSE
 11:   MIN TEMP[5].x, TEMP[0].yyyy, TEMP[1].yyyy
 12:   MIN TEMP[5].x, TEMP[5].xxxx, TEMP[2].yyyy
 13:   ADD TEMP[5].x, TEMP[5].xxxx, IMM[1].wwww
 14:   MOV TEMP[3].y, TEMP[5].xxxx
 15: ENDIF
 16: FSGE TEMP[4].x, TEMP[4].xxxx, IMM[2].xxxx
 17: UIF TEMP[4].xxxx
 18:   MAX TEMP[4].x, TEMP[0].xxxx, TEMP[1].xxxx
 19:   MAX TEMP[4].x, TEMP[4].xxxx, TEMP[2].xxxx
 20:   ADD TEMP[3].x, TEMP[4].xxxx, IMM[1].zzzz
 21: ELSE
 22:   MIN TEMP[4].x, TEMP[0].xxxx, TEMP[1].xxxx
 23:   MIN TEMP[4].x, TEMP[4].xxxx, TEMP[2].xxxx
 24:   ADD TEMP[3].x, TEMP[4].xxxx, IMM[1].wwww
 25: ENDIF
 26: ADD TEMP[0].xy, TEMP[0].xyyy, -TEMP[3].xyyy
 27: ADD TEMP[1].xy, TEMP[1].xyyy, -TEMP[3].xyyy
 28: ADD TEMP[2].xy, TEMP[2].xyyy, -TEMP[3].xyyy
 29: MAD TEMP[3].xy, TEMP[3].xyyy, CONST[1][0].xyyy, CONST[1][0].zwww
 30: MOV TEMP[4].zw, IMM[1].zzzz
 31: MOV TEMP[4].xy, TEMP[3].xyxx
 32: MOV OUT[2].xy, TEMP[2].xyxx
 33: MOV OUT[1].xy, TEMP[1].xyxx
 34: MOV OUT[1].zw, TEMP[0].xxxy
 35: MOV OUT[0], TEMP[4]
 36: END`},
}

A  => cmd/demo/test_shaders.go +129 -0
@@ 1,129 @@
// Code generated by build.go. DO NOT EDIT.

package main

import "gioui.org/gpu/backend"

var (
	shader_input_vert = backend.ShaderSources{
		Inputs:    []backend.InputLocation{{Name: "position", Location: 0, Semantic: "POSITION", SemanticIndex: 0, Type: 0x0, Size: 4}},
		GLSL100ES: "\nattribute vec4 position;\n\nvoid main()\n{\n    gl_Position = position;\n}\n\n",
		GLSL300ES: "#version 300 es\n\nlayout(location = 0) in vec4 position;\n\nvoid main()\n{\n    gl_Position = position;\n}\n\n",
		GLSL130:   "#version 130\n\nin vec4 position;\n\nvoid main()\n{\n    gl_Position = position;\n}\n\n",
		GLSL150:   "#version 150\n\nin vec4 position;\n\nvoid main()\n{\n    gl_Position = position;\n}\n\n",
		/*
		   static float4 gl_Position;
		   static float4 position;

		   struct SPIRV_Cross_Input
		   {
		       float4 position : POSITION;
		   };

		   struct SPIRV_Cross_Output
		   {
		       float4 gl_Position : SV_Position;
		   };

		   void vert_main()
		   {
		       gl_Position = position;
		   }

		   SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
		   {
		       position = stage_input.position;
		       vert_main();
		       SPIRV_Cross_Output stage_output;
		       stage_output.gl_Position = gl_Position;
		       return stage_output;
		   }

		*/
		HLSL: []byte{0x44, 0x58, 0x42, 0x43, 0x35, 0xe9, 0xae, 0x29, 0x96, 0x7e, 0x7c, 0xe6, 0x40, 0xb0, 0x4e, 0x29, 0xd9, 0x98, 0x51, 0x7c, 0x1, 0x0, 0x0, 0x0, 0x10, 0x2, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x9c, 0x0, 0x0, 0x0, 0xe0, 0x0, 0x0, 0x0, 0x5c, 0x1, 0x0, 0x0, 0xa8, 0x1, 0x0, 0x0, 0xdc, 0x1, 0x0, 0x0, 0x41, 0x6f, 0x6e, 0x39, 0x5c, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfe, 0xff, 0x34, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x1, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfe, 0xff, 0x1f, 0x0, 0x0, 0x2, 0x5, 0x0, 0x0, 0x80, 0x0, 0x0, 0xf, 0x90, 0x4, 0x0, 0x0, 0x4, 0x0, 0x0, 0x3, 0xc0, 0x0, 0x0, 0xff, 0x90, 0x0, 0x0, 0xe4, 0xa0, 0x0, 0x0, 0xe4, 0x90, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0xc, 0xc0, 0x0, 0x0, 0xe4, 0x90, 0xff, 0xff, 0x0, 0x0, 0x53, 0x48, 0x44, 0x52, 0x3c, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xf, 0x0, 0x0, 0x0, 0x5f, 0x0, 0x0, 0x3, 0xf2, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x67, 0x0, 0x0, 0x4, 0xf2, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0, 0x5, 0xf2, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46, 0x1e, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3e, 0x0, 0x0, 0x1, 0x53, 0x54, 0x41, 0x54, 0x74, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x52, 0x44, 0x45, 0x46, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x4, 0xfe, 0xff, 0x0, 0x1, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, 0x4c, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2e, 0x31, 0x0, 0x49, 0x53, 0x47, 0x4e, 0x2c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf, 0x0, 0x0, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x0, 0xab, 0xab, 0xab, 0x4f, 0x53, 0x47, 0x4e, 0x2c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x53, 0x56, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x0},
	}
	shader_simple_frag = backend.ShaderSources{
		GLSL100ES: "precision mediump float;\nprecision highp int;\n\nvoid main()\n{\n    gl_FragData[0] = vec4(0.25, 0.550000011920928955078125, 0.75, 1.0);\n}\n\n",
		GLSL300ES: "#version 300 es\nprecision mediump float;\nprecision highp int;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main()\n{\n    fragColor = vec4(0.25, 0.550000011920928955078125, 0.75, 1.0);\n}\n\n",
		GLSL130:   "#version 130\n\nout vec4 fragColor;\n\nvoid main()\n{\n    fragColor = vec4(0.25, 0.550000011920928955078125, 0.75, 1.0);\n}\n\n",
		GLSL150:   "#version 150\n\nout vec4 fragColor;\n\nvoid main()\n{\n    fragColor = vec4(0.25, 0.550000011920928955078125, 0.75, 1.0);\n}\n\n",
		/*
		   static float4 fragColor;

		   struct SPIRV_Cross_Output
		   {
		       float4 fragColor : SV_Target0;
		   };

		   void frag_main()
		   {
		       fragColor = float4(0.25f, 0.550000011920928955078125f, 0.75f, 1.0f);
		   }

		   SPIRV_Cross_Output main()
		   {
		       frag_main();
		       SPIRV_Cross_Output stage_output;
		       stage_output.fragColor = fragColor;
		       return stage_output;
		   }

		*/
		HLSL: []byte{0x44, 0x58, 0x42, 0x43, 0xf5, 0x46, 0xde, 0x66, 0x24, 0x29, 0xa8, 0xbb, 0x56, 0xea, 0x73, 0xb5, 0x6b, 0x73, 0x12, 0x72, 0x1, 0x0, 0x0, 0x0, 0xdc, 0x1, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0xd0, 0x0, 0x0, 0x0, 0x4c, 0x1, 0x0, 0x0, 0x98, 0x1, 0x0, 0x0, 0xa8, 0x1, 0x0, 0x0, 0x41, 0x6f, 0x6e, 0x39, 0x50, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, 0x2c, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x2, 0xff, 0xff, 0x51, 0x0, 0x0, 0x5, 0x0, 0x0, 0xf, 0xa0, 0x0, 0x0, 0x80, 0x3e, 0xcd, 0xcc, 0xc, 0x3f, 0x0, 0x0, 0x40, 0x3f, 0x0, 0x0, 0x80, 0x3f, 0x1, 0x0, 0x0, 0x2, 0x0, 0x8, 0xf, 0x80, 0x0, 0x0, 0xe4, 0xa0, 0xff, 0xff, 0x0, 0x0, 0x53, 0x48, 0x44, 0x52, 0x38, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0x65, 0x0, 0x0, 0x3, 0xf2, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0, 0x8, 0xf2, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x80, 0x3e, 0xcd, 0xcc, 0xc, 0x3f, 0x0, 0x0, 0x40, 0x3f, 0x0, 0x0, 0x80, 0x3f, 0x3e, 0x0, 0x0, 0x1, 0x53, 0x54, 0x41, 0x54, 0x74, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x52, 0x44, 0x45, 0x46, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x4, 0xff, 0xff, 0x0, 0x1, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, 0x4c, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2e, 0x31, 0x0, 0x49, 0x53, 0x47, 0x4e, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x4f, 0x53, 0x47, 0x4e, 0x2c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x0, 0xab, 0xab},
	}
	shader_simple_vert = backend.ShaderSources{
		GLSL100ES: "\nvoid main()\n{\n    float x;\n    float y;\n    if (gl_VertexID == 0)\n    {\n        x = 0.0;\n        y = 0.5;\n    }\n    else\n    {\n        if (gl_VertexID == 1)\n        {\n            x = 0.5;\n            y = -0.5;\n        }\n        else\n        {\n            x = -0.5;\n            y = -0.5;\n        }\n    }\n    gl_Position = vec4(x, y, 0.5, 1.0);\n}\n\n",
		GLSL300ES: "#version 300 es\n\nvoid main()\n{\n    float x;\n    float y;\n    if (gl_VertexID == 0)\n    {\n        x = 0.0;\n        y = 0.5;\n    }\n    else\n    {\n        if (gl_VertexID == 1)\n        {\n            x = 0.5;\n            y = -0.5;\n        }\n        else\n        {\n            x = -0.5;\n            y = -0.5;\n        }\n    }\n    gl_Position = vec4(x, y, 0.5, 1.0);\n}\n\n",
		GLSL130:   "#version 130\n\nvoid main()\n{\n    float x;\n    float y;\n    if (gl_VertexID == 0)\n    {\n        x = 0.0;\n        y = 0.5;\n    }\n    else\n    {\n        if (gl_VertexID == 1)\n        {\n            x = 0.5;\n            y = -0.5;\n        }\n        else\n        {\n            x = -0.5;\n            y = -0.5;\n        }\n    }\n    gl_Position = vec4(x, y, 0.5, 1.0);\n}\n\n",
		GLSL150:   "#version 150\n\nvoid main()\n{\n    float x;\n    float y;\n    if (gl_VertexID == 0)\n    {\n        x = 0.0;\n        y = 0.5;\n    }\n    else\n    {\n        if (gl_VertexID == 1)\n        {\n            x = 0.5;\n            y = -0.5;\n        }\n        else\n        {\n            x = -0.5;\n            y = -0.5;\n        }\n    }\n    gl_Position = vec4(x, y, 0.5, 1.0);\n}\n\n",
		/*
		   static float4 gl_Position;
		   static int gl_VertexIndex;
		   struct SPIRV_Cross_Input
		   {
		       uint gl_VertexIndex : SV_VertexID;
		   };

		   struct SPIRV_Cross_Output
		   {
		       float4 gl_Position : SV_Position;
		   };

		   void vert_main()
		   {
		       float x;
		       float y;
		       if (gl_VertexIndex == 0)
		       {
		           x = 0.0f;
		           y = 0.5f;
		       }
		       else
		       {
		           if (gl_VertexIndex == 1)
		           {
		               x = 0.5f;
		               y = -0.5f;
		           }
		           else
		           {
		               x = -0.5f;
		               y = -0.5f;
		           }
		       }
		       gl_Position = float4(x, y, 0.5f, 1.0f);
		   }

		   SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
		   {
		       gl_VertexIndex = int(stage_input.gl_VertexIndex);
		       vert_main();
		       SPIRV_Cross_Output stage_output;
		       stage_output.gl_Position = gl_Position;
		       return stage_output;
		   }

		*/
		HLSL: []byte{0x44, 0x58, 0x42, 0x43, 0xc8, 0x20, 0x5c, 0x22, 0xec, 0xe9, 0xb2, 0x29, 0x40, 0xdf, 0x7c, 0x5a, 0x28, 0xea, 0xc, 0xb8, 0x1, 0x0, 0x0, 0x0, 0x48, 0x2, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x34, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0xe8, 0x0, 0x0, 0x0, 0xcc, 0x1, 0x0, 0x0, 0x52, 0x44, 0x45, 0x46, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x4, 0xfe, 0xff, 0x0, 0x1, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, 0x4c, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2e, 0x31, 0x0, 0x49, 0x53, 0x47, 0x4e, 0x2c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x53, 0x56, 0x5f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x44, 0x0, 0x4f, 0x53, 0x47, 0x4e, 0x2c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x53, 0x56, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x0, 0x53, 0x48, 0x44, 0x52, 0xdc, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x37, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x4, 0x12, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x67, 0x0, 0x0, 0x4, 0xf2, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x2, 0x1, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x7, 0x12, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x37, 0x0, 0x0, 0xf, 0x32, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x0, 0xbf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, 0x0, 0x0, 0x0, 0xbf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x37, 0x0, 0x0, 0xc, 0x32, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0, 0x8, 0xc2, 0x20, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x80, 0x3f, 0x3e, 0x0, 0x0, 0x1, 0x53, 0x54, 0x41, 0x54, 0x74, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
	}
)

A  => go.mod +9 -0
@@ 1,9 @@
module eliasnaur.com/unik

go 1.14

require (
	gioui.org v0.0.0-20200403084947-efce78d414f3
	golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3
	golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
)

A  => go.sum +27 -0
@@ 1,27 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20200403084947-efce78d414f3 h1:H0RIP0Zv5XJ+w2G4aOCpv+iDWZEctswYiRDuUU8Dq2g=
gioui.org v0.0.0-20200403084947-efce78d414f3/go.mod h1:AHI9rFr6AEEHCb8EPVtb/p5M+NMJRKH58IOp8O3Je04=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

A  => kernel/atomic_amd64.go +22 -0
@@ 1,22 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

//go:noescape
func OrUint8(addr *byte, val byte)

//go:noescape
func StoreUint8(addr *byte, val byte)

//go:noescape
func StoreUint16(addr *uint16, val uint16)

//go:nosplit
func LoadUint16(addr *uint16) uint16 {
	return *addr
}

//go:nosplit
func LoadUint8(addr *byte) byte {
	return *addr
}

A  => kernel/cmos_amd64.go +179 -0
@@ 1,179 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

// Functions for reading the real time clock of the CMOS.

// readCMOSTime reads the CMOS clock and converts it to UNIX time in
// seconds.
//go:nosplit
func readCMOSTime() int64 {
	waitForCMOS()
	t := readCMOSTime0()
	// The CMOS may be updating its time during our reading.
	// Read the time until it is stable.
	for {
		waitForCMOS()
		t2 := readCMOSTime0()
		if t2 == t {
			break
		}
		t = t2
	}
	return t
}

//go:nosplit
func readCMOSTime0() int64 {
	sec, min, hour := readCMOSReg(0x00), readCMOSReg(0x02), readCMOSReg(0x04)
	day, month, year, century := readCMOSReg(0x07), readCMOSReg(0x08), readCMOSReg(0x09), readCMOSReg(0x32)
	statusB := readCMOSReg(0x0b)
	pm := false
	// Check for 12-hour format.
	if statusB&1<<1 != 0 {
		// Read and reset PM bit.
		pm = hour&1<<7 != 0
		hour = hour & 0x7f
	}
	// Check for decimal format.
	if statusB&1<<2 == 0 {
		sec, min, hour = decToBin(sec), decToBin(min), decToBin(hour)
		day, month, year, century = decToBin(day), decToBin(month), decToBin(year), decToBin(century)
	}
	if pm {
		hour = (hour + 12) % 24
	}
	return cmosToUnix(int(century)*100+int(year), int(month)-1, int(day)-1, int(hour), int(min), int(sec))
}

// decToBin converts a decimal value to binary.
//go:nosplit
func decToBin(v uint8) uint8 {
	return (v & 0x0F) + ((v / 16) * 10)
}

// cmosToUnix is similar to time.Date(...).Unix(), except that we can't use
// that from the kernel. Note that the month and day are zero-based.
//go:nosplit
func cmosToUnix(year, month, day, hour, min, sec int) int64 {
	const (
		secondsPerMinute = 60
		secondsPerHour   = 60 * secondsPerMinute
		secondsPerDay    = 24 * secondsPerHour
		absoluteZeroYear = -292277022399
		internalYear     = 1

		absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
		unixToInternal     int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
		internalToUnix     int64 = -unixToInternal
		daysPer400Years          = 365*400 + 97
		daysPer100Years          = 365*100 + 24
		daysPer4Years            = 365*4 + 1
	)

	var daysBefore = [...]int32{
		0,
		31,
		31 + 28,
		31 + 28 + 31,
		31 + 28 + 31 + 30,
		31 + 28 + 31 + 30 + 31,
		31 + 28 + 31 + 30 + 31 + 30,
		31 + 28 + 31 + 30 + 31 + 30 + 31,
		31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
		31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
		31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
		31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
		31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
	}
	// Normalize.
	year, month = norm(year, month, 11)
	min, sec = norm(min, sec, 60)
	hour, min = norm(hour, min, 60)
	day, hour = norm(day, hour, 24)

	y := uint64(int64(year) - absoluteZeroYear)

	// Compute days since the absolute epoch.

	// Add in days from 400-year cycles.
	n := y / 400
	y -= 400 * n
	d := daysPer400Years * n

	// Add in 100-year cycles.
	n = y / 100
	y -= 100 * n
	d += daysPer100Years * n

	// Add in 4-year cycles.
	n = y / 4
	y -= 4 * n
	d += daysPer4Years * n

	// Add in non-leap years.
	n = y
	d += 365 * n

	// Add in days before this month.
	d += uint64(daysBefore[month])
	if isLeap(year) && month >= 2 /* March */ {
		d++ // February 29
	}

	// Add in days before today.
	d += uint64(day)

	// Add in time elapsed today.
	abs := d * secondsPerDay
	abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec)

	unix := int64(abs) + (absoluteToInternal + internalToUnix)

	return unix
}

//go:nosplit
func isLeap(year int) bool {
	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}

// norm returns nhi, nlo such that
//	hi * base + lo == nhi * base + nlo
//	0 <= nlo < base
//go:nosplit
func norm(hi, lo, base int) (nhi, nlo int) {
	if lo < 0 {
		n := (-lo-1)/base + 1
		hi -= n
		lo += n * base
	}
	if lo >= base {
		n := lo / base
		hi += n
		lo -= n * base
	}
	return hi, lo
}

// waitForCMOS waits for the CMOS busy flag to clear. The
// flag is in bit 7 of the status A register.
//go:nosplit
func waitForCMOS() {
	for {
		statusA := readCMOSReg(0x0a)
		if statusA&1<<7 == 0 {
			return
		}
	}
}

//go:nosplit
func readCMOSReg(reg uint8) uint8 {
	const (
		cmosAddr = 0x70
		cmosData = 0x71
	)
	outb(cmosAddr, reg)
	return inb(cmosData)
}

A  => kernel/debug.go +124 -0
@@ 1,124 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"fmt"
	"sort"
)

type pageTableRange struct {
	vaddr virtualAddress
	paddr physicalAddress
	size  int
}

func Verify() {
	entries := dumpPageTable(globalPT)
	verifyPageTable(entries)
}

func verifyPageTable(entries []pageTableRange) {
	type addrRange struct {
		start uint64
		end   uint64
		r     pageTableRange
	}
	var pranges []addrRange
	for _, e := range entries {
		if e.vaddr >= physicalMapOffset {
			// Ignore the identity mapped memory which overlaps
			// existing mappings per design.
			continue
		}
		pranges = append(pranges, addrRange{
			start: uint64(e.paddr),
			end:   uint64(e.paddr) + uint64(e.size),
			r:     e,
		})
	}
	overlap := false
	sort.Slice(pranges, func(i, j int) bool {
		r1, r2 := pranges[i], pranges[j]
		if r1.start < r2.start {
			return true
		}
		if r1.start == r2.start && r1.end < r2.end {
			return true
		}
		if r1.start == r2.start && r1.end == r2.end {
			overlap = true
			fmt.Printf("overlapping range: %#x %#x\n", r1.r, r2.r)
		}
		return false
	})
	for i := 0; i < len(pranges)-1; i++ {
		r1 := pranges[i]
		r2 := pranges[i+1]
		if r1.end > r2.start {
			overlap = true
			fmt.Printf("overlapping range: %#x %#x\n", r1.r, r2.r)
		}
	}
	if overlap {
		/*for _, e := range pranges {
			fmt.Printf("range: vaddr: %#x paddr: %#x size: %#x\n", e.r.vaddr, e.r.paddr, e.r.size)
		}*/
		panic("overlapping ranges")
	}
}

func dumpPageTable(pml4 *pageTable) []pageTableRange {
	var entries []pageTableRange
	for i, pml4e := range pml4 {
		if !pml4e.present() {
			continue
		}
		vaddr := virtualAddress(i * pageSizeRoot)
		// Sign extend.
		if vaddr&(maxVirtAddress>>1) != 0 {
			vaddr |= ^(maxVirtAddress - 1)
		}
		pdpt := pml4e.getPageTable()
		for i, pdpte := range pdpt {
			if !pdpte.present() {
				continue
			}
			vaddr := vaddr + virtualAddress(i)*pageSize1GB
			if pageFlags(pdpte)&pageSizeFlag != 0 {
				// 1GB page.
				paddr := physicalAddress(pdpte) & (_MAXPHYADDR - 1) &^ (pageSize1GB - 1)
				entries = append(entries, pageTableRange{vaddr, paddr, pageSize1GB})
			} else {
				pd := pdpte.getPageTable()
				for i, pde := range pd {
					if !pde.present() {
						continue
					}
					vaddr := vaddr + virtualAddress(i)*pageSize2MB
					if pageFlags(pde)&pageSizeFlag != 0 {
						// 2MB page.
						paddr := physicalAddress(pde) & (_MAXPHYADDR - 1) &^ (pageSize2MB - 1)
						entries = append(entries, pageTableRange{vaddr, paddr, pageSize2MB})
					} else {
						pt := pde.getPageTable()
						for i, e := range pt {
							if !e.present() {
								continue
							}
							vaddr := vaddr + virtualAddress(i)*pageSize
							// 4kb page.
							paddr := physicalAddress(e) & (_MAXPHYADDR - 1) &^ (pageSize - 1)
							entries = append(entries, pageTableRange{vaddr, paddr, pageSize})
						}
					}
				}
			}
		}
	}
	return entries
}

func dumpPageEntry(vaddr virtualAddress, paddr physicalAddress, size int) {
	fmt.Printf("mapping vaddr: %#x paddr: %#x size %#x\n", vaddr, paddr, size)
}

A  => kernel/interrupt_amd64.go +177 -0
@@ 1,177 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"sync/atomic"
	"unsafe"
)

const (
	_IA32_APIC_BASE = 0x1b
)

const (
	intDivideError            intVector = 0x0
	intGeneralProtectionFault intVector = 0xd
	intPageFault              intVector = 0xe
	intSSE                    intVector = 0x13

	apic
)

const (
	firstAvailableInterrupt intVector = 0x20 + iota
	intAPICError
	intTimer
	intFirstUser

	intLastUser           = intFirstUser + 10
	intSpurious intVector = 0xff
)

var pendingInterrupts [intLastUser - intFirstUser]bool

var (
	apicBase virtualAddress
	// Interrupt handlers write 0 to apicEOI to
	// signal end of interrupt handling.
	apicEOI *uint32
)

//go:nosplit
func initAPIC() error {
	_, _, _, edx := cpuid(0x1, 0)
	if edx&(1<<9) == 0 {
		return kernError("initAPIC: no APIC available")
	}
	maskPIC()
	apicBaseMSR := rdmsr(_IA32_APIC_BASE)
	if apicBaseMSR&(1<<8) == 0 {
		return kernError("initAPIC: not running on the boot CPU")
	}
	apicBase = virtualAddress(apicBaseMSR &^ 0xfff)
	// Enable APIC.
	wrmsr(_IA32_APIC_BASE, apicBaseMSR|1<<11)

	apicEOI = (*uint32)(unsafe.Pointer(apicBase + 0xb0))

	// Map the APIC page.
	flags := pageFlagWritable | pageFlagNX | pageFlagNoCache
	globalMap.mustAddRange(apicBase, apicBase+pageSize, flags)
	if err := mmapAligned(&globalMem, globalPT, apicBase, apicBase+pageSize, physicalAddress(apicBase), flags); err != nil {
		return err
	}

	globalIDT.install(intDivideError, ring0, istGeneric, divFault)
	globalIDT.install(intGeneralProtectionFault, ring0, istGeneric, generalProtectionFaultTrampoline)
	globalIDT.install(intSSE, ring0, istGeneric, sseException)
	globalIDT.install(intPageFault, ring0, istPageFault, pageFaultTrampoline)

	globalIDT.install(intAPICError, ring0, istGeneric, unknownInterruptTrampoline)
	globalIDT.install(intSpurious, ring0, istGeneric, unknownInterruptTrampoline)
	installUserHandlers()

	reloadIDT()
	// Mask LINT0-1.
	const apicIntMasked = 1 << 17
	apicWrite(0x350 /* LVT LINT0*/, apicIntMasked)
	apicWrite(0x360 /* LVT LINT1*/, apicIntMasked)
	// Setup error interrupt.
	apicWrite(0x370 /* LVT Error*/, uint32(intAPICError))
	// Setup spurious interrupt handler and enable interrupts.
	apicWrite(0x0f0, 0x100|uint32(intSpurious))

	return nil
}

//go:nosplit
func divFault() {
	fatal("division by 0")
}

//go:nosplit
func gpFault(addr uint64) {
	outputString("fault address: ")
	outputUint64(addr)
	outputString("\n")
	fatal("general protection fault")
}

//go:nosplit
func sseException() {
	fatal("SSE exception")
}

//go:nosplit
func installUserHandlers() {
	vector := intFirstUser
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline0)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline1)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline2)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline3)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline4)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline5)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline6)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline7)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline8)
	vector++
	globalIDT.install(vector, ring0, istGeneric, userInterruptTrampoline9)
	vector++
	if vector != intLastUser {
		fatal("not enough users interrupt handlers declared")
	}
}

//go:nosplit
func apicWrite(reg int, val uint32) {
	atomic.StoreUint32((*uint32)(unsafe.Pointer(apicBase+virtualAddress(reg))), val)
}

//go:nosplit
func apicRead(reg int) uint32 {
	return atomic.LoadUint32((*uint32)(unsafe.Pointer(apicBase + virtualAddress(reg))))
}

//go:nosplit
func maskPIC() {
	const (
		PIC1_DATA = 0x21
		PIC2_DATA = 0xa1
	)
	outb(PIC1_DATA, 0xff)
	outb(PIC2_DATA, 0xff)
}

//go:nosplit
func userInterrupt(vector uint64) {
	pendingInterrupts[vector] = true
}

//go:nosplit
func unknownInterrupt() {
	fatal("unexpected interrupt")
}

func unknownInterruptTrampoline()

func userInterruptTrampoline0()
func userInterruptTrampoline1()
func userInterruptTrampoline2()
func userInterruptTrampoline3()
func userInterruptTrampoline4()
func userInterruptTrampoline5()
func userInterruptTrampoline6()
func userInterruptTrampoline7()
func userInterruptTrampoline8()
func userInterruptTrampoline9()

func generalProtectionFaultTrampoline()

A  => kernel/kernel.go +5 -0
@@ 1,5 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

func rt0()

A  => kernel/kernel_amd64.go +237 -0
@@ 1,237 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"encoding/binary"
	"unsafe"
)

// kernError is an error type usable in kernel code.
type kernError string

const (
	_MSR_IA32_EFER = 0xc0000080
	_MSR_FS_BASE   = 0xc0000100

	_EFER_SCE = 1 << 0  // Enable SYSCALL.
	_EFER_NXE = 1 << 11 // Enable no-execute page bit.

	_XCR0_FPU = 1 << 0
	_XCR0_SSE = 1 << 1
	_XCR0_AVX = 1 << 2

	_CR4_DE         = 1 << 3
	_CR4_PSE        = 1 << 4
	_CR4_PAE        = 1 << 5
	_CR4_FXSTOR     = 1 << 9
	_CR4_OSXMMEXCPT = 1 << 10
	_CR4_FSGSBASE   = 1 << 16
	_CR4_OSXSAVE    = 1 << 18
)

type stack [10 * pageSize]byte

var (
	// Kernel stack.
	kstack stack

	fpuContextSize uint64
	kstackTop      uint64
)

//go:nosplit
func kernelStackTop() uint64 {
	return uint64(kstack.top())
}

//go:nosplit
func runKernel(mmapSize, descSize, kernelImageSize uint64, mmapAddr, kernelImage *byte) {
	mmap := (*(*[1 << 30]byte)(unsafe.Pointer(mmapAddr)))[:mmapSize:mmapSize]
	img := (*(*[1 << 30]byte)(unsafe.Pointer(kernelImage)))[:kernelImageSize:kernelImageSize]
	if err := initKernel(descSize, mmap, img); err != nil {
		fatalError(err)
	}
	if err := runGo(); err != nil {
		fatalError(err)
	}
	fatal("runKernel: runGo returned")
}

//go:nosplit
func initKernel(descSize uint64, mmap, kernelImage []byte) error {
	kstackTop = kernelStackTop()
	efiMap := efiMemoryMap{mmap: mmap, stride: int(descSize)}
	setCR4Reg(_CR4_PAE | _CR4_PSE | _CR4_DE | _CR4_FXSTOR | _CR4_OSXMMEXCPT)
	loadGDT()
	kernelThread.self = &kernelThread
	thread0.self = &thread0
	thread0.makeCurrent()
	if err := initMemory(efiMap, kernelImage); err != nil {
		return err
	}
	if err := initAPIC(); err != nil {
		return err
	}
	initSYSCALL()
	if err := initVDSO(); err != nil {
		return err
	}
	if err := initThreads(); err != nil {
		return err
	}
	if err := initClock(); err != nil {
		return err
	}
	return nil
}

//go:nosplit
func runGo() error {
	// Allocate initial stack.
	ssize := uint64(unsafe.Sizeof(stack{}))
	addr, err := globalMap.mmap(0, ssize, pageFlagNX|pageFlagWritable|pageFlagUserAccess)
	if err != nil {
		return err
	}
	stack := (*stack)(unsafe.Pointer(addr))

	// Allocate initial thread.
	t, err := globalThreads.newThread()
	if err != nil {
		return err
	}
	t.sp = uint64(stack.top())
	t.makeCurrent()

	// Set up sane initial state, in particular the MXCSR flags.
	saveThread()
	// Prepare program environment on stack.
	const envSize = 256
	setupEnv(stack[len(stack)-envSize:])
	t.sp -= envSize
	t.flags = _FLAG_RESERVED | _FLAG_IF
	// Jump to Go runtime start.
	t.ip = uint64(funcPC(jumpToGo))
	resumeThread()
	return nil
}

// setupEnv sets up the argv, auxv and env on the stack, mimicing
// the Linux kernel.
//go:nosplit
func setupEnv(stack []byte) {
	args := stack
	bo := binary.LittleEndian
	bo.PutUint64(args, 1) // 1 argument, the process name.
	args = args[8:]
	// First argument, address of binary name.
	binAddr := args[:8]
	args = args[8:]
	bo.PutUint64(args, 0) // NULL separator.
	args = args[8:]
	bo.PutUint64(args, 0) // No envp.
	args = args[8:]
	// Build auxillary vector.
	// Page size.
	bo.PutUint64(args, _AT_PAGESZ)
	args = args[8:]
	bo.PutUint64(args, pageSize)
	args = args[8:]
	// End of auxv.
	bo.PutUint64(args, _AT_NULL)
	args = args[8:]
	bo.PutUint64(args, 0)
	// Binary name.
	bo.PutUint64(binAddr, uint64(uintptr(unsafe.Pointer(&args[0]))))
	n := copy(args, []byte("kernel\x00"))
	args = args[n:]
}

//go:nosplit
func funcPC(f func()) uintptr {
	return **(**uintptr)(unsafe.Pointer(&f))
}

//go:nosplit
func wrmsr(register uint32, value uint64) {
	wrmsr0(register, uint32(value), uint32(value>>32))
}

//go:nosplit
func rdmsr(register uint32) uint64 {
	lo, hi := rdmsr0(register)
	return uint64(hi)<<32 | uint64(lo)
}

//go:nosplit
func fatalError(err error) {
	// The only error type supported is kernError,
	// but we can't call its Error method directly,
	// because the compiler wrapper is not nosplit.
	switch err := err.(type) {
	case kernError:
		fatal(err.Error())
	default:
		fatal("unsupported error")
	}
}

//go:nosplit
func fatal(msg string) {
	outputString("fatal error: ")
	outputString(msg)
	outputString("\n")
	halt()
}

// cpuidMaxExt returns the highest supported CPUID extended
// function number.
//go:nosplit
func cpuidMaxExt() uint32 {
	eax, _, _, _ := cpuid(0x80000000, 0)
	return eax
}

//go:nosplit
func hasInvariantTSC() bool {
	maxExt := cpuidMaxExt()
	if maxExt < 0x80000007 {
		return false
	}
	_, _, _, edx := cpuid(0x80000007, 0)
	return edx&(1<<8) != 0
}

//go:nosplit
func (s *stack) slice() []byte {
	stackTop := uintptr(unsafe.Pointer(&s[0])) + unsafe.Sizeof(*s)
	// Align to 16 bytes.
	alignment := int(stackTop & 0xf)
	return s[:len(s)-alignment]
}

//go:nosplit
func (s *stack) top() virtualAddress {
	stackTop := uintptr(unsafe.Pointer(&s[0])) + unsafe.Sizeof(*s)
	// Align to 16 bytes.
	stackTop = stackTop &^ 0xf
	return virtualAddress(stackTop)
}

func wrmsr0(register, lo, hi uint32)
func rdmsr0(register uint32) (lo, hi uint32)
func halt()
func cpuid(function, sub uint32) (eax, ebx, ecx, edx uint32)
func jumpToGo()
func fninit()
func setCR4Reg(flags uint64)
func outb(port uint16, b uint8)
func inb(port uint16) uint8
func outl(port uint16, b uint32)
func inl(port uint16) uint32

//go:nosplit
func (k kernError) Error() string {
	return string(k)
}

A  => kernel/kernel_amd64.s +522 -0
@@ 1,522 @@
// SPDX-License-Identifier: Unlicense OR MIT

#include "textflag.h"

#define CONTEXT_SELF 0*8
#define CONTEXT_IP 1*8
#define CONTEXT_SP 2*8
#define CONTEXT_FLAGS 3*8
#define CONTEXT_BP 4*8
#define CONTEXT_AX 5*8
#define CONTEXT_BX 6*8
#define CONTEXT_CX 7*8
#define CONTEXT_DX 8*8
#define CONTEXT_SI 9*8
#define CONTEXT_DI 10*8
#define CONTEXT_R8 11*8
#define CONTEXT_R9 12*8
#define CONTEXT_R10 13*8
#define CONTEXT_R11 14*8
#define CONTEXT_R12 15*8
#define CONTEXT_R13 16*8
#define CONTEXT_R14 17*8
#define CONTEXT_R15 18*8
#define CONTEXT_FSBASE 19*8
#define CONTEXT_FPSTATE 20*8

// Field offsets of type clock.
#define CLOCK_SEQ 0
#define CLOCK_SECONDS 8
#define CLOCK_NANOSECONDS 16

// Send end-of-interrupt to the APIC.
#define APICEOI	MOVQ	·apicEOI(SB), AX \
			MOVL	$0, (AX)

// INTERRUPT_SAVE/RESTORE assumes stack is aligned so that
// SP % 16 == 8.
#define INTERRUPT_SAVE SUBQ    $16*8+512, SP \
			MOVQ    BP, 0*8(SP) \
			MOVQ    AX, 1*8(SP) \
			MOVQ    BX, 2*8(SP) \
			MOVQ    CX, 3*8(SP) \
			MOVQ    DX, 4*8(SP) \
			MOVQ    SI, 5*8(SP) \
			MOVQ    DI, 6*8(SP) \
			MOVQ    R8, 7*8(SP) \
			MOVQ    R9, 8*8(SP) \
			MOVQ    R10, 9*8(SP) \
			MOVQ    R11, 10*8(SP) \
			MOVQ    R12, 11*8(SP) \
			MOVQ    R13, 12*8(SP) \
			MOVQ    R14, 13*8(SP) \
			MOVQ    R15, 14*8(SP) \
			FXSAVE	15*8(SP)

#define INTERRUPT_RESTORE FXRSTOR	15*8(SP) \
			MOVQ	0*8(SP), BP \
			MOVQ	1*8(SP), AX \
			MOVQ	2*8(SP), BX \
			MOVQ	3*8(SP), CX \
			MOVQ	4*8(SP), DX \
			MOVQ	5*8(SP), SI \
			MOVQ	6*8(SP), DI \
			MOVQ	7*8(SP), R8 \
			MOVQ	8*8(SP), R9 \
			MOVQ	9*8(SP), R10 \
			MOVQ	10*8(SP), R11 \
			MOVQ	11*8(SP), R12 \
			MOVQ	12*8(SP), R13 \
			MOVQ	13*8(SP), R14 \
			MOVQ	14*8(SP), R15 \
			ADDQ	$16*8+512, SP

#define USER_TRAMPOLINE(VECTOR) INTERRUPT_SAVE \
	PUSHQ $VECTOR \
	CALL ·userInterrupt(SB) \
	ADDQ	$8, SP \
	APICEOI \
	INTERRUPT_RESTORE \
	IRETQ

TEXT ·syscallTrampoline(SB),NOSPLIT|NOFRAME,$0
	SWAPGS
	// SYSCALL passes return address in CX, flags in R11.
	MOVQ	CX, CONTEXT_IP(GS)
	MOVQ	R11, CONTEXT_FLAGS(GS)
	// Save stack pointer.
	MOVQ	SP, CONTEXT_SP(GS)

	// Switch stack.
	MOVQ	·kstackTop(SB), SP

	CALL	·saveThread(SB)

	SUBQ	$8*8, SP

	MOVQ	CONTEXT_SELF(GS), BX
	MOVQ	BX, 0*8(SP) // Thread.
	MOVQ	AX, 1*8(SP) // Syscall number.
	// Up to 6 arguments.
	MOVQ	DI, 2*8(SP)
	MOVQ	SI, 3*8(SP)
	MOVQ	DX, 4*8(SP)
	MOVQ	R10, 5*8(SP)
	MOVQ	R8, 6*8(SP)
	MOVQ	R9, 7*8(SP)
	CALL	·sysenter(SB)

	ADDQ	$8*8, SP

	UNDEF // sysenter never returns.

// vdsoGettimeofday uses the C ABI.
TEXT ·vdsoGettimeofday(SB),NOSPLIT|NOFRAME,$0
	MOVQ	$·unixClock(SB), R10

retry:
	MOVQ	CLOCK_SEQ(R10), R8
	// Retry if seq is odd, indicating a write in progress.
	TESTB	$1, R8
	JNZ		retry
	MOVQ	CLOCK_SECONDS(R10), CX
	MOVL	CLOCK_NANOSECONDS(R10), AX
	// Retry if seq changed during the read.
	MOVQ	CLOCK_SEQ(R10), R9
	CMPQ	R8, R9
	JNE		retry
	// Convert to milliseconds.
	MOVL	$0, DX
	MOVL	$1000, R9
	DIVL	R9
	// Address of the result is in DI.
	MOVQ	CX, 0(DI) // Seconds.
	MOVL	AX, 8(DI) // Microseconds.
	MOVQ	$0, AX // Success.
	RET

TEXT ·generalProtectionFaultTrampoline(SB),NOSPLIT|NOFRAME,$0
	// The error code offsets the interrupt alignment by 8.
	// Re-align.
	SUBQ	$1*8, SP
	INTERRUPT_SAVE

	MOVQ	18*8+512(SP), AX // Instruction pointer from interrupt frame.

	SUBQ	$1*8, SP
	MOVQ	AX, 0*8(SP)
	CALL	·gpFault(SB)
	ADDQ	$1*8, SP

	INTERRUPT_RESTORE

	// Pop error code and alignment.
	ADDQ	$2*8, SP

	IRETQ

TEXT ·pageFaultTrampoline(SB),NOSPLIT|NOFRAME,$0
	// The error code offsets the interrupt alignment by 8.
	// Re-align.
	SUBQ	$1*8, SP
	INTERRUPT_SAVE

	MOVQ	17*8+512(SP), AX // Error code from interrupt frame.
	MOVQ	CR2, BX	// Fault address.

	SUBQ	$2*8, SP
	MOVQ	AX, 0*8(SP)
	MOVQ	BX, 1*8(SP)
	CALL	·handlePageFault(SB)
	ADDQ	$2*8, SP

	INTERRUPT_RESTORE

	// Pop error code and alignment.
	ADDQ	$2*8, SP

	IRETQ

TEXT ·timerTrampoline(SB),NOSPLIT|NOFRAME,$0
	SWAPGS
	// Save CX and R11 not saved by saveThread.
	MOVQ	CX, CONTEXT_CX(GS)
	MOVQ	R11, CONTEXT_R11(GS)

	CALL	·saveThread(SB)

	// Save return address, stack pointer, flags from the
	// interrupt stack frame.
	MOVQ	3*8(SP), AX // SP.
	MOVQ	AX, CONTEXT_SP(GS)
	MOVQ	2*8(SP), AX	// rflags.
	MOVQ	AX, CONTEXT_FLAGS(GS)
	MOVQ	0*8(SP), AX // Return address.
	MOVQ	AX, CONTEXT_IP(GS)

	// Send end-of-interrupt.
	APICEOI

	MOVQ	CONTEXT_SELF(GS), BX
	MOVQ	$·kernelThread(SB), CX
	CMPQ	BX, CX
	JEQ	kernelThread

	// Pop interrupt frame (5 words)
	ADDQ	$5*8, SP

	SUBQ	$8, SP
	MOVQ	BX, 0*8(SP) // Thread.
	CALL	·interruptSchedule(SB)
	ADDQ	$8, SP

	UNDEF // interruptSchedule never returns.

kernelThread:
	// We're interrupting the kernel thread, just
	// return.
	CALL	·restoreThread(SB)

	// Restore remaining registers.
	MOVQ	CONTEXT_CX(GS), CX
	MOVQ	CONTEXT_R11(GS), R11

	SWAPGS
	IRETQ

TEXT ·rt0(SB),NOSPLIT|NOFRAME,$0
	// Switch stack.
	CALL	·kernelStackTop(SB)
	MOVQ	0(SP), BP
	MOVQ	BP, SP

	SUBQ	$5*8, SP
	MOVQ	DI, 0(SP)	// Memory map size
	MOVQ	SI, 8(SP)	// Memory map descriptor size
	MOVQ	DX, 16(SP)	// Kernel image size
	MOVQ	CX, 24(SP)	// Memory map
	MOVQ	R8, 32(SP)	// Kernel image
	CALL	·runKernel(SB)
	ADDQ	$5*8, SP

	// runKernel should never return.
	UNDEF
	RET

TEXT ·jumpToGo(SB),NOSPLIT|NOFRAME,$0
	JMP _rt0_amd64_linux(SB)

TEXT ·rdmsr0(SB),NOSPLIT,$0-16
	MOVL register+0(FP), CX
	RDMSR
	MOVL	AX, lo+8(FP)
	MOVL	DX, hi+12(FP)
	RET

TEXT ·wrmsr0(SB),NOSPLIT,$0-12
	MOVL	register+0(FP), CX
	MOVL	lo+4(FP), AX
	MOVL	hi+8(FP), DX
	WRMSR
	RET

TEXT ·lgdt(SB),NOSPLIT,$0-8
	MOVQ	addr+0(FP), AX
	LGDT	(AX)
	RET

TEXT ·lidt(SB),NOSPLIT,$0-8
	MOVQ	addr+0(FP), AX
	LIDT	(AX)
	RET

TEXT ·setCSReg(SB),NOSPLIT,$0-2
	MOVW    seg+0(FP), AX
	// Allocate space for the long pointer (addr, segment).
	SUBQ	$16, SP
	MOVW	AX, 8(SP) // Segment.
	// The long jump should only change the CS segment
	// register. Use the address of the instruction
	// just after the jump.
	// The Go assembler can't load the instruction pointer.
	// MOVQ	IP+8, BX
	BYTE $0x48; BYTE $0x8d; BYTE $0x1d; BYTE $0x08; BYTE $0x00; BYTE $0x00; BYTE $0x0
	// Use raw opcodes to ensure their size.
	// MOVQ BX, 0(SP)
	BYTE $0x48; BYTE $0x89; BYTE $0x1c; BYTE $0x24
	// LJMPQ	(SP)
	BYTE $0x48; BYTE $0xFF; BYTE $0x2C; BYTE $0x24
	ADDQ	$16, SP
	RET

TEXT ·setCR3Reg(SB),NOSPLIT,$0-8
	MOVQ	addr+0(FP), AX
	MOVQ	AX, CR3
	RET

TEXT ·setCR4Reg(SB),NOSPLIT,$0-8
	MOVQ	flags+0(FP), AX
	MOVQ	AX, CR4
	RET

TEXT ·setDSReg(SB),NOSPLIT,$0-2
	MOVW	seg+0(FP), AX
	MOVW	AX, DS
	RET

TEXT ·setSSReg(SB),NOSPLIT,$0-2
	MOVW	seg+0(FP), AX
	MOVW	AX, SS
	RET

TEXT ·setESReg(SB),NOSPLIT,$0-2
	MOVW	seg+0(FP), AX
	MOVW	AX, ES
	RET

TEXT ·setFSReg(SB),NOSPLIT,$0-2
	MOVW	seg+0(FP), AX
	MOVW	AX, FS
	RET

TEXT ·setGSReg(SB),NOSPLIT,$0-2
	MOVW	seg+0(FP), AX
	MOVW	AX, GS
	RET

TEXT ·ltr(SB),NOSPLIT,$0-2
	MOVW	seg+0(FP), AX
	LTR	AX
	RET

TEXT ·yield0(SB),NOSPLIT,$0-0
	STI
	HLT
	CLI
	RET

TEXT ·halt(SB),NOSPLIT,$0-0
hlt:
	HLT
	JMP hlt
	RET

TEXT ·cpuid(SB),NOSPLIT,$0-24
	MOVL	function+0(FP), AX
	MOVL	sub+4(FP), CX
	CPUID
	MOVL	AX, eax+8(FP)
	MOVL	BX, ebx+12(FP)
	MOVL	CX, ecx+16(FP)
	MOVL	DX, edx+20(FP)
	RET

TEXT ·inl(SB),NOSPLIT,$0-12
	MOVW	port+0(FP), DX
	INL
	MOVL	AX, ret+8(FP)
	RET

TEXT ·outl(SB),NOSPLIT,$0-8
	MOVW	port+0(FP), DX
	MOVL	b+4(FP), AX
	OUTL
	RET

TEXT ·inb(SB),NOSPLIT,$0-9
	MOVW	port+0(FP), DX
	INB
	MOVB	AX, ret+8(FP)
	RET

TEXT ·outb(SB),NOSPLIT,$0-3
	MOVW	port+0(FP), DX
	MOVB	b+2(FP), AX
	OUTB
	RET

TEXT ·swapgs(SB),NOSPLIT|NOFRAME,$0-0
	SWAPGS
	RET

TEXT ·fninit(SB),NOSPLIT|NOFRAME,$0-0
	BYTE $0xdb; BYTE $0xe3; // FNINIT instruction.
	RET

TEXT ·currentThread(SB),NOSPLIT,$0-8
	MOVQ	CONTEXT_SELF(GS), AX
	MOVQ	AX, ret+0(FP)
	RET

TEXT ·saveThread(SB),NOSPLIT|NOFRAME,$0
	MOVQ	BP, CONTEXT_BP(GS)
	MOVQ	AX, CONTEXT_AX(GS)
	MOVQ	BX, CONTEXT_BX(GS)
	MOVQ	DX, CONTEXT_DX(GS)
	MOVQ	SI, CONTEXT_SI(GS)
	MOVQ	DI, CONTEXT_DI(GS)
	MOVQ	R8, CONTEXT_R8(GS)
	MOVQ	R9, CONTEXT_R9(GS)
	MOVQ	R10, CONTEXT_R10(GS)
	MOVQ	R12, CONTEXT_R12(GS)
	MOVQ	R13, CONTEXT_R13(GS)
	MOVQ	R14, CONTEXT_R14(GS)
	MOVQ	R15, CONTEXT_R15(GS)

	// Save floating point state.
	MOVQ	CONTEXT_SELF(GS), AX
	ADDQ	$CONTEXT_FPSTATE, AX
	FXSAVE (AX)

	MOVQ	CONTEXT_AX(GS), AX

	RET

TEXT ·restoreThread(SB),NOSPLIT|NOFRAME,$0
	// Restore fsbase.
	MOVQ	CONTEXT_FSBASE(GS), AX
	MOVL	$0xc0000100, CX // IA32_FS_BASE
	MOVQ	AX, DX
	SHRQ	$32, DX
	WRMSR

	// Restore floating point state.
	MOVQ	CONTEXT_SELF(GS), AX
	ADDQ	$CONTEXT_FPSTATE, AX
	FXRSTOR	(AX)

	// Restore registers.
	MOVQ	CONTEXT_BP(GS), BP
	MOVQ	CONTEXT_AX(GS), AX
	MOVQ	CONTEXT_BX(GS), BX
	MOVQ	CONTEXT_DX(GS), DX
	MOVQ	CONTEXT_SI(GS), SI
	MOVQ	CONTEXT_DI(GS), DI
	MOVQ	CONTEXT_R8(GS), R8
	MOVQ	CONTEXT_R9(GS), R9
	MOVQ	CONTEXT_R10(GS), R10
	MOVQ	CONTEXT_R12(GS), R12
	MOVQ	CONTEXT_R13(GS), R13
	MOVQ	CONTEXT_R14(GS), R14
	MOVQ	CONTEXT_R15(GS), R15

	RET

TEXT ·resumeThreadFast(SB),NOSPLIT|NOFRAME,$0
	CALL	·restoreThread(SB)

	MOVQ	CONTEXT_IP(GS), CX
	MOVQ	CONTEXT_FLAGS(GS), R11
	MOVQ	CONTEXT_SP(GS), SP
	SWAPGS
	BYTE	$0x48; BYTE $0x0f; BYTE $0x07; // SYSRETQ

TEXT ·resumeThread(SB),NOSPLIT|NOFRAME,$0
	// Create return frame for IRETQ that will restore the stack
	// and instruction pointers and the flags. The frame is 5
	// values, but we need to pop the return address as well.
	SUBQ	$(5-1)*8, SP
	MOVQ	$(4 << 3) | 3, 4*8(SP) // SS = segmentData3 << 3 | ring3
	MOVQ	CONTEXT_SP(GS), AX
	MOVQ	AX, 3*8(SP) // SP = context.sp
	MOVQ	CONTEXT_FLAGS(GS), BX
	MOVQ	BX, 2*8(SP)	// RFLAGS = context.rflags
	MOVQ	$(5 << 3) | 3, 1*8(SP) // CS = segment64Code3 << 3 | ring3
	MOVQ	CONTEXT_IP(GS), CX
	MOVQ	CX, 0*8(SP) // IP = context.ip

	CALL ·restoreThread(SB)

	// Restore remaining registers.
	MOVQ	CONTEXT_CX(GS), CX
	MOVQ	CONTEXT_R11(GS), R11

	SWAPGS
	IRETQ // Resume thread

TEXT ·StoreUint16(SB), NOSPLIT, $0-10
	MOVQ    addr+0(FP), BX
	MOVW    val+8(FP), AX
	XCHGW   AX, 0(BX)
	RET

TEXT ·StoreUint8(SB), NOSPLIT, $0-9
	MOVQ    addr+0(FP), BX
	MOVB    val+8(FP), AX
	XCHGB   AX, 0(BX)
	RET

TEXT ·OrUint8(SB), NOSPLIT, $0-9
	MOVQ    addr+0(FP), AX
	MOVB    val+8(FP), BX
	LOCK
	ORB BX, (AX)
	RET

TEXT ·unknownInterruptTrampoline(SB),NOSPLIT|NOFRAME,$0
	INTERRUPT_SAVE
	CALL ·unknownInterrupt(SB)
	APICEOI
	INTERRUPT_RESTORE
	IRETQ

TEXT ·userInterruptTrampoline0(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(0)
TEXT ·userInterruptTrampoline1(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(1)
TEXT ·userInterruptTrampoline2(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(2)
TEXT ·userInterruptTrampoline3(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(3)
TEXT ·userInterruptTrampoline4(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(4)
TEXT ·userInterruptTrampoline5(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(5)
TEXT ·userInterruptTrampoline6(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(6)
TEXT ·userInterruptTrampoline7(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(7)
TEXT ·userInterruptTrampoline8(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(8)
TEXT ·userInterruptTrampoline9(SB),NOSPLIT|NOFRAME,$0
	USER_TRAMPOLINE(9)

A  => kernel/memory_amd64.go +905 -0
@@ 1,905 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"math/bits"
	"reflect"
	"unsafe"
)

const (
	_EFI_MEMORY_RUNTIME = 0x8000000000000000
)

const (
	// Page sizes
	pageSize    = 1 << 12
	pageSize2MB = 1 << 21
	pageSize1GB = 1 << 30

	pageSizeRoot = 1 << 39

	pageTableSize = 512
)

// physicalMapOffset is the offset at which the physical memory
// is identity mapped. It is 0 until after initPageTables.
var physicalMapOffset virtualAddress

// The maximum physical address addressable by the processor.
const _MAXPHYADDR physicalAddress = 1 << 52

// The maximum virtual address.
var maxVirtAddress virtualAddress

type efiMemoryMap struct {
	mmap   []byte
	stride int
}

type elfImage struct {
	phdr                []byte
	phdrSize, phdrCount int
}

type elfSegmentHeader struct {
	pType   uint32
	pFlags  uint32
	pOffset uint64
	pVaddr  uint64
	pPaddr  uint64
	pFilesz uint64
	pMemsz  uint64
	pAlign  uint64
}

// efiMemoryDescriptor is a version 1 EFI_MEMORY_DESCRIPTOR.
type efiMemoryDescriptor struct {
	_type         efiMemoryType
	physicalStart physicalAddress
	virtualStart  virtualAddress
	numberOfPages uint64
	attribute     uint64
}

type efiMemoryType uint32

type pageFlags uint64

type physicalAddress uintptr

type virtualAddress uintptr

// pageTable is the hardware representation of a 4-level page table.
type pageTable [pageTableSize]pageTableEntry

// pageTableEntry is the hardware representation of a page table
// entry.
type pageTableEntry uint64

type pageVisitor func(addr virtualAddress, entry *pageTableEntry)

// memory is a simple allocator for physical memory, tracking free
// pages with a bitmap.
type memory struct {
	start physicalAddress
	// The index into bits of the last allocated block.
	word int
	// bits represent each physical memory page with one bit. 1
	// mean free, 0 means allocated or reserved.
	bits []uint64
}

// virtMemory tracks the all reserved virtual memory ranges
// and their flags.
type virtMemory struct {
	// ranges is the list of memory ranges, sorted by range.
	ranges []memoryRange
	// next is the address to start searching for a free range.
	next virtualAddress
}

type memoryRange struct {
	start virtualAddress
	end   virtualAddress
	flags pageFlags
}

var (
	hugePage1GBSupport = false
	nxSupport          = false
)

var (
	globalMem memory
	globalPT  *pageTable
	globalMap virtMemory
)

const (
	pageFlagPresent    pageFlags = 1 << 0
	pageFlagWritable   pageFlags = 1 << 1
	pageFlagNX         pageFlags = 1 << 63
	pageFlagUserAccess pageFlags = 1 << 2
	pageFlagNoCache    pageFlags = 1 << 4
	allPageFlags                 = pageFlagPresent | pageFlagWritable | pageFlagNX | pageFlagUserAccess | pageFlagNoCache

	pageSizeFlag pageFlags = 1 << 7
)

const (
	efiLoaderCode          efiMemoryType = 1
	efiLoaderData          efiMemoryType = 2
	efiBootServicesCode    efiMemoryType = 3
	efiBootServicesData    efiMemoryType = 4
	efiRuntimeServicesCode efiMemoryType = 5
	efiRuntimeServicesData efiMemoryType = 6
	efiConventionalMemory  efiMemoryType = 7
)

const (
	_ELFMagic = 0x464C457F
	_PT_LOAD  = 1
)

const virtMapSize = 1 << 30

//go:nosplit
func newELFImage(img []byte) (elfImage, error) {
	magic := *(*uint32)(unsafe.Pointer(&img[0]))
	if magic != _ELFMagic {
		return elfImage{}, kernError("kernel: invalid ELF image magic")
	}
	phdrOff := *(*uint64)(unsafe.Pointer(&img[32]))
	phdrSize := *(*uint16)(unsafe.Pointer(&img[54]))
	phdrCount := *(*uint16)(unsafe.Pointer(&img[56]))
	phdr := img[phdrOff : phdrSize*phdrCount]
	return elfImage{phdr: phdr, phdrSize: int(phdrSize), phdrCount: int(phdrCount)}, nil
}

//go:nosplit
func initMemory(efiMap efiMemoryMap, kernelImage []byte) error {
	if err := setupPageTable(efiMap, kernelImage); err != nil {
		return err
	}
	// Hold the virtual memory map structure and the physical identity
	// map in the upper half of the virtual address space.
	virtMapStart := maxVirtAddress >> 1
	// Addresses must be sign extended (in canonical form).
	virtMapStart |= ^(maxVirtAddress - 1)
	// Allocate 1 GB of virtual memory for the virtual memory ranges.
	virtMapEnd := virtMapStart + virtMapSize
	// Identity map physical memory in the upper half of the virtual
	// memory space.
	if err := identityMapMem(&globalMem, globalPT, efiMap, virtMapEnd); err != nil {
		return err
	}
	if err := identityMapKernel(&globalMem, globalPT, kernelImage); err != nil {
		return err
	}
	physicalMapOffset = virtMapEnd
	switchMemoryMap(&efiMap, &kernelImage)

	// Initialize virtual memory map.
	vmap, err := newVirtMemory(&globalMem, globalPT, virtMapStart, virtMapSize)
	if err != nil {
		return err
	}
	if err := addKernelRanges(&vmap, kernelImage); err != nil {
		return err
	}
	// Reserve the upper half of the virtual memory, up until the vDSO
	// starting address.
	vmap.mustAddRange(physicalMapOffset, vdsoAddress, pageFlagWritable|pageFlagNX)
	if err := mapReservedMem(&globalMem, globalPT, &vmap, efiMap); err != nil {
		return err
	}
	freeLoaderMem(&globalMem, efiMap)
	globalMap = vmap
	return nil
}

//go:nosplit
func switchMemoryMap(efiMap *efiMemoryMap, kernelImage *[]byte) {
	// Activate new memory map.
	setCR3Reg(uintptr(unsafe.Pointer(globalPT)))
	// Offset pointers allocated with 0-based offsets.
	*(*uintptr)(unsafe.Pointer(&globalPT)) += uintptr(physicalMapOffset)
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&globalMem.bits))
	hdr.Data += uintptr(physicalMapOffset)
	hdr = (*reflect.SliceHeader)(unsafe.Pointer(&efiMap.mmap))
	hdr.Data += uintptr(physicalMapOffset)
	hdr = (*reflect.SliceHeader)(unsafe.Pointer(kernelImage))
	hdr.Data += uintptr(physicalMapOffset)
}

//go:nosplit
func setupPageTable(efiMap efiMemoryMap, kernelImage []byte) error {
	initPagingFeatures()
	if nxSupport {
		// Enable no-execute bit.
		efer := rdmsr(_MSR_IA32_EFER)
		wrmsr(_MSR_IA32_EFER, efer|_EFER_NXE)
	}

	if err := initMemBitmap(&globalMem, efiMap); err != nil {
		return err
	}
	if err := reserveImageMem(&globalMem, kernelImage); err != nil {
		return err
	}
	page, _, err := globalMem.alloc(pageSize)
	if err != nil {
		return err
	}
	globalPT = (*pageTable)(unsafe.Pointer(physToVirt(page)))
	return nil
}

//go:nosplit
func newVirtMemory(mem *memory, pt *pageTable, start virtualAddress, size uint64) (virtMemory, error) {
	var vm virtMemory
	// Leave the lowest addresses unmapped.
	vm.next = 0x100000
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&vm.ranges))
	hdr.Data = uintptr(start)
	hdr.Cap = int(uintptr(size) / unsafe.Sizeof(vm.ranges[0]))
	// Eagerly allocate the first page to fit its own mapping. The
	// rest is faulted in.
	addr, _, err := mem.alloc(pageSize)
	if err != nil {
		return virtMemory{}, err
	}
	flags := pageFlagWritable | pageFlagNX
	if err := mmapAligned(mem, pt, start, start+pageSize, addr, flags); err != nil {
		return virtMemory{}, err
	}
	// Add vm's own address range.
	vm.mustAddRange(start, start+virtualAddress(size), flags)
	return vm, nil
}

// faultPage is called from the page fault interrupt handler.
//go:nosplit
func faultPage(addr virtualAddress) error {
	addr = addr & ^virtualAddress(pageSize-1)
	r, ok := globalMap.rangeForAddress(addr, pageSize)
	if !ok {
		return kernError("faultPage: page fault for unmapped address")
	}
	flags := r.flags
	if flags == pageFlagNX {
		return kernError("faultPage: page fault for PROT_NONE address")
	}
	paddr, _, err := globalMem.alloc(pageSize)
	if err != nil {
		return err
	}
	return mmapAligned(&globalMem, globalPT, addr, addr+pageSize, paddr, flags)
}

// identityMapMem makes the physical memory directly addressable for
// purposes such as page tables.
//go:nosplit
func identityMapMem(mem *memory, pt *pageTable, efiMap efiMemoryMap, offset virtualAddress) error {
	start := ^physicalAddress(0)
	end := physicalAddress(0)
	for i := 0; i < efiMap.len(); i++ {
		desc := efiMap.entry(i)
		if !desc.isUsable() {
			continue
		}
		if desc.physicalStart < start {
			start = desc.physicalStart
		}
		dend := desc.physicalStart + physicalAddress(desc.numberOfPages*pageSize)
		if dend > end {
			end = dend
		}
	}
	if start > end {
		return kernError("identityMapMem: start > end")
	}
	size := uint64(end - start)
	vaddr := offset + virtualAddress(start)
	return mmapAligned(mem, pt, vaddr, vaddr+virtualAddress(size), start, pageFlagWritable|pageFlagNX)
}

//go:nosplit
func freeLoaderMem(mem *memory, efiMap efiMemoryMap) {
	for i := 0; i < efiMap.len(); i++ {
		desc := efiMap.entry(i)
		if desc._type == efiLoaderData {
			start := desc.physicalStart
			end := start + physicalAddress(desc.numberOfPages*pageSize)
			mem.setFree(true, start, end)
		}
	}
}

//go:nosplit
func mapReservedMem(mem *memory, pt *pageTable, vmap *virtMemory, efiMap efiMemoryMap) error {
	for i := 0; i < efiMap.len(); i++ {
		desc := efiMap.entry(i)
		if !desc.isRuntime() {
			continue
		}
		// Identity map UEFI runtime addresses.
		addr := desc.physicalStart
		vaddr := virtualAddress(addr)
		end := vaddr + virtualAddress(desc.numberOfPages*pageSize)
		flags := pageFlagWritable
		vmap.mustAddRange(vaddr, end, flags)
		if err := mmapAligned(mem, pt, vaddr, end, addr, flags); err != nil {
			return err
		}
	}
	return nil
}

//go:nosplit
func addKernelRanges(vmap *virtMemory, image []byte) error {
	elfImg, err := newELFImage(image)
	if err != nil {
		return err
	}
	for i := 0; i < elfImg.phdrCount; i++ {
		seg := elfImg.readSegHeader(i)
		if seg.pType != _PT_LOAD {
			continue
		}
		start := seg.start()
		end := seg.end()
		flags := seg.flags() | pageFlagUserAccess
		vmap.mustAddRange(start, end, flags)
	}
	return nil
}

//go:nosplit
func identityMapKernel(mem *memory, pt *pageTable, image []byte) error {
	elfImg, err := newELFImage(image)
	if err != nil {
		return err
	}
	mmapAligned := mmapAligned // Cheat the nosplit checks.
	for i := 0; i < elfImg.phdrCount; i++ {
		seg := elfImg.readSegHeader(i)
		if seg.pType != _PT_LOAD {
			continue
		}
		start := seg.start()
		end := seg.end()
		flags := seg.flags() | pageFlagUserAccess
		err = mmapAligned(mem, pt, start, end, physicalAddress(start), flags)
		if err != nil {
			return err
		}
	}
	return nil
}

//go:nosplit
func (e *elfImage) readSegHeader(idx int) *elfSegmentHeader {
	off := idx * e.phdrSize
	hdr := e.phdr[off : off+int(unsafe.Sizeof(elfSegmentHeader{}))]
	return (*elfSegmentHeader)(unsafe.Pointer(&hdr[0]))
}

//go:nosplit
func reserveImageMem(mem *memory, image []byte) error {
	elfImg, err := newELFImage(image)
	if err != nil {
		return err
	}
	for i := 0; i < elfImg.phdrCount; i++ {
		seg := elfImg.readSegHeader(i)
		if seg.pType != _PT_LOAD {
			continue
		}
		mem.setFree(false, physicalAddress(seg.start()), physicalAddress(seg.end()))
	}
	return nil
}

//go:nosplit
func (e *elfSegmentHeader) start() virtualAddress {
	return virtualAddress(e.pVaddr)
}

//go:nosplit
func (e *elfSegmentHeader) end() virtualAddress {
	sz := (e.pMemsz + e.pAlign - 1) &^ uint64(e.pAlign-1)
	return e.start() + virtualAddress(sz)
}

//go:nosplit
func (e *elfSegmentHeader) flags() pageFlags {
	flags := pageFlagNX
	const (
		PF_X = 0x1
		PF_W = 0x2
	)
	if e.pFlags&PF_X != 0 {
		flags &^= pageFlagNX
	}
	if e.pFlags&PF_W != 0 {
		flags |= pageFlagWritable
	}
	return flags
}

// initMem initializes a memory allocator from an EFI memory map.
//go:nosplit
func initMemBitmap(mem *memory, efiMap efiMemoryMap) error {
	// Determine the highest usable physical memory address and
	// largest memory region.
	var maxAddr physicalAddress
	minAddr := ^physicalAddress(0)
	var largestDesc *efiMemoryDescriptor
	for i := 0; i < efiMap.len(); i++ {
		desc := efiMap.entry(i)
		if !desc.isUsable() {
			continue
		}
		min := desc.physicalStart
		max := min + physicalAddress(desc.numberOfPages*pageSize)
		if min < minAddr {
			minAddr = min
		}
		if max > maxAddr {
			maxAddr = max
		}
		// The EFI memory map is itself located in an
		// EFILoaderData region. Don't re-use it before we're
		// done with it.
		if desc._type == efiLoaderData {
			continue
		}
		if largestDesc == nil || desc.numberOfPages > largestDesc.numberOfPages {
			largestDesc = desc
		}
	}
	if largestDesc == nil {
		return kernError("initMem: no initial memory")
	}
	// Compute the number of pages the memory bitmap takes up.
	rng := uint64(maxAddr - minAddr)
	nbits := (rng + pageSize - 1) / pageSize
	nbytes := (nbits + 8 - 1) / 8
	npages := (nbytes + pageSize - 1) / pageSize
	if npages > largestDesc.numberOfPages {
		return kernError("initMem: memory bitmap doesn't fit in available memory")
	}
	mem.start = minAddr
	nwords := (nbytes + 8 - 1) / 8
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&mem.bits))
	hdr.Data = uintptr(largestDesc.physicalStart)
	hdr.Len = int(nwords)
	hdr.Cap = int(nwords)
	// Clear bitmap.
	for i := range mem.bits {
		mem.bits[i] = 0
	}
	// Mark free memory.
	for i := 0; i < efiMap.len(); i++ {
		desc := efiMap.entry(i)
		if !desc.isUsable() || desc._type == efiLoaderData {
			continue
		}
		start := desc.physicalStart
		end := start + physicalAddress(desc.numberOfPages*pageSize)
		if start < end {
			mem.setFree(true, start, end)
		}
	}
	// Reserve memory for the allocator itself.
	mem.setFree(false, largestDesc.physicalStart, largestDesc.physicalStart+physicalAddress(npages*pageSize))
	return nil
}

//go:nosplit
func (m *memory) setFree(free bool, start, end physicalAddress) {
	if start&^(pageSize-1) != start || end&^(pageSize-1) != end {
		fatal("markFree: unaligned memory range")
	}
	if start > end {
		fatal("markFree: start > end")
	}
	if start < m.start {
		fatal("markFree: start > m.start")
	}
	start -= m.start
	end -= m.start
	startBit := uint64(start / pageSize)
	endBit := uint64(end / pageSize)
	startWord := startBit / 64
	endWord := endBit / 64
	// Set the bits of the first and last word(s).
	startPattern := uint64(1)<<(64-startBit%64) - 1
	endPattern := ^(uint64(1)<<(64-endBit%64) - 1)
	if startWord == endWord {
		startPattern &= endPattern
		endPattern = startPattern
	}
	var pattern uint64
	if free {
		pattern = ^uint64(0)
		m.bits[startWord] |= startPattern
		m.bits[endWord] |= endPattern
	} else {
		pattern = 0
		m.bits[startWord] &^= startPattern
		m.bits[endWord] &^= endPattern
	}
	// Mark the middle bits.
	for i := startWord + 1; i < endWord; i++ {
		m.bits[i] = pattern
	}
}

// alloc allocates at most maxSize bytes of contiguous memory, rounded
// up to the page size. alloc returns at least a page of memory.
//go:nosplit
func (m *memory) alloc(maxSize int) (physicalAddress, int, error) {
	pageIdx, ok := m.nextFreePage()
	if !ok {
		return 0, 0, kernError("alloc: out of memory")
	}
	addr := physicalAddress(pageIdx * pageSize)
	var size int
	for maxSize > 0 {
		if !m.mark(pageIdx) {
			break
		}
		pageIdx++
		size += pageSize
		maxSize -= pageSize
	}
	mem := sliceForMem(physToVirt(addr), size)
	for i := range mem {
		mem[i] = 0
	}
	return addr, size, nil
}

//go:nosplit
func (m *memory) mark(pageIdx int) bool {
	wordIdx := pageIdx / 64
	bit := pageIdx % 64
	mask := uint64(1 << (64 - bit - 1))
	word := m.bits[wordIdx]
	if word&mask == 0 {
		return false
	}
	m.bits[wordIdx] = word &^ mask
	return true
}

//go:nosplit
func (m *memory) nextFreePage() (int, bool) {
	for i := 0; i < len(m.bits); i++ {
		idx := (i + m.word) % len(m.bits)
		w := m.bits[idx]
		b := bits.LeadingZeros64(w)
		if b == 64 {
			continue
		}
		m.word = idx
		return idx*64 + b, true
	}
	return 0, false
}

//go:nosplit
func (m *efiMemoryMap) entry(i int) *efiMemoryDescriptor {
	off := i * m.stride
	return (*efiMemoryDescriptor)(unsafe.Pointer(&m.mmap[off]))
}

//go:nosplit
func (m *efiMemoryMap) len() int {
	return len(m.mmap) / m.stride
}

// mmapAligned maps the virtual address range to a physical address
// range.
//go:nosplit
func mmapAligned(mem *memory, pml4 *pageTable, start, end virtualAddress, paddr physicalAddress, flags pageFlags) error {
	if paddr%pageSize != 0 {
		fatal("mmap: pagetable entry not aligned")
	}
	for start < end {
		size := end - start
		// Look up PML4 entry.
		pml4e := (start / pageSizeRoot) % pageTableSize
		pdpt, err := pml4.lookupOrCreatePageTable(mem, int(pml4e))
		if err != nil {
			return err
		}
		pdpte := (start / pageSize1GB) % pageTableSize
		if size >= pageSize1GB && start%pageSize2MB == 0 && paddr%pageSize1GB == 0 && hugePage1GBSupport {
			// Map a 1 GB page.
			pdpt[pdpte].mmap(paddr, flags|pageSizeFlag)
			paddr += pageSize1GB
			start += pageSize1GB
			continue
		}
		pd, err := pdpt.lookupOrCreatePageTable(mem, int(pdpte))
		if err != nil {
			return err
		}
		pde := (start / pageSize2MB) % pageTableSize
		if size >= pageSize2MB && start%pageSize2MB == 0 && paddr%pageSize2MB == 0 {
			// Map a 2MB page.
			pd[pde].mmap(paddr, flags|pageSizeFlag)
			paddr += pageSize2MB
			start += pageSize2MB
			continue
		}
		pt, err := pd.lookupOrCreatePageTable(mem, int(pde))
		if err != nil {
			return err
		}
		e := (start / pageSize) % pageTableSize
		pt[e].mmap(paddr, flags)
		paddr += pageSize
		start += pageSize
	}
	return nil
}

//go:nosplit
func (p *pageTable) lookupOrCreatePageTable(mem *memory, index int) (*pageTable, error) {
	entry := &p[index]
	if entry.present() {
		return entry.getPageTable(), nil
	}
	page, _, err := mem.alloc(pageSize)
	if err != nil {
		return nil, err
	}
	entry.setPageTable(page)
	return (*pageTable)(unsafe.Pointer(physToVirt(page))), nil
}

// setPageTable points the entry to a page table.
//go:nosplit
func (e *pageTableEntry) setPageTable(addr physicalAddress) {
	*e = pageTableEntry(addr) | pageTableEntry(pageFlagPresent|pageFlagWritable|pageFlagUserAccess)
}

// getPageTable reads a page table reference from the entry.
//go:nosplit
func (e *pageTableEntry) getPageTable() *pageTable {
	if pageFlags(*e)&pageSizeFlag != 0 {
		fatal("getPageTable: not a page table")
	}
	addr := physicalAddress(*e) & (_MAXPHYADDR - 1)
	// The address is page-aligned.
	addr = addr & ^(physicalAddress(pageSize) - 1)
	return (*pageTable)(unsafe.Pointer(physToVirt(addr)))
}

//go:nosplit
func (e *pageTableEntry) present() bool {
	return pageFlags(*e)&pageFlagPresent != 0
}

//go:nosplit
func (e *pageTableEntry) mmap(addr physicalAddress, flags pageFlags) {
	if !nxSupport {
		flags &= ^pageFlagNX
	}
	flags |= pageFlagPresent
	*e = pageTableEntry(addr) | pageTableEntry(flags)
}

//go:nosplit
func (e *pageTableEntry) setFlags(flags pageFlags) {
	*e &= ^pageTableEntry(allPageFlags)
	*e |= pageTableEntry(flags)
}

// isRuntime reports whether the memory region is used for the UEFI
// runtime.
//go:nosplit
func (e *efiMemoryDescriptor) isRuntime() bool {
	return e.attribute&_EFI_MEMORY_RUNTIME != 0
}

// isUsable reports whether the memory region is available for use.
//go:nosplit
func (e *efiMemoryDescriptor) isUsable() bool {
	if e.isRuntime() {
		return false
	}
	switch e._type {
	case efiLoaderCode, efiLoaderData, efiBootServicesCode, efiBootServicesData, efiConventionalMemory:
		return true
	default:
		return false
	}
}

//go:nosplit
func physToVirt(addr physicalAddress) virtualAddress {
	return physicalMapOffset + virtualAddress(addr)
}

//go:nosplit
func sliceForMem(addr virtualAddress, size int) []byte {
	var slice []byte
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
	hdr.Len = size
	hdr.Cap = size
	hdr.Data = uintptr(addr)
	return slice
}

//go:nosplit
func initPagingFeatures() {
	maxExt := cpuidMaxExt()
	if maxExt < 0x80000001 {
		return
	}
	_, _, _, edx := cpuid(0x80000001, 0)
	nxSupport = edx&(1<<20) != 0
	hugePage1GBSupport = edx&(1<<26) != 0
	maxVirtAddress = 1 << 32
	if edx&(1<<29) != 0 {
		maxVirtAddress = 1 << 48
	}
	if maxExt < 0x80000008 {
		return
	}
	eax, _, _, _ := cpuid(0x80000008, 0)
	virtWidth := (eax >> 8) & 0xff
	maxVirtAddress = 1 << virtWidth
}

// mmap reserves a virtual memory range size bytes big, preferring
// addr as starting address.
//go:nosplit
func (vm *virtMemory) mmap(addr virtualAddress, size uint64, flags pageFlags) (virtualAddress, error) {
	if addr == 0 {
		addr = vm.next
	}
	start := addr.Align()
	end := (addr + virtualAddress(size)).AlignUp()
	if vm.addRange(start, end, flags) {
		return start, nil
	}
	// Forward search for a starting address where the range fits.
	idx := vm.closestRange(vm.next)
	for ; idx < len(vm.ranges); idx++ {
		start := vm.ranges[idx].end
		end := (start + virtualAddress(size)).AlignUp()
		if vm.addRange(start, end, flags) {
			vm.next = end
			return start, nil
		}
	}
	return 0, kernError("mmap: failed to allocate memory")
}

// mmapFixed reserves a virtual memory range size bytes big at the
// page aligned address addr.
//go:nosplit
func (vm *virtMemory) mmapFixed(addr virtualAddress, size uint64, flags pageFlags) bool {
	if addr != addr.Align() {
		return false
	}
	end := (addr + virtualAddress(size)).AlignUp()
	return vm.addRange(addr, end, flags)
}

// mustAddRange is like addRange but calls fatal if the range
// overlaps.
//go:nosplit
func (vm *virtMemory) mustAddRange(start, end virtualAddress, flags pageFlags) {
	if !vm.addRange(start, end, flags) {
		fatal("mustAddRange: adding overlapping range")
	}
}

// rangeGorAddress returns the range that contains the
// address range or false if such range exists.
//go:nosplit
func (vm *virtMemory) rangeForAddress(addr virtualAddress, size int) (memoryRange, bool) {
	i := vm.closestRange(addr)
	if i >= len(vm.ranges) {
		return memoryRange{}, false
	}
	r := vm.ranges[i]
	if !r.containsRange(addr, size) {
		return memoryRange{}, false
	}
	return r, true
}

// addRange adds a memory range to the map. If the range overlaps
// an existing range, addRange does nothing and returns false.
//go:nosplit
func (vm *virtMemory) addRange(start, end virtualAddress, flags pageFlags) bool {
	if start > end {
		fatal("addRange: invalid range")
	}
	i := vm.closestRange(start)
	r := memoryRange{start: start, end: end, flags: flags}
	if i < len(vm.ranges) {
		if vm.ranges[i].overlaps(r) {
			return false
		}
	}
	// Expand.
	vm.ranges = vm.ranges[:len(vm.ranges)+1]
	copy(vm.ranges[i+1:], vm.ranges[i:])
	vm.ranges[i] = r
	return true
}

// closestRange finds the lowest index i where vm.ranges[i].end > addr.
//go:nosplit
func (vm *virtMemory) closestRange(addr virtualAddress) int {
	i, j := 0, len(vm.ranges)
	for i < j {
		h := int(uint(i+j) >> 1)
		if vm.ranges[h].end <= addr {
			i = h + 1
		} else {
			j = h
		}
	}
	return i
}

//go:nosplit
func (r memoryRange) containsRange(addr virtualAddress, size int) bool {
	return r.start <= addr && addr+virtualAddress(size) <= r.end
}

//go:nosplit
func (r memoryRange) contains(addr virtualAddress) bool {
	return r.start <= addr && addr < r.end
}

//go:nosplit
func (r memoryRange) overlaps(r2 memoryRange) bool {
	return r.start <= r2.start && r.end > r2.start ||
		r2.start <= r.start && r2.end > r.start
}

// Align the address downwards to the page size.
func (a virtualAddress) Align() virtualAddress {
	return a &^ virtualAddress(pageSize-1)
}

// Align the address upwards to the page size.
func (a virtualAddress) AlignUp() virtualAddress {
	return (a + pageSize - 1) & ^virtualAddress(pageSize-1)
}

//go:nosplit
func handlePageFault(errCode uint64, addr virtualAddress) {
	const (
		faultFlagPresent = 1 << 0
	)
	if errCode&faultFlagPresent != 0 {
		outputString("page fault address: ")
		outputUint64(uint64(addr))
		outputString("\n")
		fatal("handlePageFault: page protection fault")
	}
	if err := faultPage(addr); err != nil {
		outputString("page fault address: ")
		outputUint64(uint64(addr))
		outputString("\n")
		fatalError(err)
	}
}

func pageFaultTrampoline()

func setCR3Reg(addr uintptr)

A  => kernel/segment_amd64.go +216 -0
@@ 1,216 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"encoding/binary"
	"unsafe"
)

// Types and code for setting up processor segments and task
// state structure. Segmenting and task switching is largely
// disabled in 64-bit mode, but a GDT and a TSS is nevertheless
// required.

// segmentDescriptor represents a 64-bit segment descriptor.
// Uses uint64 type to force 8-byte alignment.
type segmentDescriptor uint64

// TSS structure for amd64. Hardware task switching is not available
// in 64-bit mode, but a TSS structure must be defined to specify
// interrupt and ring 0 stacks.
type tss [25]uint32

// Global interrupt descriptor table, never touched after
// initialization.
var globalIDT idt

// Golbal task state structure, never touched after initialization.
var globalTSS tss

// The global descriptor table, never touched after initialization.
var globalGDT [segmentEnd]segmentDescriptor

// Interrupt and SYSCALL stack.
var (
	istack         stack
	pageFaultStack stack
)

// Segment selectors. Note that the SYSCALL/SYSRET
// instructions force the particular positions of the selectors.
// See the Intel Architectures Manual Vol 3., 5.8.8 ("Fast System
// Calls in 64-bit Mode"). Additionally, assembly constructs IRETQ
// stack frames with hardcoded segments.
const (
	// Mandatory null selector.
	_ = iota
	// Ring 0 code (64-bit).
	segmentCode0
	// Ring 0 data.
	segmentData0
	// Ring 3 code (32-bit).
	segment32Code3
	// Ring 3 data.
	segmentData3
	// Ring 3 code (64-bit).
	segment64Code3
	// TSS.
	segmentTSS0
	// TSS high address.
	segmentTSS0High
	// End sentinal for determining limit.
	segmentEnd
)

// There are 256 interrupts available.
type idt [256]idtDescriptor

// IDT descriptor. Uses uint64 to force 8-byte alignment.
type idtDescriptor [2]uint64

type segmentFlags uint32
type privLevel uint32
type intVector uint8

const (
	ring0 privLevel = 0
	ring3 privLevel = 3
)

const (
	segFlagAccess  segmentFlags = 1 << 8
	segFlagWrite                = 1 << 9
	segFlagCode                 = 1 << 11
	segFlagSystem               = 1 << 12
	segFlagPresent              = 1 << 15
	segFlagLong                 = 1 << 21
)

const (
	istGeneric = 1
	// Use a separate stack for page faults to handle faults
	// that occur during interrupts.
	istPageFault = 2
)

//go:nosplit
func loadGDT() {
	globalTSS.setISP(istGeneric, uint64(istack.top()))
	globalTSS.setISP(istPageFault, uint64(pageFaultStack.top()))
	globalTSS.setRSP(0, uint64(istack.top()))
	tssAddr := uintptr(unsafe.Pointer(&globalTSS))
	tssLimit := uint32(unsafe.Sizeof(globalTSS) - 1)
	// Block all I/O ports.
	globalTSS.setIOPerm(uint16(tssLimit + 1))
	globalGDT[segmentCode0] = newSegmentDescriptor(0, 0, segFlagSystem|segFlagCode|segFlagLong, ring0)
	globalGDT[segmentData0] = newSegmentDescriptor(0, 0, segFlagSystem|segFlagWrite, ring0)
	globalGDT[segment32Code3] = newSegmentDescriptor(0, 0, segFlagSystem|segFlagCode|segFlagLong, ring3)
	globalGDT[segmentData3] = newSegmentDescriptor(0, 0, segFlagSystem|segFlagWrite, ring3)
	globalGDT[segment64Code3] = newSegmentDescriptor(0, 0, segFlagSystem|segFlagCode|segFlagLong, ring3)
	// The 64-bit TSS structure spans two descriptor entries,
	// with the high 32-bit address in the second entry.
	globalGDT[segmentTSS0] = newSegmentDescriptor(uint32(tssAddr), tssLimit, segFlagAccess|segFlagCode, ring0)
	globalGDT[segmentTSS0High] = segmentDescriptor(tssAddr >> 32)
	// The GDT register is a 10 byte value: a 16-bit limit followed by
	// the 64-bit address.
	var gdtAddr [10]uint8
	addr := uintptr(unsafe.Pointer(&globalGDT))
	// GDT should be 8-byte aligned for best performance.
	if addr%8 != 0 {
		fatal("loadGDT: bad GDT alignment")
	}
	limit := unsafe.Sizeof(globalGDT) - 1
	binary.LittleEndian.PutUint64(gdtAddr[2:], uint64(addr))
	binary.LittleEndian.PutUint16(gdtAddr[:2], uint16(limit))
	lgdt(uint64(uintptr(unsafe.Pointer(&gdtAddr))))
	data0 := uint16(segmentData0<<3 | ring0)
	code0 := uint16(segmentCode0<<3 | ring0)
	tss0 := uint16(segmentTSS0<<3 | ring0)
	setCSReg(code0)
	setSSReg(data0)
	setDSReg(data0)
	setESReg(data0)
	setFSReg(data0)
	setGSReg(data0)
	ltr(tss0)
}

//go:nosplit
func reloadIDT() {
	// The GDT register is a 10 byte value: a 16-bit limit followed by
	// the 64-bit address.
	var idtAddr [10]uint8
	addr := uintptr(unsafe.Pointer(&globalIDT))
	// GDT should be 8-byte aligned for best performance.
	if addr%8 != 0 {
		fatal("reloadIDT: bad GDT alignment")
	}
	limit := unsafe.Sizeof(globalIDT) - 1
	binary.LittleEndian.PutUint64(idtAddr[2:], uint64(addr))
	binary.LittleEndian.PutUint16(idtAddr[:2], uint16(limit))
	lidt(uint64(uintptr(unsafe.Pointer(&idtAddr))))
}

// install an interrupt handler.
//go:nosplit
func (t *idt) install(interrupt intVector, level privLevel, ist uint8, trampoline func()) {
	sel := uint32(segmentCode0<<3 | ring0)
	pc := funcPC(trampoline)
	flags := uint32(segFlagPresent)
	// Use a trap gate, which does not affect the IF flag on entry.
	const trapGate = 0xe
	w0 := sel<<16 | uint32(pc&0xffff)
	w1 := uint32(pc&0xffff0000) | flags | uint32(level)<<13 | trapGate<<8 | uint32(ist)
	w2 := uint32(pc >> 32)
	t[interrupt][0] = uint64(w1)<<32 | uint64(w0)
	t[interrupt][1] = uint64(w2)
}

// setRSP sets the address for the kernel stack
// number idx.
//go:nosplit
func (t *tss) setRSP(idx int, rsp uint64) {
	if idx < 0 || idx > 2 {
		fatal("setRSP: stack index out of range")
	}
	t[1+idx*16] = uint32(rsp)
	t[1+idx*16+1] = uint32(rsp >> 32)
}

// setISP sets the address for the interrupt stack
// number idx (1-based).
//go:nosplit
func (t *tss) setISP(idx int, rsp uint64) {
	if idx < 1 || idx > 7 {
		fatal("setRSP: stack index out of range")
	}
	t[7+idx*2] = uint32(rsp)
	t[7+idx*2+1] = uint32(rsp >> 32)
}

//go:nosplit
func (t *tss) setIOPerm(addr uint16) {
	t[24] = uint32(addr) << 16
}

//go:nosplit
func newSegmentDescriptor(base uint32, limit uint32, flags segmentFlags, level privLevel) segmentDescriptor {
	if limit > 0xfffff {
		fatal("newSegmentDesciptor: limit too high")
	}
	flags |= segFlagPresent
	w0 := base<<16 | limit&0xffff
	w1 := base&0xff000000 | uint32(limit&0xf0000) | uint32(flags) | uint32(level)<<13 | (base>>16)&0xff
	return segmentDescriptor(uint64(w1)<<32 | uint64(w0))
}

func lgdt(addr uint64)
func lidt(addr uint64)
func setCSReg(seg uint16)
func setDSReg(seg uint16)
func setSSReg(seg uint16)
func setESReg(seg uint16)
func setFSReg(seg uint16)
func setGSReg(seg uint16)
func ltr(seg uint16)

A  => kernel/syscall_amd64.go +341 -0
@@ 1,341 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"time"
	"unsafe"
)

const (
	_MSR_LSTAR = 0xc0000082
	_MSR_STAR  = 0xc0000081
	_MSR_FSTAR = 0xc0000084
)

const (
	// SYSCALL numbers.
	_SYS_write          = 1
	_SYS_mmap           = 9
	_SYS_pipe           = 22
	_SYS_pipe2          = 293
	_SYS_arch_prctl     = 158
	_SYS_uname          = 63
	_SYS_rt_sigaction   = 13
	_SYS_rt_sigprocmask = 14
	_SYS_sigaltstack    = 131
	_SYS_clone          = 56
	_SYS_exit_group     = 231
	_SYS_exit           = 60
	_SYS_nanosleep      = 35
	_SYS_futex          = 202
	_SYS_epoll_create1  = 291
	_SYS_epoll_pwait    = 281
	_SYS_epoll_ctl      = 233

	// Custom syscall numbers.
	_SYS_outl = 0x80000000 + iota
	_SYS_inl
	_SYS_iomap
	_SYS_alloc
	_SYS_waitinterrupt

	_ARCH_SET_FS = 0x1002

	_AT_PAGESZ = 6
	_AT_NULL   = 0

	_MAP_ANONYMOUS = 0x20
	_MAP_PRIVATE   = 0x2
	_MAP_FIXED     = 0x10

	_PROT_WRITE = 0x2
	_PROT_EXEC  = 0x4

	_CLONE_VM      = 0x100
	_CLONE_FS      = 0x200
	_CLONE_FILES   = 0x400
	_CLONE_SIGHAND = 0x800
	_CLONE_SYSVSEM = 0x40000
	_CLONE_THREAD  = 0x10000
)

// Processor flags.
const (
	_FLAG_RESERVED = 1 << 2 // Always set.
	_FLAG_TF       = 1 << 8
	_FLAG_IF       = 1 << 9
	_FLAG_DF       = 1 << 10
	_FLAG_VM       = 1 << 17
	_FLAG_AC       = 1 << 18
)

// Errnos.
const (
	_EOK     = 0
	_ENOTSUP = ^uint64(95) + 1
	_ENOMEM  = ^uint64(0xc) + 1
	_EINVAL  = ^uint64(0x16) + 1
)

const (
	_FUTEX_WAIT         = 0
	_FUTEX_WAKE         = 1
	_FUTEX_PRIVATE_FLAG = 128
	_FUTEX_WAIT_PRIVATE = _FUTEX_WAIT | _FUTEX_PRIVATE_FLAG
	_FUTEX_WAKE_PRIVATE = _FUTEX_WAKE | _FUTEX_PRIVATE_FLAG
)

type timespec struct {
	seconds     int64
	nanoseconds int32
}

//go:nosplit
func initSYSCALL() {
	// Setup segments for SYSCALL/SYSRET.
	syscallSeg := uint64(segmentCode0<<3 | ring0)
	sysretSeg := uint64(segment32Code3<<3 | ring3)
	wrmsr(_MSR_STAR, uint64(uint64(syscallSeg)<<32|uint64(sysretSeg)<<48))
	// Clear flags on entry to SYSCALL handler.
	wrmsr(_MSR_FSTAR, _FLAG_IF|_FLAG_TF|_FLAG_AC|_FLAG_VM|_FLAG_TF|_FLAG_DF)
	// Setup SYSCALL handler.
	wrmsr(_MSR_LSTAR, uint64(funcPC(syscallTrampoline)))

	// Enable SYSCALL instruction.
	efer := rdmsr(_MSR_IA32_EFER)
	wrmsr(_MSR_IA32_EFER, efer|_EFER_SCE)
}

//go:nosplit
func sysenter(t *thread, sysno, a0, a1, a2, a3, a4, a5 uint64) {
	t.block = blockCondition{
		syscall: 1,
	}
	ret0, ret1 := sysenter0(t, sysno, a0, a1, a2, a3, a4, a5)
	// Return values are passed in AX, DX.
	t.setSyscallResult(ret0, ret1)
	if t.block.conditions == 0 {
		resumeThreadFast()
	} else {
		globalThreads.schedule(t)
	}
	fatal("sysenter: resume failed")
}

//go:nosplit
func (ts *timespec) duration() (time.Duration, bool) {
	if ts == nil || ts.seconds < 0 {
		return 0, false
	}
	dur := time.Duration(ts.seconds)*time.Second + time.Duration(ts.nanoseconds)*time.Nanosecond
	return dur, true
}

//go:nosplit
func sysenter0(t *thread, sysno, a0, a1, a2, a3, a4, a5 uint64) (uint64, uint64) {
	switch sysno {
	case _SYS_write:
		fd := a0
		p := virtualAddress(a1)
		n := uint32(a2)
		const dummyFd = 0
		if fd == dummyFd {
			return uint64(n), 0
		}
		if fd != 1 && fd != 2 {
			return _ENOTSUP, 0
		}
		bytes := sliceForMem(p, int(n))
		output(bytes)
		return uint64(len(bytes)), 0
	case _SYS_mmap:
		addr := virtualAddress(a0)
		n := a1
		// prot := a2
		flags := a3
		// fd := a4
		// off := a5
		supported := _MAP_ANONYMOUS | _MAP_PRIVATE | _MAP_FIXED
		if flags & ^uint64(supported) != 0 {
			return _ENOTSUP, 0
		}
		/*var pf pageFlags
		if prot&_PROT_WRITE != 0 {
			pf |= pageWritable
		}
		if prot&_PROT_EXEC == 0 {
			pf |= pageNotExecutable
		}*/
		// Always use the most lenient flags for now.
		pf := pageFlagWritable | pageFlagUserAccess
		if flags&_MAP_FIXED != 0 {
			if !globalMap.mmapFixed(addr, n, pf) {
				// Ignore error and assume the range is
				// already mapped.
			}
			return uint64(addr), 0
		} else {
			addr, err := globalMap.mmap(addr, n, pf)
			if err != nil {
				return _ENOMEM, 0
			}
			return uint64(addr), 0
		}
	case _SYS_clone:
		flags := a0
		// Support only the particular set of flags used by Go.
		const expFlags = _CLONE_VM |
			_CLONE_FS |
			_CLONE_FILES |
			_CLONE_SIGHAND |
			_CLONE_SYSVSEM |
			_CLONE_THREAD
		if flags != expFlags {
			return _ENOTSUP, 0
		}
		stack := a1
		clone, err := globalThreads.newThread()
		if err != nil {
			return _ENOMEM, 0
		}
		clone.context = t.context
		clone.sp = stack
		clone.ax = 0 // Return 0 from the cloned thread.
		return uint64(clone.id), 0
	case _SYS_exit_group:
		t.block.conditions = deadCondition
		return _EOK, 0
	case _SYS_arch_prctl:
		switch code := a0; code {
		case _ARCH_SET_FS:
			addr := a1
			t.fsbase = addr
			wrmsr(_MSR_FS_BASE, addr)
			return _EOK, 0
		}
	case _SYS_uname:
		// Ignore for now; the Go runtime only uses uname to detect buggy
		// Linux kernel versions.
		return _EOK, 0
	case _SYS_futex:
		addr := a0
		val := a2
		switch op := a1; op {
		case _FUTEX_WAIT, _FUTEX_WAIT_PRIVATE:
			ts := (*timespec)(unsafe.Pointer(uintptr(a3)))
			if d, ok := ts.duration(); ok {
				t.sleepFor(d)
			}
			t.block.conditions |= futexCondition
			t.block.futex = addr
			return 0, 0
		case _FUTEX_WAKE, _FUTEX_WAKE_PRIVATE:
			globalThreads.futexWakeup(addr, int(val))
			return _EOK, 0
		}
	case _SYS_rt_sigprocmask, _SYS_sigaltstack, _SYS_rt_sigaction:
		// Ignore signals.
		return _EOK, 0
	case _SYS_nanosleep:
		ts := (*timespec)(unsafe.Pointer(uintptr(a0)))
		if d, ok := ts.duration(); ok {
			t.sleepFor(d)
		}
		return _EOK, 0
	case _SYS_epoll_create1:
		return _EOK, 0
	case _SYS_epoll_ctl:
		return _EOK, 0
	case _SYS_pipe2:
		return _EOK, 0
	case _SYS_epoll_pwait:
		timeout := time.Duration(a3) * time.Millisecond
		if timeout >= 0 {
			t.sleepFor(timeout)
		} else {
			t.block.conditions = deadCondition
		}
		return _EOK, 0
	case _SYS_outl:
		port := uint16(a0)
		val := uint32(a1)
		outl(port, val)
		return _EOK, 0
	case _SYS_inl:
		port := uint16(a0)
		val := inl(port)
		return uint64(val), 0
	case _SYS_iomap:
		vaddr := virtualAddress(a0)
		addr := physicalAddress(a1)
		size := a2
		if vaddr&(pageSize-1) != 0 {
			return _EINVAL, 0
		}
		if addr&(pageSize-1) != 0 {
			return _EINVAL, 0
		}
		size = (size + pageSize - 1) &^ (pageSize - 1)
		r, ok := globalMap.rangeForAddress(vaddr, int(size))
		if !ok {
			return _EINVAL, 0
		}
		err := mmapAligned(&globalMem, globalPT, vaddr, vaddr+virtualAddress(size), addr, r.flags)
		if err != nil {
			// TODO: free virtual map.
			return _ENOMEM, 0
		}
		return _EOK, 0
	case _SYS_alloc:
		maxSize := a0
		addr, size, err := globalMem.alloc(int(maxSize))
		if err != nil {
			return _ENOMEM, 0
		}
		return uint64(addr), uint64(size)
	case _SYS_waitinterrupt:
		t.block.conditions = interruptCondition
		return 0, 0
	}
	return _ENOTSUP, 0
}

const COM1 = 0x3f8

//go:nosplit
func output(b []byte) {
	for i := 0; i < len(b); i++ {
		outb(COM1, b[i])
	}
}

//go:nosplit
func outputString(b string) {
	for i := 0; i < len(b); i++ {
		outb(COM1, b[i])
	}
}

//go:nosplit
func outputUint64(v uint64) {
	onlyZero := true
	outputString("0x")
	for i := 15; i >= 0; i-- {
		// Extract the ith nibble.
		nib := byte((v >> (i * 4)) & 0xf)
		if onlyZero && i > 0 && nib == 0 {
			// Skip leading zeros.
			continue
		}
		onlyZero = false
		switch {
		case 0 <= nib && nib <= 9:
			outb(COM1, nib+'0')
		default:
			outb(COM1, nib-10+'a')
		}
	}
}

func syscallTrampoline()

A  => kernel/thread_amd64.go +318 -0
@@ 1,318 @@
// SPDX-License-Identifier: Unlicense OR MIT

package kernel

import (
	"reflect"
	"time"
	"unsafe"
)

const maxThreads = 100

const (
	_IA32_KERNEL_GS_BASE = 0xc0000102
	_IA32_GS_BASE        = 0xc0000101
)

var globalThreads threads

type tid uint64

type threads struct {
	threads []thread
}

// thread represents per-thread context and bookkeeping. Must be
// 8 byte aligned so its fields must have known sizes.
type thread struct {
	self *thread
	context

	id tid

	block blockCondition
}

type blockCondition struct {
	syscall    uint32
	conditions waitConditions

	// For sleepCondition.
	sleep struct {
		monotoneTime uint64
		duration     time.Duration
	}

	// For futexCondition.
	futex uint64

	_ uint32
}

// waitConditions is a set of potential conditions that will wake up a
// thread.
type waitConditions uint32

const (
	interruptCondition waitConditions = 1 << iota
	sleepCondition
	futexCondition
	deadCondition
)

const scheduleTimeSlice = 10 * time.Millisecond

// Thread state for early initialization.
var thread0 thread

// Kernel thread for yielding.
var kernelThread thread

// context represent a thread's CPU state. The exact layout of context
// is known to the thread assembly functions.
type context struct {
	ip    uint64
	sp    uint64
	flags uint64
	bp    uint64
	ax    uint64
	bx    uint64
	cx    uint64
	dx    uint64
	si    uint64
	di    uint64
	r8    uint64
	r9    uint64
	r10   uint64
	r11   uint64
	r12   uint64
	r13   uint64
	r14   uint64
	r15   uint64

	fsbase uint64

	// fpState is space for the floating point context, including
	// alignment. FXSAVE/FXRSTOR needs 512 bytes.
	fpState [512]byte
}

//go:nosplit
func initThreads() error {
	// assembly expects the thread context after self.
	if unsafe.Offsetof(thread{}.self) != 0 {
		fatal("initThreads: invalid thread.self field alignment")
	}
	if unsafe.Offsetof(thread{}.context) != 8 {
		fatal("initThreads: invalid thread.context field alignment")
	}
	if unsafe.Offsetof(thread{}.fpState)%16 != 0 {
		fatal("initThreads: invalid thread.context field alignment")
	}
	return globalThreads.init()
}

//go:nosplit
func (ts *threads) init() error {
	size := unsafe.Sizeof(ts.threads[0]) * maxThreads
	addr, err := globalMap.mmap(0, uint64(size), pageFlagNX|pageFlagWritable)
	if err != nil {
		return err
	}
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&ts.threads))
	hdr.Data = uintptr(addr)
	hdr.Cap = int(size / unsafe.Sizeof(ts.threads[0]))
	return nil
}

//go:nosplit
func (ts *threads) newThread() (*thread, error) {
	if len(ts.threads) == cap(ts.threads) {
		return nil, kernError("newThread: too many threads")
	}
	tid := tid(len(ts.threads))
	ts.threads = ts.threads[:tid+1]
	newt := &ts.threads[tid]
	*newt = thread{
		id: tid,
	}
	newt.self = newt
	return newt, nil
}

// Schedule selects an appropriate thread to resume and makes it
// current.
//go:nosplit
func (ts *threads) schedule(t *thread) {
	for {
		updateClock()
		maxDur := 24 * time.Hour
		monotoneTime := unixClock.monotoneMillis()
		for i := 0; i < len(ts.threads); i++ {
			// Round-robin scheduling.
			tid := (int(t.id) + 1) % len(ts.threads)
			t = &ts.threads[tid]
			if dur, ok := t.runnable(monotoneTime); !ok {
				if dur > 0 && dur < maxDur {
					maxDur = dur
				}
				continue
			}
			t.block.conditions = 0
			t.makeCurrent()
			setTimer(scheduleTimeSlice)
			if t.block.syscall != 0 {
				resumeThreadFast()
			} else {
				resumeThread()
			}
			fatal("schedule: resume failed")
		}
		setTimer(maxDur)
		yield()
	}
}