From ec594504ca4c064a422015ca944dd77a0e8caf14 Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Sun, 1 Jun 2025 01:55:33 +0200 Subject: [PATCH] Update content --- new.fish | 2 +- trees/common-macros.tree | 3 - trees/index.tree | 6 +- trees/latex-preamble.tree | 280 ++++++++++++++++++++++++++++++++++++ trees/lsvvnuqnxrspxwxw.tree | 4 +- trees/macros.tree | 50 +++++++ trees/mrtztpskvpzqsnoq.tree | 70 ++++++++- trees/mvqpkwvnyptltyms.tree | 3 +- trees/numwsuwkoozlrqss.tree | 7 +- trees/oomvxowovlvqrnnm.tree | 2 +- trees/refs/amdgpu.tree | 1 + trees/refs/amdvlk.tree | 1 + trees/wqtquyvsvowuklvk.tree | 215 +++++++++++++++++++++++++++ trees/wzvmmkrtxtwlzyrr.tree | 5 +- trees/xtwokuxkmmmosmmw.tree | 7 +- 15 files changed, 636 insertions(+), 20 deletions(-) delete mode 100644 trees/common-macros.tree create mode 100644 trees/latex-preamble.tree create mode 100644 trees/macros.tree create mode 100644 trees/wqtquyvsvowuklvk.tree diff --git a/new.fish b/new.fish index 96416b4..1fdc4db 100755 --- a/new.fish +++ b/new.fish @@ -12,6 +12,6 @@ echo > $tree_path "\ \title{} \author{joshuarsimmons} \date{$creation_date} -\import{common-macros} +\import{macros} " diff --git a/trees/common-macros.tree b/trees/common-macros.tree deleted file mode 100644 index c6ebacf..0000000 --- a/trees/common-macros.tree +++ /dev/null @@ -1,3 +0,0 @@ -\title{Common Macros} - -\p{This tree defines some commonly used macros, if I ever write any.} diff --git a/trees/index.tree b/trees/index.tree index c2c5517..aafde8b 100644 --- a/trees/index.tree +++ b/trees/index.tree @@ -3,15 +3,17 @@ \author{joshuarsimmons} \meta{author}{false} -\import{common-macros} +\import{macros} \put\transclude/toc{false} \p{i make video games and do stuff and exist. in reverse order. you may enjoy these free-range, organic, and 100\% robot-free words at leisure.} + \subtree{ \title{Latest [Blog](mvqpkwvnyptltyms) Post} - \transclude{wzvmmkrtxtwlzyrr} + \put\transclude/expanded{false} + \transclude{wqtquyvsvowuklvk} } \subtree{ diff --git a/trees/latex-preamble.tree b/trees/latex-preamble.tree new file mode 100644 index 0000000..2a3cd64 --- /dev/null +++ b/trees/latex-preamble.tree @@ -0,0 +1,280 @@ +\def\latex-preamble/diagrams{ + \startverb + \usepackage{tikz, tikz-cd, mathtools, amssymb, stmaryrd} + \usetikzlibrary{matrix,arrows} + \usetikzlibrary{backgrounds,fit,positioning,calc,shapes} + \usetikzlibrary{decorations.pathreplacing} + \usetikzlibrary{decorations.pathmorphing} + \usetikzlibrary{decorations.markings} + + \tikzset{ + desc/.style={sloped, fill=white,inner sep=2pt}, + upright desc/.style={fill=white,inner sep=2pt}, + pullback/.style = { + append after command={ + \pgfextra{ + \draw ($(\tikzlastnode) + (.2cm,-.5cm)$) -- ++(0.3cm,0) -- ++(0,0.3cm); + } + } + }, + pullback 45/.style = { + append after command={ + \pgfextra{ + \draw[rotate = 45] ($(\tikzlastnode) + (.2cm,-.5cm)$) -- ++(0.3cm,0) -- ++(0,0.3cm); + } + } + }, + ne pullback/.style = { + append after command={ + \pgfextra{ + \draw ($(\tikzlastnode) + (-.2cm,-.5cm)$) -- ++(-0.3cm,0) -- ++(0,0.3cm); + } + } + }, + sw pullback/.style = { + append after command={ + \pgfextra{ + \draw ($(\tikzlastnode) + (.2cm,.5cm)$) -- ++(0.3cm,0) -- ++(0,-0.3cm); + } + } + }, + dotted pullback/.style = { + append after command={ + \pgfextra{ + \draw [densely dotted] ($(\tikzlastnode) + (.2cm,-.5cm)$) -- ++(0.3cm,0) -- ++(0,0.3cm); + } + } + }, + muted pullback/.style = { + append after command={ + \pgfextra{ + \draw [gray] ($(\tikzlastnode) + (.2cm,-.5cm)$) -- ++(0.3cm,0) -- ++(0,0.3cm); + } + } + }, + pushout/.style = { + append after command={ + \pgfextra{ + \draw ($(\tikzlastnode) + (-.2cm,.5cm)$) -- ++(-0.3cm,0) -- ++(0,-0.3cm); + } + } + }, + between/.style args={#1 and #2}{ + at = ($(#1)!0.5!(#2)$) + }, + diagram/.style = { + on grid, + node distance=2cm, + commutative diagrams/every diagram, + line width = .5pt, + every node/.append style = { + commutative diagrams/every cell, + } + }, + fibration/.style = { + -{Triangle[open]} + }, + etale/.style = { + -{Triangle[open]} + }, + etale cover/.style= { + >={Triangle[open]},->.> + }, + opfibration/.style = { + -{Triangle} + }, + lies over/.style = { + |-{Triangle[open]} + }, + op lies over/.style = { + |-{Triangle} + }, + embedding/.style = { + {right hook}-> + }, + open immersion/.style = { + {right hook}-{Triangle[open]} + }, + closed immersion/.style = { + {right hook}-{Triangle} + }, + closed immersion*/.style = { + {left hook}-{Triangle} + }, + embedding*/.style = { + {left hook}-> + }, + open immersion*/.style = { + {left hook}-{Triangle[open]} + }, + exists/.style = { + densely dashed + }, + } + + \newlength{\dontworryaboutit} + + \tikzset{ + inline diagram/.style = { + commutative diagrams/every diagram, + commutative diagrams/cramped, + line width = .5pt, + every node/.append style = { + commutative diagrams/every cell, + anchor = base, + inner sep = 0pt + }, + every path/.append style = { + outer xsep = 2pt + } + } + } + + \tikzset{ + square/nw/.style = {}, + square/ne/.style = {}, + square/se/.style = {}, + square/sw/.style = {}, + square/north/.style = {->}, + square/south/.style = {->}, + square/west/.style = {->}, + square/east/.style = {->}, + square/north/node/.style = {above}, + square/south/node/.style = {below}, + square/west/node/.style = {left}, + square/east/node/.style = {right}, + } + + \ExplSyntaxOn + + \bool_new:N \l_jon_glue_west + + \keys_define:nn { jon-tikz/diagram } { + nw .tl_set:N = \l_jon_tikz_diagram_nw, + sw .tl_set:N = \l_jon_tikz_diagram_sw, + ne .tl_set:N = \l_jon_tikz_diagram_ne, + se .tl_set:N = \l_jon_tikz_diagram_se, + + width .tl_set:N = \l_jon_tikz_diagram_width, + height .tl_set:N = \l_jon_tikz_diagram_height, + + north .tl_set:N = \l_jon_tikz_diagram_north, + south .tl_set:N = \l_jon_tikz_diagram_south, + west .tl_set:N = \l_jon_tikz_diagram_west, + east .tl_set:N = \l_jon_tikz_diagram_east, + + nw/style .code:n = {\tikzset{square/nw/.style = {#1}}}, + sw/style .code:n = {\tikzset{square/sw/.style = {#1}}}, + ne/style .code:n = {\tikzset{square/ne/.style = {#1}}}, + se/style .code:n = {\tikzset{square/se/.style = {#1}}}, + + glue .choice:, + glue / west .code:n = {\bool_set:Nn \l_jon_glue_west \c_true_bool}, + + glue~target .tl_set:N = \l_jon_tikz_glue_target, + + north/style .code:n = {\tikzset{square/north/.style = {#1}}}, + north/node/style .code:n = {\tikzset{square/north/node/.style = {#1}}}, + south/style .code:n = {\tikzset{square/south/.style = {#1}}}, + south/node/style .code:n = {\tikzset{square/south/node/.style = {#1}}}, + west/style .code:n = {\tikzset{square/west/.style = {#1}}}, + west/node/style .code:n = {\tikzset{square/west/node/.style = {#1}}}, + east/style .code:n = {\tikzset{square/east/.style = {#1}}}, + east/node/style .code:n = {\tikzset{square/east/node/.style = {#1}}}, + + draft .meta:n = { + nw = {\__jon_tikz_diagram_fmt_placeholder:n {nw}}, + sw = {\__jon_tikz_diagram_fmt_placeholder:n {sw}}, + se = {\__jon_tikz_diagram_fmt_placeholder:n {se}}, + ne = {\__jon_tikz_diagram_fmt_placeholder:n {ne}}, + north = {\__jon_tikz_diagram_fmt_placeholder:n {north}}, + south = {\__jon_tikz_diagram_fmt_placeholder:n {south}}, + west = {\__jon_tikz_diagram_fmt_placeholder:n {west}}, + east = {\__jon_tikz_diagram_fmt_placeholder:n {east}}, + } + } + + \tl_set:Nn \l_jon_tikz_diagram_width { 2cm } + \tl_set:Nn \l_jon_tikz_diagram_height { 2cm } + + + \cs_new:Npn \__jon_tikz_diagram_fmt_placeholder:n #1 { + \texttt{\textcolor{red}{#1}} + } + + \keys_set:nn { jon-tikz/diagram } { + glue~target = {}, + } + + + \cs_new:Nn \__jon_tikz_render_square:nn { + \group_begin: + \keys_set:nn {jon-tikz/diagram} {#2} + \bool_if:nTF \l_jon_glue_west { + \node (#1ne) [right = \l_jon_tikz_diagram_width~of~\l_jon_tikz_glue_target ne,square/ne] {$\l_jon_tikz_diagram_ne$}; + \node (#1se) [below = \l_jon_tikz_diagram_height~of~#1ne,square/se] {$\l_jon_tikz_diagram_se$}; + \draw[square/north] (\l_jon_tikz_glue_target ne) to node [square/north/node] {$\l_jon_tikz_diagram_north$} (#1ne); + \draw[square/east] (#1ne) to node [square/east/node] {$\l_jon_tikz_diagram_east$} (#1se); + \draw[square/south] (\l_jon_tikz_glue_target se) to node [square/south/node] {$\l_jon_tikz_diagram_south$} (#1se); + } { + \node (#1nw) [square/nw] {$\l_jon_tikz_diagram_nw$}; + \node (#1sw) [below = \l_jon_tikz_diagram_height~of~#1nw,square/sw] {$\l_jon_tikz_diagram_sw$}; + \draw[square/west] (#1nw) to node [square/west/node] {$\l_jon_tikz_diagram_west$} (#1sw); + + \node (#1ne) [right = \l_jon_tikz_diagram_width~of~#1nw,square/ne] {$\l_jon_tikz_diagram_ne$}; + \node (#1se) [below = \l_jon_tikz_diagram_height~of~#1ne,square/se] {$\l_jon_tikz_diagram_se$}; + \draw[square/north] (#1nw) to node [square/north/node] {$\l_jon_tikz_diagram_north$} (#1ne); + \draw[square/east] (#1ne) to node [square/east/node] {$\l_jon_tikz_diagram_east$} (#1se); + \draw[square/south] (#1sw) to node [square/south/node] {$\l_jon_tikz_diagram_south$} (#1se); + } + \group_end: + } + + \NewDocumentCommand\SpliceDiagramSquare{D<>{}m}{ + \__jon_tikz_render_square:nn {#1} {#2} + } + + + \NewDocumentCommand\DiagramSquare{D<>{}O{}m}{ + \begin{tikzpicture}[diagram,#2,baseline=(#1sw.base)] + \__jon_tikz_render_square:nn {#1} {#3} + \end{tikzpicture} + } + + \ExplSyntaxOff + \stopverb +} + +\def\latex-preamble/string-diagrams{ + \startverb + \RequirePackage{tikz} + \RequirePackage{amsmath} + \usetikzlibrary{backgrounds, intersections, calc, spath3, fit} + + \definecolor{catccolor}{RGB}{255,244,138} + \tikzstyle{dot}=[circle, draw=black, fill=black, minimum size=1mm, inner sep=0mm] + \tikzstyle{catc}=[catccolor!60] + \tikzstyle{catd}=[orange!40] + \tikzstyle{cate}=[red!40] + \tikzstyle{catf}=[blue!10] + \tikzstyle{catg}=[green!25] + + + \tikzstyle{blue halo}=[fill=blue!10, opacity=0.7, rounded corners] + \tikzstyle{white halo}=[fill=white, opacity=0.7, rounded corners] + + \NewDocumentCommand\CreateRect{D<>{} m m}{ + \path + coordinate (#1nw) + ++(#2,-#3) coordinate (#1se) + coordinate (#1sw) at (#1se -| #1nw) + coordinate (#1ne) at (#1nw -| #1se) + ; + + \path[spath/save = #1north] (#1nw) to (#1ne); + \path[spath/save = #1west] (#1nw) to (#1sw); + \path[spath/save = #1east] (#1ne) to (#1se); + \path[spath/save = #1south] (#1sw) to (#1se); + } + \stopverb +} diff --git a/trees/lsvvnuqnxrspxwxw.tree b/trees/lsvvnuqnxrspxwxw.tree index 57828f0..3fcc101 100644 --- a/trees/lsvvnuqnxrspxwxw.tree +++ b/trees/lsvvnuqnxrspxwxw.tree @@ -2,7 +2,7 @@ \author{joshuarsimmons} \date{2025-05-27} -\import{common-macros} +\import{macros} \p{Vulkan's built-in allocation functionality is extremely limited, implementations are:} @@ -14,6 +14,8 @@ \p{These issues typically require the user to implement some form of memory allocator to sub-allocate resources from larger memory blocks. In this adventure, rather than doing the smart thing and using [Vulkan Memory Allocator](vulkan-memory-allocator), we'll be rolling our own from scratch!} +\p{Our goal is to allocate a smaller number of large \em{super-blocks} directly from the driver, and from those \em{super-blocks}, \em{sub-allocate} to service allocations from the application. There's a large variety of practical options here, but we'll be using [TLSF](mrtztpskvpzqsnoq)} + \transclude{mrtztpskvpzqsnoq} \transclude{numwsuwkoozlrqss} \transclude{xtwokuxkmmmosmmw} \ No newline at end of file diff --git a/trees/macros.tree b/trees/macros.tree new file mode 100644 index 0000000..e843180 --- /dev/null +++ b/trees/macros.tree @@ -0,0 +1,50 @@ +\title{Common Macros} + +\import{latex-preamble} + +\p{This tree defines some commonly used macros that I've stolen.} + +\p{\code{codeinline[lang][body]}: An inline, syntax-highlighted code block specifying the language.} + +\def\codeinline[lang][body]{ + \[class]{shj-lang-\lang}{\body} +} + +\p{\code{codeblock[lang][body]}: A syntax-highlighted code block specifying the language.} + +\def\codeblock[lang][body]{ + \[class]{shj-lang-\lang}{\body} +} + +\def\mark[body]{\{\body}} + +\def\table[body]{ + \{\body} +} +\def\tr[body]{ + \{\body} +} +\def\td[body]{ + \{\body} +} +\def\th[body]{ + \{\body} +} +\def\hr{ + \{} +} + +\def\brc[x]{#{{\mathopen{}\left\{\x\right\}\mathclose{}}}} +\def\prn[x]{#{{\mathopen{}\left(\x\right)\mathclose{}}}} + +\alloc\base/tex-preamble + +\def\texfig[~body]{ + \scope{ + \put?\base/tex-preamble{ + \latex-preamble/string-diagrams + \latex-preamble/diagrams + } + \figure{\tex{\get\base/tex-preamble}{\body{}}} + } +} \ No newline at end of file diff --git a/trees/mrtztpskvpzqsnoq.tree b/trees/mrtztpskvpzqsnoq.tree index afa95c6..046bdf9 100644 --- a/trees/mrtztpskvpzqsnoq.tree +++ b/trees/mrtztpskvpzqsnoq.tree @@ -1,13 +1,77 @@ \title{The [TLSF](masmano-ripoll-2008) Allocator} \author{joshuarsimmons} \date{2025-05-27} -\import{common-macros} + +\import{macros} \p{[TLSF](masmano-ripoll-2008) is a simple, fast, constant time allocator algorithm designed primarily for embedded use-cases. Empty blocks are stored in a series of free-lists, using a [linear-log](linear-log-bucketing) sequence of bucket sizes to bound internal fragmentation and minimise external fragmentation. A two-level bitmap acceleration structure tracks which free-lists contain blocks, allowing good-fit searches to complete using a couple of bit instructions widely supported by modern CPUs.} +\p{Memory is passed to TLSF as \em{super-blocks} representing large chunks of contiguous memory. The allocator then operates by subdividing those \em{super-blocks} into \em{blocks}.} + +\subtree[nrvkouzksuzynqkz]{ + \title{Binning} + + \p{Searching all unused blocks to find the most suitable block to allocate from would be prohibitively expensive so unused blocks are stored in a set of free-lists seggregated by their size. The strategy we used to seggregate into bins must strike a delicate balance:} + + \ol{ + \li{It must be fast to calculate the bin for a given size.} + \li{Bin sizes must approximate allocation sizes to minimize fragmentation.} + \li{The number of bins should ideally be small and bounded.} + } + + \p{So to achieve those goals we'll use [linear-log bucketing](linear-log-bucketing).} + + \figure{ + \tex{ + \startverb + \usepackage[dvipsnames]{xcolor} + \usepackage{tikz,interval} + + \usetikzlibrary{calc,matrix,shapes.multipart,chains,arrows} + \usepackage{kpfonts} + + \definecolor{palette-1}{RGB}{230,232,191} + \stopverb + }{ + \startverb + \begin{tikzpicture}[% + arraynode/.style={% + draw, + palette-1 + }, + rowlabel/.style={% + minimum size=0pt, + draw=none, + palette-1 + }, + array/.style={% + matrix of nodes, + nodes = arraynode, + column sep=-\pgflinewidth, + row sep=-\pgflinewidth, + minimum height=.6cm, + minimum width=2cm, + column 1/.style={nodes = {rowlabel}}, + } + ] + + \matrix[array] (array) { + Linear & 1--4 & 5--8 & 9--12 & 13--16 \\ + $2^4$ & 17--20 & 21--24 & 25--28 & 29--32 \\ + $2^5$ & 31--40 & 41--48 & 49--56 & 13--64 \\ + $2^6$ & 65--80 & 81--96 & 97--112 & 113--128\\ + }; + + \end{tikzpicture} + \stopverb + } + \figcaption{An illustration of bin sizes in a linear-log scheme showing linear progression until #{2^4}, with #{2^2} sub-bins per range, for a total of 16 bins.} + } +} + \p{Allocation proceeds by scanning the bitmap acceleration structure, if any suitable blocks are found, that block is immediately used to service the allocation. If there would be significant left over space, the block will be split and the left-over memory inserted back into the free-lists.} -\pre\verb<<<| +\codeblock{rs}\verb<<<| /// Search the acceleration structure for a non-empty list suitable for an /// allocation of the given size. /// @@ -51,7 +115,7 @@ fn search_non_empty_bin(&self, size: u32) -> Option<(u32, Bin)> { \p{Freeing allocated memory does some cusory validation of the allocation to prevent trivial double-frees, then returns the block to the free-lists. Adjacent free physical blocks are merged immediately.} -\pre\verb<<<| +\codeblock{rs}\verb<<<| pub fn free(&mut self, allocation: Allocation) { let mut block_index = allocation.block_index; let generation = self.blocks[block_index].generation; diff --git a/trees/mvqpkwvnyptltyms.tree b/trees/mvqpkwvnyptltyms.tree index 6fa9e90..265acae 100644 --- a/trees/mvqpkwvnyptltyms.tree +++ b/trees/mvqpkwvnyptltyms.tree @@ -1,7 +1,8 @@ \title{Blog} \author{joshuarsimmons} \date{2025-05-27} -\import{common-macros} + +\import{macros} \p{This is my blog, it is brimming with wisdom and whimsy. There is a secret atom feed, but I don't know how to make a link to it yet.} diff --git a/trees/numwsuwkoozlrqss.tree b/trees/numwsuwkoozlrqss.tree index 8cd9d94..25ea320 100644 --- a/trees/numwsuwkoozlrqss.tree +++ b/trees/numwsuwkoozlrqss.tree @@ -1,13 +1,14 @@ \title{Thread-local Frame Bump Allocator} \author{joshuarsimmons} \date{2025-05-29} -\import{common-macros} + +\import{macros} \p{In order to reduce pressure on the general purpose allocator we implement a double-buffered thread-local bump allocator for transient allocations which last only a single frame. This allows the calling thread to avoid needing to take a lock, and reduces the complex allocation bookkeeping to a few branches and a subtract.} \p{If a transient buffer is requested that is beyond the block size for the transient allocator, it will fall-back to the general allocator, and immediately queue the buffer for deferred destruction.} -\pre\verb<<<| +\codeblock{rs}\verb<<<| let texture: &[u8] = _; let buffer = device.request_transient_buffer_with_data( @@ -37,6 +38,6 @@ device.cmd_copy_buffer_to_image( ); <<< -\p{The \code{request_transient_buffer_with_data} function returns a handle bound to the lifetime of the \code{frame} parameter which prevents the common error of persisting references to frame allocated data after the frame has been submitted. At the beginning of a frame the linear allocators are reset, and the underlying buffers are recycled.} +\p{The \codeinline{plain}{request_transient_buffer_with_data} function returns a handle bound to the lifetime of the \codeinline{plain}{frame} parameter which prevents the common error of persisting references to frame allocated data after the frame has been submitted. At the beginning of a frame the linear allocators are reset, and the underlying buffers are recycled.} \p{For this scheme to be production-ready it would also be important that the system track the amount of memory used each frame, and release excess memory that is unused over a longer period of time to the general allocator. As it is currently implemented, any buffers allocated by the transient memory pools will remain attached to those pools until shutdown.} diff --git a/trees/oomvxowovlvqrnnm.tree b/trees/oomvxowovlvqrnnm.tree index 14ec404..5198801 100644 --- a/trees/oomvxowovlvqrnnm.tree +++ b/trees/oomvxowovlvqrnnm.tree @@ -3,6 +3,6 @@ \date{2025-05-27} \tag{blog} -\import{common-macros} +\import{macros} \p{This is a blog post! The very first one!} diff --git a/trees/refs/amdgpu.tree b/trees/refs/amdgpu.tree index d7a6360..7cc0c5c 100644 --- a/trees/refs/amdgpu.tree +++ b/trees/refs/amdgpu.tree @@ -1,5 +1,6 @@ \title{amdgpu} \taxon{Reference} +\author/literal{AMD} \meta{external}{https://github.com/torvalds/linux/tree/master/drivers/gpu/drm/amd/amdgpu} \p{The Linux upstream kernel driver developed by AMD for AMD GCN and RDNA graphics cards.} \ No newline at end of file diff --git a/trees/refs/amdvlk.tree b/trees/refs/amdvlk.tree index 3eb0364..92c8d59 100644 --- a/trees/refs/amdvlk.tree +++ b/trees/refs/amdvlk.tree @@ -1,5 +1,6 @@ \title{AMDVLK} \taxon{Reference} +\author/literal{AMD} \meta{external}{https://github.com/GPUOpen-Drivers/AMDVLK} \p{AMD's open-source Vulkan driver for Radeon graphics adapters on Linux. It is built on top of AMD's Platform Abstraction Library, a shared component that is designed to encapsulate certain hardware and OS-specific programming details for many of AMD's 3D and compute drivers. Utilizes the [amdgpu](amdgpu) kernel driver.} \ No newline at end of file diff --git a/trees/wqtquyvsvowuklvk.tree b/trees/wqtquyvsvowuklvk.tree new file mode 100644 index 0000000..c2744c8 --- /dev/null +++ b/trees/wqtquyvsvowuklvk.tree @@ -0,0 +1,215 @@ +\title{I could make github in a weekend!} +\author{joshuarsimmons} +\date{2025-06-02} +\tag{blog} +\import{macros} + +\p{In typical engineer fashion I decided last weekend to quickly throw together my own github. This of course being a small weekend task. Let's compare feature lists:} + +\table{ + \tr{ + \th{} + \th{github} + \th{git.nega.tv} + } + \tr{ + \th{Git Hosting} + \td{☑} + \td{☑} + } + \tr{ + \th{SSH Repo Access} + \td{☑} + \td{☑} + } + \tr{ + \th{HTTPS Repo Access} + \td{☑} + \td{☑} + } + \tr{ + \th{Web Code View} + \td{☑} + \td{☑} + } + \tr{ + \th{Issue Tracking} + \td{☑} + \td{☐} + } + \tr{ + \th{Code Review} + \td{☑} + \td{☐} + } + \tr{ + \th{Automation} + \td{☑} + \td{☐} + } + \tr{ + \th{Project Wiki} + \td{☑} + \td{☐} + } + \tr{ + \th{Analytics and Social} + \td{☑} + \td{☐} + } + \tr{ + \th{AI Dogshit} + \td{☑} + \td{☐} + } +} + +\p{Recently I've found my enjoyment of the github platform rapidly declining, along with my expectation of future enjoyment. It seems some boffin at microsoft hq has commanded a hard pivot to AI and thus everything is going to get The Treatment. I also just expect they'll start to squeeze on what you actually get out of your "free" access. So I figured I'd migrate away, and vaguely considered a few options:} + +\ul{ + \li{[SourceHut](https://sourcehut.org/) is a bit too much for me, with its mailinglist based workflows, and requirements that don't really work for game development (tiny repos only).} + \li{[Codeberg](https://codeberg.org/) is open-source exclusive, which is cool but I don't want to be stuck with only public repos.} + \li{Self-hosted [Gitlab](https://about.gitlab.com/) is really just a pure clone of github, AI memes and all, which didn't feel like an improvement. Plus it's gigantic and largely unnecessary for my needs.} + \li{[Forgejo](https://forgejo.org/) has a cringe name.} + \li{I didn't actually think of [Bitbucket](https://bitbucket.org/product/) at the time, but urgh Atlassian. And more AI memes.} + \li{[Azure Git Repos](https://azure.microsoft.com/en-us/products/devops/repos) would maybe work, but I expect it's only a matter of time for that to just be github in a trenchcoat with all the same problems. Plus they just can't manage to be normal and have a normal pricing page.} +} + +\p{With all the reasonable options out of the way, the only solution left is to self-host our own thing. I already had this VPS sitting idle anyway and I hate free time so this leaves us with three major issues.} + +\subtree[xkxuvykxxpksnsql]{ + \title{SSH Access} + + \p{In the most basic variation ssh access for git repos is trivial, you create a new linux user for everybody with git access, and set their shell to [git-shell](https://git-scm.com/docs/git-shell) so they can't do anything much other than push changes. However, that approach is a bit questionable if you plan on having more than a handful of users, or more than a single server, or to give multiple users commit access to a single repository.} + + \p{I had no plan to do any of those things, but I wanted to support them anyway, and to isolate git access behind a 'git' user account in the way that the Real Github does it. We're serious engineers here. Once again there are a few turnkey options, most notably [gitosis](https://github.com/tv42/gitosis) and [gitolite](https://gitolite.com/gitolite/index.html), but we'll roll our own.} + + \p{There's a handy feature of [sshd](https://linux.die.net/man/8/sshd) which we will use, \code{command=} in the \code{AuthorizedKeys} file. Essentially, instead of executing the user's login shell (configured in \code{/etc/passwd}), we can configure — per authorized key — the specific command to run after authentication. This enables us to associate a "git user" with each public key, and interject our own code to decide whether to authorize a particular action based on that information.} + + \p{For example, git user account's \code{authorized_keys} file might look like:} + \codeblock{text}\verb<<<| +command="/home/git/git-shell-multiplex josh",restrict sk-ecdsa-nsa-backdoor AAAA... +command="/home/git/git-shell-multiplex sophie",restrict sk-ecdsa-nsa-backdoor AAAB... +command="/home/git/git-shell-multiplex bazza69",restrict sk-ecdsa-nsa-backdoor AAAC... +<<< + + \p{Basically the goal here is to only accept the three commands that a git remote actually requires, and to validate the permissions for the repo accessed against the username associated with the public key that we setup in \code{authorized_keys}. We just need to write \em{git-shell-multiplex} in Rust (of course) and we're off to the races. Rust is to some degree, a terrible choice for this kind of scripting, but we don't let bad ideas get in the way of doing whatever we like and \mark{you can find the resulting script [here!](https://git.nega.tv/?p=josh/git-shell-multiplex;a=blob;f=src/main.rs;h=db2d9cbc1c3dbf763a6204c8fc0a2b39f6c7e30f)}} + + \p{For permissions this currently re-uses the \code{git-daemon-export-ok} marker file to determine whether a project is \strong{public} (readable by anybody with an account, this file name will make sense later), and looks for a file called \code{git-shell-multiplex-contributors} for a list of users with write access in addition to the \strong{owner} of the repository (the one whose name is at the front of the path e.g. \code{josh/repo.git} would be owned by the user "josh").} + + \p{With these pieces in place, we can do \code{git clone git@nega.tv:josh/narcissus.git} and \code{git push origin main}! Not too hard after all.} + + \p{One downside of this is the configuration nightmare contained in \code{authorized_keys}. Since we have one user it's no big deal, but it's worth noting that you can also replace the \code{authorized_keys} \em{file} with an \code{authorized_keys} \em{command} using \code{AuthorizedKeysCommand} in [sshd_config](https://linux.die.net/man/5/sshd_config). This could then find the appropriate keys and user configuration directly from some database, and likewise the multiplexer itself could source the user and permission data from a central location rather than digging around looking for specially named files.} +} + +\subtree[zrqzpyrszyrsrzol]{ + \title{HTTPS Git Access} + + \p{Git has two different [http protocols](https://git-scm.com/docs/http-protocol), a simple v1 protocol, and a smart v2 protocol. This doesn't matter too much for us because I don't care about pushing via https, so we're basically just going to setup [git-http-backend](https://git-scm.com/docs/git-http-backend) in read-only configuration. This requires [fastcgi](wwkumtqzypttmwql) to be configured!} + + \p{My server is using [nginx](https://nginx.org/), and the configuration shown will reflect that.} + + \code{nginx.conf (excerpt)} + \codeblock{plain}\verb<<<| +# requests that need to go to git-http-backend +location ~ ^(.*)\.git/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ { + include fastcgi_params; + gzip off; + fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend; + fastcgi_param PATH_INFO $1/$2; + fastcgi_param GIT_PROJECT_ROOT /var/git; + fastcgi_param REMOTE_USER $remote_user; + fastcgi_pass unix:/run/fcgiwrap.sock; +} +<<< + + \p{In short, we filter out a bunch of git-specific paths and punt them to \code{git-http-backend}. Of special note is the captures in the location regex, they're reconstructed into \code{PATH_INFO} somewhat strangely so that we can expose a http url in the form \code{https://git.nega.tv/josh/git-shell-multiplex.git}, and have it find the repo folder itself at \code{/var/git/josh/git-shell-multiplex/}, without the \code{.git} suffix.} + + \p{\code{git-http-backend} looks for the file \code{git-daemon-export-ok} in a repo, and only allows access to those with the marker file. This actually leaks the existance of private repos, since it reports 'no permissions' rather than 'not found' but for our purposes this isn't a big deal.} +} + +\subtree[xnoxronsnxroorxy]{ + \title{Web Interface} + + \p{There are two simple options for a web based repository viewer, [cgit](https://git.zx2c4.com/cgit/about/), and [gitweb](https://git-scm.com/docs/gitweb). Since gitweb is hosted as part of the git distribution I chose that one, but honestly I'm not super excited about either option. This requires [fastcgi](wwkumtqzypttmwql).} + + \p{My server is using [nginx](https://nginx.org/), and the configuration shown will reflect that.} + + \code{nginx.conf (excerpt)} + \codeblock{plain}\verb<<<| +location /gitweb.cgi { + include fastcgi_params; + gzip off; + fastcgi_param SCRIPT_FILENAME /var/www/git/gitweb.cgi; + fastcgi_param PATH_INFO $uri; + fastcgi_param GITWEB_CONFIG /etc/gitweb.conf; + fastcgi_pass unix:/run/fcgiwrap.sock; +} + +location / { + root /var/www/git; + index gitweb.cgi; +} +<<< + + \p{There's nothing really interesting in the config here, in \code{gitweb.conf} we enable syntax highlighting and that's about it. Everything is as it comes. gitweb will only expose repositories which contain the \code{git-daemon-export-ok} marker file.} +} + +\subtree[wwkumtqzypttmwql]{ + \title{FastCGI Setup} + + \p{Honestly I'm somewhat baffled by the fact that attempting to get CGI to work, which is one of the first things I ever did on a webserver back when I was like 15 years old, still continues to be a thing which exists in the current day. We invented the iPhone, and haven't invented a way to never look at CGI scripts again. Incredible.} + + \p{Both [gitweb](xnoxronsnxroorxy), and [git-http-backend](zrqzpyrszyrsrzol) use CGI, so we're going to set it up to play nice with the git user and the httpd user, and we're going to do all of this in a systemd service triggered by a systemd socket.} + + \code{/etc/systemd/system/fcgiwrap.socket} + \codeblock{plain}\verb<<<| +[Unit] +Description=fcgiwrap Socket + +[Socket] +ListenStream=/run/fcgiwrap.sock + +[Install] +WantedBy=sockets.target +<<< + +\code{/etc/systemd/system/fcgiwrap.service} + +\codeblock{plain}\verb<<<| +[Unit] +Description=Simple CGI Server +After=nss-user-lookup.target + +[Service] +ExecStart=/usr/sbin/fcgiwrap +User=git +Group=nginx +StandardError=syslog + +[Install] +Also=fcgiwrap.socket +<<< + + \p{Take note of the user and group of the service, the user being git avoids issues with the git repository being accessed (via gitweb) from a user that does not own the repository, and the group being nginx gives access to the socket to the nginx service which actually hosts everything. This is probably not strictly ideal, you maybe want to add one or the other group to one or the other user instead, but it was enough to get us up and running.} + + \p{Also note the \codeinline{plain}{StandardError=syslog} line. You're going to want this when you try to debug why the stupid CGI script isn't working properly.} +} + +\subtree[uyxuonuyzzyxxlwv]{ + \title{Future Work} + + \p{This is enough to achieve the most basic basics of a scm host. It's good enough for me, but I wouldn't mind having some more bells and whistles.} + + \ul{ + \li{Issue Tracker.} + \li{Code Review.} + \li{Build automation, and BORS equivalent.} + \li{Web view with code analysis. Would be nice to somehow plug rust-analyzer into the code view so it's actually explorable.} + \li{Beautiful Rust monolith web application instead of nightmare configuration file soup.} + } + + \p{However these are all significantly more work than is achievable in a single weekend, so they'll have to wait until next weekend.} +} + +\p{Finally, you can see the fruits of my labor over at [git.nega.tv](https://git.nega.tv/)} \ No newline at end of file diff --git a/trees/wzvmmkrtxtwlzyrr.tree b/trees/wzvmmkrtxtwlzyrr.tree index a7fb275..2d188d9 100644 --- a/trees/wzvmmkrtxtwlzyrr.tree +++ b/trees/wzvmmkrtxtwlzyrr.tree @@ -2,11 +2,12 @@ \author{joshuarsimmons} \date{2025-05-27} \tag{blog} -\import{common-macros} + +\import{macros} \p{They say change is as good as a holiday, it isn't, but anyway here's another round of the semi-regular website refresh. I like the idea behind a web of smaller notes, and to that end this website is a “[forest](https://www.forester-notes.org/tfmt-000V/)” created with [Forester](https://www.forester-notes.org/index/).} -\p{I wasn't really excited about incremental tree identifiers so instead I generate 16 character identifiers using the alphabet \code{[k-z]}. This is an idea that I stole from [jujutsu](jujutsu)'s change ids.} +\p{I wasn't really excited about incremental tree identifiers so instead I generate 16 character identifiers using the alphabet \codeinline{plain}{[k-z]}. This is an idea that I stole from [jujutsu](jujutsu)'s change ids.} \p{\strong{TODO:}} \ul{ diff --git a/trees/xtwokuxkmmmosmmw.tree b/trees/xtwokuxkmmmosmmw.tree index a39fbe3..40087df 100644 --- a/trees/xtwokuxkmmmosmmw.tree +++ b/trees/xtwokuxkmmmosmmw.tree @@ -1,12 +1,13 @@ \title{Zeroed Memory} \author{joshuarsimmons} \date{2025-05-28} -\import{common-macros} + +\import{macros} \p{When CPU memory is provided from the kernel to a desktop application, it's well-defined that the contents of that memory are zeroed. While the mechanics differ between operating systems, this is safe for an application to rely on, and is generally just important for security when pages could come from other processes. With video memory allocations it's... less straightforward.} -\p{On Windows, fresh pages from the OS should always be zeroed. DX12 specifies that newly allocated memory is always zeroed, and then provides an opt-out via \code{D3D12_HEAP_FLAG_CREATE_NOT_ZEROED}, where drivers can recycle memory within a single process. Practically speaking this means that Vulkan drivers on Windows also default to zeroing video memory before handing it off to an application.} +\p{On Windows, fresh pages from the OS should always be zeroed. DX12 specifies that newly allocated memory is always zeroed, and then provides an opt-out via \codeinline{plain}{D3D12_HEAP_FLAG_CREATE_NOT_ZEROED}, where drivers can recycle memory within a single process. Practically speaking this means that Vulkan drivers on Windows also default to zeroing video memory before handing it off to an application.} \p{On Linux the situation is the opposite. Not only do drivers avoid clearing memory within a process, but until recently [amdgpu](amdgpu) did not even [clear](https://lore.kernel.org/amd-gfx/da90e4c0-067b-2ffe-01df-f59c2b7ec556@amd.com/) [pages](https://lore.kernel.org/amd-gfx/20240829172645.1678920-1-alexander.deucher@amd.com/) from other processes. This means it's vitally important for you to manually clear your memory where such a thing is load-bearing. It's a common enough source of problems that mesa has a frequently applied [driconf quirk](https://gitlab.freedesktop.org/mesa/mesa/-/blob/fe2c93a78893419bd90757c5ebaf17f33c05a976/src/util/driconf.h#L535-537) which force zeroes video memory.} -\p{To bring order to the chaos, [VK_EXT_zero_initialize_device_memory](vk-ext-zero-device-memory) is a new extension which introduces a flag, \code{VK_MEMORY_ALLOCATE_ZERO_INITIALIZE_BIT_EXT}, enabling applications to request the driver perform zero initialization on a per-allocation basis.} \ No newline at end of file +\p{To bring order to the chaos, [VK_EXT_zero_initialize_device_memory](vk-ext-zero-device-memory) is a new extension which introduces a flag, \codeinline{plain}{VK_MEMORY_ALLOCATE_ZERO_INITIALIZE_BIT_EXT}, enabling applications to request the driver perform zero initialization on a per-allocation basis.} \ No newline at end of file -- 2.49.0