6684 - experimental primitives for streams

This is a hacky special case. The alternative would be more general support
for generics.

One observation: we might be able to type-check some primitives using `sig`s.
Only if they don't return anything, since primitives usually need to support
arbitrary registers. I'm not doing that yet, though. It eliminates the
possibility of writing tests for them in mu.subx, which can't see 400.mu.
But it's an alternative:

  sig allocate out: (addr handle _)
  sig populate out: (addr handle array _), n: int
  sig populate-stream out: (addr handle stream _), n: int
  sig read-from-stream s: (addr stream _T), out: (addr _T)
  sig write-to-stream s: (addr stream _T), in: (addr _T)

We could write the tests in Mu. But then we're testing behavior rather
than the code generated. There are trade-offs. By performing type-checking
in mu.subx I retain the option to write both kinds of tests.
This commit is contained in:
Kartik Agaram 2020-07-29 21:41:29 -07:00
parent 129bb8117b
commit bae22d720f
4 changed files with 320 additions and 0 deletions

108
309stream.subx Normal file
View File

@ -0,0 +1,108 @@
# Some unsafe methods not intended to be used directly in SubX, only through
# Mu after proper type-checking.
write-to-stream: # s: (addr stream), in: (addr byte), n: int
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
52/push-edx
53/push-ebx
57/push-edi
# edi = s
8b/-> *(ebp+8) 7/r32/edi
# var swrite/edx: int = s->write
8b/-> *edi 2/r32/edx
# if (swrite + n > s->size) abort
8b/-> *(ebp+0x10) 1/r32/ecx
01/add-to %ecx 2/r32/edx
3b/compare 1/r32/ecx *(edi+8)
0f 8f/jump-if-> $write-to-stream:abort/disp32
# var out/edx: (addr byte) = s->data + s->write
8d/copy-address *(edi+edx+0xc) 2/r32/edx
# var outend/ebx: (addr byte) = out + n
8b/-> *(ebp+0x10) 3/r32/ebx
8d/copy-address *(edx+ebx) 3/r32/ebx
# eax = in
8b/-> *(ebp+0xc) 0/r32/eax
# var inend/ecx: (addr byte) = in + n
8b/-> *(ebp+0x10) 1/r32/ecx
8d/copy-address *(eax+ecx) 1/r32/ecx
#
(_append-4 %edx %ebx %eax %ecx) # => eax
# s->write += n
8b/-> *(ebp+0x10) 1/r32/ecx
01/add-to *edi 1/r32/ecx
$write-to-stream:end:
# . restore registers
5f/pop-to-edi
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
$write-to-stream:abort:
(write-buffered Stderr "write-to-stream: stream full\n")
(flush Stderr)
bb/copy-to-ebx 1/imm32
(syscall_exit)
# never gets here
read-from-stream: # s: (addr stream), out: (addr byte), n: int
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
52/push-edx
53/push-ebx
56/push-esi
# esi = s
8b/-> *(ebp+8) 6/r32/esi
# var sread/edx: int = s->read
8b/-> *(esi+4) 2/r32/edx
# if (sread + n > s->write) abort
8b/-> *(ebp+0x10) 1/r32/ecx
01/add-to %ecx 2/r32/edx
3b/compare 1/r32/ecx *esi
0f 8f/jump-if-> $read-from-stream:abort/disp32
# var in/edx: (addr byte) = s->data + s->read
8d/copy-address *(esi+edx+0xc) 2/r32/edx
# var inend/ebx: (addr byte) = in + n
8b/-> *(ebp+0x10) 3/r32/ebx
8d/copy-address *(edx+ebx) 3/r32/ebx
# eax = out
8b/-> *(ebp+0xc) 0/r32/eax
# var outend/ecx: (addr byte) = out + n
8b/-> *(ebp+0x10) 1/r32/ecx
8d/copy-address *(eax+ecx) 1/r32/ecx
#
(_append-4 %eax %ecx %edx %ebx) # => eax
# s->read += n
8b/-> *(ebp+0x10) 1/r32/ecx
01/add-to *(esi+4) 1/r32/ecx
$read-from-stream:end:
# . restore registers
5e/pop-to-esi
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
$read-from-stream:abort:
(write-buffered Stderr "read-from-stream: stream empty\n")
(flush Stderr)
bb/copy-to-ebx 1/imm32
(syscall_exit)
# never gets here

15
402stream.mu Normal file
View File

