FunkASM

Got a calculator project that you're working on, tell everyone about it
  • Author
  • Message
Offline
User avatar

add

Topic Starter

FunkASM

Post16 February 2015, 19:33

NanoWar wrote:It's always good to know the problems of a programming language and what you really hate about it. Then you can try to change that.

What I dislike about ASM is the register poverty. I really want to abtract these away. And the nonexistence of function parameters (or lack of methods of documentation/discovery) annoys me.

HASM should introduce functions and implement register spilling.


We can make this a collaborative project on github since we now have an organization on github.
What I'm thinking right now is very much a C-like language, but I'm very unsure.
Some constructions just don't feel necessary/relevant that are in C, so it's likely to be a Bare-Minimum-C if anything like C at all..
What do we want and/or need?
  • Functions
  • Register Spilling
  • Macros
  • Speed/Size Optimizations
  • Good code readability
With more to come.

I guess we'll need to consider a lot/few more things before we start working on HASM, like what language we are implementing this in, cross-compilation, libraries and so forth.
Last edited by add on 09 November 2017, 13:26, edited 1 time in total.
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post16 February 2015, 23:19

I think just a little abstraction would be fun, also without register spilling:
Code: Select all
a = *hl ; ld a, (hl)
hl += de ; add hl, de
bc = {{1, 8}, -1} ; ld bc, $18FF


but with spilling we could do more complex stuff:
Code: Select all
reg8 _target_reg = hl[num _number]
; would be something like:
#if _number = 0
      _target_reg = *hl
#elseif _number = 1
      hl++
      _target_reg = *hl
#else
      push  de
            de =_number
            hl += de
            _target_reg = *hl
      pop   de
#end
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post17 February 2015, 12:59

I'll leave this to you all, C makes my head spin... I agree though on the register poverty. You really do need to use the shadows more. exx and ex af,af' only take up 4 t-states and give you a whole new set of registers to work with. Still, working with ARM and other processors is like a dream with all the registers (and easy manipulation of the stack). I do wish the z80 had more registers, but it really wouldn't fit in the instruction set, at least not without adding more 2-byte/prefixed instructions which wouldn't make them as fast.
Offline
User avatar

add

Topic Starter

Re: HASM

Post17 February 2015, 14:08

chickendude wrote:I'll leave this to you all, C makes my head spin... I agree though on the register poverty. You really do need to use the shadows more. exx and ex af,af' only take up 4 t-states and give you a whole new set of registers to work with. Still, working with ARM and other processors is like a dream with all the registers (and easy manipulation of the stack). I do wish the z80 had more registers, but it really wouldn't fit in the instruction set, at least not without adding more 2-byte/prefixed instructions which wouldn't make them as fast.


I didn't mean that it was going to be a C clone language, that'd be boring, and also probably done already.
No, a layer of convenience for programming is the goal, I think we will need your input also chickendude. :-)
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post17 February 2015, 17:45

Well i think you can really use the Funk Library as a starting point. There was also EZAsm which had some cool stuff, too. I helped Joel out with some of the (simpler) assembly routines back in the day. We used to talk on AIM ;)
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post17 February 2015, 18:52

I already had a folder named FunkAsm where I grabbed the above examples from :)
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post24 February 2015, 10:52

(btw: HASM is already taken, maybe change name later)

I found http://www.rust-lang.org/ very beautiful and we could borrow some syntax and ideas from Rust!
Offline
User avatar

add

Topic Starter

Re: HASM

Post24 February 2015, 13:49

Ah, ok.
I haven't looked much at Rust before, will do now as it seems nice.
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post26 February 2015, 21:42

Maybe something like this?

Code: Select all
fn Collides? (pos: hl) -> z, action?: y // carryyy :)
    if OutOfBounds? (pos)
        goto _no
    end

    let tile: a = GetTileFromMapBuffer (pos)

    match tile
        = Tiles::Chest                  => goto _yes_action
        < TileAreas::WalkableEnd        => goto _no
        < TileAreas::WalkableActionEnd  => goto _no_action
        _                               => {} // fall through to _no
    end

    // interfere that return register is f, bracket first zero, then carry

    _no:
    return {false, false} // or 1

    _no_action:
    return {false, true} // or 1; scf
   
    _yes:
    return {true, false} // cp a

    _yes_action:
    return {true, true} // cp a; scf
end
 
Offline
User avatar

add

Topic Starter

Re: HASM

Post02 March 2015, 12:42

Hm, not sure, that code snippet looks kinda odd to me.
I'll have to study it a bit more if I'm going to give a proper proposition.
Offline
User avatar

add

Topic Starter

