Jupiter Ace Forth: Utilidades

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 03 Ago 2022 12:53

Último mensaje de la página anterior:

Respecto a evitar el uso de ENDGOTO al final de cada definición que usa GOTO/LABEL:

Lo de usar mis propias versiones modificadas de DOES>, RUNS> y ; lo he probado y funciona.

Pero cuando editas la palabra que las contiene, se listan la nueva y la original seguidas. Lo cual te obliga a borrar todas las instancias de la original antes de entrar la palabra editada. O sea, es más engorroso que usar el ENDGOTO.

Por lo que descarto lo de eliminar el ENDGOTO, al menos con este enfoque.

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 03 Ago 2022 16:11

He hecho la versión compatible con los "nuevos" códigos marcadores de inicio (11 y 12) y que NO necesita INIGOTO. Sigue haciendo falta el ENDGOTO como en la versiones ULOOPGOT y ULOOPGOTN

Código Fuente

Código: Seleccionar todo

\ ****************************************************
\ * Conjunto de Estructuras de Control de Usuario    *
\ *                                                  *
\ *  Bucles Sin Signo: ULOOP/+ULOOP/NEXT             *
\ *     Son compatibles con DO, LEAVE, I, I' y J     *
\ *                                                  *
\ *  Salto Incondicional: GOTO/LABEL/KILLOOP/ENDGOTO *
\ ****************************************************

DECIMAL

\ Versión en código máquina de multiplicar por 2

BASE C@ DECIMAL 16 BASE C!
CREATE 2*
 DF C, CB C, 23 C, CB C, 12 C,
 D7 C, FD C, E9 C,
BASE C! 2* DUP 2- !

\ Versión en código máquina de dividir por 2

BASE C@ DECIMAL 16 BASE C!
CREATE 2/
 DF C, CB C, 2A C, CB C, 1B C,
 D7 C, FD C, E9 C,
BASE C! 2/ DUP 2- !

\ Runtime de ULOOP en código máquina

BASE C@ DECIMAL 16 BASE C!
CREATE ULOOPMC
 21 C, 04 C, 00 C, 39 C, 34 C,
 20 C, 04 C, 23 C, 34 C, 18 C,
 01 C, 23 C, 23 C, 5E C, 23 C,
 56 C, EB C, F1 C, C1 C, D1 C,
 D5 C, C5 C, F5 C, B7 C, ED C,
 52 C, 28 C, 0F C, 38 C, 0D C,
 DF C, EB C, 4E C, 23 C, 46 C,
 2B C, 09 C, C1 C, D1 C, E5 C,
 C5 C, FD C, E9 C, C1 C, D1 C,
 E1 C, E1 C, D5 C, C5 C, DF C,
 FD C, E9 C,
BASE C! ULOOPMC DUP 2- !

\ Runtime de +ULOOP en código máquina

BASE C@ DECIMAL 16 BASE C!
CREATE ULOOPMC+
 CD C, 4E C, 08 C, 21 C, 04 C,
 00 C, 39 C, 5E C, 23 C, 56 C,
 EB C, 09 C, 78 C, EB C, 72 C,
 2B C, 73 C, 23 C, 23 C, 4E C,
 23 C, 46 C, 60 C, 69 C, CB C,
 7F C, 28 C, 09 C, B7 C, ED C,
 52 C, 28 C, 18 C, 30 C, 16 C,
 18 C, 07 C, B7 C, ED C, 52 C,
 28 C, 0F C, 38 C, 0D C, DF C,
 EB C, 4E C, 23 C, 46 C, 2B C,
 09 C, C1 C, D1 C, E5 C, C5 C,
 FD C, E9 C, C1 C, D1 C, E1 C,
 E1 C, D5 C, C5 C, DF C, FD C,
 E9 C,
BASE C! ULOOPMC+ DUP 2- !

\ Runtime de NEXT en código máquina
\ y también es el Runtime de GOTO

BASE C@ DECIMAL 16 BASE C!
CREATE MCNEXT
 DF C, 62 C, 6B C, 4E C, 23 C,
 46 C, EB C, 09 C, D1 C, C1 C,
 E5 C, D5 C, FD C, E9 C,
BASE C! MCNEXT DUP 2- !

10 VARIABLE MRK     \ Contiene el marcador usado por al definición:
                    \ 10 si COLON o Runtime de DEFINER/COMPILER
                    \ 11 si COMPILER antes de RUNS>
                    \ 12 si DEFINER  antes de DOES>
                   
0 COMPILER INIGOTO  \ Chequea cual es el valor del marcador usado por la definición en curso.
                    \ Esta palabra ha de ser la primera de la definición cuando se usa GOTO/LABEL
                    \ Si GOTO/LABEL se usan en una DEFINER o COMPILER tiene que ir al principio de la
                    \ parte compilante si se usan allí y/o de la parte Runtime si se usan allí. O sea se
                    \ tratan por separado las dos zonas como si fueran dos definiciones distintas (que de hecho
                    \ lo son, de ahí los marcadores diferentes para cada zona):
 
 DUP MRK !
RUNS>
 DROP
;

: DEPTH ( - n)
\ Coloca en la pila el tamaño de la misma (sin contar n)

 15419 @ HERE 12 +
 - 2/
;
 
: ERROR ( n - )
\ Aborta el programa con el mensaje "ERROR n"
 
 15421 C! ABORT
;
 
: ROLLD ( n - )
\ Como ROLL pero en sentido contrario: Saca n de la pila y la rota colocando el elemento top en la posición n y
\ subiendo todos los que están por encima de él una posición.
\ e.g. < 6 5 3 8 21 4 ROLLD > -> < 6 21 5 3 8 >

 DUP 2 <
 IF
  DROP EXIT
 THEN
 DUP DEPTH 2- >
 IF
  2 ERROR
 THEN
 SWAP >R DEPTH OVER -
 2* HERE + 12 +
 DUP >R OVER 1- 2*
 + DUP ROT 1- 0
 DO
  DUP 2- SWAP OVER @
  SWAP !
 LOOP
 DROP R> R> SWAP !
;
 
: ENCRUST
 ( n,i - )
\ Dada la posición i de la pila (sin contar ni a n ni a i), incrusta n justo debajo de dicha posición.
\ e.g. < 50 40 30 20 10 88 4 ENCRUST > -> < 50 88 40 30 20 10 >

 1+ ROLLD
;

: CHKMRK ( - ) 
\ Busca el marcador de inicio de definición en curso.
\ Almacena en la variable MRK el primer valor que encuentra en la pila que sea un 10 o un 11 o un 12.

 DEPTH 1+ 1
 DO
  I PICK DUP 10 =
  IF
   LEAVE
  ELSE
   DUP 11 =
   IF
    LEAVE
   ELSE
    DUP 12 =
    IF
     LEAVE
    ELSE
     DROP
    THEN
   THEN
  THEN
 LOOP
 MRK !
;

: SEARCH>10 ( n - n2)
 \ n es el número a buscar antes de encontrar un 10.
 \ n2 es la posición que ocupa en el stack (1->top, 0->no encontrado)
 
 DEPTH 1+ 2
 DO
  I PICK 10 =
  IF
   DROP 0 LEAVE
  ELSE
   I PICK OVER =
   IF
    DROP I 1- LEAVE
   THEN
  THEN
 LOOP
;


