io.inc
8l_HeavyThing/io.inc
	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright Â© 2015, 2016 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/>.
	; ------------------------------------------------------------------------
	;       
	; io.inc: a do-nothing input/output "base object"
	; 
	; base virtual method functionality for chained/unchained io
	; 
	; destroy/clone/send calls populate FORWARD through the links
	; receive/connected/error/timeout calls populate BACKWARD
	;


io_vmethods_ofs = 0
io_parent_ofs = 8
io_child_ofs = 16

io_base_size = 24


	; the virtual methods we are interested in:
io_vdestroy = 0
io_vclone = 8
io_vconnected = 16
io_vsend = 24
io_vreceive = 32
io_verror = 40
io_vtimeout = 48


if used io$vtable | defined include_everything

dalign
io$vtable:
	dq	io$destroy, io$clone, io$connected, io$send, io$receive, io$error, io$timeout

end if


	; our implementations for each of the aforementioned, along with a
	; default constructor put here mainly for reference:

if used io$new | defined include_everything
	; no arguments, returns a new io object with its vtable pointed correctly
falign
io$new:
	prolog	io$new
	mov	edi, io_base_size
	call	heap$alloc_clear	; parent/child set to null for us
	mov	qword [rax], io$vtable
	epilog

end if

if used io$addchild | defined include_everything
	; two arguments: rdi == parent, rsi == child
falign
io$addchild:
	prolog	io$addchild
	mov	[rdi+io_child_ofs], rsi
	mov	[rsi+io_parent_ofs], rdi
	epilog
end if

if used io$destroy | defined include_everything
	; single argument in rdi: our io object

	; NOTE: descendents of this should call io$destroy directly, _LAST_ (which will then free the pointer)
falign
io$destroy:
	prolog	io$destroy
	; NOTE: we do not walk UP the tree, our job is only to destroy ourself and our child if any
	; for the real destroy calls, it is up to them to walk the tree beforehand such that this
	; happens correctly
	; 
	cmp	qword [rdi+io_child_ofs], 0
	je	.nochild
	; otherwise, we have a child
	push	rdi
	mov	rdi, [rdi+io_child_ofs]
	mov	rsi, [rdi]	; virtual method pointer location
	call	qword [rsi+io_vdestroy]
	pop	rdi
calign
.nochild:
	call	heap$free
	epilog

end if

if used io$clone | defined include_everything
	; single argument in rdi: io object to clone
	; returns new (copy), with new _copies_ of our child if any
	; NOTE: descendents of this need to do the same goods (child/parent clone/set)
falign
io$clone:
	prolog	io$clone
	cmp	qword [rdi+io_child_ofs], 0
	je	.nochild
	push	rdi
	mov	rdi, [rdi+io_child_ofs]
	mov	rsi, [rdi]	; virtual method pointer location
	call	qword [rsi+io_vclone]
	; so now we have rax == our child clone, rdi is our original
	push	rax
	mov	edi, io_base_size
	call	heap$alloc
	pop	rsi		; child
	pop	rdx		; our original
	mov	rcx, [rdx]	; our original's vmethod pointer
	mov	[rax], rcx
	mov	qword [rax+io_parent_ofs], 0	; parent = null
	mov	[rax+io_child_ofs], rsi		; our child
	mov	[rsi+io_parent_ofs], rax	; set our child's parent to us
	epilog
calign
.nochild:
	push	rdi
	mov	edi, io_base_size
	call	heap$alloc
	xor	esi, esi
	pop	rdx		; our original
	mov	rcx, [rdx]	; our original's vmethod pointer
	mov	[rax], rcx
	mov	[rax+io_parent_ofs], rsi	; parent = null
	mov	[rax+io_child_ofs], rsi		; child = null
	epilog

end if

if used io$connected | defined include_everything
	; single argument in rdi: io object
	; NOTE: for epoll inbounds, rsi/edx will also get populated with the remote address
falign
io$connected:
	prolog	io$connected
	mov	rdi, [rdi+io_parent_ofs]
	test	rdi, rdi
	jnz	.hasparent
	epilog
calign
.hasparent:
	mov	rcx, [rdi]	; virtual method pointer location
	call	qword [rcx+io_vconnected]
	epilog

end if

if used io$send | defined include_everything
	; three arguments: rdi == io object, rsi == ptr to data, rdx == length of same
falign
io$send:
	prolog	io$send
	; if we have a child, populate the call downward, if not, default io$send is a do nothing, so do precisely that
	mov	rdi, [rdi+io_child_ofs]
	test	rdi, rdi
	jnz	.haschild
	epilog
calign
.haschild:
	mov	rcx, [rdi]	; virtual method pointer location
	call	qword [rcx+io_vsend]
	epilog

end if


if used io$receive | defined include_everything
	; three arguments: rdi == io object, rsi == ptr to data, rdx == length of same
	; BOOL return says: if FALSE, don't destroy the objects, if true, destroys the objects (useful for fatal error conditions, etc)
falign
io$receive:
	prolog	io$receive
	; if we have a parent, populate the call upward, if not, default io$receive layer is a do nothing, so do precisely that
	; ensuring our return is false so that we don't get destroyed
	mov	rdi, [rdi+io_parent_ofs]
	test	rdi, rdi
	jnz	.hasparent
	xor	eax, eax
	epilog
calign
.hasparent:
	mov	rcx, [rdi]	; virtual method pointer location
	call	qword [rcx+io_vreceive]
	; let our return == parent's return
	epilog

end if

if used io$error | defined include_everything
	; single argument in rdi: io object
	; this is a notification only, we aren't responsible for destruction of the chain
	; our only mission is to populate the call upward
falign
io$error:
	prolog	io$error
	mov	rdi, [rdi+io_parent_ofs]
	test	rdi, rdi
	jnz	.hasparent
	epilog
calign
.hasparent:
	mov	rsi, [rdi]	; virtual method pointer location
	call	qword [rsi+io_verror]
	epilog

end if

if used io$timeout | defined include_everything
	; single argument in rdi: io object
	; NOTE: the default here is to return FALSE, but if descendents return true, signals death should occur
falign
io$timeout:
	prolog	io$timeout
	; we walk _up_ the tree, calling each one's timeout, if any return true, then we bailout, go to the topmost of the chain
	; and destroy everyone
	mov	rdi, [rdi+io_parent_ofs]
	test	rdi, rdi
	jnz	.hasparent
	xor	eax, eax
	epilog
calign
.hasparent:
	mov	rsi, [rdi]	; virtual method pointer location
	push	rdi
	call	qword [rsi+io_vtimeout]
	pop	rdi
	test	eax, eax
	jnz	.death
	epilog
calign
.death:
	cmp	qword [rdi+io_parent_ofs], 0
	je	.death_topmost
	mov	rdi, [rdi+io_parent_ofs]
	jmp	.death
calign
.death_topmost:
	mov	rsi, [rdi]	; virtual method pointer location
	call	qword [rsi+io_vdestroy]
	xor	eax, eax	; make sure our actual return is nondestructive
	epilog

end if

if used io$link | defined include_everything
	; two arguments: rdi == io object parent, rsi == io object child
	; all this does is link them together, convenience function for HLL use
falign
io$link:
	prolog	io$link
	mov	[rdi+io_child_ofs], rsi
	mov	[rsi+io_parent_ofs], rdi
	epilog

end if
