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()
+ }
+}
+
+//go:nosplit
+func (ts *threads) futexWakeup(addr uint64, nwaiters int) {
+ for i := 0; i < len(ts.threads); i++ {
+ if nwaiters == 0 {
+ break
+ }
+ t := &ts.threads[i]
+ if t.block.conditions&futexCondition == 0 {
<