: SEARCHMRK ( - n)
\ n es dos posiciones antes del valor de la varibale MRK en la pila.
\ ENCRUST incrusta justo debajo de la posición dada, pues así lo necesita NEXT
\ Pero GOTOF necesita incrustar justo encima de la posición dada.
\ Para poder aprovechar ENCRUST añadimos el "2-" y así, cuando usemos ENCRUST incrustará justo encima.
 
 DEPTH 1+ 1
 DO
  I PICK MRK @ =
  IF
   I LEAVE
  THEN
 LOOP
 2-
;

: GETNUM ( - n)
\ Busca el número compilado como LITERAL que está al final del diccionario justo antes de la compilante de usuario
\ que contiene al GETNUM y lo pone en la pila.
\ Comprueba se sea mayor que 100 (menor número de etiqueta válido)

 HERE 4 - @  \ El número de etiqueta está 4 bytes antes del HERE al ejecutarse el GOTO/LABEL que contiene a GETNUM.
 DUP 100 < OVER 199 > OR
 IF
  100 ERROR
 THEN
;

: DELPICK ( n - )
\ Elimina el elemento en la posición n del stack (sin contar n mismo)

 ROLL DROP
;

: SEARCH>MRK<P ( n,p - m)
\ Busca un número n en la pila de arriba hacia abajo empezando en la posición p (p=1 es top).
\ Termina si encuentra el valor de la variable MRK
\ Devuele m=0 si no la encuentra o m=posición de n si la encuentra.

 DEPTH 1+ SWAP 1+
 DO
  I PICK MRK @ =
  IF
   DROP 0 LEAVE
  ELSE
   I PICK OVER =
   IF
    DROP I 1- LEAVE
   THEN
  THEN
 LOOP
;

: SEARCHLABEL ( n - m)
\ Busca una etiqueta con número n en la pila. Si la encuentra m=posición, si no, m=0
\ La etiquetas tienen como código primero un 25. Luego viene el número de etiqueta y luego el HERE

 25 1
 BEGIN
  SEARCH>MRK<P ?DUP 0=
  IF
   DROP 0 EXIT
  THEN
  OVER OVER 3 + PICK =
  IF
   SWAP DROP EXIT
  THEN
  25 SWAP 1+ 0
 UNTIL
;

: SEARCHGOTO ( n - m)
\ Busca un GOTO con número de etiqueta n en la pila. Si la encuentra m=posición, si no, m=0
\ Los GOTO (adelante) tienen como código primero un 30. Luego viene el número de etiqueta y luego el HERE
\ Los GOTO (atrás) se resuelven al momento (la etiqueta tiene que existir previamente).

 30 1
 BEGIN
  SEARCH>MRK<P ?DUP 0=
  IF
   DROP 0 EXIT
  THEN
  OVER OVER 3 + PICK =
  IF
   SWAP DROP EXIT
  THEN
  30 SWAP 1+ 0
 UNTIL
;

2 COMPILER ULOOP
\ Igual que LOOP pero trata los límites como enteros sin signo.

 3 = 0=
 IF
  5 ERROR
 THEN
 HERE - ,
 BEGIN
  DUP 5 =
 WHILE
  DROP DUP HERE SWAP -
  4 - SWAP !
 REPEAT