@ -0,0 +1,15 @@
# Tests for Mu's stream primitives.
fn test-stream {
# write an int to a stream, then read it back
var s: (stream int 4)
var s2/ecx: (addr stream int 4) <- address s
var x: int
copy-to x, 0x34
var x2/edx: (addr int) <- address x
write-to-stream s2, x2
var y: int
var y2/ebx: (addr int) <- address y
read-from-stream s2, y2
check-ints-equal y, 0x34, "F - test-stream"
}

BIN
apps/mu

Binary file not shown.

View File

@ -11699,6 +11699,18 @@ has-primitive-name?: # stmt: (addr stmt) -> result/eax: boolean
(string-equal? %esi "populate") # => eax
3d/compare-eax-and 0/imm32/false
0f 85/jump-if-!= $has-primitive-name?:end/disp32
# if (name == "populate-stream") return true
(string-equal? %esi "populate-stream") # => eax
3d/compare-eax-and 0/imm32/false
0f 85/jump-if-!= $has-primitive-name?:end/disp32
# if (name == "read-from-stream") return true
(string-equal? %esi "read-from-stream") # => eax
3d/compare-eax-and 0/imm32/false
0f 85/jump-if-!= $has-primitive-name?:end/disp32
# if (name == "write-to-stream") return true
(string-equal? %esi "write-to-stream") # => eax
3d/compare-eax-and 0/imm32/false
0f 85/jump-if-!= $has-primitive-name?:end/disp32
# var curr/ecx: (addr primitive) = Primitives
b9/copy-to-ecx Primitives/imm32
{
@ -11820,6 +11832,30 @@ check-mu-primitive: # stmt: (addr stmt), fn: (addr function), err: (addr buffer
(check-mu-populate-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
e9/jump $check-mu-primitive:end/disp32
}
# if (op == "populate-stream") check-mu-populate-stream-stmt
{
(string-equal? %ecx "populate-stream") # => eax
3d/compare-eax-and 0/imm32/false
74/jump-if-= break/disp8
(check-mu-populate-stream-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
e9/jump $check-mu-primitive:end/disp32
}
# if (op == "read-from-stream") check-mu-read-from-stream-stmt
{
(string-equal? %ecx "read-from-stream") # => eax
3d/compare-eax-and 0/imm32/false
74/jump-if-= break/disp8
(check-mu-read-from-stream-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
e9/jump $check-mu-primitive:end/disp32
}
# if (op == "write-to-stream") check-mu-write-to-stream-stmt
{
(string-equal? %ecx "write-to-stream") # => eax
3d/compare-eax-and 0/imm32/false
74/jump-if-= break/disp8
(check-mu-write-to-stream-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
e9/jump $check-mu-primitive:end/disp32
}
# otherwise check-numberlike-stmt
(check-mu-numberlike-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
$check-mu-primitive:end:
@ -12399,6 +12435,42 @@ $check-mu-populate-stmt:end:
5d/pop-to-ebp
c3/return
check-mu-populate-stream-stmt: # stmt: (addr stmt), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
$check-mu-populate-stream-stmt:end:
# . restore registers
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
check-mu-read-from-stream-stmt: # stmt: (addr stmt), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
$check-mu-read-from-stream-stmt:end:
# . restore registers
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
check-mu-write-to-stream-stmt: # stmt: (addr stmt), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
$check-mu-write-to-stream-stmt:end:
# . restore registers
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
check-mu-call: # stmt: (addr stmt), callee: (addr function), fn: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
@ -14512,6 +14584,24 @@ emit-subx-stmt: # out: (addr buffered-file), stmt: (addr stmt), primitives: (ad
(translate-mu-populate-stream-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x14) *(ebp+0x18))
e9/jump $emit-subx-stmt:end/disp32
}
# read from stream
{
# if (!string-equal?(stmt->operation, "read-from-stream")) break
(string-equal? %ecx "read-from-stream") # => eax
3d/compare-eax-and 0/imm32
0f 84/jump-if-= break/disp32
(translate-mu-read-from-stream-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x14) *(ebp+0x18))
e9/jump $emit-subx-stmt:end/disp32
}
# write to stream
{
# if (!string-equal?(stmt->operation, "write-to-stream")) break
(string-equal? %ecx "write-to-stream") # => eax
3d/compare-eax-and 0/imm32
0f 84/jump-if-= break/disp32
(translate-mu-write-to-stream-stmt *(ebp+8) *(ebp+0xc) *(ebp+0x14) *(ebp+0x18))
e9/jump $emit-subx-stmt:end/disp32
}
# - if stmt matches a primitive, emit it
{
$emit-subx-stmt:check-for-primitive:
@ -15313,6 +15403,36 @@ $addr-handle-payload-size:end:
5d/pop-to-ebp
c3/return
addr-payload-size: # s: (addr stmt-var), err: (addr buffered-file), ed: (addr exit-descriptor) -> result/eax: int
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# var t/eax: (addr type-tree) = s->value->type
8b/-> *(ebp+8) 0/r32/eax
(lookup *eax *(eax+4)) # Stmt-var-value Stmt-var-value => eax
(lookup *(eax+8) *(eax+0xc)) # Var-type Var-type => eax
# TODO: check eax != 0
# TODO: check !t->is-atom?
# TODO: check t->left == addr
# t = t->right
$addr-payload-size:skip-addr:
(lookup *(eax+0xc) *(eax+0x10)) # Type-tree-right Type-tree-right => eax
# TODO: check eax != 0
# if !t->is-atom? t = t->left
81 7/subop/compare *eax 0/imm32/false
{
75/jump-if-!= break/disp8
(lookup *(eax+4) *(eax+8)) # Type-tree-left Type-tree-left => eax
}
# TODO: check t->is-atom?
# return size(t->value)
(size-of-type-id *(eax+4)) # Type-tree-value => eax
$addr-payload-size:end:
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
translate-mu-populate-stmt: # out: (addr buffered-file), stmt: (addr stmt), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
@ -15385,6 +15505,83 @@ $translate-mu-populate-stream-stmt:end:
5d/pop-to-ebp
c3/return
translate-mu-read-from-stream-stmt: # out: (addr buffered-file), stmt: (addr stmt), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
56/push-esi
57/push-edi
# esi = stmt
8b/-> *(ebp+0xc) 6/r32/esi
# var stream/ecx: (addr stmt-var) = stmt->inouts[0]
(lookup *(esi+0xc) *(esi+0x10)) # Stmt1-inouts Stmt1-inouts => eax
89/<- %ecx 0/r32/eax
# var target/edi: (addr stmt-var) = stmt->inouts[1]
(lookup *(ecx+8) *(ecx+0xc)) # Stmt-var-next Stmt-var-next => eax
89/<- %edi 0/r32/eax
#
(emit-indent *(ebp+8) *Curr-block-depth)
(write-buffered *(ebp+8) "(read-from-stream")
(emit-subx-call-operand *(ebp+8) %ecx)
(emit-subx-call-operand *(ebp+8) %edi)
(write-buffered *(ebp+8) Space)
(addr-payload-size %edi *(ebp+0x10) *(ebp+0x14)) # => eax
(write-int32-hex-buffered *(ebp+8) %eax)
(write-buffered *(ebp+8) ")\n")
$translate-mu-read-from-stream-stmt:end:
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
translate-mu-write-to-stream-stmt: # out: (addr buffered-file), stmt: (addr stmt), err: (addr buffered-file), ed: (addr exit-descriptor)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
56/push-esi
57/push-edi
# esi = stmt
8b/-> *(ebp+0xc) 6/r32/esi
# var stream/ecx: (addr stmt-var) = stmt->inouts[0]
(lookup *(esi+0xc) *(esi+0x10)) # Stmt1-inouts Stmt1-inouts => eax
89/<- %ecx 0/r32/eax
# var target/edi: (addr stmt-var) = stmt->inouts[1]
(lookup *(ecx+8) *(ecx+0xc)) # Stmt-var-next Stmt-var-next => eax
89/<- %edi 0/r32/eax
#
(emit-indent *(ebp+8) *Curr-block-depth)
(write-buffered *(ebp+8) "(write-to-stream")
(emit-subx-call-operand *(ebp+8) %ecx)
(flush *(ebp+8))
(emit-subx-call-operand *(ebp+8) %edi)
(flush *(ebp+8))
(write-buffered *(ebp+8) Space)
(flush *(ebp+8))
(addr-payload-size %edi *(ebp+0x10) *(ebp+0x14)) # => eax
(write-int32-hex-buffered *(ebp+8) %eax)
(write-buffered *(ebp+8) ")\n")
$translate-mu-write-to-stream-stmt:end:
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
addr-handle-array-payload-size: # s: (addr stmt-var), err: (addr buffered-file), ed: (addr exit-descriptor) -> result/eax: int
# . prologue
55/push-ebp