HeavyThing - formatter.inc

Jeff Marrison

Table of functions

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; formatter.inc: a convenience object to format output into buffers or strings
	;
	; this provides "printf-like" string formatting with dynamic arguments, in a "reuseable" way
	; maximum dynamic arguments is of course register-dependent (e.g. doubles == xmm0..15, normal
	; == rsi, rdx, rcx, r8, r9, r10, r11 (noting here we are happy to extend the 6 arg normal max)
	; and you can of course "nest" them by using add_string and a second/third/whatever formatter
	; chained together
	;
	; it is a programmer error to add more than 7 normal register items, or 16 normal double items
	; (because a call then to formatter$doit would crash)
	;

formatter_regargs_ofs = 0		; int of how many register arguments we are expecting
formatter_xmmargs_ofs = 8		; int of how many xmm arguments we are expecting
formatter_items_ofs = 16		; list of formatitems
formatter_rargs_ofs = 24		; an empty (always) list for argument handling in formatter$doit
formatter_xargs_ofs = 32		; an empty (always) list for argument handling in formatter$doit
formatter_options_ofs = 40		; bool as to whether to add space between items
formatter_size = 48

if used formatter$new | defined include_everything
	; single argument in edi: bool as to whether to add space between items
falign
formatter$new:
	prolog	formatter$new
	push	rdi
	call	list$new
	push	rax
	call	list$new
	push	rax
	call	list$new
	push	rax
	mov	edi, formatter_size
	call	heap$alloc_clear
	pop	rcx rdx rsi rdi
	mov	[rax+formatter_items_ofs], rsi
	mov	[rax+formatter_rargs_ofs], rdx
	mov	[rax+formatter_xargs_ofs], rcx
	mov	[rax+formatter_options_ofs], edi
	epilog

end if

formatitem_type_ofs = 0
formatitem_width_ofs = 8
formatitem_flags_ofs = 16
formatitem_prec_ofs = 24
formatitem_value_ofs = 32

formatitem_size = 40

; formatitem_type_ofs will be one of:
formatitem_static = 0
formatitem_string = 1
formatitem_boolean = 2
formatitem_integer = 3
formatitem_unsigned = 4
formatitem_double = 5
formatitem_buffer = 6
formatitem_bufferhex = 7
formatitem_date = 8
formatitem_time = 9
formatitem_datetime = 10
formatitem_duration = 11
formatitem_rfc3164datetime = 12
formatitem_rfc5322datetime = 13
formatitem_quotedstring = 14
formatitem_yyyymmdd = 15


if used formatter$destroy | defined include_everything
	; single argument in rdi: our formatter object
falign
formatter$destroy:
	prolog	formatter$destroy
	push	rdi
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, .itemdeath
	call	list$clear
	mov	rsi, [rsp]
	mov	rdi, [rsi+formatter_items_ofs]
	call	heap$free
	mov	rsi, [rsp]
	mov	rdi, [rsi+formatter_rargs_ofs]
	call	heap$free
	mov	rsi, [rsp]
	mov	rdi, [rsi+formatter_xargs_ofs]
	call	heap$free
	pop	rdi
	call	heap$free
	epilog
falign
.itemdeath:
	; this is called for each item in the list rdi == our formatitem
	cmp	qword [rdi+formatitem_value_ofs], 0
	jne	.itemdeath_withvalue
	call	heap$free
	ret
calign
.itemdeath_withvalue:
	push	rdi
	mov	rdi, [rdi+formatitem_value_ofs]
	call	heap$free
	pop	rdi
	call	heap$free
	ret

end if

if used formatter$add_static | defined include_everything
	; two arguments: rdi == formatter object, rsi == static string (we make a copy of it)
falign
formatter$add_static:
	prolog	formatter$add_static
	push	rdi
	mov	rdi, rsi
	call	string$copy
	push	rax
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_static
	mov	[rax+formatitem_value_ofs], rsi
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_string | defined include_everything
	; two arguments: rdi == our formatter object, esi == 0 == dynamic width, else fixed width
falign
formatter$add_string:
	prolog	formatter$add_string
	push	rdi rsi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_string
	mov	dword [rax+formatitem_width_ofs], esi
	add	dword [rdi+formatter_regargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_boolean | defined include_everything
	; two arguments: rdi == our formatter object, esi == 0 == dynamic width, else fixed width
falign
formatter$add_boolean:
	prolog	formatter$add_boolean
	push	rdi rsi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_boolean
	mov	dword [rax+formatitem_width_ofs], esi
	add	dword [rdi+formatter_regargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_integer | defined include_everything
	; three arguments: rdi == our formatter object, esi == 0 == decimal, 1 == decimal with thousands separator, 2 == hex output, 3 == hex output with 0x preface, edx == 0 == dynamic width, else fixed width
falign
formatter$add_integer:
	prolog	formatter$add_integer
	push	rdi rsi rdx
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdx rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_integer
	mov	dword [rax+formatitem_width_ofs], edx
	mov	dword [rax+formatitem_flags_ofs], esi
	add	dword [rdi+formatter_regargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_unsigned | defined include_everything
	; three arguments: rdi == our formatter object, esi == 0 == decimal, 1 == decimal with thousands separator, 2 == hex output, 3 == hex output with 0x preface, edx == 0 == dynamic width, else fixed width
falign
formatter$add_unsigned:
	prolog	formatter$add_unsigned
	push	rdi rsi rdx
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdx rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_unsigned
	mov	dword [rax+formatitem_width_ofs], edx
	mov	dword [rax+formatitem_flags_ofs], esi
	add	dword [rdi+formatter_regargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_double | defined include_everything
	; five arguments: rdi == our formatter object, esi == mode, edx == decimal digits precision, ecx == 0 == dynamic width, else fixed width, r8d == bool for thousands separator
	; mode should be one of: double_string_normal, double_string_fixed, double_string_precision, double_string_exponential (see string$from_double for further details)
falign
formatter$add_double:
	prolog	formatter$add_double
	shl	r8d, 8
	or	esi, r8d
	push	rdi rsi rdx rcx
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rcx rdx rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_double
	mov	dword [rax+formatitem_width_ofs], ecx
	mov	dword [rax+formatitem_flags_ofs], esi
	mov	dword [rax+formatitem_prec_ofs], edx
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_buffer | defined include_everything
	; single argument: rdi == our formatter object
	; this assumes the argument is a buffer that contains UTF8, so when formatter$doit comes along, it will convert the buffer from UTF8
falign
formatter$add_buffer:
	prolog	formatter$add_buffer
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_buffer
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_bufferhex | defined include_everything
	; single argument: rdi == our formatter object
	; this turns whatever is in the buffer into a string from bin to hex
falign
formatter$add_bufferhex:
	prolog	formatter$add_bufferhex
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_bufferhex
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_date | defined include_everything
	; single argument: rdi == our formatter object
	; this turns an xmm argument, which is a truncated JD timestamp, into ISO8601/RFC3339 "full-date" string, which is YYYY-MM-DD
	; see date.inc for truncated jd times, durations, etc.
falign
formatter$add_date:
	prolog	formatter$add_date
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_date
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_time | defined include_everything
	; single argument: rdi == our formatter object
	; this turns an xmm argument, which is a truncated JD timestamp, into ISO8601/RFC3339 "full-time" string, which is HH:MM:SS.ssssssZ
	; see date.inc for truncated jd times, durations, etc.
falign
formatter$add_time:
	prolog	formatter$add_time
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_time
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_datetime | defined include_everything
	; two arguments: rdi == our formatter object, esi == bool as to whether to enclose it in [] brackets
	; this turns an xmm argument, which is a truncated JD timestamp, into ISO8601/RFC3339 "date-time" string, which is full-date "T" full-time
	; see date.inc for truncated jd times, durations, etc.
falign
formatter$add_datetime:
	prolog	formatter$add_datetime
	push	rdi rsi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_datetime
	mov	dword [rax+formatitem_flags_ofs], esi
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_duration | defined include_everything
	; two arguments: rdi == our formatter object, esi == minimum resolution, edx == fractional digits (or 0 for none)
	; ms resolution == fractional digits ignored (0.000s is what you get on the end)
	; duration is in DAYS (and is an xmm arg), min res is: 5 == weeks, 4 == days, 3 == hours, 2 == mins, 1 == secs, 0 == ms
	; see date.inc for truncated jd times, durations, etc.
falign
formatter$add_duration:
	prolog	formatter$add_duration
	push	rdi rsi rdx
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdx rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_duration
	mov	dword [rax+formatitem_prec_ofs], esi
	mov	dword [rax+formatitem_flags_ofs], edx
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_rfc3164datetime | defined include_everything
	; single argument: rdi == our formatter object
	; this turns an xmm argument, which is a truncated JD timestamp, into RFC3164 "TIMESTAMP" string, which is Mmm dD HH:MM:SS
falign
formatter$add_rfc3164datetime:
	prolog	formatter$add_rfc3164datetime
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_rfc3164datetime
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_rfc5322datetime | defined include_everything
	; single argument: rdi == our formatter object
	; this turns an xmm argument, which is a truncated JD timestamp, into RFC5322 "IMF-fixdate" string, which is Sun, 06 Nov 1994 09:49:37 GMT
falign
formatter$add_rfc5322datetime:
	prolog	formatter$add_rfc5322datetime
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_rfc5322datetime
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_quotedstring | defined include_everything
	; two arguments: rdi == our formatter object, esi == 0 == dynamic width, else fixed width
falign
formatter$add_quotedstring:
	prolog	formatter$add_quotedstring
	push	rdi rsi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rsi rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_quotedstring
	mov	dword [rax+formatitem_width_ofs], esi
	add	dword [rdi+formatter_regargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if

if used formatter$add_yyyymmdd | defined include_everything
	; single argument: rdi == our formatter object
	; this turns an xmm argument, which is a truncated JD timestamp, into YYYYMMDD
	; see date.inc for truncated jd times, durations, etc.
falign
formatter$add_yyyymmdd:
	prolog	formatter$add_yyyymmdd
	push	rdi
	mov	edi, formatitem_size
	call	heap$alloc_clear
	pop	rdi
	mov	dword [rax+formatitem_type_ofs], formatitem_yyyymmdd
	add	dword [rdi+formatter_xmmargs_ofs], 1
	mov	rdi, [rdi+formatter_items_ofs]
	mov	rsi, rax
	call	list$push_back
	epilog

end if


if used formatter$doit | defined include_everything
	; variable # of arguments depending on how it was setup of course, but rdi == formatter object at the very least
	; returns a new string in rax of the result
falign
formatter$doit:
	prolog	formatter$doit
	; we "precompile" our arguments into two lists such that traversing our items list becomes a simple pop_front operation
	; this does a fair bit of work, but makes for the final compilation stage being nice and easy
	push	r13 r14 r15
	mov	r13, rdi
	mov	eax, [rdi+formatter_regargs_ofs]
	jmp	qword [rax*8+.regdispatch]
dalign
.regdispatch:
	dq	.reg0, .reg1, .reg2, .reg3, .reg4, .reg5, .reg6, .reg7
dalign
.xmmdispatch:
	dq	.ready, .xmm1, .xmm2, .xmm3, .xmm4, .xmm5, .xmm6, .xmm7, .xmm8, .xmm9, .xmm10, .xmm11, .xmm12, .xmm13, .xmm14, .xmm15
calign
.reg0:
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg1:
	; our argument is sitting in rsi that we need to add to regargs
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg2:
	; rsi, rdx are the two arguments we need to deal with
	push	rdx
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg3:
	; rsi, rdx, rcx are the three arguments we need to deal with
	push	rcx rdx
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg4:
	; rsi, rdx, rcx, r8
	push	r8 rcx rdx
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg5:
	; rsi, rdx, rcx, r8, r9
	push	r9 r8 rcx rdx
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg6:
	; rsi, rdx, rcx, r8, r9, r10
	push	r10 r9 r8 rcx rdx
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]
calign
.reg7:
	; rsi, rdx, rcx, r8, r9, r10, r11
	push	r11 r10 r9 r8 rcx rdx
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	pop	rsi
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$push_back
	mov	eax, [r13+formatter_xmmargs_ofs]
	jmp	qword [rax*8+.xmmdispatch]


	; some notes here: we know that heap$alloc and list$push_back do not mash xmm regs, so we don't have to preserve them all beforehand
calign
.xmm1:
	; xmm0
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm2:
	; xmm0, xmm1
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm3:
	; xmm0, xmm1, xmm2
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm4:
	; xmm0, xmm1, xmm2, xmm3
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm5:
	; xmm0, xmm1, xmm2, xmm3, xmm4
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm6:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm7:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm8:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm9:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm10:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm11:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm10
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm12:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm10
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm11
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm13:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm10
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm11
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm12
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm14:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm10
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm11
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm12
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm13
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm15:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm10
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm11
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm12
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm13
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm14
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	jmp	.ready
calign
.xmm16:
	; xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15
	movq	rsi, xmm0
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm1
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm2
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm3
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm4
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm5
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm6
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm7
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm8
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm9
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm10
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm11
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm12
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm13
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm14
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	movq	rsi, xmm15
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$push_back
	; fallthrough to ready
calign
.ready:
	; rargs and xargs are both populated and we don't need to worry about registers/etc
	; so our formatter object is sitting in r13, r14 and r15 are unpopulated
	; we'll need one as a buffer
	call	buffer$new
	mov	r14, rax
	mov	rdi, [r13+formatter_items_ofs]
	mov	rsi, .peritem
	call	list$foreach
	; now turn the buffer into a string for our return
	mov	rdi, [r14+buffer_itself_ofs]
	mov	rsi, [r14+buffer_length_ofs]
if string_bits = 32
	call	string$from_utf32
else
	call	string$from_utf16
end if
	mov	rdi, r14
	mov	r14, rax
	call	buffer$destroy
	mov	rax, r14
	pop	r15 r14 r13
	epilog
falign
.peritem:
	; called with a single argument in rdi that is our formatitem, r13 == our formatter, r14 == destination buffer
	mov	r15, rdi
	cmp	qword [r14+buffer_length_ofs], 0
	je	.firstitem
	cmp	dword [r13+formatter_options_ofs], 0
	je	.firstitem
	; otehrwise, buffer is non-empty, and we are to add a space between items
	mov	rdi, r14
	mov	esi, ' '
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	mov	eax, [r15+formatitem_type_ofs]
	jmp	qword [rax*8+.peritemdispatch]
calign
.firstitem:
	mov	eax, [r15+formatitem_type_ofs]
	jmp	qword [rax*8+.peritemdispatch]
dalign
.peritemdispatch:
	dq	.item_static, .item_string, .item_boolean, .item_integer, .item_unsigned, .item_double, .item_buffer, .item_bufferhex, .item_date, .item_time, .item_datetime, .item_duration, .item_rfc3164datetime, .item_rfc5322datetime, .item_quotedstring, .item_yyyymmdd
calign
.item_static:
	; we can't use buffer$append_string, because that turns the string into utf8 first
	mov	rdi, r14
	mov	rsi, [r15+formatitem_value_ofs]
	mov	rdx, [rsi]			; its length in _characters_
	add	rsi, 8				; actual start of the buffer
if string_bits = 32
	shl	rdx, 2				; length in bytes
else
	shl	rdx, 1				; length in bytes
end if
	call	buffer$append
	ret
calign
.item_string:
	; pop our string argument from the rargs, check width
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	; if width == 0, just add it
	cmp	dword [r15+formatitem_width_ofs], 0
	je	.item_string_nopad
	; otherwise, we need to lpad our string, noting here we do _not_ do truncation
calign
.item_string_pad:
	mov	rdi, rax
	mov	esi, [r15+formatitem_width_ofs]
	mov	edx, ' '
	call	string$lpad
	push	rax
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free
	ret
calign
.item_string_nopad:
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	ret
calign
.item_boolean:
	; pop our bool argument from the rargs, check width
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	mov	rcx, rax
	mov	rax, .truestr
	mov	rdx, .falsestr
	test	rcx, rcx
	cmovz	rax, rdx
	; rax now has either true or false string, if width == 0, just add it
	cmp	dword [r15+formatitem_width_ofs], 0
	je	.item_string_nopad
	jmp	.item_string_pad
cleartext .truestr, 'true'
cleartext .falsestr, 'false'
cleartext .hexpreface, '0x'
cleartext .thousandseparator, ','
calign
.item_integer:
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	mov	rdi, rax
	mov	esi, 10
	mov	edx, 16
	cmp	dword [r15+formatitem_flags_ofs], 1
	cmova	esi, edx
	call	string$from_int
calign
.numeric_string_ready:
	push	rax
	cmp	dword [r15+formatitem_flags_ofs], 3
	jne	.numeric_string_nopreface
	; otherwise, add a 0x to the beginning of our string
	mov	rdi, .hexpreface
	mov	rsi, rax
	call	string$concat
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
calign
.numeric_string_nopreface:
	cmp	dword [r15+formatitem_flags_ofs], 1
	jne	.numeric_string_checkwidth
	; we need to add thousands separators to our number
	mov	rdi, rax
	call	string$reverse
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	; so now, our string is reversed, e.g.: -12345 == 54321-
	xor	ecx, ecx
	push	rcx			; our starting offset
calign
.numeric_string_addthou:
	add	dword [rsp], 3
	mov	rax, [rsp+8]		; our string
	mov	ecx, [rsp]
	; deal with the special case for when our only remaining character is the sign
	mov	edx, [rax]
	sub	edx, ecx
	cmp	edx, 1
	jne	.numeric_string_notone
	; check to see if the remaining character is '-', if so, bailout
	mov	edx, [rax]
	sub	edx, 1
if string_bits = 32
	cmp	dword [rax+rdx*4+8], '-'
else
	cmp	word [rax+rdx*2+8], '-'
end if
	je	.numeric_string_thoudone
calign
.numeric_string_notone:
	cmp	ecx, [rax]
	jae	.numeric_string_thoudone
	mov	rdi, rax
	xor	esi, esi		; start offset
	mov	rdx, rcx		; end offset
	call	string$substring
	push	rax
	mov	rdi, rax
	mov	rsi, .thousandseparator
	call	string$concat
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	; so now we have up to our thou separator and a comma joined together, now we need to rejoin it with the rest of the string at [rsp+16]
	mov	rdi, [rsp+16]		; our original string
	mov	rsi, [rsp+8]		; our comma placement
	mov	rdx, -1			; length to the end
	call	string$substr
	push	rax
	mov	rdi, [rsp+8]
	mov	rsi, rax
	call	string$concat
	mov	rdi, [rsp+24]		; our original string
	mov	[rsp+24], rax
	call	heap$free
	pop	rdi
	call	heap$free
	pop	rdi
	call	heap$free
	add	dword [rsp], 1		; +1 char for our newly added comma
	jmp	.numeric_string_addthou
calign
.numeric_string_thoudone:
	pop	rcx			; we don't need our counter anymore
	mov	rdi, [rsp]
	call	string$reverse
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	; so now our string is back in the right order, width is next
calign
.numeric_string_checkwidth:
	cmp	dword [r15+formatitem_width_ofs], 0
	je	.numeric_string_nopad
	mov	rdi, rax
	mov	esi, [r15+formatitem_width_ofs]
	mov	edx, ' '
	call	string$lpad
	push	rax
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free
	pop	rdi
	call	heap$free
	ret
calign
.numeric_string_nopad:
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free		; get rid of our string
	ret
calign
.item_unsigned:
	; same as integer only without the sign
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	mov	rdi, rax
	mov	esi, 10
	mov	edx, 16
	cmp	dword [r15+formatitem_flags_ofs], 1
	cmova	esi, edx
	call	string$from_unsigned
	jmp	.numeric_string_ready
calign
.item_double:
	; so, [r15+formatitem_width_ofs] == 0 == dynamic width, 1 == fixed width
	; [r15+formatitem_flags_ofs] == low byte is mode, next byte is bool for thousands separator or not
	; [r15+formatitem_prec_ofs] == decimal digits precision
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	mov	edi, [r15+formatitem_flags_ofs]
	mov	esi, [r15+formatitem_prec_ofs]
	and	edi, 0xff
	call	string$from_double
	push	rax
	mov	ecx, [r15+formatitem_flags_ofs]
	test	ecx, 0xff00
	jz	.numeric_string_checkwidth
	; otherwise, we need to add thousands separators to this one, exactly like the numeric string version, only we need to
	; start at the decimal (if any) +1
	mov	rdi, rax
	call	string$reverse
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	; so now, our string is reversed, e.g.: -12345.01 == 10.54321-
	xor	ecx, ecx
	push	rcx			; our starting offset
	mov	rdi, [rsp+8]
	mov	esi, '.'
	call	string$indexof_charcode
	xor	ecx, ecx
	mov	rdx, rax
	add	rdx, 1
	cmp	rax, -1
	cmovne	ecx, edx
	mov	[rsp], ecx
	jmp	.numeric_string_addthou
calign
.item_buffer:
	; this assumes the buffer contents are UTF8, and converts it accordingly
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	mov	rdi, [rax+buffer_itself_ofs]
	mov	rsi, [rax+buffer_length_ofs]
	call	string$from_utf8
	push	rax
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free		; get rid of our string
	ret
calign
.item_bufferhex:
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	mov	rdi, [rax+buffer_itself_ofs]
	mov	rsi, [rax+buffer_length_ofs]
	call	string$from_bintohex
	push	rax
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free		; get rid of our string
	ret

macro format_addstring {
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free		; get rid of our string
}

macro format_addchar {
	; char is assumed to already be sitting in esi
	mov	rdi, r14
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
}

calign
.item_date:
	; YYYY-MM-DD
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	sub	rsp, 16
	mov	rdi, rsp
	call	timestamp$to_datetime
	; so now our struct at [rsp] is valid, do our string
	movzx	edi, word [rsp+_datetime_yearofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	format_addstring
	mov	esi, '-'
	format_addchar
	movzx	edi, word [rsp+_datetime_monthofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, '-'
	format_addchar
	movzx	edi, word [rsp+_datetime_dayofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	; done
	add	rsp, 16
	ret
calign
.item_time:
	; HH:MM:SS.sssZ
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	sub	rsp, 16
	mov	rdi, rsp
	call	timestamp$to_datetime
	; so now our struct at [rsp] is valid, do our string
	movzx	edi, word [rsp+_datetime_hourofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	movzx	edi, word [rsp+_datetime_minofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	; now, our usecs is expressed as actual microseconds, not TIME-SECFRAC, so a usecs value on our side of 3 needs to be spewed out as 0.000003
	; so the simplest way to do that (albeit not the most efficient) is to convert secs and usecs both to doubles, and then use double format to do it
	; but ONLY if usecs is nonzero
	cmp	dword [rsp+_datetime_usecofs], 0
	jne	.item_time_fraction
	; otherwise, usecs is zero, so just output the sec and a trailing Z and be done with it
	movzx	edi, word [rsp+_datetime_secofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, 'Z'
	format_addchar
	; done
	add	rsp, 16
	ret
dalign
.onem	dq	1000000.0f
calign
.item_time_fraction:
	; because we are using double conversion here, we have to add a leading zero manually if the secs is not big enough
	movzx	ecx, word [rsp+_datetime_secofs]
	cmp	ecx, 10
	jae	.item_time_fraction_secsokay
	mov	esi, '0'
	format_addchar
	movzx	ecx, word [rsp+_datetime_secofs]
calign
.item_time_fraction_secsokay:
	mov	eax, [rsp+_datetime_usecofs]
	cvtsi2sd xmm0, ecx
	cvtsi2sd xmm1, eax
	divsd	xmm1, [.onem]
	addsd	xmm0, xmm1
	mov	edi, double_string_normal
	mov	esi, 6
	call	string$from_double
	push	rax
	format_addstring
	mov	esi, 'Z'
	format_addchar
	; done
	add	rsp, 16
	ret
calign
.item_datetime:
	; if formatter_datetime_fractional, then YYYY-MM-DDTHH:MM:SS.sssZ, else YYYY-MM-DDTHH:MM:SSZ
	cmp	dword [r15+formatitem_flags_ofs], 0
	je	.item_datetime_nobracket1
	mov	rdi, r14
	mov	esi, '['
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
calign
.item_datetime_nobracket1:
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	sub	rsp, 16
	mov	rdi, rsp
	call	timestamp$to_datetime
	; so now our struct at [rsp] is valid, do our string
	movzx	edi, word [rsp+_datetime_yearofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	format_addstring
	mov	esi, '-'
	format_addchar
	movzx	edi, word [rsp+_datetime_monthofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, '-'
	format_addchar
	movzx	edi, word [rsp+_datetime_dayofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, 'T'
	format_addchar
	movzx	edi, word [rsp+_datetime_hourofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	movzx	edi, word [rsp+_datetime_minofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar

if formatter_datetime_fractional
	mov	edi, [rsp+_datetime_usecofs]
	movzx	esi, word [rsp+_datetime_secofs]
	cmp	esi, 10
	jb	.item_datetime_lpad
	cvtsi2sd xmm1, edi
	cvtsi2sd xmm0, esi
	mulsd	xmm1, [.millions]
	addsd	xmm0, xmm1
	mov	edi, double_string_fixed
	mov	esi, 6
	call	string$from_double
	push	rax
	format_addstring
else
	movzx	edi, word [rsp+_datetime_secofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
end if
	mov	esi, 'Z'
	format_addchar
	cmp	dword [r15+formatitem_flags_ofs], 0
	je	.item_datetime_nobracket2
	mov	rdi, r14
	mov	esi, ']'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	; done
	add	rsp, 16
	ret
calign
.item_datetime_lpad:
	cvtsi2sd xmm1, edi
	cvtsi2sd xmm0, esi
	mulsd	xmm1, [.millions]
	addsd	xmm0, xmm1
	mov	edi, double_string_fixed
	mov	esi, 6
	call	string$from_double
	push	rax
	mov	rdi, rax
	mov	esi, 9
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, 'Z'
	format_addchar
	cmp	dword [r15+formatitem_flags_ofs], 0
	je	.item_datetime_nobracket2
	mov	rdi, r14
	mov	esi, ']'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	; done
	add	rsp, 16
	ret

calign
.item_datetime_nobracket2:
	; done
	add	rsp, 16
	ret
dalign
.millions	dq	0.000001f
calign
.item_duration:
	; depending on prec, outputs string duration, see format$duration in date.inc for more details
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	mov	edi, [r15+formatitem_prec_ofs]
	mov	esi, [r15+formatitem_flags_ofs]
	call	format$duration
	push	rax
	format_addstring
	ret
dalign
.abbrmonths:
if string_bits = 32
	dd	'J', 'a', 'n', ' '
	dd	'F', 'e', 'b', ' '
	dd	'M', 'a', 'r', ' '
	dd	'A', 'p', 'r', ' '
	dd	'M', 'a', 'y', ' '
	dd	'J', 'u', 'n', ' '
	dd	'J', 'u', 'l', ' '
	dd	'A', 'u', 'g', ' '
	dd	'S', 'e', 'p', ' '
	dd	'O', 'c', 't', ' '
	dd	'N', 'o', 'v', ' '
	dd	'D', 'e', 'c', ' '
else
	dw	'J', 'a', 'n', ' '
	dw	'F', 'e', 'b', ' '
	dw	'M', 'a', 'r', ' '
	dw	'A', 'p', 'r', ' '
	dw	'M', 'a', 'y', ' '
	dw	'J', 'u', 'n', ' '
	dw	'J', 'u', 'l', ' '
	dw	'A', 'u', 'g', ' '
	dw	'S', 'e', 'p', ' '
	dw	'O', 'c', 't', ' '
	dw	'N', 'o', 'v', ' '
	dw	'D', 'e', 'c', ' '
end if
calign
.item_rfc3164datetime:
	; "TIMESTAMP" says Mmm dD HH:MM:SS
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	sub	rsp, 16
	mov	rdi, rsp
	call	timestamp$to_datetime
	; Mmm is first up

	movzx	esi, word [rsp+_datetime_monthofs]
	sub	esi, 1
	mov	rdi, r14
if string_bits = 32
	mov	edx, 16
	shl	esi, 4
else
	mov	edx, 8
	shl	esi, 3
end if
	add	rsi, .abbrmonths
	call	buffer$append

	; next up is space-padded day
	movzx	edi, word [rsp+_datetime_dayofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, ' '
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	; we need a space
	mov	esi, ' '
	format_addchar
	; and our time
	movzx	edi, word [rsp+_datetime_hourofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	movzx	edi, word [rsp+_datetime_minofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	movzx	edi, word [rsp+_datetime_secofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	; done
	add	rsp, 16
	ret
dalign
.abbrdows:
if string_bits = 32
	; julian date to week:
	dd	'S', 'u', 'n', ','
	dd	'M', 'o', 'n', ','
	dd	'T', 'u', 'e', ','
	dd	'W', 'e', 'd', ','
	dd	'T', 'h', 'u', ','
	dd	'F', 'r', 'i', ','
	dd	'S', 'a', 't', ','
dalign
.gmt:
	dd	' ', 'G', 'M', 'T'
else
	; julian date to week:
	dw	'S', 'u', 'n', ','
	dw	'M', 'o', 'n', ','
	dw	'T', 'u', 'e', ','
	dw	'W', 'e', 'd', ','
	dw	'T', 'h', 'u', ','
	dw	'F', 'r', 'i', ','
	dw	'S', 'a', 't', ','
dalign
.gmt:
	dw	' ', 'G', 'M', 'T'
end if
calign
.item_rfc5322datetime:
	; "IMF-fixdate" says: Sun, 06 Nov 1994 08:49:37 GMT
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	; to get our DOW, we need that floored, converted to an integer, and % 7
	; TODO: verify this is right, haha
	push	rax
	movq	xmm0, rax
	call	floor
	cvtsd2si	rax, xmm0
	mov	ecx, 7
	xor	edx, edx
	div	ecx
	; remainder sitting in edx
	mov	esi, edx
	mov	rdi, r14
if string_bits = 32
	mov	edx, 16
	shl	esi, 4
else
	mov	edx, 8
	shl	esi, 3
end if
	add	rsi, .abbrdows
	call	buffer$append
	pop	rax
	movq	xmm0, rax
	sub	rsp, 16
	mov	rdi, rsp
	call	timestamp$to_datetime
	mov	esi, ' '
	format_addchar
	; next up is zero-padded day
	movzx	edi, word [rsp+_datetime_dayofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring	; pops it off the stack
	; we need a space
	mov	esi, ' '
	format_addchar
	; Mmm is first up
	movzx	esi, word [rsp+_datetime_monthofs]
	sub	esi, 1
	mov	rdi, r14
if string_bits = 32
	mov	edx, 16
	shl	esi, 4
else
	mov	edx, 8
	shl	esi, 3
end if
	add	rsi, .abbrmonths
	call	buffer$append
	; next up, full year
	movzx	edi, word [rsp+_datetime_yearofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	format_addstring
	; we need another space
	mov	esi, ' '
	format_addchar
	; and our time
	movzx	edi, word [rsp+_datetime_hourofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	movzx	edi, word [rsp+_datetime_minofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	esi, ':'
	format_addchar
	movzx	edi, word [rsp+_datetime_secofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	mov	rdi, r14
	mov	rsi, .gmt
if string_bits = 32
	mov	edx, 16
else	
	mov	edx, 8
end if
	call	buffer$append
	; done
	add	rsp, 16
	ret
calign
.item_quotedstring:
	; pop our string argument from the rargs, check width
	mov	rdi, r14
	mov	esi, '"'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	mov	rdi, [r13+formatter_rargs_ofs]
	call	list$pop_front
	; if width == 0, just add it
	cmp	dword [r15+formatitem_width_ofs], 0
	je	.item_quotedstring_nopad
	; otherwise, we need to lpad our string, noting here we do _not_ do truncation
calign
.item_quotedstring_pad:
	mov	rdi, rax
	mov	esi, [r15+formatitem_width_ofs]
	mov	edx, ' '
	call	string$lpad
	push	rax
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	pop	rdi
	call	heap$free
	mov	rdi, r14
	mov	esi, '"'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	ret
calign
.item_quotedstring_nopad:
	mov	rdi, r14
	mov	rsi, rax
	mov	rdx, [rax]
	add	rsi, 8
if string_bits = 32
	shl	rdx, 2
else
	shl	rdx, 1
end if
	call	buffer$append
	mov	rdi, r14
	mov	esi, '"'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	ret
calign
.item_yyyymmdd:
	; YYYYMMDD
	mov	rdi, [r13+formatter_xargs_ofs]
	call	list$pop_front
	movq	xmm0, rax
	sub	rsp, 16
	mov	rdi, rsp
	call	timestamp$to_datetime
	; so now our struct at [rsp] is valid, do our string
	movzx	edi, word [rsp+_datetime_yearofs]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	format_addstring
	movzx	edi, word [rsp+_datetime_monthofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	movzx	edi, word [rsp+_datetime_dayofs]
	mov	esi, 10
	call	string$from_unsigned
	; this one needs lpadded
	push	rax
	mov	rdi, rax
	mov	esi, 2
	mov	edx, '0'
	call	string$lpad
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	format_addstring
	; done
	add	rsp, 16
	ret
	

end if