After this email thread and some discussion on the irc channel, I was inspired to write a quick implementation. I started a github repo at http://github.com/atrodo/lorito to share my progress. Over the weekend I was able to write an opcode encoding scheme, a dead simple assembler (lasm) written in perl, a simple file format to hold the compiled lasm, a loader to read the files into memory, and finally a run core that will execute a file. I had another project I had to work on the past two days, but I should be able to start fleshing more of the opcodes out now. I am hoping that by the end of this weekend that I'll have enough to have a reasonable working implementation.
This project is my take on how to implement the ideas discussed so far. I tried to combine all the input I've seen so far into something that is quick and reasonable. Everything else that hasn't been covered, I used my style and taste. One point I have attempted to keep close is to allow C and other static languages to have a good chance of using this effectively. As cotto said, it's "kinda stubby", and will continue to be that way for a while. Normally, I try and make my code more robust and graceful than this, but if the design is going to continue to change drastically as it evolves, I want to make it quick and easy to change directions. A couple things to point out: - I went with a static length opcode design. Each op is 8 bytes long and contains the opcode, the opcode type (int, num, str, pmc), a destination register, two source registers and an int for immediate, jump or offset values. - Each opcode register reference (dest, src1 and src2) are all of the same type, except for a few opcodes that have a static type for one of the refrences. So, the add opcode, in parrot terms, is only add_n_n_n or add_i_i_i. Some, like set_attr and new, will violate this because it has to. - Not all opcode types will be implemented for all opcodes. For instance, the math functions will only operate int and num. The thinking behind that being that it's just going to end up being a method call, so no reason to dictate an interface and add complexity for convenience hardly anyone will every use. - lasm is a one to one mapping of the bytecode. My plan is to keep it that way. - There is a hcf opcode. I'm not sure what it will do yet. - I'm not sure if noop or end should be opcode #0. - PMCs. I've just started on them, but the current plan is for them them to simply be a chunk of memory allocated when created with the new command. A program can store anything they want into that chunk of memory. But, to be on the safe side, I am going to have a table of all the pointers used. So when a set_attr is used to save a PMC reference, it will save the actual C pointer into this pointer table, and save the index into the table into the memory region. This way, the real pointer is safely packed away, it will make the GC's job easier and the entire memory region is available for manipulation. - I'm going to rename set_attr and get_attr to reflect that they change the memory of a pmc, I'm just sure on a good name yet. - I haven't figured out how to store or reference other code blocks. - I do have a nomenclature mix up. Initially I was calling blocks of code a codeseg. That is not the same use of the word segment as parrot. That will be fixed at some point. With all of that, I've got a bit of work still to get this decently functional, but so far it's been fun and I hope I can get some input and help on it. -Jon Gentle > On Thu, Jul 8, 2010 at 3:11 PM, Allison Randal <[email protected]> wrote: > > add REG, REG, REG (integer/float with boxing/unboxing for PMC) > > sub > > mul > > div > > What types of register do we have? Just generic machine-size > registers, or are we still differentiating between different types (I > N S P) registers? If we have multiple register types still (which I > support), are these opcodes polymorphic, or do they just do the same > song-and-dance that our current PASM ops do with a short name (add) > and a long name (add_n_n_n)? > > Also, if we're talking about implementing a hash in microcode we need a > modulus. > > > set REG, REG (by value, set from constant value or another register, > > boxing/unboxing for int, num, str, pmc) > > Again, is this a single SET operation, or a family of related ops like > our current PASM set_i_i, set_i_p, set_p_i, ...)? > > I would suggest it would be much cleaner if we don't allow all ops to > take constants for arguments, we should have an op (or family of ops > with the same short name) to load constants from the constants table > into a register. All other ops should deal only with registers or > memory. > > > goto LABEL > > if BOOL, LABEL > > iseq > > isgt > > islt > > and > > or > > I'm all for minimalism, but is there any reason not to include logical > NOT and XOR? An "unless BOOL, LABEL" op would be good too, if we don't > have NOT. Almost all modern processor architectures have microcode > operators for <= and >= also, so it makes sense. Since Lorito's design > up until this point has really focused on compatibility with JIT, we > don't lose anything by providing the same kinds of ops that the > machine provides. We do want to try to keep the number of ops much > smaller than what PASM offers now (64ish should be fine), but this > doesn't have to be an academic exercise in absolute minimalism. It's > more important that we have a functional and usable opset with > performance and practicality concerns in mind. > > We don't want to be in a situation where we need to be constantly > using sequences of two or three Lorito ops to perform common > operations that the hardware can do in one cycle. Optimizers can > typically reduce the sequence down through peepholes and strength > reductions, but optimizers add a runtime cost that we don't always > want to pay. > > > new PMC, PMC (create an instance object from an existing class object or > > "struct" definition, which was defined using declarative syntax) > > > > read (fill a register from stdin, absolute minimum for testing) > > write (write a register to stdout, absolute minimum for testing) > > I would suggest we replace these with memory load and store operations > for dereferencing pointers to RAM. IO can still be done through PMC, > or we could easily set up a memory map to do file output for testing. > > > setattr (set/get a class attribute or "struct" member of a PMC) > > getattr > > call (a vtable function on a PMC, passing a varargs argument signature) > > This calls a VTABLE on a PMC. How do we call an ordinary C-style > function? I would suggest we have a "vcall REG, REG, ARGS" to call a > vtable on a PMC, and a "ccall REG, REG, ARGS" to call a cdecl > function, such as in an NCI library or in Parrot core. Having a > builtin "pcall REG, REG, REG" op to call a Parrot method would be nice > too, since it gives us a nice encapsulation boundary for PCC calls > (and really shows them as being fundamental control flow operations). > > > load (bytecode file) > > As a general nit, I would suggest "loadbc" in case we wanted to have a > "load" op for memory access, or a "load_const" to load a value from > the constants table into a register. The term "load" is just far too > easy to overload. > > > end (halt the interpreter cleanly) > > > > ------ > > > > As a side note, if we have dynamic vtables, there's not so much reason to > > make strings a separate type from PMCs. > > +1 > > > Can we drop the 'PMC' name and just call them objects? > > Well, I'm not necessarily in favor of the term "PMC", but they aren't > really "objects" in the way that the Object PMC is an object with a > particular object model. Traditionally we've used the term "PMC" to > refer to primitive types and "object" to refer to PMCs of type Object. > If we call all PMCs objects, then we lose a little bit of clarity. > > > One alternative to the variable number of arguments on 'call' is a series > of > > 'pusharg' ops before it, but I'd rather preserve the abstraction. > > I would probably prefer the use of pusharg (and poparg inside the > called function itself), since that's the way arguments are actually > passed at the machine level (and hence the form it will need to be put > in by the JIT and AOT compilers anyway). A certain amount of sugar in > the Lorito assembler would still preserve the abstraction if we > absolutely needed it. Let's just try to remember that Lorito is really > intended to be friendly to the machine, not necessarily friendly to > the programmer. We could use a higher-level "language" like PIR if we > want programmer-friendly syntax and all sorts of added abstractions. > > > The invocation of sub/method objects will happen by building up a > > callcontext object, and calling the 'invoke' vtable function on a PMC, > > passing it the call context object as an argument, and receiving the > result > > context object as the return. > > This is fine, though I would argue for the benefits of a "fast path" > kind of invocation where we don't have an invocant and we don't have > any arguments. Needing to always create a callcontext object > beforehand to pass arguments, even if we don't have any arguments, > really hurts our chances for inlining and tracing JIT optimizations. > > > There is another alternative in memory allocation at this level of the > > microcode, and that is to allocate raw blocks of memory of a requested > size, > > and allow direct manipulation of that memory. On the whole, this is one > of > > the most error-prone aspects of C (user error, that is), and makes it > harder > > to abstract away multiple garbage collection models. But, we may > absolutely > > need it to implement some of the lower-level features. > > We can't write PMCs in Lorito if we can't access raw memory buffers. > Of course, we could have C API functions to allocate and manipulate > buffers, and call those functions for every operation. I definitely > don't think that's the best, but it is possible. The more we can write > in Lorito, the more we can expose to the JIT, which means increased > opportunities for optimization. The more capable Lorito is, the more > we can write in it. Obviously we would like to strike a nice balance > here, so we can't be too aggressively minimalist. > > One option is to have the raw memory-access features be only available > in code pages marked "insecure", so some internals code and PMCs > (dynpmcs too) could use direct memory access but everything else > cannot. This helps to enforce a separation of responsibilities, and > show that PMCs are the required mechanism for playing with memory. > > --Andrew Whitworth > >
_______________________________________________ http://lists.parrot.org/mailman/listinfo/parrot-dev
