Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Jass Hot Code Reload

Discussion in 'Warcraft Editing Tools' started by LeP, Mar 20, 2019.

  1. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,479
    Resources:
    11
    Models:
    3
    Tools:
    1
    Maps:
    5
    Tutorials:
    1
    Wurst:
    1
    Resources:
    11
    There is no reason for it to not work, since JASS is supported in reforged as it is in the original client.
    If it doesn't work for you, then specify *what* isn't working.
    I simply don't get ppl just writing "doesn't work" without any explanation. What do you expect to happen from that?
     
  2. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Thanks. I don't have access to reforge but i assume it is because they modified common.j
    Unfortunately i have to compile the program for each different common.j


    Yeah i guess. Although jhcr currently doesnt provide the best error messages. But saying which step doesnt work would atleast provide some insight.
     
  3. kenkz447

    kenkz447

    Joined:
    Dec 8, 2019
    Messages:
    2
    Resources:
    0
    Resources:
    0
    Sorry, i think the author already know this issue.
    I'm using wusrt and hot-reload command form vscode, there is no compilation or runtime error with jhcr but nothing happend when press ESC key(only print debug message). In W3 classic, everything work fine!
     
  4. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Nice to hear! Once the reforge patch hits classic you can expect a working version :)
     
  5. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Updated first post to make jhcr compatible with patch 1.32
    If you encouter any bugs please do tell me since i don't want to run 1.32 myself.
    But i only added the two new types (
    minimapicon
    and
    commandbuttoneffect
    ) so it shouldn't cause any trouble.
     
  6. SinisterLuffy

    SinisterLuffy

    Joined:
    Apr 8, 2020
    Messages:
    103
    Resources:
    0
    Resources:
    0
    How do I actually use this tool? I clicked on the application and then the command prompt appears and disappears instantly. Nothing happens afterward.
     
  7. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Well i wrote down how to use this in the first post but i will try to break it down a bit more.
    First of all: this is a command-line application so would run it from some kind of console prompt.
    Also it doesn't work with the map itself but only with the mapscript so you would have to extract and re-insert the mapscript. We can use ladiks mpq-editor to do this.

    So to get started we should prepare the map like this:
    Code (Text):

    # copy the our map to some temporary file for safety reasons
    cp our-map.w3x tmp.w3x

    # then we extract the maps script file
    MPQEditor extract tmp.w3x war3map.j

    # actually run jhcr for the init phase
    # you need the common.j and Blizzard.j files in the same directory as your map
    # this step produces the file jhcr_war3map.j
    jhcr init common.j Blizzard.j war3map.j --jasshelper

    # now we insert jhcr_war3map.j as war3map.j into tmp.w3x
    MPQEditor add tmp.w3x jhcr_war3map.j war3map.j

    # now we can start the map. but we copy it again to make sure we can later overwrite tmp.w3x again
    # the path is ofcourse different for most of you
    # also i don't know if -loadfile still works, might have changed with later patches
    cp tmp.w3x run.w3x
    /path/to/WarCraft\ 3.exe -loadfile run.w3x
     
    The above steps are neccessary when you freshly start the map.
    Now you edit your map in the worldeditor and at some point press save.
    If you didn't have any syntax errors you can now reload the changes to the script for example as follows:

    Code (Text):

    # copy our map again to tmp.w3x
    cp our-map.w3x tmp.w3x

    # extract the new script
    MPQEditor extract tmp.w3x war3map.j

    # now we use jhcr update instead of jhcr init
    jhcr update war3map.j --jasshelper --preload-path='/path/to/Documents/Warcarft\ III/CustomMapData/'
     
    If all went well you can now reload the changes in your map (however you set it up. see first post).

    This is actually quite close to my setup. I run pjass over jhcr_war3map.j to make doubly sure that it should actually work and i use a Makefile to seperate those steps. But except that it's mostly the same.

    ----------

    To be honest with you i don't know if this still works with the latest patch as i don't have it installed. There shouldn't be any inherent flaw why it shouldn't work anymore but small things might need to be fixed. If it doesn't work for you i am glad to fix it if you provide enough guidance.
     
    Last edited: Apr 15, 2020
  8. Wespir

    Wespir

    Joined:
    Apr 17, 2020
    Messages:
    1
    Resources:
    0
    Resources:
    0
    Hello, i also have a problem with JHCR.exe.
    I'm using Wurst as described on this Website here: https://wurstlang.org/.
    I installed Java 1.8.0 and enabled the Wurst VSCode Extension. Naturally, i installed WurstSetup.jar, used the command <java -jar WurstSetup.jar install wurstscript> and added the wurst folder to the environment variables. To generate the Project i used <grill generate <projectName>> on the command line. Hope I didn't forget something.

    Now, I tried the wurst-extension command which runs the projects standard wurst map example in it. This will start Wc3 and load the map as expected. There are no compilation errors, Wurst is succesfully converted to Jass. Looks good, until i stumbled over the hot-reloading section of the website. It links to this post with your JHCR.exe. I downloaded and set it up accordingly, meaning I added following to the settings.json in the projects root like described there:

    "wurst.jhrcExe": "C:\\<Path>\\jhcr.exe" and
    "wurst.customMapDataPath": "C:\\<Path>\Warcraft III"

    Finally, i pressed F1 in VSCode (to run the command provided by wurst to compile the map with potential hot-reloading) and watched the show. Unfortunately, it failed and the wurst extension only throws the same exception over and over again. It states practically nothing to me, only that the process failed "when running external tool":

    An exception was thrown when running the map:
    java.io.IOException: Failure when running external tool.
    Source: Wurst language support (Extension).


    This is all. I can't figure out what exactly fails in there. I would really appreciate it if you were to look after or even fix this. It may as well be just an user error, i.e. my fault. But I can only use what is documented on the website, as this thread dosn't cover jhrc combined with a generated wurst-project with VSCode. At least that is how i understood it, did not read everything here, sorry if that is not the case.

    Edit: I have the newest Warcraft 3 version installed.
     
    Last edited: Apr 17, 2020
  9. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Personally i don't use much wurst so i don't know if they have fixed this but in the past they didn't provide the error message of jhcr so i can't tell from afar what's going wrong. Normally wurst has more problems with updating the code and not with the initial compile step so there might be an interesting error at play here. The best course of action is to reach out to me on our channel #inwc.de-maps on quakenet.org (in reasonable europe hours).

    The problem could be that i haven't compiled jhcr with the newest common.j as i think they added some new natives with the newest patch?
    In any case i need the jass file fed to jhcr.
     
  10. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    I was thinking about writing some blog-like posts to explain how JHCR actually works.
    This first one will give a high-level overview. If you like this and want me to write
    more please tell me.

    The Problem



    Modifying WarCraft 3 maps has a long iteration period due to closing WC3,
    changing your code and restarting WC3. JHCR (Jass hot code reload) tries to
    improve this cycle by replacing the slow actions of closing and starting WC3
    with a hopefully faster compiler which pushes your code changes directly into
    an already running WC3.

    But how could something like that even work? The answer is multi faceted.
    So let's start with some very basic mockups.

    The most simple Idea



    Let's say we want to reload a single function, how would that look like in code?

    Code (vJASS):

    function foo takes nothing returns nothing
        if function_was_reloaded("foo") then
            // somehow execute the new body
            // call BJDebugMsg("New Foo")
        else
            // old body of foo
            call BJDebugMsg("Foo")
        endif
    endfunction
     


    While this has many holes it's conceptually correct already. But for the sake
    of easy implementation we split up the function into two functions, the original
    function but with a new name and a new function but with the old name:

    Code (vJASS):

    function JHCR_foo takes nothing returns nothing
        call BJDebugMsg("Foo")
    endfunction

    function foo takes nothing returns nothing
        if function_was_reloaded("foo") then
            // somehow execute the new body
            // call BJDebugMsg("New Foo")
        else
            call JHCR_foo()
        endif
    endfunction
     


    This way we don't have to change other functions calling
    foo
    when we update it.
    Everything from a simple call-statement over trigger-actions to such things as
    ExecuteFunc
    still work just fine.

    The harder part is now how to actually execute the new body of
    foo
    .
    To achieve this JHCR provides both a compiler to- and an interpreter for a
    custom bytecode language which will be superficially introduced in the next
    section.

    The Bytecode



    The bytecode was carefully designed to strike a balance between size, parsing
    speed and execution speed as all of these factors play a big role in our limited
    WC3 environment.

    For example the original
    foo
    would be translated to this bytecode:

    Code (Text):

    fun 3 foo
    lit string -2 Foo
    bind string 1 -2
    call -1 2 BJDebugMsg
    ret nothing
     
    But this is of course way too verbose and also not very fast to parse.
    So this format is only used for human consumption; to communicate with WC3
    JHCR provides another format for this exact bytecode which looks like this:

    Code (Text):

    | fun  |                       |       bind          |                 |ret|
    263.....21136-2.......3.....Foo201361........-2.......22-1.......2.....29139
            |         lit         |                       |     call      |
     
    I've annotated the different parts to show the opcode they represent.

    This is way easier to parse programatically and takes up less space.
    Every bytecode uses a fixed number and we encode numbers in a fixed width
    with added padding to achieve a good tradeoff between size and parsing speed.
    For fast parsing we use the fact that
    S2I("123asdf") == S2I(123)
    so we can move
    in well defined chunks over the input string.

    There are plenty more opcodes ofcourse but we'll save them for a future post.

    Integrating



    As we've seen previously we took special care to preserve the original name
    of our function but this was mostly to integrate our system into the maps script.
    Now we will talk about integrating the maps script into JHCR.

    Functions



    Take the aboves bytecode as an example: we call the BJ-function
    BJDebugMsg
    in
    there. How do we actually achieve this?
    As JHCR takes both
    common.j
    and
    Blizzard.j
    at compile time we assign each
    function an unique id (
    BJDebugMsg
    has id 2 in our example). Now, to be able to
    call this function we generate a huge if-then-else block for each id at compile
    time to call out to different functions. If we limit ourself to only three functions,
    that is
    DisplayTimedTextToPlayer
    ,
    BJDebugMsg
    and
    foo
    we can look at the code
    JHCR generates below.

    Code (vJASS):

    function JHCR_Auto_call_predefined takes integer JHCR_reg,integer JHCR_i,integer JHCR_ctx returns nothing
        if (JHCR_i < 2) then
            call DisplayTimedTextToPlayer (JHCR_Table_get_player (JHCR_Context_bindings[JHCR_ctx],1),JHCR_Table_get_real (JHCR_Context_bindings[JHCR_ctx],2),JHCR_Table_get_real (JHCR_Context_bindings[JHCR_ctx],3),JHCR_Table_get_real (JHCR_Context_bindings[JHCR_ctx],4),JHCR_Table_get_string (JHCR_Context_bindings[JHCR_ctx],5))
        else
            if (JHCR_i < 3) then
                call BJDebugMsg (JHCR_Table_get_string (JHCR_Context_bindings[JHCR_ctx],1))
            else
                call foo ()
            endif
        endif
    endfunction
     


    This is ofcourse quite a mouthfull but it all follows the same pattern.
    The first parameter is the register the return-value of the called function is
    stored in. but since all our functions return
    nothing
    it wont be used here.
    The next parameter is the id of the function we want to call. As you can see
    we do a bunch of comparisons to get to the correct function.
    The next parameter is a struct called
    context
    in which we store a bunch of
    information for the interpreter, most importantly here the parameters we want
    passed to the function we want to call.
    Those parameters are passed via the
    bind
    opcode which you can see just before
    the
    call
    opcode in our small example above.


    Globals



    We also need to be able to read and write globals from our interpreter and we
    deploy a similar technique as we did for functions.
    For each available type in JASS (
    handle
    ,
    integer
    ,
    unit
    , etc.) we generate
    two functions to read and write a specific global variable of that type.
    As with functions we assign each global variable a per-type unique id on which
    we do a bunch of comparisons to set or read the correct value.
    Let's again only look at a very small input script with only two global variables
    bj_MAX_PLAYERS
    and
    bj_forLoopAIndex
    where
    bj_MAX_PLAYERS
    is declared constant.
    JHCR will generate the following functions:

    Code (vJASS):

    function JHCR_Auto_get_global_integer takes integer JHCR_i returns integer
        if (JHCR_i < 2) then
            return bj_MAX_PLAYERS
        else
            return bj_forLoopAIndex
        endif
    endfunction

    function JHCR_Auto_set_global_integer takes integer JHCR_i,integer JHCR_v returns nothing
        if (JHCR_i < 2) then
            return
        else
            set bj_forLoopAIndex = JHCR_v
        endif
    endfunction
     


    Note the then-case in
    JHCR_Auto_set_global_integer
    : since
    bj_MAX_PLAYERS
    is
    constant we actually can't set it. But both can of course be read.
    A similar approach is used for global arrays.

    Update



    To actually reload changed functions JHCR read the new script file and checks
    for each function if the hash of the function has changed. If that happens
    the bytecode like above is created and written to a file to be loaded by the
    Preload
    -native. For example the changed
    foo
    function would generate the
    following preload-file:

    Code (vJASS):

    function foo takes nothing returns nothing
        call BJDebugMsg("New foo")
    endfunction

    function PreloadFiles takes nothing returns nothing
        call SetPlayerTechMaxAllowed (Player (0),1,1)
        call BlzSetAbilityTooltip ('Agyv',"263.....21136-2.......7.....New foo201361........-2.......22-1.......2.....29139",0)
        call SetPlayerTechMaxAllowed (Player (0),2,0)
    endfunction
     


    The generated bytecode is of course very similar to the previously shown since
    we only changed one string literal after all.
    Now what happens with this next is topic of a future post.
    We will end this little post here even though it only scratches the surface of
    what JHCR has to do.
    I hope you enjoyed this quick overview of JHCR.
     
  11. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    A "hackish" sort of way to speed up the process is to close wc3's handle to the map file, update the map file, restart the map (while wc3 is running the map), hope wc3 doesn't crash. This avoids the delay of closing/restarting of wc3, opening the map and starting it. It's still much slower than hot code reloading though.

    When using hot code reloading, global side effects like creating units/effects/etc need special handling, restarting the map gives one a clean slate (can also be annoying).

    I am telling you to please write more (love the 80 column limit =)).


    PS: just noticed that jhcr.exe is ~2.7MiB packed, becomes ~15MiB unpacked O.O! With great power comes great file size, I guess =)
     
  12. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    In this post we're going to look at all the things related to the bytecode.
    The bytecode was custom-designed to work reasonably fast in our jass context,
    so that it would actually be useable and provide a net-positive for map
    development.

    One key insight is that the interpreter works on only one global hashtable.
    We use the same technique as many other table libraries: use an unique integer
    as the first index for the table and our provided key for the second.
    Another great feature of hashtables that we use extensively is that you can use
    any integer as any index. We will later see how that is usefull.


    Now let's first look at the two definitions of our bytecode.
    We have one in Haskell which we compile to and one corresponding file in Jass.
    You can find them here and here.

    Code (Haskell):

    data Instruction
        -- three regs
        = Lt                Type Register Register Register
        | Le                Type Register Register Register
        | Gt                Type Register Register Register
        | Ge                Type Register Register Register
        | Eq                Type Register Register Register
        | Neq               Type Register Register Register
        | Add               Type Register Register Register
        | Sub               Type Register Register Register
        | Mul               Type Register Register Register
        | Div               Type Register Register Register
     
        -- Mod only works on integers but for ease we still include the Type
        | Mod               Type Register Register Register

        | SetGlobalArray    Type Register Register Register
        | GetGlobalArray    Type Register Register Register
        | SetLocalArray     Type Register Register Register
        | GetLocalArray     Type Register Register Register
     
        -- two regs
        | Negate    Type Register Register
        | Set       Type Register Register
        | SetGlobal Type Register Register
        | GetGlobal Type Register Register
        | Bind      Type Register Register

        -- special
        | Literal Type Register (Hot.Ast Var Expr) -- encoded as: lit ty reg len string
        | Call Register Label Name
        | Convert Type Register Type Register

        -- one label
        | Label Label
        | Jmp Label
        | Function Label Name
     
        | JmpT Label Register
     
        | Not Register Register

        | Ret Type
        deriving (Show)
     
    A quick glance and we can see that we use four different data-types in the
    instructions: Type, Register, Label, Name.

    Let's look at them one by one.

    Type



    Type is just a plain old jass type like
    handle, integer, widget

    etc. There are two reasons we have a typed bytecode. The first one is once
    again because we use
    hashtable
    s to store everything and the
    hashtable API has us use the correct native. The second reason is that we avoid
    redundancy in our instruction set (only one Add-Instruction etc.).

    Register



    The Instructionset used in JHCR is a register based instructionset which follows
    from our usage of hashtables since we can have effectivly unlimited registers.
    This especially aids in the code-generation phase since we don't have to care
    about register allocation (an NP-Hard Problem). It actually has some more
    benefits of which i might speak about in a later post.

    But even though we have more or less unlimited registers we still have to follow
    some rules, which result in the calling convention used in JHCR. To explore this
    let's have a look at a small example.

    Code (vJASS):

    native print_integer takes integer x returns nothing

    function add takes integer a, integer b returns integer
        return a+b
    endfunction

    function main takes nothing returns nothing
        local integer r = add(1, 3)
        call print_integer(r)
    endfunction
     


    This snippet could be compiled to this bytecode:

    Code (Text):

    fun 123 add
    add integer 0 1 2
    ret integer

    fun 234 main
    lit integer -1 1
    lit integer -2 3
    bind integer 1 -1
    bind integer 2 -2
    call 1 123 add
    bind integer 1 1
    call -3 567 print_integer
    ret nothing
     
    Let's look at the
    add
    function first. From this function we can see that
    the parameters to a function are stored in registers 1 to n where n is the
    amount of parameters a function takes. We can also see that the return value
    is stored in register 0. And this is our calling convention.
    So let's now look at the
    main
    function. From that we can see a few things
    aswell: first of all we have to use the
    bind
    instruction to transfer values
    from local "local" registers to the registers of the to-be-called function.
    So unlike registers found in your CPU we have them scoped for each function.
    The second thing we can notice is that temporary values are assigned to
    "negative" registers -1, -2, etc. As i said before, this makes compiling just
    a bit more easy and it doesn't cost us much since we can use the "endless"
    hashtables.
    But we can also see that local variables also use positive registers. In fact
    they use registers n+1 .. n+m, where m is the amount of local variables
    declared inside that function.
    And we can see how the
    call
    instruction is used since the return value
    is directly copied into the register of our local variable:
    Code (Text):

    call 1 123 add
         ^  ^   ^
         |  |   |
         |  |   +- The Name of the function is ommited in the final bytecode but
         |  |   `- it's useful for debugging.
         |  |
         |  `- Just the internal id of the function to-be-called
         |
         +- The Interpreter takes whatever is stored in the called functions
         +- register 0 and puts it into this functions register 1 which is the
         +- the local variable r. Technically the transfer of data actually
         +- happens in when interpreting the ret instruction since we lack the
         `- type information in the call instruction.
     




    Question to the reader: how do you do local arrays with this setup?

    Label



    To achieve loops and conditions and all that good stuff we use labels and jump
    instructions. We actually don't have many at all: Label, Jmp, JmpT.
    The Label instruction just creates the point in a list of instructions under
    that "label". Do note that that label is static, so you can't create dynamic
    labels to jump to. Once interpreted the interpreter simply ignores any label
    instruction. The Jmp instruction on the other hand just jumps to the
    corresponding label instruction. The following snippet is an infinite loop.

    Code (Text):

    label 3
    jmp 3
     
    Now finally the JmpT instruction jumps to the label if and only if the value
    stored under the register is true. For an example look at the code below.
    That code is an if-statement compiled to our bytecode where the condition
    is stored in register -1.
    Code (Text):

    jmpt 2 -1
    <else branch>
    jmp 3
    label 2
    <if branch>
    label 3
     


    Name



    The Name datatype is mostly ignored because we assign unique ids to everything
    we can. There is only one case where we actually have to use an actual name.
    Can you guess?




    If we reload the script with a totaly new function that wasn't seen before,
    that new function ofcourse also gets an id but to link up the name to the new
    id in our already running map we have to transfer both. And we need that
    mapping for
    ExecuteFunc
    only (i think).


    fin



    Now that we looked at the datatype representing the bytecode we can guess how
    many of those instructions actually work. But as you might have guessed this
    is still only a fairly high-level description of what JHCR is doing so i hope
    i can shed some light on the nitty-gritty details in a later post.
     
  13. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    It seems that local arrays use JASS_MAX_ARRAY_SIZE of "register space":
    Code (vJASS):

    local integer x // reg: 1
    local integer array ys // reg: 2
    local integer z // reg: 2+JASS_MAX_ARRAY_SIZE
     


    I am not sure if it would be problematic if the jump instructions use relative jump offsets instead of labels.
    Code (vJASS):

    // with labels
    jmpt 2 -1
    <else branch>
    jmp 3
    label 2
    <if branch>
    label 3

    // with relative offsets
    jmpt <size-of-jmpt+size-of-else-branch> -1
    <else branch>
    jmp <size-of-jmp+size-of-if-branch> // included in the size-of-else-branch
    <if branch>
     

    That way the runtime doesn't have to "manage" labels, right?

    There's also this crazy thing in Jass called 'TriggerRegisterVariableEvent' which triggers when a global real variable is modified. Please don't add support for it.
     
  14. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    That's mostly due to it being a Haskell program. They just take more space.

    Correct.
    Code (vJASS):

    function a takes nothing returns integer
            local integer array x
            local integer i = 3
            return x[i]
    endfunction
     


    Code (Text):

    $ jhcr compile empty-common.j test.j --opt-asm
    fun 2 a
    lit integer 32769 3
    gla integer 0 1 32769
    ret integer
     
    Here we see that the local variable i has register 32769 because the array x takes the space before it.

    The thing is that instructions are stored in a linked list so relative offsets don't gain us much since we can't index them directly anyways. With named labels atleast the compiler is a bit more easy since we don't have to compute offsets. But if we'd used another architecture relative offsets ofc could be better.
    Edit: Especially with the optimisations i do on the instructions i would have to recompute those offsets.


    :))

    Let's just say
    TriggerRegisterVariableEvent
    already works for non-hot loaded globals. If they were actually demand for using it on newly defined globals it wouldn't be too bad i think to add it.
     
    Last edited: Oct 10, 2020
  15. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    Was wondering what the overhead of 'jhcr' compared to vanilla execution might be, so I tried to measure but could not quite get it to work. Care to take a look? (see attached archive, requires latest 'jj' (found some bugs))
     

    Attached Files:

  16. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    Got it to work (was forgetting to initialize globals (not calling 'JHCR_Interpreter_exec_globals'), and 'jhcr_war3map.j' and 'final.j' weren't completely in sync), but I am getting some unexpected timings. The slowdown seems to be ~270x O.O! It's sort of the opposite of that movie in which time goes faster in each successive level, i.e execution speed gets slower with each level of interpreter nesting =).
     

    Attached Files:

  17. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Pretty cool! I don't know if the 270x slowdown would be the same for real wc3 but atleast that's one hard number we can use. And tbh i don't even know how to judge it. Is it high for what it does? Could it be reduced by a magnitude?
     
  18. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    Yeah, it shouldn't be as dramatic because hashtable functions in wc3 are natives.

    ¯\_(ツ)_/¯

    I was looking (despite not having a clue about Haskell) in 'Jass/Opt/Rewrite/SomeRules.hs' and the stuff there seemed like a very terse way of doing tree rewriting. But then I tried (jhcr compile empty-common.j main.j --opt-asm):
    Code (vJASS):

    // main.j
    globals
        constant integer C = 1000
    endglobals

    function f1 takes nothing returns integer
        local integer a = 1234 + 5678 + C
        return a
    endfunction
     


    and got this:
    Code (Text):

    fun 1 f1
    lit integer -3 1234
    lit integer -4 5678
    add integer -2 -3 -4
    gg integer -5 1
    add integer 0 -2 -5
    ret integer
     
    I guess I was expecting something more like this (i.e constant folding):
    Code (Text):

    fun 1 f1
    lit integer 1 7912
    set integer 0 1
    ret integer
     
    My thinking was "if there is tree rewriting, why isn't there constant folding o.0?".
     
  19. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    479
    Resources:
    0
    Resources:
    0
    Well you have to consider the tradeoffs of more optimisations. While having nice
    flow-based optimizer would be sweet it would also increase loading times which
    this project tries to minimize. The instruction rewrite engine is probably the
    bigger improver in contrast to the tree rewrite engine. With the instruction
    rewrite engine we can produce lesser quality asm but with an easy implementation
    but have the rewrite engine clean up our mess.
    And this is important because we have real usecases with it coming directly
    from the compiler. And while constant propagation ofc comes up in real life code
    i wager it doesn't cause the big slowdowns in wc3/jass.
    An optimisation which i think would actually improve JHCR is some loop-independet
    code moving (and
    lit
    -instruction simplification).

    Btw. a simple constant folder (based on AST, not asm) is actually very easy,
    but the keyword here is simple. To catch all the cases you would expect
    it to catch is actually not quite as easy.
     
  20. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    It seems that 'jhcr' generates incorrect bytecode for 'and' expressions, it also generates pretty good bytecode for nested if statements, and that's a bit ironic =):

    Code (vJASS):

    function B1 takes nothing returns boolean
        call BJDebugMsg("B1()")
        return false
    endfunction

    function B2 takes nothing returns boolean
        call BJDebugMsg("B2()")
        return true
    endfunction

    function B3 takes nothing returns boolean
        call BJDebugMsg("B3()")
        return true
    endfunction

    function B4 takes nothing returns boolean
        call BJDebugMsg("B4()")
        return true
    endfunction

    function foo takes nothing returns boolean
        return B1() and B2() and B3() and B4()
    endfunction

    function bar takes nothing returns boolean
        local boolean b = false
        if B1() then
        if B2() then
        if B3() then
        if B4() then
            set b = true
        endif
        endif
        endif
        endif
        return b
    endfunction