Re: HASM

Post21 May 2015, 19:01

Maybe we could implement the new language/assembler in Rust.
The feature "No (core/mandatory) garbage collection" in any language is promising IMO. :-)
Offline
User avatar

add

Topic Starter

Re: HASM

Post22 May 2015, 10:33

*Sigh*, never ask friends for advice regarding languages that advertise themselves as a "C/C++ replacement".
I guess I'm sticking to C for now, and probably forever if this keeps up.
Why can't a good system programming language be made...
I highly doubt I'd make a good programming language designer.. :'(
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post25 May 2015, 16:28

I think i'd be ok at designing a programming language. "What should we call this one? Hm... how about adc. And this one? How about... push! Yes, and this one? ret sounds nice to me."
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post26 May 2015, 15:43

I thought I'd share some more:
Code: Select all
mod Modern

      struct vec3
            byte x
            byte y
            byte z
      end

      fn VecSum (vec3:ix &vector) -> a
            let result: a = 0
            result += vector.x
            result += vector.y
            result += vector.z
            return result
      end

/*
VecSum
      xor a
      add a, (ix+0)
      add a, (ix+1)
      add a, (ix+2)
      ret
*/

      struct Point
            byte X
            byte Y
      end

      fn AddOffset(hl base, de width, Point:bc offset) -> a
            if offset.Y > 0
                  loop offset.Y
                        base += width
                  end
            end
            base += offset.X
            return *base
      end

/*
AddOffset
      ld a, b
      or a
      jr z, ++_
_     
      add hl, de
      djnz -_
_
      add hl, bc
      ld b, a
      ld a, (hl)
      ret
*/

end
 
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post28 May 2015, 04:02

Your add offset could probably be optimized a bit for speed with an actual multiplication routine (base += width*Y), at least if your offset will generally be more than about 15 or so. The delay would also be more consistent ;) How would you return 2 bytes from that area? Eg. if you had a table of pointers to strings. "return *base+*(base+1)<<8?" that seems harder to optimize into a ld a,(hl) \ inc hl \ ld h,(hl) \ ld l,a.
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post01 June 2015, 15:55

chickendude wrote:How would you return 2 bytes from that area? Eg. if you had a table of pointers to strings. "return *base+*(base+1)<<8?" that seems harder to optimize into a ld a,(hl) \ inc hl \ ld h,(hl) \ ld l,a.


In SPASM I have ld hl, (hl) for that. But I guess you would just change the return register from a to hl.
Code: Select all
fn AddOffset(hl base, de width, Point:bc offset) -> hl;****
...


Then internally we'd have:
Code: Select all
      ...
      let result: hl = *base
      return result

;which will do
      ...
      ld a, (hl)
      inc hl
      ld h, (hl)
      ld l, a
      ret

;or
      ...
      jp funk_dereference ; also saves af
 

:)
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post19 June 2015, 15:37

Let's go for a quick code challenge: code the following statements in ASM!
(quare brackets are indirection ld a, (hl)-style)

Code: Select all
hl = [bc] + hl + [de] + [hl]
de = bc + [hl] - a
de = 0x4000 + [hl] + [hl]
a = [hl+[de]]
 


I will use it for an insight on your minds :P and maybe for compiler optimization ideas.
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post20 June 2015, 16:16

I haven't tested these and i haven't touched asm in a bit (everything's been in annoying C the past couple months :P), but here's my take on these, though i can't really think of a situation where i would actually want to do any of these :P:
hl = [bc] + hl + [de] + [hl]
Code: Select all
ld a,(bc)    ;+ [bc]
add a,(hl)  ;+ [hl]
ld b,0
 jr nc,$+3   ;check for carry
inc b
ex de,hl
add a,(hl)  ;+ [de]
 jr nc,$+3   ;again, check for carry
inc b
ld c,a    ;bc = [bc]+[de]+[hl]
ex de,hl
add hl,bc   ;hl = hl + [bc]+[de]+[hl]

de = bc + [hl] - a
Code: Select all
neg
add a,(hl)    ;[hl] - a
ld l,a
ld a,$ff   ;-1 (sign extending hl)
adc a,0   ;if there was a carry, it's a positive number (h = 0, else h = $ff)
ld h,a
add hl,bc  ;[hl] - a + bc
ex de,hl    ;put result into de

de = 0x4000 + [hl] + [hl]
Code: Select all
ld a,(hl)   ;[hl]
add a,a   ;[hl]+[hl]
ld de,$4000
ld l,a
ld a,e    ;a = 0
adc a,0   ;check if [hl]*2 carried over
ld h,a   ;hl = (hl)*2
add hl,de ;hl = $4000 + (hl)*2
ex de,hl

EDIT: This code seemed a bit too long, here's my second try:
Code: Select all
ld de,$4000
ld l,(hl)
ld h,e
add hl,hl
add hl,de
ex de,hl


a = [hl+[de]]
Code: Select all
ld a,(de)
ld e,a
ld d,0
add hl,de
ld a,(hl)
I haven't actually tested any of the code though.
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post21 June 2015, 07:37

This is my take. Dont be confused, I use *hl for indirection.
Code: Select all
hl = *bc + hl + *de + *hl
      ; Analyze:
      ;     Are there any indirections, except *hl? => yes, needs a => save a
      push AF
      ; Optimize:
      ;     All plus => sort parameters: hl, *hl, *de, *bc
      ; Load first value into hl
      ;ld hl, hl ; remove
      ; Add *HL
      ; Word add instruction needs hl and de => push de, because it is input
      push DE
      ; Optimize:
      ;     Next instruction does not use a => pre-populate a with *de
      ld a, (DE)
      ld e, (HL)
      ld d, 0
      add HL, de
      ; Add *DE
      ; Optimize:
      ;     d = fixed = 0
      ld e, a
      add hl, de
      ; Add *BC
      ld a, (BC)
      ld e, a
      add hl, de
      ; Clean stack
      pop DE
      pop AF


de = *hl - a + bc
      ; Sort parameters (result is word)
      ;     HL is word ACC
      ;     *hl, bc, -a
      ; Load first into hl
      push HL
      ld l, (hl)
      ld h, 0
      ; Add bc
      add hl, bc
      ; Sub A
      ;     Make A to word
      ld e, A
      ; Check for fixed zero => h
      ld d, h
      or a \ sbc hl, de
      ; Move to target
      ex de, hl
      ; Clean stack
      pop HL


de = 0x4000 + *hl + *hl
      ; Analyze:
      ;     Nums go last
      ;     Double parameter
      ;           Rewrite: "de = *hl * 2 + 0x4000"
      ; Load first into hl
      ;     Surround with push/pop HL
      push HL
      ld l, (HL)
      ld h, 0
      ; Mul 2
      add hl, hl
      ; Add 0x4000
      ;     Load num>255 into de
      ;           de is target, thus tmp, no surround
      ld de, 0x4000
      add hl, de
      ; Move to target
      ; Optimize ld de, hl, because hl is not needed (on stack anyway)
      ex de, hl
      ; Clean up
      pop HL
 


Ok! Next challenge:
Please also optimize for register restoring
Code: Select all
      if (*hl == a + 6) {
            c = a + *(hl+1) ; a is not a+6, but the value before the if
      }
      a = b + c + *de
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post21 June 2015, 15:51

Does your code for "de = *hl - a + bc" work? it looks like you just pop the original values hl and de back. For "de = 0x4000 + *hl + *hl", you can move the load to de (ld de,$4000) before the ld l,0 and load the zero from e, it'll save a byte and 3 t-states. And restoring registers will slow things down a bit, push/pops are pretty expensive...
And i dunno if this'd work:
Code: Select all
    add a,6
    cp (hl)    ;(hl) == a+6
     jr nz,skip
    sub 6    ;offset the a+6, later we overwrite a anyway so we don't need to worry about restoring a if the if is false
    inc hl
    add a,(hl)
    ld c,a    ;c = a + (hl+1)
    dec hl  ;restore hl from the (hl+1) bit, acc gets overwritten so no need to restore
skip:
    ld a,(de)
    add a,c
    add a,b    ;a = b + c + (de)
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post22 June 2015, 11:16

Oops yes I messed up. It is fixed now.

My solution for the IF is a little different, because for a compiler it's hard to know which registers are (not) used in a code frame:
Code: Select all
      if (*hl == a + 6) {
            c = a + *(hl+1) ; a is not a+6, but the value before the if
      }
      ; byte comparison => save a
      push AF
      ; Prepare longer statement: a+6
      add A, 6
      ; Second statement is comparable without further ado
      ; Compare
      cp (HL)
      ; Fork
      ; Could be jr instead, maybe optimize later? but then _if1_end's address changes... how?
      jp nz, _if1_end
      ; Is a needed in loop as statement? => yes => restore a
      pop AF
      ; byte addition => save a
      push AF
      ; Load first parameter in a
      ; ld a, a ; remove
      ; Prepare bracket
      ; >> register savepoint for hl (maybe add push/pop hl later)
      inc HL
      ; parameter is addable without using further registers
      add a, (hl)
      ; << restore savepoint
      ; hl restoring is easier by undoing by dec for 1
      dec hl
      ; Move to target
      ld c, a
      ; Clean stack (inside loop stack frame only) => done
      ;     a will be popped after loop
      ; Check if there is an else clause, if so, jump to end of if => no else
_if1_end
      ; Clean stack
      pop AF
     
      a = b + c + *de
      ; byte addition => needs a, but a is target, so dont need to save a
      ; Prepare addition, optimize order => *de, b, c
      ; Load first parameter into a
      ld a, (DE)
      ; Add b
      add a, B
      ; Add c
      add a, C
      ; Move to target => done
      ; Clean stack => ok
 


What always annoys me in ASM is this: a = a + *de
Code: Select all
      ; is this actually the fastest?
      ex de, hl   ; 4
      add a, (hl) ; 7
      ex de, hl   ; 4
 
Offline
User avatar

chickendude

Staff Member

Re: HASM

Post22 June 2015, 17:13

You could do something like:
Code: Select all
   push de
    ld e,a
    add a,6
    cp (hl)
     jp nz,_ifend
    ld a,e
;...
_ifend:
    pop de
It takes the same amount of bytes, but using a register to back up a instead of the stack takes less than half as many t-states. Push/pop is 21 t-states, the register loads are only 4 each. But i guess again you'd have to know that a's value could get thrown away (ie. it's not used again).

And i believe that's the fastest way to do add a,(de). You could do something like ld a,c \ ld a,(de) \ add a,c, but that's not faster or smaller and wastes a register. I don't think it's that bad though, for example it's still faster than adding to an index register. I guess you generally just try to use HL when you can or use DE for multiple thing. I often use the shadows for that, though (eg. ld hl,tileMap \ ld de,(mapWidth) \ exx \ ld hl,gbuf \ ld de,WIDTH and pass data through a).
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post24 June 2015, 09:44

If you could variably name registers for different usages, what syntax would you use?

I like let/var/def name:type = value with valuebeing always optional and type being optional for var and def.

Examples:
let number:a = 6 will just do ld a, 6 but also do a registration for a as variable named "number". This will later be used for references and in the SSA-form.
var OLD_PEN:System::Pen being a ram variable at a free saferam point depending on earlier vars with type Pen which is 2 bytes => .var OLD_PEN, 2 (funk syntax).
def MAX_ITEMS = 50 => #define MAX_ITEMS 50


I have toyed around with syntax enriched asm, what do you think?
Code: Select all
mod System
{
      struct Pen {
            byte y
            byte x
      }
      def PEN:Pen = penCol ; this is a bit weird, when I do "let pen:de = System::PEN" I know that de is of type System::Pen which holds y and x, so I can do pen.x to access penRow

      struct Cursor {
            byte x
            byte y
      }
      def CURSOR:Cursor = curRow
}

mod Inventory
{
      struct Item {
            byte type
            byte amount

            enum Items {
                  None
                  Herb ; this would be Inventory::Item::Items::Herb
                  Feather
                  WarpStaff
                  WarpWing
            }

            static table Strings {
                  txt Herb "Herb" ; these "txt"s add a data segment at end of file, e.g. Inventory.Item.Strings.Herb: .db "Herb", 0
                  txt Description.Herb "Heals 30 HP"
                  txt Feather "Feather"
                  txt Description.Feather "Shows the teleport on the map"
                  txt WarpStaff "Warp Staff"
                  txt Description.WarpStaff "Teleports to the next level"
                  txt WarpWing "Warp Wing"
                  txt Description.WarpWing "Teleports back home"
            }

            static fn GetStrings(item:a) -> name:hl, desc:de
            {
                  let ptr:hl = Strings
                  ptr += (--item)*4
                  let desc:de = ptr
                  let name:hl = *ptr
                  desc += 2
                  desc = *desc
                  return name, desc ; tuple, can do "let name:hl, desc:de = GetStrings(type)"
            }

      }

      def MAX_ITEMS = 50
      var _items = Item[MAX_ITEMS] ; memory segment of size 100 (type Item holds members "type" and "amount")
      txt _delimiter = ":  "

      fn Display()
      {
            LCD.Clear()

            let item_ptr:hl = _items
            let position:de = System::Pen(0, 1)

            loop b = 10 {
                  save bc {
                        let item:ac = *item_ptr
                        if !item.type {
                              break
                        }
                        *System::PEN = position
                        save de, hl {
                              save bc {
                                    PrintStr(Item::GetStrings(item.type).name) ; or maybe item.GetStrings().name
                                    PrintStr(_delimiter)
                              }
                              PrintWrd(item.amount)
                        }
                        position.y += 6
                        item_ptr += 2
                  }
            }
            LCD.Update()
            Keyboard.Wait2nd()
            Game.Refresh()
            LCD.Update()
      }
}
 


With the asm equivalent (raw, taken from Tornado):
Code: Select all

#define System.PEN penCol
#define System.CURSOR curRow

.module Item
      makeText(":  ", txt.Delimiter)

      .enumStart 1, "Items"
            .enum Herb
            .enum Feather
            .enum WarpStaff
            .enum WarpWing
      .enumEnd

      .text Herb, "Herb"
      .text Description.Herb, "Heals..."

      .text Feather, "Feather"
      .text Description.Feather, "Shows the teleport on the map"

      .text WarpStaff, "Warp Staff"
      .text Description.WarpStaff, "Teleports to the next level"

      .text WarpWing, "Warp Wing"
      .text Description.WarpWing, "Teleports back home"
.nomodule


Item.Strings
      .dw txt.Item.Herb
      .dw txt.Item.Description.Herb
      .dw txt.Item.Feather
      .dw txt.Item.Description.Feather
      .dw txt.Item.WarpStaff
      .dw txt.Item.Description.WarpStaff
      .dw txt.Item.WarpWing
      .dw txt.Item.Description.WarpWing
      ;...


Item.GetStrings
; inputs:
;     a = item
;
      ld    hl, Item.Strings
      dec   a ; index starts at 1
      add   a, a
      add   a, a ; x4 => two pointers
      ld    d, 0
      ld    e, a
      add   hl, de
      ld    de, hl
      ld    a, (hl)
      inc   hl
      ld    h, (hl)
      ld    l, a
      ex    de, hl
      inc   hl
      inc   hl
      ld    a, (hl)
      inc   hl
      ld    h, (hl)
      ld    l, a
      ex    de, hl
      ; hl = *item name
      ; de = *item description
      ret


Inventory.Show
      clear
      ld    b, 10
      ld    hl, Inventory.Items
      ld    de, tuple(0, 1)
Inventory.Show.loop
      ld    a, (hl) ; item type
      inc   hl
      ld    c, (hl) ; amount
      inc   hl
      or    a
      jr    z, Inventory.Show.skip
      push  bc
      push  de
      push  hl
            ; Display item
            ld    (System.PEN), de
            push  bc
                  call  Item.GetStrings
                  ; hl = pointer to pointers
                  print
                  print txt.Delimiter
            pop   bc
            ; c = amount
            ld    h, 0
            ld    l, c
            disp_hl
      pop   hl
      pop   de
      ; Next row
      ld    a, d
      add   a, 6
      ld    d, a
      pop   bc
Inventory.Show.skip
      djnz  Inventory.Show.loop
      update
      call  Keyboard.Wait2nd
      call  Refresh
      update
      ret
 
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post02 September 2015, 23:09

Currently I am able to convert the following funk code into asm:
Code: Select all
fn main()
{
      a = 10
      a = a + 1
      if a == 11
      {
            Functions.bar(a)
      }
}

fn foo(value: ix)
{
      hl = value
}

mod Functions
{
      fn bar(value: c)
      {
            foo("Hello World!")
      }
}
 


becoming this:

Code: Select all
; Generated with FunkCompiler by Robert Kuhfss

main
      ld    a, 10
      inc   a
      cp    11
      jp    nz, __if_end
      ld    c, a
      call  Functions.bar
__if_end
      ret

foo
;Inputs:
;  ix = value
      push  ix
      pop   hl
      ret


;==============================
; Module: Functions
;==============================

Functions.bar
;Inputs:
;  c = value
      ld    ix, _str_1
      call  foo
      ret


;==============================
; Strings for page 0
;==============================

_str_1: .db "Hello World!", 0
 
Attachments
FunkShow.PNG
And sublime text 3 syntax highlighting yeha
FunkShow.PNG (31.08 KiB) Viewed 3800 times
Offline
User avatar

NanoWar

Site Admin

Re: HASM

Post06 November 2017, 21:58

Little progress on FunkAsm Transpiler (https://github.com/NanoWar/FunkCompiler)

Code: Select all
mod Types
{
      type Vec3
      {
            x: word
            y: byte
            z: byte
      }
}

fn StructReference(vec: ix[Types.Vec3])
{
      a = vec.z
}


transforms into:

Code: Select all
StructReference
;Inputs:
;  ix = vec [Types.Vec3]
      ld    a, (ix + 3)
      ret
Next

Return to Announce your Projects

Who is online

Users browsing this forum: Bing [Bot] and 1 guest

cron