RUNS>
 ( R> R> 1+ >R >R I' J U< 0= DUP  ) \ Versión Forth
 ( IF                             )
 ( R> R> R> DROP DROP             )
 ( >R                             )
 ( THEN                           )
 ULOOPMC                            \ Versión Código Máquina
;
 
2 COMPILER +ULOOP
\ Igual que +LOOP pero trata los límites como enteros sin signo

 3 = 0=
 IF
  5 ERROR
 THEN
 HERE - ,
 BEGIN
  DUP 5 =
 WHILE
  DROP DUP HERE SWAP -
  4 - SWAP !
 REPEAT
RUNS>
 SWAP ULOOPMC+
;
 
2 COMPILER NEXT
\ Salta directamente al ULOOP/+ULOOP

 3 SEARCH>10 DUP 0=
 IF
  5 ERROR
 THEN
 1+ DUP >R 5 SWAP
 ENCRUST HERE R> 1+ ENCRUST
 0 ,
RUNS>
 ( DUP @ + R> DROP >R ) \ Versión Forth
 MCNEXT                 \ Versión Código Máquina
;

2 COMPILER GOTO ( - )
\ Salto incondicional adelante o atrás.
\ Los datos que deja en la pila son: El código 30 de GOTO + número de etiqueta + HERE (solo GOTO adelante)
\ Si encuentra un LABEL con su número de etiqueta    => Salto hacia atrás (lo resuelve)
\ Si NO encuentra un LABEL con su número de etiqueta => Salto hacia adelante -> Pone datos en pila (lo deja pendiente)

 CHKMRK                                 \ Hace la variable MRK = marcador en curso (10, 11 o 12)
 GETNUM SEARCHLABEL ?DUP 0=
 IF   
  \ Etiqueta aún no existe -> Es un salto hacia adelante -> Deja datos en la pila para ser resueltos por la
  \ etiqueta con el mismo número cuando se compile.
  30 SEARCHMRK ENCRUST GETNUM SEARCHMRK ENCRUST HERE SEARCHMRK ENCRUST 0 ,
 ELSE
  \ Etiqueta ya existe -> Es un salto hacia atrás -> Resolvemos el GOTO hacia atrás sin poner nada en la pila.
  1+ PICK HERE - ,
 THEN
RUNS>
  ( SWAP DROP) ( DUP @ + R> DROP >R )   \ Versión Forth
  MCNEXT DROP                           \ Versión Código Máquina.
;

0 COMPILER LABEL ( - )
\ Etiqueta destino del GOTO correspondiente.
\ Los datos que deja en la pila son: El código 25 de LABEL + número de etiqueta + HERE
\ Los datos de una etiqueta NO se borran cuando se resuelve, pues un LABEL puede corresponder a varios GOTOs.
\ Lo primero que hace es comprobar que el número de etiqueta no esté repetido.
\ Luego busca si existe un GOTO pendiente con el mismo número de etiqueta y si lo encuentra -> lo resuelve (adelante)
\ Si no encuentra ningún GOTO pendiente, NO hace nada.

 GETNUM SEARCHLABEL 0= 0=       \ Etiqueta repetida -> ERROR 101
 IF   
  101 ERROR
 THEN

 CHKMRK                         \ Hace la variable MRK = marcador en curso (10, 11 o 12)
 \ Coloca sus datos en la pila
 25 SEARCHMRK ENCRUST GETNUM SEARCHMRK ENCRUST HERE SEARCHMRK ENCRUST

 \ Resuelve cualguier GOTO pendiente con el mismo número de etiqueta.
 BEGIN
  GETNUM SEARCHGOTO ?DUP 0= 0=
 WHILE
  DUP DELPICK DUP DELPICK 1- ROLL
  DUP HERE SWAP - SWAP !
 REPEAT
RUNS>
 DROP DROP
;

: KILLOOP ( n - )
\ n es el número de bucles DO/LOOP/+LOOP/ULOOP/+ULOOP que hay que "cerrar" cuando se ejecute el GOTOF.
\ e.g. Si el GOTOF está dentro de 3 bucles anidados y salta al interior del más externo habrá que
\      cerrar 2 bucles (el más externo sigue en uso).
\ OJO: Si hay >R/R> pendientes de cerrar, hay que cerrarlos también, pero tienes que hacerlo tú.
\      Pero es muy peligroso. No se recomienda usar GOTOF/LABELF si se están usando pares >R/R>
\ IMPORTANTE: KILLOOP ha de ir justo antes del GOTOF/B (sin nada entre ambas) e.g. KILLOOP 100 GOTOF

 DUP 1 <
 IF
  DROP EXIT
 THEN
 R>
 SWAP
 BEGIN
  R> DROP R> DROP 1- ?DUP 0=
 UNTIL
 >
;

0 COMPILER ENDGOTO ( - )
\ Si en una palabra se usa GOTO/LABEL hay que terminarla con ENDGOTO para que elimine los datos de etiquetas de
\ la pila. Esto implica que pueden haber etiquetas que no las use nadie y gasten memoria y tiempo de ejecución.

 BEGIN
  25 1 SEARCH>MRK<P ?DUP 0=
  IF
   EXIT
  THEN
  1+ DUP DELPICK DUP DELPICK 1- DELPICK
  0
 UNTIL
RUNS>
 DROP
;

uloopgotf.TZX
(1.66 KiB) Descargado 8 veces

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 03 Ago 2022 22:27

Comentar respecto a las estructuras de control que todas, incluso las sencillas, pueden fallar según lo que se haga.

Pongo un ejemplo de como puede fallar la estructura IF/THEN haciendo algo que, en principio, no es incorrecto aunque sí algo poco habitual.

Defino la palabra TEST de esta manera:

: TEST IF [ 3 ] THEN LITERAL ;

Cuando la entre me dará ERROR 5.

En teoría, TEST definido así, al listarlo (si no hubiera fallado) debería dar:

: TEST
IF
THEN
3
;

Lo normal es cargar el 3 en la pila justo antes del LITERAL y no como lo hemos hecho.

Si lo hubiéramos hecho así:
: TEST IF  THEN [ 3 ] LITERAL ;

El resultado sería el listado de arriba, como era de esperar.

Si en vez de un 3 hubiéramos usado, por casualidad, un 2, la palabra se compila sin error pero la listarla vemos que NO es lo que se pretendía que fuera:

: TEST IF [ 2 ]  THEN LITERAL ;

Si la listas:

: TEST
IF
THEN
24083
;

NOTA: El 24083 puede ser otro número, depende del tamaño del diccionario en el momento de definir TEST.

Si ejecutamos:
1 TEST .

nos imprime 24083. Bien.

Pero si ejecutamos:
0 TEST .

en mi caso se reinició el Ace Forth (se borró el diccionario). Es porque el IF ante un valor falso, ejecuta el salto y éste se programó mal...

El porqué tiene que ver con los códigos de control que pone IF y lo que hay en la pila en el momento de cerrar el IF con el THEN. El IF pone un 2 y el THEN espera ese 2 en el top de la pila.

Dejo la explicación detallada de lo que ha pasado en los diferentes casos al lector del post.

En general, hay que vigilar un poco lo que hacemos con la pila cuando se compilan estructuras de Control, tanto si son las que vienen con Ace Forth como las que podamos hacer nosotros.

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 05 Ago 2022 13:01

Solo aclarar un dato incorrecto que di hablando de los "nuevos" códigos 11 y 12:

Dije que son marcadores de inicio de definición. Como el 10 en el caso de las COLON. Pero hay que añadir que también se usan como marcadores para cálculos durante la compilación de la palabra. Me explico:

Por ejemplo, el par DEFINER/DOES>. Cuando se ejecuta DEFINER, NO solo coloca el 12 en la pila, sino que justo debajo coloca el valor de HERE en ese momento para que luego DOES> pueda calcular valores que ha de compilar. Y algo parecido con COMPILER/RUNS> y el 11. En ambos casos es en la compilación de la parte primera, no así en la parte segunda (Runtime), que pone un 10 y nada más (marcador de inicio de definición puro y duro)

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 08 Ago 2022 12:22

En este este post comentaba que la versión de SIN del paquete Mathematical & Trigonometrical Functions era, en modo FAST, casi 2.5 veces más rápido que la misma función definida según las páginas 92 y 93 de Manual de Jupiter Ace.

Hoy he encontrado esto sobre la versión usando solo números enteros para calcular SIN y COS. Ya se ha hablado mucho del tema en libros/artículos de la época pues Forth, en general, carece de números en coma flotante. Me he decidido a probarlo. Pensaba que la diferencia sería abismal, pero, aunque significativa, no es para tanto.

La idea es poder utilizar estos senos en aplicaciones que usen gráficos que no requieren mucha precisión.

Pongo aquí los resultados obtenidos comparando los resultados del Seno versión números enteros con el Seno versión Manual del JA:

Primero van los grados (en grados sexagesimales), luego el resultado entero (1 = 10000) y por último el resultado en coma flotante.
sinustest.png
sinustest.png (16.81 KiB) Visto 539 veces

Como se ve, la precisión es aceptable para ser enteros.

En cuanto a la velocidad de ejecución, estos son los resultados:
1 - Tiempo en segundos para 0 a 90 grados en incrementos de 1 grado. (91 senos) (Slow/Fast)
2 - Senos por segundo (Slow/Fast)

.                               SIN/s
Versión Slow Fast Slow Fast
----------------------------------------
JA 24.9 22.3 3.5 4.0
M&T 13.6 9.4 6.7 9.7
INT 10.2 6.6 8.9 13.8

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 14 Ago 2022 16:57

Mirándome el desensamblado de la ROM del Jupiter Ace he visto varias cosas interesantes.

1- Hay una subrutina en $084E que te pone en el par de registros BC el entero que hay en el top de la pila. Esto ya hace tiempo que lo sé, pero creo que no lo he comentado nunca.

2- Hay muchas Palabras de uso interno. Supongo que para ahorrar espacio las han hecho "Headerless" (sin cabecera). No es que no tengan cabecera, sino que tienen una cabecera con solo del Code Field, nada más.

El punto 1 puede ser de utilidad en assembler.

El punto 2 es interesante porque algunas de esas palabras "Headerless" pueden ser útiles.

A modo de ejemplo, voy a comentar una en concreto:

La que tiene su CFA en $0688. En el desemsamblado la han llamado "stk-zero". Simplemente coloca un cero en la pila. Además, es una primitiva, pues el contenido de su CFA apunta al inicio de su PF.

; ----------------------------
; The 'stk-zero' Internal Word
; ----------------------------
; ( -- 0 )

L0688: DEFW L068A ; headerless 'code field'

; ---

L068A: LD DE,$0000 ; load DE with the value zero.
RST 10H ; stack Data Word DE

JP (IY) ; to 'next'.

Lo que se me ha ocurrido es crear palabras que nos permitan usar estas "palabras internas" como una palabra más del Ace Forth. Lo que hago es crear un nuevo tipo de palabra. Las llamaré tipo "Internas".

Vienen a ser un tercer caso de primitivas: Las normales en las que (CFA) = PFA. Las "raras" de BoldField en las que (CFA) apunta dentro del PF pero no precisamente al inicio y ésta en las que (CFA) apunta a la ROM y no tienen PF.

En el caso del stk-zero voy a crear la palabra CERO así:

CREATE CERO

Ahora solo hemos de colocar en su CF el contenido del CF de stk-zero que vale $068A (1674)

1674 CERO 2- !

Y ya tenemos nuestra palabra CERO de tipo "Interno" y sin PF (Parameter Field).

CERO no es ni listable ni editable.

Vamos a ver que tal se porta:

: TEST1 0 ;
: TEST2 CERO ;

Mido los tiempos de ejecución en segundos para 10.000 veces.

Slow Fast
TEST1 1.7 0.9
TEST2 1.5 0.6


Vemos que en modo FAST la palabra CERO es un 50% más rápida que poner en la pila un cero compilado.

Hasta ahora he encontrado seis casos de palabras Headerless que pueden ser útiles:

  • stk_zero $068A Coloca un cerro en la pila
  • stk_digit $07BA Toma un número de la pila y si es el ascii de un carácter válido en la base actual devuelve el dígito correspondiente. En el top de la pila pone un flag: 1 si correcto 0 si inválido
  • stk-char $0A09 Parece la inversa de la anterior, pero solo lo he mirado por encima.
  • branch $1278 Unconditional branch.
  • ?branch $128F Conditional branch
  • check-for $12DA Check internal marker for control structure.

La que me interesan sobremaner son las tres últimas. Pues serían útiles en la creación de estructuras de control de usuario. Pero me temo que van a dar problemas. Ya lo estudiaré más adelante.

La pega está en que algunos de mis programas, como por ejemplo WLIST y el Extractor de Fuentes Semiautomático EXTRACTSOU fallarán si se encuentran con este tipo de palabras "Internas" al estilo CERO.

De momento, dado que, por un lado, no me he encontrado nunca con este tipo de palabras, y por otro lado no veo que valgan la pena por los tres primeros casos, no modificaré nada.

En cambio, si los tres últimos, sobre todo el branch y el ?branch se pudieran aprovechar, sí me veo entonces dispuesto a crear las seis (y cualquier otra que vea) y modificar mis programas para este nuevo tipo de palabra "Interna".

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 15 Ago 2022 16:49

Uno de mi muchos defectos es que tengo tendencia a aceptar lo que me parece que debería ser con cuatro comprobaciones, sin comprobarlo a fondo.

Hace poco hablaba, en otro hilo, de las palabras originales del del Ace Forth (las que hay cuando lo acabas de arrancar) que están escritas en código máquina (primitivas) y las que están escritas internamente como definiciones COLON, COMPILER, etc

En general, cuando importa que se ejecuten rápido serán primitivas. Cuando no es tan crítica la velocidad de ejecución, se harán como COLON, etc.

Pues bien, probé con unas cuantas de las palabras manipuladoras del STACK y como me salieron que eran primitivas, pues di por supuesto que lo eran todas, pues sería lo lógico al ser palabras de las que se espera que todas sean lo más rápidas posible.

Pues NO -banghead

Hoy viendo que un programa usaba sus propias versiones de OVER y ROT pensé que era porque habrían hecho versiones del código máquina más efectivas. Cual fue mi sorpresa cuando vi que esas dos palabras en el Ace Forth NO son primitivas, está escritas como definiciones COLON internas.

Volví a comprobar las palabras de manejo de la PILA (STACK) del JA y veo que todas son primitivas menos esas dos:

Primitivas: DROP, DUP, SWAP, ROLL y PICK
NO-primitivas: OVER y ROT

Entonces supuse que sería por una cuestión de espacio. Que tanto OVER como ROT serían más cortas así que en código máquina. Pero esta vez, recién escaldado, me dije: "compruébalo".

Y NO, no es por cuestión de espacio. De hecho, las versiones en código máquina de esas dos palabras que lleva el programa en cuestión (TURTLE.TAP) ocupan 19 bytes cada una (38 bytes en total). Las versiones del Ace FORTH ocupan 18 y 20 bytes respectivamente (38 en total).

Son iguales, pero si las pusiéramos en la ROM, al ser las cabeceras de la palabras originales del JA 2 bytes más cortas, tendríamos que ocuparían solo 34 bytes frente a los 38 que ocupan las actuales.

Creo, y es solo una idea, que se les pasó hacerlo al modo que lo han hecho los del TURTLE.TAP. No han hecho unas versiones código máquina desde cero, sino que han aprovechado las rutinas en código máquina del ROLL del JA. Por un lado sabemos que 3 ROLL es lo mismo que ROT. Por otro lado han hecho un uso parcial de las rutinas del ROLL para hacer un OVER. Todo muy ingenioso.

Y la cosa es seria, pues las versiones nuevas son mucho más rápidas que las del JA. El ROT unas 3 veces y el OVER 4-5 veces más rápidos.

Es que incluso, usando las versiones que vienen con el JA, es más rápido (casi el doble) hacer un 3 ROLL que ROT...

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 22 Ago 2022 16:45

A vueltas con GOTO/LABEL

He visto que la parte Runtime de GOTO (la que determina la velocidad de ejecución de los GOTO) se puede mejorar con un sencillo cambio. Basta editar GOTO y cambiar su Runtime de:

SWAP DROP MCNEXT
a
MCNEXT DROP

Este ahorro de un SWAP implica un aumento en la velocidad del ~36% (fast y slow).

Como es fácil de hacer a mano, pues no pongo el nuevo fichero.TZX
Sí que he modificado el código fuente del post del GOTO/LABEL anterior a éste.

EDIT GOTO <hacer cambios> REDEFINE GOTO

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 24 Ago 2022 11:27

.
De vuelta con los SIN

En el último post sobre el tema resumía los resultados así:
En cuanto a la velocidad de ejecución, estos son los resultados:
1 - Tiempo en segundos para 0 a 90 grados en incrementos de 1 grado. (91 senos) (Slow/Fast)
2 - Senos por segundo (Slow/Fast)

.                               SIN/s
Versión Slow Fast Slow Fast
----------------------------------------
JA 24.9 22.3 3.5 4.0
M&T 13.6 9.4 6.7 9.7
INT 10.2 6.6 8.9 13.8


Hoy voy a hablar del método más rápido y con una precisión como la del INT (senos enteros) pero con grados y senos en como flotante (aka decimal).

Un poco de historia

Cuando pensaba comprarme el JA y tras estudiarme el manual (hará unos 30 años), una cosa que me preocupaba era la velocidad de cálculo de los Senos. Recuerdo que aquella tarde que me pasé probándolo en la tienda (por suerte estaba conectado a un teclado mecánico), una de las cosas que hice fue copiar la palabra SIN del manual y cronometrarla.

No me gustaba lo lento que era (aunque comparado con mi HP-29C era aceptable en velocidad, pero no en precisión).

Me pasé por la librería Díaz de Santos y encontré unos libros sobre Forth. Uno era más bien un folleto-libro. Largo y estrecho y de pocas páginas sobre el estándar Forth-79 (creo). Lo compré, pero unos años después lo perdí.

Otro siempre pensé que era el de Leo Brody, pero ahora que lo tengo en PDF veo que no, pues aquél lo "consulté" para ver que decía de los senos y proponía, entre otras, la solución de crear una tabla de senos. Ese libro lo hubiera comprado de no ser que costaba 5.000 pesetas de la época (carísimo) y encima era pequeño. No he podido identificarlo aún. Y aquí quedó el asunto.
.


Ayer husmeando por internet sobre Forth di con esta página sobre el tema. Usa número enteros. La tabla es de 0 a 90 grados de 1 en 1 (91 senos).

Decidí hacerme una versión al estilo de ésta pero usando número decimales tanto para los grados como para la tabla. Además, mi versión interpola los resultados que no están en la tabla (interpolación lineal)

La he hecho y la he cronometrado. El resultado en modo FAST es de 42 senos por segundo. Resultado que triplica la velocidad del método INT.

Si le quito el verificador de margen (que comprueba que el ángulo está entre 0. y 90.) la velocidad aumenta a 60 senos por segundo.

Si además no interpolara sería rapidísimo:640 senos por segundo y con la misma precisión que el SIN del manual (pues se obtuvieron con SIN), a costa de restringirnos a valores de grado en grado (0, 1, 2 ··· 89 y 90) como el método INT.

La precisión del método interpolado es similar al método INT (4 dígitos). Para la ristra de pruebas que hice siempre eran correctos los 3 primeros decimales. La prueba fue de 0 a 90 grados pero para cada grado de valor entero n calculaba n.2 n.5 n.7 (e.g. n=65 -> 65.2 65.5 y 65.7). Para cada valor (270 en total) se restaba del que devuelve el SIN del JA y la diferencia siempre era menor de 5*10^-5 (0.00005).

Esta es la versión Interpolada. Le llamo ISIN a la palabra que calcula el seno Interpolado . NO me he preocupado de optimizar el código (más adelante quizás). Varias palabras las he copiado de otras fuentes (e.g. F0<)
NOTA: NO incluye el reductor de otros ángulos fuera del margen a ángulos de la tabla, pero es fácil de hacer.

Código: Seleccionar todo

\ Rutina de alta velocidad para calcular SENOS con baja precision en el rango 0. y 90. grados (coma flotante).

\ Utiliza una tabla con los SENOs de los ángulos 0 a 90 de grado en grado (91 valores en total)
\ Si el ángulo de entrada no es uno de estos, se hace una interpolación lineal:
\ e.g.
\ SIN(78.72 )=STABLA(78)+[STABLA(79)-STABLA(78)]*0.72

\ La tabla se hizo calculando los 91 senos con la función SIN del manual del Jupiter Ace (pags 92-93).
\ Aquí se incluye el generador del resultado como STABLA.

DECIMAL

16 BASE C!
CREATE STABLA
 00 C, 00 C, 00 C, 00 C, 24 C, 45 C, 17 C, 3F C, 95 C, 89 C, 34 C, 3F C, 60 C, 33 C, 52 C, 3F C, 65 C, 75 C, 69 C,
 3F C, 57 C, 15 C, 87 C, 3F C, 29 C, 45 C, 10 C, 40 C, 69 C, 18 C, 12 C, 40 C, 72 C, 91 C, 13 C, 40 C, 35 C, 64 C,
 15 C, 40 C, 48 C, 36 C, 17 C, 40 C, 09 C, 08 C, 19 C, 40 C, 12 C, 79 C, 20 C, 40 C, 51 C, 49 C, 22 C, 40 C, 22 C,
 19 C, 24 C, 40 C, 19 C, 88 C, 25 C, 40 C, 38 C, 56 C, 27 C, 40 C, 72 C, 23 C, 29 C, 40 C, 17 C, 90 C, 30 C, 40 C,
 68 C, 55 C, 32 C, 40 C, 20 C, 20 C, 34 C, 40 C, 68 C, 83 C, 35 C, 40 C, 08 C, 46 C, 37 C, 40 C, 32 C, 07 C, 39 C,
 40 C, 37 C, 67 C, 40 C, 40 C, 19 C, 26 C, 42 C, 40 C, 71 C, 83 C, 43 C, 40 C, 91 C, 39 C, 45 C, 40 C, 71 C, 94 C,
 46 C, 40 C, 10 C, 48 C, 48 C, 40 C, 00 C, 00 C, 50 C, 40 C, 37 C, 50 C, 51 C, 40 C, 20 C, 99 C, 52 C, 40 C, 39 C,
 46 C, 54 C, 40 C, 93 C, 91 C, 55 C, 40 C, 77 C, 35 C, 57 C, 40 C, 85 C, 77 C, 58 C, 40 C, 16 C, 18 C, 60 C, 40 C,
 61 C, 56 C, 61 C, 40 C, 21 C, 93 C, 62 C, 40 C, 88 C, 27 C, 64 C, 40 C, 59 C, 60 C, 65 C, 40 C, 31 C, 91 C, 66 C,
 40 C, 98 C, 19 C, 68 C, 40 C, 59 C, 46 C, 69 C, 40 C, 06 C, 71 C, 70 C, 40 C, 40 C, 93 C, 71 C, 40 C, 53 C, 13 C,
 73 C, 40 C, 46 C, 31 C, 74 C, 40 C, 10 C, 47 C, 75 C, 40 C, 46 C, 60 C, 76 C, 40 C, 46 C, 71 C, 77 C, 40 C, 11 C,
 80 C, 78 C, 40 C, 36 C, 86 C, 79 C, 40 C, 18 C, 90 C, 80 C, 40 C, 52 C, 91 C, 81 C, 40 C, 38 C, 90 C, 82 C, 40 C,
 71 C, 86 C, 83 C, 40 C, 45 C, 80 C, 84 C, 40 C, 68 C, 71 C, 85 C, 40 C, 25 C, 60 C, 86 C, 40 C, 16 C, 46 C, 87 C,
 40 C, 45 C, 29 C, 88 C, 40 C, 04 C, 10 C, 89 C, 40 C, 98 C, 87 C, 89 C, 40 C, 08 C, 63 C, 90 C, 40 C, 48 C, 35 C,
 91 C, 40 C, 99 C, 04 C, 92 C, 40 C, 87 C, 71 C, 92 C, 40 C, 84 C, 35 C, 93 C, 40 C, 94 C, 96 C, 93 C, 40 C, 19 C,
 55 C, 94 C, 40 C, 54 C, 10 C, 95 C, 40 C, 01 C, 63 C, 95 C, 40 C, 56 C, 12 C, 96 C, 40 C, 21 C, 59 C, 96 C, 40 C,
 90 C, 02 C, 97 C, 40 C, 67 C, 43 C, 97 C, 40 C, 50 C, 81 C, 97 C, 40 C, 27 C, 16 C, 98 C, 40 C, 05 C, 48 C, 98 C,
 40 C, 90 C, 76 C, 98 C, 40 C, 63 C, 02 C, 99 C, 40 C, 41 C, 25 C, 99 C, 40 C, 18 C, 45 C, 99 C, 40 C, 99 C, 61 C,
 99 C, 40 C, 65 C, 75 C, 99 C, 40 C, 36 C, 86 C, 99 C, 40 C, 88 C, 93 C, 99 C, 40 C, 42 C, 98 C, 99 C, 40 C, 00 C,
 00 C, 10 C, 41 C,
DECIMAL

: 2DUP ( F - F,F)
 OVER OVER
;

: 2SWAP ( F2,F1 - F1,F2)
 4 ROLL 4 ROLL
;
 
: 2OVER ( F2,F1 - F2,F1,F2)
 4 PICK 4 PICK
;

: 2ROT ( F3,F2,F1 - F2,F1,F3)
 6 ROLL 6 ROLL
;

: ERROR ( n - )
\ Imprime ERROR n en pantalla y ABORTA

 15421 C! ABORT
;

: F0= ( F - f true if F=0)
 0= SWAP 0= AND
;
 
: F0< ( F - f true if F<0)
 SWAP DROP 0<
;

: F> ( F2,F1 - f true if F2>F1)
 F- 2DUP F0< ROT ROT
 F0= OR 0=
;
 
: FLOAT ( n - F)
\ Convierte un entero en decimal

 DUP 0< SWAP ABS UFLOAT
 ROT
 IF
  FNEGATE
 THEN
;
 
: 2@ ( adr. - F)
\ Pone en la pila el número decimal almacenado a partir de la dirección de memoria adr.

 DUP @ SWAP 2+ @
;

: FINT ( F1 - F2)
\ Trunca la parte decimal de F1 y deja solo la parte entera (como numero decimal)
\ e.g. 7.82 FINT -> 7.

 INT FLOAT
;
 
 
: >RAD ( degs - rads)
\ ISIN no la usa. Pero es útil para el SIN del manual del JA que espera el ángulo en radianes, no en grados.

 .0174533 F*
;

: @TABLE ( n - F)
\ n es un entero entre 0 y 90. F es el valor del seno del ángulo n almacenado en STABLA

 4 * STABLA + 2@
;

: INTERVAL? ( F - f)
\ Devuelve falso (f=0) si 0.=<F<=90. Si F está fuera del márgen permitido devuelve verdadero (f=1)

 2DUP F0< ROT ROT 90.
 F> OR
;
 
: ISIN ( F - sin(F))
\ F ha de ester en el intervalo 0. a 90.
\ ISIN siempre interpola entre dos valores contiguos en STABLE
\ Si el F coincide con algúnos de los valores de la tabla (0. 1. 2. 3. .... 89. 90.) la interpolación
\ dará el mismo valor que si lo tomara directamente, pero es más lento...

 2DUP INTERVAL?
 IF
  19 ERROR
 THEN
 2DUP INT DUP 1 +
 @TABLE ROT @TABLE 2SWAP 2OVER
 F- 2ROT 2DUP FINT F-
 F* F+
;


isin.TZX
(866 Bytes) Descargado 6 veces

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 25 Ago 2022 22:10

Hace poco abrí este hilo EigthyOne: Modificar fichero de la ROM por un problema que tuve al intentar modificar el fichero de la ROM del Ace Forth que usa el emulador.

La idea era implementar una versión más rápida del ROT. Era una variación de la idea subyacente en un ROT en código máquina que implementa el programa TURTLE: Es lo mismo hacer ROT que 3 ROLL.

El ROTTurtle hacía eso en código máquina aprovechando la rutina primitiva del ROLL del Ace Forth.

Yo inicialmente intenté algo más sencillo. Hacer el 3 ROT pero como definición COLON interna. Aunque no es tan rápido como el ROTTurtle, sí es más rápido que el ROTROM.

Visto que daba problemas, pasé a implementar directamente en la ROM la versión TURTLE. Pero daba los mismos contratiempos.

Tras penosas comprobaciones, el problema resultó ser que ROLL es imcompatible con una rutina del Ace Forth que utiliza ROT en su funcionamiento. Concretamente es la rutina del RESTART 32 (o 20 hex), que se encarga manejar los errores.

Tanto una como la otra modificación de la ROM hacían que ROT utilizara ROLL. Como RESTART 32 utiliza ROT, entonces RESTART 32 utiliza ROLL. Y esto se producía en un entorno en el que ROLL daba error y llama a RESTAR 32. Dando lugar a un bucle infinito (hasta que se llena la pila de retornos).

Al principio desistí, pues había muy poco "hueco" (10 bytes) para meter la modificación y no creí que cupiera si tenía que añadir más código.

Hoy he vuelto a mirarme el tema y he encontrado la manera de implementar en la ROM el ROTTurtle con una ligerísima modificación que, afortunadísimamente, cabe justo en el espacio del ROTROM.

En el esquema siguiente, a la izquierda está ROTTurtle, a la derecha está ROTModificado y en el centro el código máquina de ROLL (listado del centro copiado del desensamblado de la ROM del Jupiter Ace Archive).
NOTA: En el esquema pone ROTROM en la parte derecha. Debería poner ROTModificado

rot_rom red.png
rot_rom red.png (92.92 KiB) Visto 403 veces

Vemos que ROTTurtle hace el registro BC=3 y ejecuta el código máquina de ROLL evitando ejecutar el CALL $084E. Este CALL llama a una subrutina que carga en el registro BC el valor que hay en el top de la pila. Pero ROTTurtle ya ha hecho previamente BC=3.

También vemos que la instrucción JR NC,$095B salta siempre, pues C solo se activa si BC era 0 o negativo (ver comentarios en el desemsamblado original citado). Cuando no salta se ejecuta RESTART 32($20) con código de error #7 pero como ROTTurtel hace BC=3, seguro que se ejecutará el salto.

Una vez retorna del CALL se ejecuta un salto al resto del código de ROLL. Las instrucciones marcadas en color rojo se encargan de comprobar que los punteros de la pila están bien. Si están mal salta a $04D7:

L04D7:  RST     20H                     ; Error 2
DEFB $02 ; Data stack underflow.

donde se realiza un RESTART 32($20) con código de error #2

Estas tres instrucciones en rojo son la que provocan el problema. Hay que hacer que ROTModificado se las salte, pero es necesario que se ejecute el EX DE,HL.

Y aquí viene la gran suerte que he tenido: La instrucción EX DE,HL solo ocupa un byte. Lo cual permite que el código de ROTModificado ocupe 10 bytes... pfiuuu, por los pelos!.

¿Qué consecuencias trae que ROTModificado se salte estas tres líneas que comprueban el estado de los punteros de la pila?

ROTROM (el original del Ace Forth) solo genera error si se ejecuta con la pila vacía. En cambio, si en la pila hay, en lugar de 3 o más números, solo dos o uno, NO da error, y es una situación errónea.

Pues bien, la versión Modificada tampoco da error cuando no hay ningún número en la pila. Pero en este último caso no hace nada (*). Todo queda como estaba. Por lo que este comportamiento es asumible.

Pero, aunque aceptable, hay que tenerlo presente: ROTOriginal da error si la pila está vacía pero ROTModificado NO.

También implementé en la ROM la versión OVER de TURTLE. Ésta no dio problemas nunca, pues no ejecuta la parte problemática (**):

LD   BC,$0002
CALL $0950
JP (IY)

Y precisamente porque no ejecuta la comprobación de punteros, tampoco da error cuando se ejecuta con la pila vacía (ni si hay solo un número). Pero también es asumible, pues el OVER original no da error cuando en la pila hay solo un número, cuando debería.

Las velocidades en modo FAST son (ejecuciones por segundo)

ROT(ROM) 1886
ROT(Turtle) 4500
ROT(Modificado) 4787
OVER(ROM) 1886
OVER(Turtle) 5487

Como era de esperar, la versión ROTModificado es algo más rápida que la ROTTurtle pues no ejecuta las tres instrucciones que están en rojo.

(*) En realidad sí que hace algo, ROTa los 3 primeros números de la zona de seguridad de 12 bytes (6 números) que hay entre la pila y el final del diccionario. Pero como es eso, una zona de seguridad, pues no pasa nada.

(**) Solo ejecuta el código a partir de $0950. Esta parte del ROLL, cuando se le suministra n=2 equivale a OVER. Luego la parte del código de ROLL que empieza en $0938 se encargaría de mover el contenido de la la pila una posición dando como resultado 2 ROLL que equivale a un SWAP. Pero como NO se ejecuta, nos quedamos con un OVER. Muy ingenioso la verdad.

Fichero Ace Forth ROM con ROTModificado y OVERTurtle en lugar de los originales.
ace.zip
(8.14 KiB) Descargado 4 veces

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 27 Ago 2022 13:29

Hoy he encotrado este Pack del señor Dutra de Lacerda con multitud de cosas interesantes para el Jupiter Ace.

Pongo el primer par de párrafos del fichero Read-me.1st:
Archive content
===============
The core of all this, is the patched ROMs. These are shared as a restoration.
And then: The "An ACE back, and Forth" (2Vol) ebook, a report on the Jupiter ACE.
Full of facts, with reminders and Libraires never shown before (why not? keep asking!)

Snapshots in this archive for ZEsarUX (and ACE32)
ALL files may be used with BOTH ace.rom and alternate_ace.rom
This alternate Rom is TOOL.ROM (it replaces with benefits the previous CODE.rom)


Todo parece de lo más interesante.

Por lo poco que he visto del señor Dutra antes de este Pack, me atrevo a aseverar que es un auténtico "Monstruo del Ace Forth".

JupACE PACK 3.7 (DOCs, Patches, Emulator).zip

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 29 Ago 2022 15:59

Estoy viendo que los cálculos de velocidad que hago no son consistentes. El mayor problema es que no duran mucho para tener un "cronómetro" que mide de décima en décima de segundo.

La solución pasa por hacerlos mucho más largos. Hasta hace poco era reacio por lo aburrido de mediciones de larga duración pero, tonto de mí, hasta hará cosa de un par de semanas no caí en la cuenta que puedo aumentar la velocidad del emulador a 20x o más sin afectar a los resultados. Pues mi cronómetro se reduce a "contar frames" y si aumento la velocidad, también aumenta en la misma proporción la velocidad de los frames...

Cronometro usando dos palabras:

: TIMEX_INI ( - )
0 15405 ! 0 15403 !
;

: TIMEX ( - n)
15403 @ 624 3125 */
;

TIMEX_INI pone el contador de frames a 0 y TIMEX pone en la pila el tiempo transcurrido en décimas de segundo desde que se ejecutó TIMEX_INI (lo calcula contando los frames transcurridos y conociendo la duración de los mismos).
Ver las páginas 142-143 y los ejercicios 2 y 3 de la pág 144 del Manual del JA.

Sigo un esquema similar (casi idéntico) al de estos benchmarks que ya comenté hacia el principio de este hilo.

Veámoslo aplicado al ROTOriginal vs ROTModificado vs ROTTools vs ROTDutra (*):

: DOL ( - n)
TIMEX_INI 10000 0
DO
10 0
DO
10 20 30
LOOP
SP!
LOOP
TIMEX
;


: ROTEST ( - n)
TIMEX_INI 10000 0
DO
10 0
DO
10 20 30 ROT
LOOP
SP!
LOOP
TIMEX
;

Ejecuto:
DOL . ROTEST .

Los tiempos en segundos son (Fast):

Orig. Modif. Tools Dutra
DOL 39.4 39.4 -- 39.4
ROTEST 87.8 55.8 -- 61.3

Luego la velocidad de ejecución de ROT es:

ROTOrigi. -> 100000/(87.8-39.4) = 2066 ejecuciones/s
ROTModif. -> 100000/(55.8-39.4) = 6097 ejecuciones/s (3x)
ROTDutra -> 100000/(61.3-39.4) = 4566 ejecuciones/s (2.2x)

Ahora, de modo parecido, calculamos los tiempos para OVER

Los tiempos en segundos son (Fast):

Orig. Turtle. Tools Dutra
DOL2 31.6 31.6 31.6 31.6
OVERTEST 77.1 42.4 49.2 50.0

Luego la velocidad de ejecución de OVER es:

OVEROrigi. -> 100000/(77.1-31.6) = 2197 ejecuciones/s
OVERTurtle -> 100000/(42.4-31.6) = 9259 ejecuciones/s (4.2x)
OVERTools -> 100000/(49.2-31.6) = 5681 ejecuciones/s (2.6x)
OVERDutra -> 100000/(50.0-31.6) = 5434 ejecuciones/s (2.5x)


Por lo que se ve, a menos que me haya equivocado con algo, las versiones de ROT y OVER del TURTLE Graphics Pack son las más rápidas.

Además, la versión ROTModif obtenida modificando ligeramente la versión del Turtle Pack, cabe justo en el espacio ROM de la original (10 bytes) sin provocar que RSTh20 funcione mal.

(*) He añadido a las versiones Original y Modificada, las de la ROM Ace_Tools y la de la librería de Dutra (ambas del Pack de Dutra).

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 29 Ago 2022 22:57

DUP es una primitiva del Ace Forth
2DUP se puede programar como : 2DUP OVER OVER ;
Si hemos definido (o puesto en ROM) la versión OVERTurtle podemos estimar que 2DUP estará algo menor de 4600 ej/s dado que ejecuta dos OVER y éste es de 9259 ej/s

He cronometrado el DUP Original y el 2DUP en código máquina de Dutra. Veamos los resultados. (Fast)

DUPOrig. 8696 ej/s
2DUPOver < 4600 ej/s (usa OVERTurtle)
2DUPDutra 12658 ej/s -shock

NO solo es más rápido el 2DUPDutra que el 2DUPOver<Turtle> sino que además es más rápido que el DUP !!!

Lo primero que pensé es: NO puede ser ¿2DUP más rápido que DUP? y revisé todo el proceso de cronometraje buscando el error que no encontré...

La cosa se aclara al ver el código máquina de ambas palabras. El DUP original usa tres RSTs (24, 16 y 16 -> toma un número de la pila y lo repone dos veces), con lo que su PF solo ocupa 5 bytes. El 2DUP de Dutra usa un LDIR para duplicar las dos primeras posiciones de la pila ocupando su PF 20 bytes.

O sea,
DUPOriginal ocupa poca memoria pero es relativamente lento.
2DUPDutra ocupa más memoria pero es muy rápido.

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 30 Ago 2022 17:07

Vamos a modificar un poco más el OVERTurtle.

Primero vamos a hacer una versión que siga cabiendo en el espacio del OVEROriginal en la ROM pero que sea más rápido aún que el OVERTurtle

La idea es simple. Partimos el OVERTurtle. En el siguiente esquema tenemos OVERTurtle a la izquierda y en el centro el ROLL de la ROM original del Jupiter Ace.

Over Turtle RAM red.png
Over Turtle RAM red.png (72.98 KiB) Visto 261 veces

Si lo examinamos bien, vemos que el código que va dese $0950 hasta el JR NC,L095B lo único que hace es pasar de BC=2 a BC=4. Además el Carry flag estará a cero y continuará en L095B.

Por consiguiente, podemos cambiar la definición de OVERTurtle a esta otra:

LD BC,0004
XOR A
CALL 095B
JP (IY)

Como vemos se ahorra mucho código del ROLL, pues el tramo que evitamos lo único que hace que nos interesa a nuestro caso es hacer BC=4 y eso ya lo hacemos nosotros. Hemos tenido que poner el XOR A para asegurarnos que el Carry flag está a 0 por la resta que viene más abajo (SBC).

Esta nueva versión de OVERTurtle a la que llamaremos OVERTurtle-2 es algo más rápida. Ocupa un byte más pero cabe perfectamente en la ROM y nos sobra todavía un byte.

Si lo cronometramos nos da:

OVERTurtle-1 9259 ej/s
OVERTurtle-2 10638 ej/s

Aún podemos modificar más OVERTurtle-2 para que sea todavía más rápido, pero a costa de aumentar su tamaño tanto que ya no cabe en la ROM. Será una versión rápida pero en RAM. La llamaremos OVERTurtle-3

Lo que haremos es, en lugar de hacer un CALL al código del ROLL, copiaremos el código de ROLL que usamos directamente al PF de nuestra palabra. Así nos ahorramos un CALL y su correspondiente RET. También no ahorramos un par PUSH HL/POP HL que no necesitamos (marcados en rojo en el esquema).

Así la definición de OVERTurtle-3 será:

LD BC,0004
XOR A
LD HL,($3C3B)
SBC HL,BC
LD E,(HL)
INC HL
LD D,(HL)
RST $10
JP (IY)

La cronometro:
                          Cabe en ROM
OVERTurtle-1 9259 ej/s Sí
OVERTurtle-2 10638 ej/s Sí
OVERTurtle-3 12658 ej/s No


Voy a poner OVERTurtle-2 en mi ROM modificada (que ya incluye ROTModif) en lugar del OVERTurtle
ace.rar
(8.07 KiB) Descargado 4 veces

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 30 Ago 2022 21:58

El otro día probé la ROM Ace_Tools del Pack de Dutra en el EightyOne 1.28 y funciona pero la pantalla no se ve muy bien. Se ve que lo que ha hecho de reducir el tamaño en bytes de los caracteres para ganar espacio en la ROM, el EightyOne no lo entiende bien del todo.

Hoy, tras volver a leer la documentación dice que solo funciona correctamente en el ACE32 y en el ZEsarUX. Lo he probado en este último y sí, se ve perfecto. Otro punto para el ZEsarUX vs EightyOne.

Pongo imagen de como se ve en el EigthyOne. Funcionar funciona y he podido medir la velocidad de OVER con mis programas y demás. Pero se ve mal. Debería verse la pantalla toda en negro con los caracteres en blanco sobre negro.
screen1.png
screen1.png (11.04 KiB) Visto 255 veces

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 01 Sep 2022 22:37

El ebook del Pack de Dutra An ACE back, and Forth he encontrado muchas cosas interesantes. Entre ellas su versión de la palabra CODE.

En el Manual del Jupiter Ace la palabra CODE se define con DEFINER. Esto obliga a que cualquier palabra creada con él necesita que CODE esté presente en el diccionario para poder ejecutarse.

DEFINER CODE
DOES>
CALL
;

CODE HALT
HEX 76 C, FD C, E9 C,

HALT contiene los bytes del código máquina y cuando lo ejecutamos deja su PFA (la dirección de inicio de su Parameter Field) en la pila y se ejecuta el Runtime de CODE, que no es más que un CALL que ejecuta el código máquina a partir de la dirección que toma de la pila.

Otro inconveniente además de exigir la presencia de CODE, es el OVERHEAD que conlleva la ejecución de una palabra definida con una definidora de usuario. Por un lado ha de poner su PFA en la pila y por otro ha de ejecutarse el CALL desde CODE.

Lo que yo hacía, tras estudiar el Assembler-Disassembler de Boldfield, era crearlas como tipo CREATE y luego cambiar el CF de ésta por otro que apuntara a su PFA, tal como he comentado en varias ocasiones. Todo eso a mano:

CREATE HALT
HEX 76 C, FD C, E9 C,
y la transformo de tipo CREATE a una primitiva así:
HALT DUP 2- !

Pues bien, lo que hace Dutra es definir la palabra CODE así:

: CODE
CREATE HERE DUP 2- !
;

Ahora HALT se definiría así:

CODE HALT
HEX 76 C, FD C, E9 C,

En el fondo es lo mismo que yo hacía, pero así es mucho mejor, más cómodo y más elegante .

Si es el caso que el código máquina lo he generado con el TASM y luego quiero cargarlo en el PF de la primitiva que creo, lo puedo hacer también con CODE. Así, si creo la palabra MICODIGO que va a tener un PF con el código máquina de, digamos, 80 bytes, haría esto:

CODE MICODIGO 80 ALLOT

y solo restaría cargar el fichero .bin del TASM con el código máquina en el PF de MICODIGO (con Load Memory Block en el EightyOne).

Elurdio
Mensajes: 507
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 112 veces
Agradecimiento recibido: 95 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 01 Sep 2022 22:38

Otra cosa interesante del ebook de Dutra, que además me dejó boquiabierto, es una versión de la palabra S" descompilable.

La palabra S" no existe en el JA. Pero se puede programar.

S" permite compilar texto dentro de una palabra. Cuando se ejecuta S" deja en la pila la dirección de inicio y la longitud del texto que compiló, listo para TYPE.

Veamos un ejemplo sencillo de su uso:

: MISTR ( - adr,n)
S" Cosas de la vida"
;

Todo el texto entre S" y " se compila en MISTR (como siempre, ha de haber al menos un espacio entre S" y el primer carácter del texto).
Cuando se ejecuta S" dejará en la pila la dirección de inicio y la longitud del texto que compiló listo para TYPE. Así si hacemos:

MISTR TYPE
Obtenemos:
Cosas de la vida

La palabra S" la definimos con COMPILER así: (adaptación de la versión de Marcos Cruz de aquí)
-1 COMPILER S"
ASCII " WORD COUNT
DUP 1+ , ( guarda el tamaño del campo de parámetros )
DUP C, ( guarda la longitud de la cadena)
0
DO
DUP I + C@ C, ( guarda los caracteres de la cadena )
LOOP
DROP
RUNS>
2+ ( dirección de la longitud de la cadena )
COUNT ( pila: adr. primer carácter, longitud cadena)
;

Si listamos/editamos MISTR vemos:
: MISTR
S"
;

El texto compilado (Cosas de la vida) no se descompila (típico de las palabras compilantes de usuario en el Ace Forth). Si editamos MISTR tendremos que volver a teclear el texto de nuevo...

Pues bien, en la versión de S" de Dutra el texto compilado SÍ se descompila. El truco es sencillo pero muy ingenioso. El texto se ingresa como un comentario, o sea, entre paréntesis. Como sabemos, los comentarios Ace Forth los descompila.

Así, si S" es la versión de Dutra haríamos:

: MISTR ( - adr,n)
S" ( Cosas de la vida)
;

Si listamos/editamos MISTR vemos:
: MISTR
S" ( Cosas de la vida)
;

Que podemos editar tranquilamente.

La definición de S" es:
0 COMPILER S"
RUNS>
2+ ( dirección de la longitud de la cadena)
DUP 2+ SWAP @ ( esto es similar al COUNT de antes{*})
;

Ahora la parte compilante no hace nada pues ya se encargará ( de compilar el comentario {paréntesis es un palabra compilante del Ace Forth}
El Runtime es muy parecido al de antes, {*} solo que NO podemos usar COUNT, pues éste espera una cadena contada en la que el tamaño se almacena en un byte, mientras que los comentarios almacenan el tamaño en 2 bytes. Lo que hemos puesto equivale a la definición de un COUNT para cadenas contadas con el tamaño del texto almacenado en 2 bytes en vez de en uno.

Otro detalle es que en ambos casos, el Runtime le suma dos al OFA (Operand Field Address) que hay en la pila. Pero es por motivos diferentes.

En el primer caso es porque al ser una compilante de tamaño indeterminado (no se sabe hasta que hemos escrito el texto) los dos primeros bytes contienen el tamaño del OF (sin contarse ellos mismos). De ahí el +2 para saltarnoslo y llegar al primer byte de la cadena contada, el que contiene el tamaño de la misma.

En el segundo caso el tamaño del OF es fijo (cero en nuestro caso) por lo que el tamaño se guarda en S" no aquí. En este caso el +2 es para que se salte la CA (Compilation Address) de la palabra paréntesis. A continuación viene el tamaño del comentario. Ver la estructura de la palabra ( aquí

NOTA: En la versión original del S" en el ebook de Dutra, el Runtime es la palabra (S") en código máquina. Lo que yo he puesto sería la versión en Forth de dicha palabra, para que se vea lo que hace. Obviamente (S") se ejecuta mucho más rápido.


Volver a “Jupiter Ace”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado