diff --git a/.gitattributes b/.gitattributes index 37ce5f1..3b1db11 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,3 @@ .gitattributes export-ignore README export-ignore .info export-ignore -tests/** export-ignore diff --git a/tests/actionscript/RayTracer.as b/tests/actionscript/RayTracer.as deleted file mode 100644 index e13635a..0000000 --- a/tests/actionscript/RayTracer.as +++ /dev/null @@ -1,1113 +0,0 @@ - //print("sanity check"); - var size; - //Scene scene; - var scene; - /** - * Lights for the rendering scene - * Light lights[]; - */ - var lights; - - /** - * Objects (spheres) for the rendering scene - * Primitive prim[]; - */ - var prim; - - /** - * The view for the rendering scene - * View view; - */ - var view; - /** - * Temporary ray - * Ray tRay = new Ray(); - */ - //print("point 1"); - var tRay = new Ray(); - //print("point2"); - /** - * Alpha channel - */ - var alpha = 255 << 24; - - /** - * Null vector (for speedup, instead of new Vec(0,0,0) - */ - var voidVec = new Vec(0.0,0.0,0.0); - - /** - * Temporary vect - */ - var L = new Vec(0.0,0.0,0.0); - - /** - * Current intersection instance (only one is needed!) - */ - var inter = new Isect(); - - /** - * Height of the Image to be rendered - */ - var height; - - /** - * Width of the Image to be rendered - */ - var width; - - var datasizes = new Array(4); - datasizes[0] = 50; - datasizes[1] = 150; - datasizes[2] = 500; - datasizes[3] = 7; - - var checksum = 0; - var lastValue = 0; - var size; - - var numobjects; - - var start = new Date(); - JGFrun(3); - var elapsed = new Date() - start; - - function JGFsetsize(sizel) { - size = sizel; - } - - function JGFinitialise() { - //print("Entering JGFinitialise"); - // set image size - width = height = datasizes[size]; - - // create the objects to be rendered - scene = createScene(); - - // get lights, objects etc. from scene. - setScene(scene); - - numobjects = scene.getObjects(); - - } - - function JGFapplication() { - // Set interval to be rendered to the whole picture - // (overkill, but will be useful to retain this for parallel versions) - var interval = new Interval(0, width, height, 0, height, 1); - - // Do the business! - render(interval); - - } - - function JGFvalidate() { - //print("entering JFGvalidate"); - var refval = new Array(4); - refval[0] = 2676692; - refval[1] = 29827635; - refval[2] = 29827635; - refval[3] = 5158; - var dev = checksum - refval[size]; - if (dev != 0) { - print("Validation failed"); - print("Pixel checksum = " + checksum); - print("Reference value = " + refval[size]); - }else - { - print("Validation successfull " + checksum); - } - } - - function JGFtidyup() { - scene = null; - lights = null; - prim = null; - tRay = null; - inter = null; - } - - function JGFrun(size) { - //print("entering JFGrun"); - JGFsetsize(size); - JGFinitialise(); - JGFapplication(); - JGFvalidate(); - //JGFtidyup(); - } - - /****************Start Class RayTracer*************/ - /** - * Create and initialize the scene for the rendering picture. - * - * @return The scene just created - */ - - function createScene() { - //print("entering createScene"); - var x = 0; - var y = 0; - - var scene = new Scene(); - - /* create spheres */ - - var p; - var nx = 4; - var ny = 4; - var nz = 4; - for (var i = 0; i < nx; i++) { - for (var j = 0; j < ny; j++) { - for (var k = 0; k < nz; k++) { - var xx = 20.0 / (nx - 1) * i - 10.0; - var yy = 20.0 / (ny - 1) * j - 10.0; - var zz = 20.0 / (nz - 1) * k - 10.0; - p = new Sphere(new Vec(xx, yy, zz), 3); - - // p.setColor(i/(double) (nx-1), j/(double)(ny-1), - // k/(double) (nz-1)); - p.setColor(0, 0, (i + j) / (nx + ny - 2)); - p.surf.shine = 15.0; - p.surf.ks = 1.5 - 1.0; - p.surf.kt = 1.5 - 1.0; - scene.addObject(p); - } - } - } - - /* Creates five lights for the scene */ - scene.addLight(new Light(100, 100, -50, 1.0)); - scene.addLight(new Light(-100, 100, -50, 1.0)); - scene.addLight(new Light(100, -100, -50, 1.0)); - scene.addLight(new Light(-100, -100, -50, 1.0)); - scene.addLight(new Light(200, 200, 0, 1.0)); - - /* Creates a View (viewing point) for the rendering scene */ - var v = new View(new Vec(x, 20, -30), new Vec(x, y, 0), new Vec(0, 1,0), 1.0, 35.0 * 3.14159265 / 180.0, 1.0); - /* - * v.from = new Vec(x, y, -30); v.at = new Vec(x, y, -15); v.up = new - * Vec(0, 1, 0); v.angle = 35.0 * 3.14159265 / 180.0; v.aspect = 1.0; - * v.dist = 1.0; - * - */ - scene.setView(v); - - return scene; - } - - function setScene(scene) { - //print("entering setScene"); - // Get the objects count - var nLights = scene.getLights(); - var nObjects = scene.getObjects(); - - lights = new Array(nLights); - prim = new Array(nObjects); - - // Get the lights - for (var l = 0; l < nLights; l++) { - lights[l] = scene.getLight(l); - } - - // Get the primitives - for (var o = 0; o < nObjects; o++) { - prim[o] = scene.getObject(o); - } - - // Set the view - view = scene.getView(); - } - - function render(interval) { - //print("entering render"); - // Screen variables - var row = new Array(interval.width * (interval.yto - interval.yfrom)); - var pixCounter = 0; // iterator - - // Rendering variables - var x, y, red, green, blue; - var xlen, ylen; - var viewVec; - - viewVec = Vec.sub(view.at, view.from); - - viewVec.normalize(); - - var tmpVec = new Vec(0.0,0.0,0.0); - tmpVec.setVec(viewVec); - tmpVec.scale(Vec.dot(view.up, viewVec)); - - var upVec = Vec.sub(view.up, tmpVec); - upVec.normalize(); - - var leftVec = Vec.cross(view.up, viewVec); - leftVec.normalize(); - - var frustrumwidth = view.dist * Math.tan(view.angle); - - upVec.scale(-frustrumwidth); - leftVec.scale(view.aspect * frustrumwidth); - - var r = new Ray(); - r.setRay(view.from, voidVec); - var col = new Vec(0.0,0.0,0.0); - - // Header for .ppm file - // System.out.println("P3"); - // System.out.println(width + " " + height); - // System.out.println("255"); - - // All loops are reversed for 'speedup' (cf. thinking in java p331) - - // For each line - for (y = interval.yfrom; y < interval.yto; y++) { - //print("outer loop in render :"+y); - ylen = (2.0 * y) / interval.width - 1.0; - // System.out.println("Doing line " + y); - // For each pixel of the line - for (x = 0; x < interval.width; x++) { - //print("innter loop in render: "+x); - xlen = (2.0 * x) / interval.width - 1.0; - r.D = Vec.comb(xlen, leftVec, ylen, upVec); - r.D.add(viewVec); - r.D.normalize(); - //print("executing trace"); - col = trace2(0, 1.0, r); - if(col == undefined) - { - print("col is set: "+col); - print("r is: "+r); - } - // computes the color of the ray - red = (col.x * 255.0); - if (red > 255) - red = 255; - green = (col.y * 255.0); - //print("green is set"); - if (green > 255) - green = 255; - blue = (col.z * 255.0); - //print("blue is set"); - if (blue > 255) - blue = 255; - //print("adding checksum"); - red = Math.floor(red); - green = Math.floor(green); - blue = Math.floor(blue); - checksum += red; - checksum += green; - checksum += blue; - - // RGB values for .ppm file - // System.out.println(red + " " + green + " " + blue); - // Sets the pixels - row[pixCounter++] = alpha | (red << 16) | (green << 8) | (blue); - } // end for (x) - } // end for (y) - - } - - function intersect( r, maxt) { - var tp; - var i, nhits; - //print("entering intersect"); - nhits = 0; - inter.t = 1e9; - for (i = 0; i < prim.length; i++) { - // uses global temporary Prim (tp) as temp.object for speedup - tp = prim[i].intersect(r); - if (tp != null && tp.t < inter.t) { - inter.t = tp.t; - inter.prim = tp.prim; - inter.surf = tp.surf; - inter.enter = tp.enter; - nhits++; - } - } - return nhits > 0 ? true : false; - } - - /** - * Checks if there is a shadow - * - * @param r - * The ray - * @return Returns 1 if there is a shadow, 0 if there isn't - */ - function Shadow( r, tmax) { - if (intersect(r, tmax)) - return 0; - return 1; - } - - /** - * Return the Vector's reflection direction - * - * @return The specular direction - */ - function SpecularDirection( I, N) { - var r; - r = Vec.comb(1.0 / Math.abs(Vec.dot(I, N)), I, 2.0, N); - r.normalize(); - return r; - } - - /** - * Return the Vector's transmission direction - */ - function TransDir( m1, m2, I, N) { - var n1, n2, eta, c1, cs2; - var r; - n1 = m1 == null ? 1.0 : m1.ior; - n2 = m2 == null ? 1.0 : m2.ior; - eta = n1 / n2; - c1 = -Vec.dot(I, N); - cs2 = 1.0 - eta * eta * (1.0 - c1 * c1); - if (cs2 < 0.0) - return null; - r = Vec.comb(eta, I, eta * c1 - Math.sqrt(cs2), N); - r.normalize(); - return r; - } - - /** - * Returns the shaded color - * - * @return The color in Vec form (rgb) - */ - function shade( level, weight, P, N, I, hit) { - var n1, n2, eta, c1, cs2; - var r; - var tcol; - var R; - var t, diff, spec; - var surf; - var col; - var l; - - col = new Vec(0.0,0.0,0.0); - surf = hit.surf; - R = new Vec(0.0,0.0,0.0); - if (surf.shine > 1e-6) { - R = SpecularDirection(I, N); - } - - // Computes the effectof each light - for (l = 0; l < lights.length; l++) { - L.sub2(lights[l].pos, P); - if (Vec.dot(N, L) >= 0.0) { - t = L.normalize(); - - tRay.P = P; - tRay.D = L; - - // Checks if there is a shadow - if (Shadow(tRay, t) > 0) { - diff = Vec.dot(N, L) * surf.kd * lights[l].brightness; - - col.adds_two(diff, surf.color); - if (surf.shine > 1e-6) { - spec = Vec.dot(R, L); - if (spec > 1e-6) { - spec = Math.pow(spec, surf.shine); - col.x += spec; - col.y += spec; - col.z += spec; - } - } - } - } // if - } // for - - tRay.P = P; - if (surf.ks * weight > 1e-3) { - tRay.D = SpecularDirection(I, N); - tcol = trace2(level + 1, surf.ks * weight, tRay); - col.adds_two(surf.ks, tcol); - } - if (surf.kt * weight > 1e-3) { - if (hit.enter > 0) - tRay.D = TransDir(null, surf, I, N); - else - tRay.D = TransDir(surf, null, I, N); - tcol = trace2(level + 1, surf.kt * weight, tRay); - col.adds_two(surf.kt, tcol); - } - - // garbaging... - tcol = null; - surf = null; - - return col; - } - - /** - * Launches a ray - */ - function trace2( level, weight, r) { - //print("entering trace"); - var P, N; - var hit; - //print("checking recursion in trace"); - // Checks the recursion level - if (level > 6) { - return new Vec(0.0,0.0,0.0); - } - - hit = intersect(r, 1e6); - //print("hit is: "+hit); - if (hit) { - P = r.point(inter.t); - N = inter.prim.normal(P); - if (Vec.dot(r.D, N) >= 0.0) { - N.negate(); - } - return shade(level, weight, P, N, r.D, inter); - } - // no intersection --> col = 0,0,0 - return voidVec; - } - /****************End Class RayTracer***************/ - -class Interval { - /* - * public int number; public int width; public int height; public int yfrom; - * public int yto; public int total; - */ - var number; - - var width; - - var height; - - var yfrom; - - var yto; - - var total; - - function Interval( number, width, height, yfrom, yto, total) { - this.number = number; - this.width = width; - this.height = height; - this.yfrom = yfrom; - this.yto = yto; - this.total = total; - } -} -class Isect { - //public double t; - var t; - //public int enter; - var enter; - //public Primitive prim; - var prim; - //public Surface surf; - var surf; -} - -class Light { - //public Vec pos; - var pos; - //public double brightness; - var brightness; - function Light( x, y, z, brightnessl) { - this.pos = new Vec(x, y, z); - this.brightness = brightnessl; - } -} - -class Primitive { - var surf = new Surface(); - - function setColor( r, g, b) { - surf.color = new Vec(r, g, b); - } - //abstract - function normal(pnt){} - //abstract - function intersect(ry){} - - //abstract, override - function toString(){} - //abstract - function getCenter(){} - //abstract - - function setCenter(c){} -} - -class Ray { - var P, D; - - function setRay(pnt, dir) { - //print("set ray start"); - P = new Vec(pnt.x, pnt.y, pnt.z); - D = new Vec(dir.x, dir.y, dir.z); - //print("set ray after init"); - D.normalize(); - //print("set ray after normalize"); - } - function Ray() - { - //print("start Ray()"); - P = new Vec(0.0,0.0,0.0); - D = new Vec(0.0,0.0,0.0); - //print("end Ray()"); - } - - function point(t) { - return new Vec(P.x + D.x * t, P.y + D.y * t, P.z + D.z * t); - } - - //@Override - function toString() { - return "{" + P.toString() + " -> " + D.toString() + "}"; - } -} -class Scene { - var lights; - - var objects; - - var view; - - function Scene() { - this.lights = new Array(); - this.objects = new Array(); - } - - function addLight(l) { - this.lights.push(l); - } - - function addObject(object) { - this.objects.push(object); - } - - function setView(view) { - this.view = view; - } - - function getView() { - return this.view; - } - - function getLight(number) { - return this.lights[number]; - } - - function getObject( number) { - return objects[number]; - } - - function getLights() { - //print("start getLights"); - return this.lights.length; - } - - function getObjects() { - return this.objects.length; - } - - function setObject(object, pos) { - this.objects[pos] = object; - } -} -class Sphere extends Primitive { - var c; - - var r, r2; - - var v, b; // temporary vecs used to minimize the memory load - - function Sphere( center, radius) { - c = center; - r = radius; - r2 = r * r; - v = new Vec(0.0,0.0,0.0); - b = new Vec(0.0,0.0,0.0); - } - - //@Override - override function intersect(ry) { - var b, disc, t; - var ip; - v.sub2(c, ry.P); - b = Vec.dot(v, ry.D); - disc = b * b - Vec.dot(v, v) + r2; - if (disc < 0.0) { - return null; - } - disc = Math.sqrt(disc); - t = (b - disc < 1e-6) ? b + disc : b - disc; - if (t < 1e-6) { - return null; - } - ip = new Isect(); - ip.t = t; - ip.enter = Vec.dot(v, v) > r2 + 1e-6 ? 1 : 0; - ip.prim = this; - ip.surf = surf; - return ip; - } - - //@Override - override function normal(p) { - var r; - r = Vec.sub(p, c); - r.normalize(); - return r; - } - - //@Override - override function toString() { - return "Sphere {" + c.toString() + "," + r + "}"; - } - - //@Override - override function getCenter() { - return c; - } - - //@Override - override function setCenter(c) { - this.c = c; - } -} - -class Surface { - var color; - - var kd; - - var ks; - - var shine; - - var kt; - - var ior; - - function Surface() { - color = new Vec(1, 0, 0); - kd = 1.0; - ks = 0.0; - shine = 0.0; - kt = 0.0; - ior = 1.0; - } - - //@Override - function toString() { - return "Surface { color=" + color + "}"; - } -} -/** - * This class reflects the 3d vectors used in 3d computations - */ -class Vec { - - /** - * The x coordinate - */ - var x; - - /** - * The y coordinate - */ - var y; - - /** - * The z coordinate - */ - var z; - - x = 0.0; - y = 0.0; - z = 0.0; - - /** - * Constructor - * - * @param a - * the x coordinate - * @param b - * the y coordinate - * @param c - * the z coordinate - */ - function Vec( a, b, c) { - x = a; - y = b; - z = c; - } - /** - * Copy constructor - */ - function setVec(a) { - x = a.x; - y = a.y; - z = a.z; - } - /** - * Add a vector to the current vector - * - * @param: a The vector to be added - */ - function add( a) { - x += a.x; - y += a.y; - z += a.z; - } - - /** - * adds: Returns a new vector such as new = sA + B - */ - function adds( s, a, b) { - return new Vec(s * a.x + b.x, s * a.y + b.y, s * a.z + b.z); - } - - /** - * Adds vector such as: this+=sB - * - * @param: s The multiplier - * @param: b The vector to be added - */ - function adds_two( s, b) { - x += s * b.x; - y += s * b.y; - z += s * b.z; - } - - /** - * Substracs two vectors - */ - static function sub( a, b) { - return new Vec(a.x - b.x, a.y - b.y, a.z - b.z); - } - - /** - * Substracts two vects and places the results in the current vector Used - * for speedup with local variables -there were too much Vec to be gc'ed - * Consumes about 10 units, whether sub consumes nearly 999 units!! cf - * thinking in java p. 831,832 - */ - function sub2( a, b) { - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - } - - static function mult( a, b) { - return new Vec(a.x * b.x, a.y * b.y, a.z * b.z); - } - - static function cross( a, b) { - return new Vec(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - - a.y * b.x); - } - - static function dot( a, b) { - return a.x * b.x + a.y * b.y + a.z * b.z; - } - - static function comb( a, A, b, B) { - return new Vec(a * A.x + b * B.x, a * A.y + b * B.y, a * A.z + b * B.z); - } - - function comb2( a, A, b, B) { - x = a * A.x + b * B.x; - y = a * A.y + b * B.y; - z = a * A.z + b * B.z; - } - - function scale( t) { - x *= t; - y *= t; - z *= t; - } - - function negate() { - x = -x; - y = -y; - z = -z; - } - - function normalize() { - var len; - len = Math.sqrt(x * x + y * y + z * z); - if (len > 0.0) { - x /= len; - y /= len; - z /= len; - } - return len; - } - - //@Override - function toString() { - return "<" + x + "," + y + "," + z + ">"; - } -} - -class View { - /* - * public Vec from; public Vec at; public Vec up; public double dist; public - * double angle; public double aspect; - */ - var from; - - var at; - - var up; - - var dist; - - var angle; - - var aspect; - - function View( froml, atl, upl, distl, anglel, aspectl) { - this.from = froml; - this.at = atl; - this.up = upl; - this.dist = distl; - this.angle = anglel; - this.aspect = aspectl; - } -} -/*****************Start Vector class*****************/ -//http://sourceforge.net/projects/jsvector/ -// Vector Constructor -- constructs the object -// Vector Constructor -- constructs the object - -// Vector is now a builtin class - using that instead -/* -function Vector(inc) { - if (inc == 0) { - inc = 100; - } - - // Properties - this.data = new Array(inc); - this.increment = inc; - this.size = 0; - - // Methods - this.getCapacity = getCapacity; - this.getSize = getSize; - this.isEmpty = isEmpty; - this.getLastElement = getLastElement; - this.getFirstElement = getFirstElement; - this.getElementAt = getElementAt; - this.addElement = addElement; - this.insertElementAt = insertElementAt; - this.removeElementAt = removeElementAt; - this.removeAllElements = removeAllElements; - this.indexOf = indexOf; - this.contains = contains - this.resize = resize; - this.toString = toString; - this.sort = sort; - this.trimToSize = trimToSize; - this.clone = clone; - this.overwriteElementAt; -} - -// getCapacity() -- returns the number of elements the vector can hold -function getCapacity() { - return this.data.length; -} - -// getSize() -- returns the current size of the vector -function getSize() { - return this.size; -} - -// isEmpty() -- checks to see if the Vector has any elements -function isEmpty() { - return this.getSize() == 0; -} - -// getLastElement() -- returns the last element -function getLastElement() { - if (this.data[this.getSize() - 1] != null) { - return this.data[this.getSize() - 1]; - } -} - -// getFirstElement() -- returns the first element -function getFirstElement() { - if (this.data[0] != null) { - return this.data[0]; - } -} - -// getElementAt() -- returns an element at a specified index -function getElementAt(i) { - try { - return this.data[i]; - } - catch (e) { - return "Exception " + e + " occured when accessing " + i; - } -} - -// addElement() -- adds a element at the end of the Vector -function addElement(obj) { - if(this.getSize() == this.data.length) { - this.resize(); - } - this.data[this.size++] = obj; -} - -// insertElementAt() -- inserts an element at a given position -function insertElementAt(obj, index) { - try { - if (this.size == this.capacity) { - this.resize(); - } - - for (var i=this.getSize(); i > index; i--) { - this.data[i] = this.data[i-1]; - } - this.data[index] = obj; - this.size++; - } - catch (e) { - return "Invalid index " + i; - } -} - -// removeElementAt() -- removes an element at a specific index -function removeElementAt(index) { - try { - var element = this.data[index]; - - for(var i=index; i<(this.getSize()-1); i++) { - this.data[i] = this.data[i+1]; - } - - this.data[getSize()-1] = null; - this.size--; - return element; - } - catch(e) { - return "Invalid index " + index; - } -} - -// removeAllElements() -- removes all elements in the Vector -function removeAllElements() { - this.size = 0; - - for (var i=0; i=0 && compareValue > currentValue) { - this.data[j+1] = this.data[j]; - j--; - if (j >=0) { - compareObj = this.data[j]; - compareValue = compareObj[f]; - } - } - this.data[j+1] = currentObj; - } -} - -// clone() -- copies the contents of a Vector to another Vector returning the new Vector. -function clone() { - var newVector = new Vector(this.size); - - for (var i=0; i - - simple example build file - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/asm/gameport.asm b/tests/asm/gameport.asm deleted file mode 100644 index fb920d9..0000000 --- a/tests/asm/gameport.asm +++ /dev/null @@ -1,39 +0,0 @@ -Game Port - -; GAMEPORT.ASM -; - - .MODEL TINY - - .DATA - - yes DB 13,10,"Game port is installed.",13,10,"$" - no DB 13,10,"Game port is not installed.",13,10,"$" - - .CODE - ORG 100h - -start: mov al, 1 ;value to write to port - mov dx, 201h ;port number - out dx, al ;write to port - mov cx, 0F00h ;# of loops - -port_loop: - in al, dx ;read from port - and al, 0Fh ;if jstick present, then AL should be - cmp al, 0Fh ; 0Fh after ANDing with 0Fh. - je jstick_exists - loop port_loop - mov dx, OFFSET no ;gameport not installed - jmp SHORT done - -jstick_exists: - mov dx, OFFSET yes ;gameport installed - -done: mov ah, 9h - int 21h - - mov ax, 4c00h - int 21h - -END start diff --git a/tests/asm/queueservers.asm b/tests/asm/queueservers.asm deleted file mode 100644 index 8d34da8..0000000 --- a/tests/asm/queueservers.asm +++ /dev/null @@ -1,164 +0,0 @@ -Gets a list of Queue servers under Novell Netware 3.11 - -%PAGESIZE 55,200 -%SUBTTL "Get List of Queue Servers under Netware 3.11" -; Net_Q.Asm -; - - .MODEL SMALL - - - .STACK 100h - -DOSint macro function - mov ah,function - int 21h -ENDM - - .DATA - STDOUT = 1 ; the stdout device handle - - DOS_WRITE_TO_HANDLE = 040h ; Write to File Handle - DOS_TERMINATE_EXE = 04Ch ; Terminate Program - - NOVELL_FUNCTION = 0E3h -; -; Object Types -; note that they're all big endian -; - OT_USER = 0100h - OT_USER_GROUP = 0200h - OT_PRINT_QUEUE = 0300h ; Print Queue object type - OT_FILE_SERVER = 0400h - - -BragMsg DB 0dh,0ah,"NET_Q.EXE",9,"WWW" - DB 9,"Version 1.00",0dh,0ah - DB 9,9,"released to the public domain by the author",0dh,0ah,0dh,0ah -BragLen = $ - BragMsg - -Crlf DB 0dh,0ah,0 - - SCAN_REQ STRUC ; bindery ScanObject request packet structure - MyLength DW 55 ; the length of this buffer - Function DB 37h ; scan object subfunction number - ObjectID DD -1 ; all ones for initial object search - ObjectType DW -1 ; wild card -- looks for all objects - ObjNameLen DB 1 ; at least one character - ObjName DB 47 DUP ('*') ; fill with wildcards to start - SCAN_REQ ENDS - - SCAN_REP STRUC ; bindery ScanObject request packet structure - MyLength DW 57 - RObjectID DD 0 ; all ones for initial object search - RObjectType DW 0 ; wild card -- looks for all objects - RObjName DB 48 DUP (0) ; fill with wildcards to start - ObjFlag DB 0 - ObjSecurty DB 0 - ObjHasProp DB 0 - ENDS - - ScanObjReq SCAN_REQ <> - ScanObjRep SCAN_REP <> - - .CODE - -; -; This is the main part of the code -; -; Test code gets and prints the name of all print queues from the -; logged server -- NO ERROR CHECKING IS DONE, so be careful! -; - -Start: - mov ax,@data - mov ds,ax ; set up the data segment - mov dx,OFFSET BragMsg ; prepare to print out brag line(s) - mov cx,BragLen - mov bx,STDOUT ; print to STDOUT - DOSint DOS_WRITE_TO_HANDLE - jc Exit ; if carry is set, there was an error - - mov [ScanObjReq.ObjectType],OT_PRINT_QUEUE - ; - ; in this case the name is already set up, (a wildcard) but if a - ; specific name were desired, it would be moved to - ; ScanObjReq.ObjName, with the appropriate length (not including - ; optional terminating NULL char set up in ScanObjReq.ObjNameLen. - ; -@@MoreQueues: - call BindScan - jc Exit - - lea dx,[ScanObjRep.ObjName] - call Puts - lea dx,[Crlf] - call Puts - jmp @@MoreQueues - -Exit: - DOSint DOS_TERMINATE_EXE ; return with error code preset in AL - -; -; BindScan -; -; scans the bindery for the object name set in the request buffer -; -BindScan proc - push ds si di es dx ax - - lea si,[ScanObjReq] ; point DS:DI to request buffer - mov dx,ds - mov es,dx - lea di,[ScanObjRep] ; point ES:SI to reply buffer - DOSint NOVELL_FUNCTION - jb @@Exit - - cld ; make sure to count up - mov si,OFFSET ScanObjRep.ObjectID - mov di,OFFSET ScanObjReq.ObjectID - movsw - movsw - - clc - -@@Exit: - pop ax dx es di si ds - ret - -BindScan endp - -; Puts -; -; prints a NUL terminated string to stdout -; -; INPUTS: ds:dx points to ASCIIZ string -; -; OUTPUTS: prints string to stdout -; -; RETURNS: ax = number of bytes actually printed -; carry set on error -; -; DESTROYED: ax -; -Puts proc - push bx cx di es - - push ds - pop es - mov cx,0ffffh ; maximum length of string - mov di,dx - cld - mov al,0 ; we're looking for NUL - repne scasb - dec di - mov cx,di - sub cx,dx - mov bx,STDOUT ; write to this device - DOSint DOS_WRITE_TO_HANDLE - - pop es di cx bx - ret -Puts endp - - END Start diff --git a/tests/asp/serialnumber.asp b/tests/asp/serialnumber.asp deleted file mode 100644 index edaef7c..0000000 --- a/tests/asp/serialnumber.asp +++ /dev/null @@ -1,14 +0,0 @@ - - - -<% -dim fs,d -set fs=Server.CreateObject("Scripting.FileSystemObject") -set d=fs.GetDrive("c:") -Response.Write("The serialnumber is " & d.SerialNumber) -set d=nothing -set fs=nothing -%> - - - diff --git a/tests/awk/ctime.awk b/tests/awk/ctime.awk deleted file mode 100644 index 0a50d26..0000000 --- a/tests/awk/ctime.awk +++ /dev/null @@ -1,11 +0,0 @@ -# ctime.awk -# -# awk version of C ctime(3) function - -function ctime(ts, format) -{ - format = "%a %b %d %H:%M:%S %Z %Y" - if (ts == 0) - ts = systime() # use current time as default - return strftime(format, ts) -} diff --git a/tests/basic/threads.bas b/tests/basic/threads.bas deleted file mode 100644 index 92b68f4..0000000 --- a/tests/basic/threads.bas +++ /dev/null @@ -1,30 +0,0 @@ -SuperStrict - -' Threading tutorial 1: -' A basic loading thread - - -' a threadable function -' threadable functions must return an Object and take 1 object as input, they don't need to be used -Function loadResources:Object(in:Object) - Print "Starting a child thread..." - For Local counter:Int = 0 Until 20 ' just a loop to make stuff happen - Print "Pretending to load resource " + counter - Delay(300) ' just to make this take some time like loading a real resource would - Next - Print "Child thread complete." -End Function - - - -'####### Main code starts here - -' Create a thread with loadResources() and Null as it's input object value -Local loadingThread:TThread = CreateThread(loadResources, Null) - -Print "Starting the main loop..." -While(ThreadRunning(loadingThread)) ' as long as that child thread is still running... - Print "Waiting on our resources..." - Delay(100) ' we could do whatever we want here... -Wend -Print "Main loop complete." diff --git a/tests/cobol/Acme99.cbl b/tests/cobol/Acme99.cbl deleted file mode 100644 index 3dca945..0000000 --- a/tests/cobol/Acme99.cbl +++ /dev/null @@ -1,200 +0,0 @@ - $ SET SOURCEFORMAT"FREE" -IDENTIFICATION DIVISION. -PROGRAM-ID. ACME99. -AUTHOR. Michael Coughlan. -*CS431399R-EXAM. - -ENVIRONMENT DIVISION. -INPUT-OUTPUT SECTION. -FILE-CONTROL. - SELECT ORDER-FILE ASSIGN TO "ORDERS.DAT" - ORGANIZATION IS LINE SEQUENTIAL. - - SELECT STOCK-FILE ASSIGN TO "STOCK.DAT" - ORGANIZATION IS RELATIVE - ACCESS MODE IS DYNAMIC - RELATIVE KEY IS STOCK-REC-POINTER-WB - FILE STATUS IS STOCK-STATUS-WB. - - SELECT MANF-FILE ASSIGN TO "MANF.DAT" - ORGANIZATION IS INDEXED - ACCESS MODE IS RANDOM - RECORD KEY IS MANF-CODE-FC - ALTERNATE RECORD KEY IS MANF-NAME-FC - WITH DUPLICATES - FILE STATUS IS MANF-STATUS-WB. - - - -DATA DIVISION. -FILE SECTION. -FD ORDER-FILE. -01 ORDER-REC-FA. - 02 ITEM-DESC-FA PIC X(30). - 02 MANF-NAME-FA PIC X(30). - 02 QTY-REQUIRED-FA PIC 9(6). - 02 COST-OF-ITEMS-FA PIC 9(5)V99. - 02 POSTAGE-FA PIC 99V99. - -FD STOCK-FILE. -01 STOCK-REC-FB. - 02 STOCK-NUM-FB PIC 9(5). - 02 MANF-CODE-FB PIC X(4). - 02 ITEM-DESC-FB PIC X(30). - 02 QTY-IN-STOCK-FB PIC 9(6). - 02 REORDER-LEVEL-FB PIC 999. - 02 REORDER-QTY-FB PIC 9(6). - 02 ITEM-COST-FB PIC 9(5). - 02 ITEM-WEIGHT-FB PIC 9(5). - 02 ON-ORDER-FB PIC X. - 88 NOT-ON-ORDER VALUE "N". - 88 ON-ORDER VALUE "Y". - -FD MANF-FILE. -01 MANF-REC-FC. - 02 MANF-CODE-FC PIC X(4). - 02 MANF-NAME-FC PIC X(30). - 02 MANF-ADDRESS-FC PIC X(70). - - - - -WORKING-STORAGE SECTION. -01 CALL-ITEMS-WA. - 02 POST-CHARGE-WA PIC 99V99. - 02 POST-NUM-WA PIC 99. - -01 FILE-DATA-WB. - 02 STOCK-REC-POINTER-WB PIC 9(5). - 02 STOCK-STATUS-WB PIC XX. - 02 MANF-STATUS-WB PIC XX. - 02 FILLER PIC 9 VALUE 0. - 88 END-OF-FILE VALUE 1. - -01 UNSTRING-DATA-WC. - 02 UNSTRING-POINTER-WC PIC 99. - 88 END-OF-ADDRESS VALUE 71. - 02 HOLD-STRING-WC PIC X(10). - 02 COUNTY-WC PIC X(9). - 88 NORTHERN-COUNTY - VALUE "ANTRIM", "ARMAGH", "DERRY", "DOWN", - "FERMANAGH", "TYRONE". - 02 COUNTRY-WC PIC X(10). - 88 EEC-COUNTRY - VALUE "AUSTRIA", "BELGIUM", "DENMARK", "ENGLAND", "FINLAND", - "FRANCE", "GERMANY", "GREECE", "IRELAND", "ITALY", - "LUXEMBOURG", "PORTUGAL", "SCOTLAND", "SPAIN", - "SWEDEN", "WALES". - 88 IRELAND VALUE "IRELAND". - - 02 COUNTRY-FLAGS-WC PIC 9. - 88 OTHER-EEC VALUE 1. - 88 REPUBLIC VALUE 0. - -01 POSTAGE-DATA-WD. - 02 TOTAL-WEIGHT-WD PIC 9(5). - 88 OVER-WEIGHT VALUE 50001 THRU 99999. - - - -PROCEDURE DIVISION. -CREATE-REORDER-FILE. - OPEN I-O STOCK-FILE. - OPEN INPUT MANF-FILE. - OPEN OUTPUT ORDER-FILE. - READ STOCK-FILE NEXT RECORD - AT END SET END-OF-FILE TO TRUE - END-READ. - PERFORM UNTIL END-OF-FILE - IF (QTY-IN-STOCK-FB NOT GREATER THAN REORDER-LEVEL-FB) - AND (NOT-ON-ORDER) - PERFORM CREATE-REORDER-RECORD - PERFORM UPDATE-STOCK-RECORD - END-IF - READ STOCK-FILE NEXT RECORD - AT END SET END-OF-FILE TO TRUE - END-READ - END-PERFORM. - CLOSE STOCK-FILE, MANF-FILE, ORDER-FILE. - STOP RUN. - -CREATE-REORDER-RECORD. - MOVE MANF-CODE-FB TO MANF-CODE-FC. - READ MANF-FILE - KEY IS MANF-CODE-FC - INVALID KEY DISPLAY "CRR MANF STATUS = " - MANF-STATUS-WB "CODE = " MANF-CODE-FC - END-READ. - PERFORM EXTRACT-ADDRESS-ITEMS. - - MOVE ZEROS TO POSTAGE-FA, COST-OF-ITEMS-FA. - IF EEC-COUNTRY - PERFORM GET-POSTAGE - MULTIPLY ITEM-COST-FB BY REORDER-QTY-FB - GIVING COST-OF-ITEMS-FA - MOVE POST-CHARGE-WA TO POSTAGE-FA - END-IF. - - MOVE ITEM-DESC-FB TO ITEM-DESC-FA. - MOVE MANF-NAME-FC TO MANF-NAME-FA. - MOVE REORDER-QTY-FB TO QTY-REQUIRED-FA. - WRITE ORDER-REC-FA. - -GET-POSTAGE. - IF IRELAND AND NOT NORTHERN-COUNTY - SET REPUBLIC TO TRUE - ELSE - SET OTHER-EEC TO TRUE - END-IF. - MULTIPLY ITEM-WEIGHT-FB BY REORDER-QTY-FB - GIVING TOTAL-WEIGHT-WD - ON SIZE ERROR MOVE 99999 TO TOTAL-WEIGHT-WD. - - EVALUATE TOTAL-WEIGHT-WD ALSO REPUBLIC ALSO OTHER-EEC - WHEN 1 THRU 500 ALSO TRUE ALSO FALSE MOVE 1 TO POST-NUM-WA - WHEN 1 THRU 500 ALSO FALSE ALSO TRUE MOVE 2 TO POST-NUM-WA - WHEN 501 THRU 1000 ALSO TRUE ALSO FALSE MOVE 3 TO POST-NUM-WA - WHEN 501 THRU 1000 ALSO FALSE ALSO TRUE MOVE 4 TO POST-NUM-WA - WHEN 1001 THRU 3000 ALSO TRUE ALSO FALSE MOVE 5 TO POST-NUM-WA - WHEN 1001 THRU 3000 ALSO FALSE ALSO TRUE MOVE 6 TO POST-NUM-WA - WHEN 3001 THRU 5000 ALSO TRUE ALSO FALSE MOVE 7 TO POST-NUM-WA - WHEN 3001 THRU 5000 ALSO FALSE ALSO TRUE MOVE 8 TO POST-NUM-WA - WHEN 5001 THRU 10000 ALSO TRUE ALSO FALSE MOVE 9 TO POST-NUM-WA - WHEN 5001 THRU 10000 ALSO FALSE ALSO TRUE MOVE 10 TO POST-NUM-WA - WHEN 10001 THRU 50000 ALSO TRUE ALSO FALSE MOVE 11 TO POST-NUM-WA - WHEN 10001 THRU 50000 ALSO FALSE ALSO TRUE MOVE 12 TO POST-NUM-WA - WHEN 50001 THRU 99999 ALSO ANY ALSO ANY MOVE ZEROS - TO POST-CHARGE-WA - WHEN OTHER DISPLAY "EVALUATE WRONG:- WEIGHT = " TOTAL-WEIGHT-WD - " COUNTRY FLAG = " COUNTRY-FLAGS-WC - END-EVALUATE. - IF NOT OVER-WEIGHT - CALL "POSTAGE-RATE" - USING BY CONTENT POST-NUM-WA - BY REFERENCE POST-CHARGE-WA - END-IF. - - - -UPDATE-STOCK-RECORD. - MOVE "Y" TO ON-ORDER-FB. - REWRITE STOCK-REC-FB - INVALID KEY DISPLAY "STOCK REWRITE STATUS = " STOCK-STATUS-WB - END-REWRITE. - - - -EXTRACT-ADDRESS-ITEMS. - MOVE 1 TO UNSTRING-POINTER-WC. - PERFORM UNTIL END-OF-ADDRESS - MOVE HOLD-STRING-WC TO COUNTY-WC - UNSTRING MANF-ADDRESS-FC DELIMITED BY "," - INTO HOLD-STRING-WC - WITH POINTER UNSTRING-POINTER-WC - END-PERFORM. - MOVE HOLD-STRING-WC TO COUNTRY-WC. - -*debugging displays - DISPLAY "COUNTY = " COUNTY-WC. - DISPLAY "COUNTRY = " COUNTRY-WC. - diff --git a/tests/cobol/DriverProg.cbl b/tests/cobol/DriverProg.cbl deleted file mode 100644 index 7af7f1d..0000000 --- a/tests/cobol/DriverProg.cbl +++ /dev/null @@ -1,132 +0,0 @@ - $ SET SOURCEFORMAT"FREE" -IDENTIFICATION DIVISION. -PROGRAM-ID. DriverProg. -AUTHOR. Michael Coughlan. -* This program demonstrates the use of the CALL verb -* it calls three external sub-programs that help to demonstrate -* some of the features of the CALL. -* The "MultiplyNums" sub-program takes five parameters. The first two -* are the numbers to be multiplied, the second two are strings to -* demonstrate that strings can be passed as parameters and the -* last is the returned result of multiplying the two numbers. -* The "Fickle" sub-program demonstrates a program that exhibits -* State Memory. -* The "Steadfast" sub-program demonstrates how a sub-program that -* uses the IS INITIAL phrase can avoid State Memory. - -ENVIRONMENT DIVISION. -DATA DIVISION. - -WORKING-STORAGE SECTION. -01 UserNumber PIC 99. - -01 PrnResult PIC 9(6). -* field declared as COMP cannot be DISPLAYed -* it is necessary to move it to a DISPLAY field. -* DISPLAY is the default value for a field and -* need not be declared. - - -* Parameters must be either 01-level's or elementry -* data-items. -01 Parameters. - 02 Number1 PIC 9(3). - 02 Number2 PIC 9(3). - 02 FirstString PIC X(19) VALUE "First parameter = ". - 02 SecondString PIC X(19) VALUE "Second parameter = ". - 02 Result PIC 9(6) COMP. -* I've made this a COMP field to demonstrate that COMP -* items can be passed as parameters but a COMP field cannot -* be DISPLAYed and so is moved to a DISPLAY field before DISPLAYing it. - - - -PROCEDURE DIVISION. -Begin. - PERFORM CallMultiplyNums. - PERFORM CallFickle - PERFORM CallSteadfast - - PERFORM MakeFickleSteadfast. - - STOP RUN. - - -CallMultiplyNums. - DISPLAY "Input 2 numbers (3 digits each) to be multiplied" - DISPLAY "First number - " WITH NO ADVANCING - ACCEPT Number1 - DISPLAY "Second number - " WITH NO ADVANCING - ACCEPT Number2. - DISPLAY "The first string is " FirstString. - DISPLAY "The second string is " SecondString. - DISPLAY ">>>>>>>>> Calling the sub-program now". - - CALL "MultiplyNums" - USING BY CONTENT Number1, Number2, FirstString, - BY REFERENCE SecondString, Result. - -* The USING phrase specifies the parameters to be passed to the -* sub-program. The order of the parameters is important as the -* sub-program recognizes them by relative location not by name -* -* Parameters should be passed BY CONTENT when you are not expecting -* them to get a value from the called program. We have not passed -* SecondString by content and you can see that its value is -* overwritten by the called program. - - DISPLAY "Back in the main program now <<<<<<<<<<<". - MOVE Result to PrnResult. - DISPLAY Number1 " multiplied by " Number2 " is = " PrnResult. - - DISPLAY "The first string is " FirstString. - DISPLAY "The second string is " SecondString. - - -CallFickle. - DISPLAY SPACE - DISPLAY "------------------- Calling Fickle ---------" - MOVE 10 TO UserNumber - CALL "Fickle" USING BY CONTENT UserNumber - MOVE 10 TO UserNumber - CALL "Fickle" USING BY CONTENT UserNumber - MOVE 10 TO UserNumber - CALL "Fickle" USING BY CONTENT UserNumber. -* Every time I call Fickle with the same value -* produces a different result. This is because -* it remembers its state from one call to the next. -* It has "State Memory". - - -CallSteadFast. - DISPLAY SPACE - DISPLAY "------------------- Calling Steadfast ---------" - MOVE 10 TO UserNumber - CALL "Steadfast" USING BY CONTENT UserNumber - MOVE 10 TO UserNumber - CALL "Steadfast" USING BY CONTENT UserNumber - MOVE 10 TO UserNumber - CALL "Steadfast" USING BY CONTENT UserNumber. -* Every time I call Steadfast with the same value -* it produces the same result. We have eliminated -* State Memory by using the IS INITIAL phrase in -* Steadfast - - -MakeFickleSteadfast. - DISPLAY SPACE - DISPLAY "----- Making fickle act like Steadfast -------" - CANCEL "Fickle" - MOVE 10 TO UserNumber - CALL "Fickle" USING BY CONTENT UserNumber - - CANCEL "Fickle" - MOVE 10 TO UserNumber - CALL "Fickle" USING BY CONTENT UserNumber - - CANCEL "Fickle" - MOVE 10 TO UserNumber - CALL "Fickle" USING BY CONTENT UserNumber. -* We can make Fickle act like Steadfast by using -* the CANCEL verb to set it into its initial state -* each time we call it diff --git a/tests/coffee/campfire.coffee b/tests/coffee/campfire.coffee deleted file mode 100644 index 6fb5586..0000000 --- a/tests/coffee/campfire.coffee +++ /dev/null @@ -1,251 +0,0 @@ -Robot = require '../robot' -Adapter = require '../adapter' - -HTTPS = require 'https' -EventEmitter = require('events').EventEmitter - -class Campfire extends Adapter - - send: (user, strings...) -> - if strings.length > 0 - @bot.Room(user.room).speak strings.shift(), (err, data) => - @robot.logger.error "Campfire error: #{err}" if err? - @send user, strings... - - reply: (user, strings...) -> - @send user, strings.map((str) -> "#{user.name}: #{str}")... - - topic: (user, strings...) -> - @bot.Room(user.room).topic strings.join(" / "), (err, data) => - @robot.logger.error "Campfire error: #{err}" if err? - - run: -> - self = @ - - options = - token: process.env.HUBOT_CAMPFIRE_TOKEN - rooms: process.env.HUBOT_CAMPFIRE_ROOMS - account: process.env.HUBOT_CAMPFIRE_ACCOUNT - - bot = new CampfireStreaming(options, @robot) - - withAuthor = (callback) -> (id, created, room, user, body) -> - bot.User user, (err, userData) -> - if userData.user - author = self.userForId(userData.user.id, userData.user) - self.robot.brain.data.users[userData.user.id].name = userData.user.name - self.robot.brain.data.users[userData.user.id].email_address = userData.user.email_address - author.room = room - callback id, created, room, user, body, author - - bot.on "TextMessage", withAuthor (id, created, room, user, body, author) -> - unless bot.info.id == author.id - self.receive new Robot.TextMessage(author, body) - - bot.on "EnterMessage", withAuthor (id, created, room, user, body, author) -> - unless bot.info.id == author.id - self.receive new Robot.EnterMessage(author) - - bot.on "LeaveMessage", withAuthor (id, created, room, user, body, author) -> - unless bot.info.id == author.id - self.receive new Robot.LeaveMessage(author) - - bot.Me (err, data) -> - bot.info = data.user - bot.name = bot.info.name - - for roomId in bot.rooms - do (roomId) -> - bot.Room(roomId).join (err, callback) -> - bot.Room(roomId).listen() - - bot.on "reconnect", (roomId) -> - bot.Room(roomId).join (err, callback) -> - bot.Room(roomId).listen() - - @bot = bot - - self.emit "connected" - -exports.use = (robot) -> - new Campfire robot - -class CampfireStreaming extends EventEmitter - constructor: (options, @robot) -> - unless options.token? and options.rooms? and options.account? - @robot.logger.error "Not enough parameters provided. I Need a token, rooms and account" - process.exit(1) - - @token = options.token - @rooms = options.rooms.split(",") - @account = options.account - @domain = @account + ".campfirenow.com" - @authorization = "Basic " + new Buffer("#{@token}:x").toString("base64") - - Rooms: (callback) -> - @get "/rooms", callback - - User: (id, callback) -> - @get "/users/#{id}", callback - - Me: (callback) -> - @get "/users/me", callback - - Room: (id) -> - self = @ - logger = @robot.logger - - show: (callback) -> - self.post "/room/#{id}", "", callback - - join: (callback) -> - self.post "/room/#{id}/join", "", callback - - leave: (callback) -> - self.post "/room/#{id}/leave", "", callback - - lock: (callback) -> - self.post "/room/#{id}/lock", "", callback - - unlock: (callback) -> - self.post "/room/#{id}/unlock", "", callback - - # say things to this channel on behalf of the token user - paste: (text, callback) -> - @message text, "PasteMessage", callback - - topic: (text, callback) -> - body = {room: {topic: text}} - self.put "/room/#{id}", body, callback - - sound: (text, callback) -> - @message text, "SoundMessage", callback - - speak: (text, callback) -> - body = { message: { "body":text } } - self.post "/room/#{id}/speak", body, callback - - message: (text, type, callback) -> - body = { message: { "body":text, "type":type } } - self.post "/room/#{id}/speak", body, callback - - # listen for activity in channels - listen: -> - headers = - "Host" : "streaming.campfirenow.com" - "Authorization" : self.authorization - - options = - "agent" : false - "host" : "streaming.campfirenow.com" - "port" : 443 - "path" : "/room/#{id}/live.json" - "method" : "GET" - "headers": headers - - request = HTTPS.request options, (response) -> - response.setEncoding("utf8") - - buf = '' - - response.on "data", (chunk) -> - if chunk is ' ' - # campfire api sends a ' ' heartbeat every 3s - - else if chunk.match(/^\s*Access Denied/) - # errors are not json formatted - logger.error "Campfire error on room #{id}: #{chunk}" - - else - # api uses newline terminated json payloads - # buffer across tcp packets and parse out lines - buf += chunk - - while (offset = buf.indexOf("\r")) > -1 - part = buf.substr(0, offset) - buf = buf.substr(offset + 1) - - if part - try - data = JSON.parse part - self.emit data.type, data.id, data.created_at, data.room_id, data.user_id, data.body - catch err - logger.error "Campfire error: #{err}" - - response.on "end", -> - logger.error "Streaming connection closed for room #{id}. :(" - setTimeout (-> - self.emit "reconnect", id - ), 5000 - - response.on "error", (err) -> - logger.error "Campfire response error: #{err}" - - request.on "error", (err) -> - logger.error "Campfire request error: #{err}" - - request.end() - - # Convenience HTTP Methods for posting on behalf of the token"d user - get: (path, callback) -> - @request "GET", path, null, callback - - post: (path, body, callback) -> - @request "POST", path, body, callback - - put: (path, body, callback) -> - @request "PUT", path, body, callback - - request: (method, path, body, callback) -> - logger = @robot.logger - - headers = - "Authorization" : @authorization - "Host" : @domain - "Content-Type" : "application/json" - - options = - "agent" : false - "host" : @domain - "port" : 443 - "path" : path - "method" : method - "headers": headers - - if method is "POST" || method is "PUT" - if typeof(body) isnt "string" - body = JSON.stringify body - - body = new Buffer(body) - options.headers["Content-Length"] = body.length - - request = HTTPS.request options, (response) -> - data = "" - - response.on "data", (chunk) -> - data += chunk - - response.on "end", -> - if response.statusCode >= 400 - switch response.statusCode - when 401 - throw new Error "Invalid access token provided, campfire refused the authentication" - else - logger.error "Campfire error: #{response.statusCode}" - - try - callback null, JSON.parse(data) - catch err - callback null, data or { } - - response.on "error", (err) -> - logger.error "Campfire response error: #{err}" - callback err, { } - - if method is "POST" || method is "PUT" - request.end(body, 'binary') - else - request.end() - - request.on "error", (err) -> - logger.error "Campfire request error: #{err}" diff --git a/tests/cpp/DeadlockDetector.h b/tests/cpp/DeadlockDetector.h deleted file mode 100644 index 0f9e3ab..0000000 --- a/tests/cpp/DeadlockDetector.h +++ /dev/null @@ -1,588 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: sw=4 ts=4 et : - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Chris Jones - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_DeadlockDetector_h -#define mozilla_DeadlockDetector_h - -#include - -#include "plhash.h" -#include "prlock.h" - -#include "nsTArray.h" - -#ifdef NS_TRACE_MALLOC -# include "nsTraceMalloc.h" -#endif // ifdef NS_TRACE_MALLOC - -namespace mozilla { - - -// FIXME bug 456272: split this off into a convenience API on top of -// nsStackWalk? -class NS_COM_GLUE CallStack -{ -private: -#ifdef NS_TRACE_MALLOC - typedef nsTMStackTraceID callstack_id; - // needs to be a macro to avoid disturbing the backtrace -# define NS_GET_BACKTRACE() NS_TraceMallocGetStackTrace() -#else - typedef void* callstack_id; -# define NS_GET_BACKTRACE() 0 -#endif // ifdef NS_TRACE_MALLOC - - callstack_id mCallStack; - -public: - /** - * CallStack - * *ALWAYS* *ALWAYS* *ALWAYS* call this with no arguments. This - * constructor takes an argument *ONLY* so that |GET_BACKTRACE()| - * can be evaluated in the stack frame of the caller, rather than - * that of the constructor. - * - * *BEWARE*: this means that calling this constructor with no - * arguments is not the same as a "default, do-nothing" - * constructor: it *will* construct a backtrace. This can cause - * unexpected performance issues. - */ - CallStack(const callstack_id aCallStack = NS_GET_BACKTRACE()) : - mCallStack(aCallStack) - { - } - CallStack(const CallStack& aFrom) : - mCallStack(aFrom.mCallStack) - { - } - CallStack& operator=(const CallStack& aFrom) - { - mCallStack = aFrom.mCallStack; - return *this; - } - bool operator==(const CallStack& aOther) const - { - return mCallStack == aOther.mCallStack; - } - bool operator!=(const CallStack& aOther) const - { - return mCallStack != aOther.mCallStack; - } - - // FIXME bug 456272: if this is split off, - // NS_TraceMallocPrintStackTrace should be modified to print into - // an nsACString - void Print(FILE* f) const - { -#ifdef NS_TRACE_MALLOC - if (this != &kNone && mCallStack) { - NS_TraceMallocPrintStackTrace(f, mCallStack); - return; - } -#endif - fputs(" [stack trace unavailable]\n", f); - } - - /** The "null" callstack. */ - static const CallStack kNone; -}; - - -/** - * DeadlockDetector - * - * The following is an approximate description of how the deadlock detector - * works. - * - * The deadlock detector ensures that all blocking resources are - * acquired according to a partial order P. One type of blocking - * resource is a lock. If a lock l1 is acquired (locked) before l2, - * then we say that |l1 <_P l2|. The detector flags an error if two - * locks l1 and l2 have an inconsistent ordering in P; that is, if - * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error - * because a thread acquiring l1,l2 according to the first order might - * race with a thread acquiring them according to the second order. - * If this happens under the right conditions, then the acquisitions - * will deadlock. - * - * This deadlock detector doesn't know at compile-time what P is. So, - * it tries to discover the order at run time. More precisely, it - * finds some order P, then tries to find chains of resource - * acquisitions that violate P. An example acquisition sequence, and - * the orders they impose, is - * l1.lock() // current chain: [ l1 ] - * // order: { } - * - * l2.lock() // current chain: [ l1, l2 ] - * // order: { l1 <_P l2 } - * - * l3.lock() // current chain: [ l1, l2, l3 ] - * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } - * // (note: <_P is transitive, so also |l1 <_P l3|) - * - * l2.unlock() // current chain: [ l1, l3 ] - * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } - * // (note: it's OK, but weird, that l2 was unlocked out - * // of order. we still have l1 <_P l3). - * - * l2.lock() // current chain: [ l1, l3, l2 ] - * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3, - * l3 <_P l2 (!!!) } - * BEEP BEEP! Here the detector will flag a potential error, since - * l2 and l3 were used inconsistently (and potentially in ways that - * would deadlock). - */ -template -class DeadlockDetector -{ -public: - /** - * ResourceAcquisition - * Consists simply of a resource and the calling context from - * which it was acquired. We pack this information together so - * that it can be returned back to the caller when a potential - * deadlock has been found. - */ - struct ResourceAcquisition - { - const T* mResource; - CallStack mCallContext; - - ResourceAcquisition( - const T* aResource, - const CallStack aCallContext=CallStack::kNone) : - mResource(aResource), - mCallContext(aCallContext) - { - } - ResourceAcquisition(const ResourceAcquisition& aFrom) : - mResource(aFrom.mResource), - mCallContext(aFrom.mCallContext) - { - } - ResourceAcquisition& operator=(const ResourceAcquisition& aFrom) - { - mResource = aFrom.mResource; - mCallContext = aFrom.mCallContext; - return *this; - } - }; - typedef nsTArray ResourceAcquisitionArray; - -private: - typedef nsTArray HashEntryArray; - typedef typename HashEntryArray::index_type index_type; - typedef typename HashEntryArray::size_type size_type; - enum { - NoIndex = HashEntryArray::NoIndex - }; - - /** - * Value type for the ordering table. Contains the other - * resources on which an ordering constraint |key < other| - * exists. The catch is that we also store the calling context at - * which the other resource was acquired; this improves the - * quality of error messages when potential deadlock is detected. - */ - struct OrderingEntry - { - OrderingEntry() : - mFirstSeen(CallStack::kNone), - mOrderedLT() // FIXME bug 456272: set to empirical - { // dep size? - } - ~OrderingEntry() - { - } - - CallStack mFirstSeen; // first site from which the resource appeared - HashEntryArray mOrderedLT; // this <_o Other - }; - - static void* TableAlloc(void* /*pool*/, PRSize size) - { - return operator new(size); - } - static void TableFree(void* /*pool*/, void* item) - { - operator delete(item); - } - static PLHashEntry* EntryAlloc(void* /*pool*/, const void* key) - { - return new PLHashEntry; - } - static void EntryFree(void* /*pool*/, PLHashEntry* entry, PRUintn flag) - { - delete static_cast(const_cast(entry->key)); - delete static_cast(entry->value); - entry->value = 0; - if (HT_FREE_ENTRY == flag) - delete entry; - } - static PLHashNumber HashKey(const void* aKey) - { - return NS_PTR_TO_INT32(aKey) >> 2; - } - static const PLHashAllocOps kAllocOps; - - // Hash table "interface" the rest of the code should use - - PLHashEntry** GetEntry(const T* aKey) - { - return PL_HashTableRawLookup(mOrdering, HashKey(aKey), aKey); - } - - void PutEntry(T* aKey) - { - PL_HashTableAdd(mOrdering, aKey, new OrderingEntry()); - } - - // XXX need these helper methods because OrderingEntry doesn't have - // XXX access to underlying PLHashEntry - - /** - * Add the order |aFirst <_o aSecond|. - * - * WARNING: this does not check whether it's sane to add this - * order. In the "best" bad case, when this order already exists, - * adding it anyway may unnecessarily result in O(n^2) space. In - * the "worst" bad case, adding it anyway will cause - * |InTransitiveClosure()| to diverge. - */ - void AddOrder(PLHashEntry* aLT, PLHashEntry* aGT) - { - static_cast(aLT->value)->mOrderedLT - .InsertElementSorted(aGT); - } - - /** - * Return true iff the order |aFirst < aSecond| has been - * *explicitly* added. - * - * Does not consider transitivity. - */ - bool IsOrdered(const PLHashEntry* aFirst, const PLHashEntry* aSecond) - const - { - return NoIndex != - static_cast(aFirst->value)->mOrderedLT - .BinaryIndexOf(aSecond); - } - - /** - * Return a pointer to the array of all elements "that" for - * which the order |this < that| has been explicitly added. - * - * NOTE: this does *not* consider transitive orderings. - */ - PLHashEntry* const* GetOrders(const PLHashEntry* aEntry) const - { - return static_cast(aEntry->value)->mOrderedLT - .Elements(); - } - - /** - * Return the number of elements "that" for which the order - * |this < that| has been explicitly added. - * - * NOTE: this does *not* consider transitive orderings. - */ - size_type NumOrders(const PLHashEntry* aEntry) const - { - return static_cast(aEntry->value)->mOrderedLT - .Length(); - } - - /** Make a ResourceAcquisition out of |aEntry|. */ - ResourceAcquisition MakeResourceAcquisition(const PLHashEntry* aEntry) - const - { - return ResourceAcquisition( - static_cast(aEntry->key), - static_cast(aEntry->value)->mFirstSeen); - } - - // Throwaway RAII lock to make the following code safer. - struct PRAutoLock - { - PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); } - ~PRAutoLock() { PR_Unlock(mLock); } - PRLock* mLock; - }; - -public: - static const PRUint32 kDefaultNumBuckets; - - /** - * DeadlockDetector - * Create a new deadlock detector. - * - * @param aNumResourcesGuess Guess at approximate number of resources - * that will be checked. - */ - DeadlockDetector(PRUint32 aNumResourcesGuess = kDefaultNumBuckets) - { - mOrdering = PL_NewHashTable(aNumResourcesGuess, - HashKey, - PL_CompareValues, PL_CompareValues, - &kAllocOps, 0); - if (!mOrdering) - NS_RUNTIMEABORT("couldn't initialize resource ordering table"); - - mLock = PR_NewLock(); - if (!mLock) - NS_RUNTIMEABORT("couldn't allocate deadlock detector lock"); - } - - /** - * ~DeadlockDetector - * - * *NOT* thread safe. - */ - ~DeadlockDetector() - { - PL_HashTableDestroy(mOrdering); - PR_DestroyLock(mLock); - } - - /** - * Add - * Make the deadlock detector aware of |aResource|. - * - * WARNING: The deadlock detector owns |aResource|. - * - * Thread safe. - * - * @param aResource Resource to make deadlock detector aware of. - */ - void Add(T* aResource) - { - PRAutoLock _(mLock); - PutEntry(aResource); - } - - // Nb: implementing a Remove() method makes the detector "more - // unsound." By removing a resource from the orderings, deadlocks - // may be missed that would otherwise have been found. However, - // removing resources possibly reduces the # of false positives, - // and additionally saves space. So it's a trade off; we have - // chosen to err on the side of caution and not implement Remove(). - - /** - * CheckAcquisition This method is called after acquiring |aLast|, - * but before trying to acquire |aProposed| from |aCallContext|. - * It determines whether actually trying to acquire |aProposed| - * will create problems. It is OK if |aLast| is NULL; this is - * interpreted as |aProposed| being the thread's first acquisition - * of its current chain. - * - * Iff acquiring |aProposed| may lead to deadlock for some thread - * interleaving (including the current one!), the cyclical - * dependency from which this was deduced is returned. Otherwise, - * 0 is returned. - * - * If a potential deadlock is detected and a resource cycle is - * returned, it is the *caller's* responsibility to free it. - * - * Thread safe. - * - * @param aLast Last resource acquired by calling thread (or 0). - * @param aProposed Resource calling thread proposes to acquire. - * @param aCallContext Calling context whence acquisiton request came. - */ - ResourceAcquisitionArray* CheckAcquisition(const T* aLast, - const T* aProposed, - const CallStack& aCallContext) - { - NS_ASSERTION(aProposed, "null resource"); - PRAutoLock _(mLock); - - PLHashEntry* second = *GetEntry(aProposed); - OrderingEntry* e = static_cast(second->value); - if (CallStack::kNone == e->mFirstSeen) - e->mFirstSeen = aCallContext; - - if (!aLast) - // don't check if |0 < proposed|; just vamoose - return 0; - - PLHashEntry* first = *GetEntry(aLast); - - // this is the crux of the deadlock detector algorithm - - if (first == second) { - // reflexive deadlock. fastpath b/c InTransitiveClosure is - // not applicable here. - ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray(); - if (!cycle) - NS_RUNTIMEABORT("can't allocate dep. cycle array"); - cycle->AppendElement(MakeResourceAcquisition(first)); - cycle->AppendElement(ResourceAcquisition(aProposed, - aCallContext)); - return cycle; - } - if (InTransitiveClosure(first, second)) { - // we've already established |last < proposed|. all is well. - return 0; - } - if (InTransitiveClosure(second, first)) { - // the order |proposed < last| has been deduced, perhaps - // transitively. we're attempting to violate that - // constraint by acquiring resources in the order - // |last < proposed|, and thus we may deadlock under the - // right conditions. - ResourceAcquisitionArray* cycle = GetDeductionChain(second, first); - // show how acquiring |proposed| would complete the cycle - cycle->AppendElement(ResourceAcquisition(aProposed, - aCallContext)); - return cycle; - } - // |last|, |proposed| are unordered according to our - // poset. this is fine, but we now need to add this - // ordering constraint. - AddOrder(first, second); - return 0; - } - - /** - * Return true iff |aTarget| is in the transitive closure of |aStart| - * over the ordering relation `<_this'. - * - * @precondition |aStart != aTarget| - */ - bool InTransitiveClosure(const PLHashEntry* aStart, - const PLHashEntry* aTarget) const - { - if (IsOrdered(aStart, aTarget)) - return true; - - index_type i = 0; - size_type len = NumOrders(aStart); - for (const PLHashEntry* const* it = GetOrders(aStart); - i < len; ++i, ++it) - if (InTransitiveClosure(*it, aTarget)) - return true; - return false; - } - - /** - * Return an array of all resource acquisitions - * aStart <_this r1 <_this r2 <_ ... <_ aTarget - * from which |aStart <_this aTarget| was deduced, including - * |aStart| and |aTarget|. - * - * Nb: there may be multiple deductions of |aStart <_this - * aTarget|. This function returns the first ordering found by - * depth-first search. - * - * Nb: |InTransitiveClosure| could be replaced by this function. - * However, this one is more expensive because we record the DFS - * search stack on the heap whereas the other doesn't. - * - * @precondition |aStart != aTarget| - */ - ResourceAcquisitionArray* GetDeductionChain( - const PLHashEntry* aStart, - const PLHashEntry* aTarget) - { - ResourceAcquisitionArray* chain = new ResourceAcquisitionArray(); - if (!chain) - NS_RUNTIMEABORT("can't allocate dep. cycle array"); - chain->AppendElement(MakeResourceAcquisition(aStart)); - - NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain), - "GetDeductionChain called when there's no deadlock"); - return chain; - } - - // precondition: |aStart != aTarget| - // invariant: |aStart| is the last element in |aChain| - bool GetDeductionChain_Helper(const PLHashEntry* aStart, - const PLHashEntry* aTarget, - ResourceAcquisitionArray* aChain) - { - if (IsOrdered(aStart, aTarget)) { - aChain->AppendElement(MakeResourceAcquisition(aTarget)); - return true; - } - - index_type i = 0; - size_type len = NumOrders(aStart); - for (const PLHashEntry* const* it = GetOrders(aStart); - i < len; ++i, ++it) { - aChain->AppendElement(MakeResourceAcquisition(*it)); - if (GetDeductionChain_Helper(*it, aTarget, aChain)) - return true; - aChain->RemoveElementAt(aChain->Length() - 1); - } - return false; - } - - /** - * The partial order on resource acquisitions used by the deadlock - * detector. - */ - PLHashTable* mOrdering; // T* -> PLHashEntry - - /** - * Protects contentious methods. - * Nb: can't use mozilla::Mutex since we are used as its deadlock - * detector. - */ - PRLock* mLock; - - DeadlockDetector(const DeadlockDetector& aDD); - DeadlockDetector& operator=(const DeadlockDetector& aDD); -}; - - -template -const PLHashAllocOps DeadlockDetector::kAllocOps = { - DeadlockDetector::TableAlloc, DeadlockDetector::TableFree, - DeadlockDetector::EntryAlloc, DeadlockDetector::EntryFree -}; - - -template -// FIXME bug 456272: tune based on average workload -const PRUint32 DeadlockDetector::kDefaultNumBuckets = 64; - - -} // namespace mozilla - -#endif // ifndef mozilla_DeadlockDetector_h diff --git a/tests/cpp/ceded-test.cpp b/tests/cpp/ceded-test.cpp deleted file mode 100644 index 5a706e8..0000000 --- a/tests/cpp/ceded-test.cpp +++ /dev/null @@ -1,597 +0,0 @@ -/* Test file for C++ language. - * Attempt to include as many aspects of the C++ language as possible. - * Do not include things tested in test.c since that shares the - * same language. - * - * $Id: test.cpp,v 1.22 2008/05/17 20:12:55 zappo Exp $ - * - */ - -/* An include test */ -#include - -#include - -#include "c++-test.hh" - -#include - -double var1 = 1.2; - -int simple1(int a) { - -} - -struct foo1 { - int test; -}; - -struct foo2 : public foo1 { - const int foo21(int a, int b); - const int foo22(int a, int b) { return 1 } -}; - -/* Classes */ -class class1 { -private: - int var11; - struct foo1 var12; -public: - int p_var11; - struct foo p_var12; -}; - -class i_class1 : public class1 { -private: - int var11; - struct foo var12; -public: - int p_var11; - struct foo p_var12; -}; - -class class2 { -private: - int var21; - struct foo var22; -public: - int p_var21; - struct foo p_var22; -}; - -class i_class2 : public class1, public class2 { -private: - int var21; - struct foo var22; -protected: - int pt_var21; -public: - int p_var21; - struct foo p_var22; -}; - -class class3 { - /* A class with strange things in it */ -public: - class3(); /* A constructor */ - enum embedded_foo_enum { - a, b, c - } embed1; - struct embedded_bar_struct { - int a; - int b; - } embed2; - class embedded_baz_class { - embedded_baz_class(); - ~embedded_baz_class(); - } embed3; - ~class3(); /* destructor */ - - /* Methods */ - int method_for_class3(int a, char b); - - int inline_method(int c) { return c; } - - /* Operators */ - class3& operator^= (const class3& something); - - /* Funny declmods */ - const class3 * const method_const_ptr_ptr(const int * const argconst) const = 0; -}; - -class3::class3() -{ - /* Constructor outside the definition. */ -} - -int class3::method_for_class3(int a, char b) -{ -} - -int class3::method1_for_class3( int a, int &b) -{ - int cvariablename; - class3 fooy[]; - class3 moose = new class3; - - // Complktion testing line should find external members. - a = fooy[1].me ; - b = cv ; - - if (fooy.emb) { - simple1(c); - } - - cos(10); - abs(10); - - return 1; -} - -char class3::method2_for_class3( int a, int b) throw ( exception1 ) -{ - return 'a'; -} - -void *class3::method3_for_class3( int a, int b) throw ( exception1, exception2 ) -{ - int q = a; - return "Moose"; -} - -void *class3::method31_for_class3( int a, int b) throw ( ) -{ - int q = a; - return "Moose"; -} - -void *class3::method4_for_class3( int a, int b) reentrant -{ - class3 ct; - - ct.method5_for_class3(1,a); - - pritf(); -} - -/* - * A method on class3. - */ -void *class3::method5_for_class3( int a, int b) const -{ -} - -/* - * Namespace parsing tests - */ -namespace NS { - class class_in_namespace { - int equiv(const NS::class_in_namespace *) const; - }; -} - -int NS::class_in_namespace::equiv(const NS::class_in_namespace *cin) const -{ - return 0; -} - -// Stuff Klaus found. -// Inheritance w/out a specifying for public. -class class4 : class1 { - // Pure virtual methods. - void virtual print () const = 0; - -public: - // The whacky constructor type - class4() - try : class1(args) - { - // constructor body - } - catch () - { - - } - - -}; - -class class5 : public virtual class4 { - // Virtual inheritance -}; - -class class6 : class1 { - // Mutable - mutable int i; -}; - -/* Namespaces */ -namespace namespace1 { - void ns_method1() { } - - class n_class1 { - public: - void method11(int a) { } - }; - - /* This shouldn't parse due to missing semicolon. */ - class _n_class2 : public n_class1 { - void n_c2_method1(int a, int b) { } - }; - - // Macros in the namespace -#define NSMACRO 1 - - // Template in the namespace - template T nsti1(const Foo& foo); - template<> int nsti1(const Foo& foo); - -} - -namespace namespace2 { - - using namespace1::n_class1; - -} - -/* Initializers */ -void tinitializers1(): inita1(False), - inita2(False) -{ - inita1= 1; -} - -/* How about Extern C type things. */ -int funny_prototype(int ,int b,float c) -{ - -} - -extern "C" -int extern_c_1(int a, int b) -{ - - funny_prototype(1,2,3.4); - - printf("Moose", ); - - return 1; -} - -extern "C" { - - int extern_c_2(int a, int b) - { - return 1; - } - -} - -// Some operator stuff -class Action -{ - // Problems!! operator() and operator[] can not be parsed with semantic - // 1.4.2 but with latest c.by - virtual void operator()(int i, char *p ) = 0; - virtual String& operator[]() = 0; - virtual void operator!() = 0; - virtual void operator->() = 0; - virtual T& operator+=(); - virtual T& operator*(); - virtual T& operator*=(); -}; - -// class with namespace qualified parents -class Multiinherit : public virtual POA::Parent, - public virtual POA::Parent1, - Parent -{ -private: - int i; - -public: - Multiinherit(); - ~Multiinherit(); - - // method with a list of qualified exceptions - void* throwtest() - throw(Exception0, - Testnamespace::Exception1, - Testnamespace::Excpetion2, - Testnamespace::testnamespace1::Exception3); - -}; - -void* -Multiinherit::throwtest() - throw (Exception0, - Testnamespace::Exception1, - Testnamespace::Excpetion2, - Testnamespace::testnamespace1::Exception3) -{ - return; -} - -// Jens Rock : Nested classes or structs defined -// outside of the containing class/struct. -class container -{ - public: - struct contained; - container(); - ~container(); -}; - -struct container::contained -{ - public: - contained(); - ~contained(); -}; - -/* - * Ok, how about some template stuff. - */ -template > -const CT& max (const CT& a, const CT& b) -{ - return a < b ? b : a; -} - -// Arne Schmitz found this one -std::vector &a, &b, &c; - -class TemplateUsingClass -{ - typedef TestClassMap::iterator iterator; - typedef map TestClassMap; - - // typedefs with const and volatile - typedef const map const_TestClassMap; - typedef TestClassMap::iterator volatile volatile_iterator; - - map mapclassvarthingy; -}; - -template T ti1(const Foo& foo); -template<> int ti1(const Foo& foo); - - -// ----------------------------------- -// Now some namespace and related stuff -// ----------------------------------- - -using CORBA::LEX::get_token; -using Namespace1; - -using namespace POA::std; -using namespace Test; - - - -namespace Parser -{ - namespace - { - using Lexer::get_test; - string str = ""; - } - - namespace XXX - { - - class Foobar : public virtual POA::Parent, - public virtual POA::Parent1, - private POA::list, - private map - { - int i; - list >::const_iterator l; - public: - - Foobar(); - ~Foobar(); - }; - } - - - void test_function(int i); - -}; - -// unnamed namespaces - even nested -namespace -{ - namespace - { - using Lexer::get_test; - string str = ""; - class FooClass - { - FooClass(); - }; - } - - // some builtin types - long long ll = 0; - long double d = 0.0; - unsigned test; - unsigned long int **uli = 0; - signed si = 0; - signed short ss = 0; - short int i = 0; - long int li = 0; - - // expressions with namespace/class-qualifyiers - ORB_var cGlobalOrb = ORB::_nil(); - ORB_var1 cGlobalOrb1 = ORB::_test; - - class Testclass - { - #define TEST 0 - ini i; - - public: - - Testclass(); - ~Testclass(); - }; - - static void test_function(unsigned int i); - -}; - - -// outside method implementations which should be grouped to type Test -XXX& -Test::waiting() -{ - return; -} - -void -Test::print() -{ - return; -} - -// outside method implementations with namespaces which should be grouped to -// their complete (incl. namespace) types -void* -Parser::XXX::Foobar::wait(int i, const char const * const * p) -{ - return; -} - -void* -Namespace1::Test::wait1(int i) -{ - return; -} - -int -Namespace1::Test::waiting(int i) -{ - return; -} - -// a class with some outside implementations which should all be grouped to -// this class declaration -class ClassWithExternals -{ -private: - int i; - -public: - ClassWithExternals(); - ~ClassWithExternals(); - void non_nil(); -}; - - -// Foobar is not displayed; seems that semantic tries to add this to the class -// Foobar but can not find/display it, because contained in the namespace above. -void -Foobar::non_nil() -{ - return; -} - -// are correctly grouped to the ClassWithExternals class -void -ClassWithExternals::non_nil() -{ - String s = "lödfjg dlfgkdlfkgjdl"; - return; -} - -ClassWithExternals::ClassWithExternals() -{ - return; -} - -void -ClassWithExternals::~ClassWithExternals() -{ - return; -} - - -// ------------------------------- -// Now some macro and define stuff -// ------------------------------- - -#define TEST 0 -#define TEST1 "String" - -// The first backslash makes this macro unmatched syntax with semantic 1.4.2! -// With flexing \+newline as nothing all is working fine! -#define MZK_ENTER(METHOD) \ -{ \ - CzkMethodLog lMethodLog(METHOD,"Framework");\ -} - -#define ZK_ASSERTM(METHOD,ASSERTION,MESSAGE) \ - { if(!(ASSERTION))\ - {\ - std::ostringstream lMesgStream; \ - lMesgStream << "Assertion failed: " \ - << MESSAGE; \ - CzkLogManager::doLog(CzkLogManager::FATAL,"",METHOD, \ - "Assert",lMesgStream); \ - assert(ASSERTION);\ - }\ - } - -// Test if not newline-backslashes are handled correctly -string s = "My \"quoted\" string"; - -// parsed fine as macro -#define FOO (arg) method(arg, "foo"); - -// With semantic 1.4.2 this parsed as macro BAR *and* function method. -// With latest c.bnf at least one-liner macros can be parsed correctly. -#define BAR (arg) CzkMessageLog method(arg, "bar"); - -// some const and volatile stuff -char * p1 = "Hello"; // 1. variable Pointer, variable Data -const char * p2 = "Hello"; // 2. variable pointer, constant data -char * const p3 = "Hello"; // 3. constant pointer, variable data -const char * const p4 = "Hello"; // 4. constant pointer, constant data - -// Case 2 and 4 can exchange first "const" and "char" -char const * p21 = "Hello"; // variable pointer, constant data -char const * const p41 = "Hello"; // constant pointer, constant data - -char volatile a = 0; // a volatile char -void foo(bar const &arg); // a reference to a const bar -int foobar(bar const * const p); // a const pointer to a const bar -int foobar(bar const volatile * const p); // a const pointer to a const bar -int foobar3(char* p); // a const pointer to a const bar - -// Should not be parsed because this is invalid code -int const & const r3 = i; - -boolean i = 0; -boolean & r1 = i; -boolean const & r2 = i; - -// const * sequences can be very long in C++ ;-) -char const * const * const * const * ppp; - -// complex function declarationen with named pointer-arguments -const char** foobar1(volatile char const * const **p); -const char** foobar11(volatile Test::Namespace::Char const * const **p); - -// complex function declarationen with unnamed pointer-arguments -const char* foobar2(const char***); -const char* foobar21(const Test::Namespace::Char***); - -// string literal parsing even with wchar_t -char const *p = "string1"; -char const *q = "string1" "str\"ing2" "string3"; -wchar_t testc = L'a'; - -wchar_t const *wp = L"string with a \" in it"; -wchar_t const *wq = L"string \n\t\"test" L"string2"; -wchar_t const *wr = L"string L"; diff --git a/tests/cpp/issue82.cpp b/tests/cpp/issue82.cpp deleted file mode 100644 index 8267704..0000000 --- a/tests/cpp/issue82.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -namespace phantom { namespace io_stream { namespace proto_http { -namespace handler_bts { - -} // namespace handler_bts - -}}} // namespace phantom::io_stream::proto_http diff --git a/tests/cpp/jstracer.cpp b/tests/cpp/jstracer.cpp deleted file mode 100644 index 8ff4923..0000000 --- a/tests/cpp/jstracer.cpp +++ /dev/null @@ -1,14392 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=4 sw=4 et tw=99: - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released - * May 28, 2008. - * - * The Initial Developer of the Original Code is - * Brendan Eich - * - * Contributor(s): - * Andreas Gal - * Mike Shaver - * David Anderson - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "jsstdint.h" -#include "jsbit.h" // low-level (NSPR-based) headers next -#include "jsprf.h" -#include // standard headers next - -#if defined(_MSC_VER) || defined(__MINGW32__) -#include -#ifdef _MSC_VER -#define alloca _alloca -#endif -#endif -#ifdef SOLARIS -#include -#endif -#include - -#include "nanojit/nanojit.h" -#include "jsapi.h" // higher-level library and API headers -#include "jsarray.h" -#include "jsbool.h" -#include "jscntxt.h" -#include "jsdate.h" -#include "jsdbgapi.h" -#include "jsemit.h" -#include "jsfun.h" -#include "jsinterp.h" -#include "jsiter.h" -#include "jsmath.h" -#include "jsobj.h" -#include "jsopcode.h" -#include "jsregexp.h" -#include "jsscope.h" -#include "jsscript.h" -#include "jsstaticcheck.h" -#include "jstracer.h" -#include "jsxml.h" - -#include "jsatominlines.h" -#include "jsscriptinlines.h" - -#include "jsautooplen.h" // generated headers last -#include "imacros.c.out" - -using namespace nanojit; - -#if JS_HAS_XML_SUPPORT -#define ABORT_IF_XML(v) \ - JS_BEGIN_MACRO \ - if (!JSVAL_IS_PRIMITIVE(v) && OBJECT_IS_XML(BOGUS_CX, JSVAL_TO_OBJECT(v)))\ - ABORT_TRACE("xml detected"); \ - JS_END_MACRO -#else -#define ABORT_IF_XML(v) ((void) 0) -#endif - -/* - * Never use JSVAL_IS_BOOLEAN because it restricts the value (true, false) and - * the type. What you want to use is JSVAL_IS_SPECIAL(x) and then handle the - * undefined case properly (bug 457363). - */ -#undef JSVAL_IS_BOOLEAN -#define JSVAL_IS_BOOLEAN(x) JS_STATIC_ASSERT(0) - -JS_STATIC_ASSERT(sizeof(JSTraceType) == 1); - -/* Map to translate a type tag into a printable representation. */ -static const char typeChar[] = "OIDXSNBF"; -static const char tagChar[] = "OIDISIBI"; - -/* Blacklist parameters. */ - -/* - * Number of iterations of a loop where we start tracing. That is, we don't - * start tracing until the beginning of the HOTLOOP-th iteration. - */ -#define HOTLOOP 2 - -/* Attempt recording this many times before blacklisting permanently. */ -#define BL_ATTEMPTS 2 - -/* Skip this many hits before attempting recording again, after an aborted attempt. */ -#define BL_BACKOFF 32 - -/* Number of times we wait to exit on a side exit before we try to extend the tree. */ -#define HOTEXIT 1 - -/* Number of times we try to extend the tree along a side exit. */ -#define MAXEXIT 3 - -/* Maximum number of peer trees allowed. */ -#define MAXPEERS 9 - -/* Max call depths for inlining. */ -#define MAX_CALLDEPTH 10 - -/* Max native stack size. */ -#define MAX_NATIVE_STACK_SLOTS 1024 - -/* Max call stack size. */ -#define MAX_CALL_STACK_ENTRIES 64 - -/* Max global object size. */ -#define MAX_GLOBAL_SLOTS 4096 - -/* Max memory needed to rebuild the interpreter stack when falling off trace. */ -#define MAX_INTERP_STACK_BYTES \ - (MAX_NATIVE_STACK_SLOTS * sizeof(jsval) + \ - MAX_CALL_STACK_ENTRIES * sizeof(JSInlineFrame) + \ - sizeof(JSInlineFrame)) /* possibly slow native frame at top of stack */ - -/* Max number of branches per tree. */ -#define MAX_BRANCHES 32 - -#define CHECK_STATUS(expr) \ - JS_BEGIN_MACRO \ - JSRecordingStatus _status = (expr); \ - if (_status != JSRS_CONTINUE) \ - return _status; \ - JS_END_MACRO - -#ifdef JS_JIT_SPEW -#define ABORT_TRACE_RV(msg, value) \ - JS_BEGIN_MACRO \ - debug_only_printf(LC_TMAbort, "abort: %d: %s\n", __LINE__, (msg)); \ - return (value); \ - JS_END_MACRO -#else -#define ABORT_TRACE_RV(msg, value) return (value) -#endif - -#define ABORT_TRACE(msg) ABORT_TRACE_RV(msg, JSRS_STOP) -#define ABORT_TRACE_ERROR(msg) ABORT_TRACE_RV(msg, JSRS_ERROR) - -#ifdef JS_JIT_SPEW -struct __jitstats { -#define JITSTAT(x) uint64 x; -#include "jitstats.tbl" -#undef JITSTAT -} jitstats = { 0LL, }; - -JS_STATIC_ASSERT(sizeof(jitstats) % sizeof(uint64) == 0); - -enum jitstat_ids { -#define JITSTAT(x) STAT ## x ## ID, -#include "jitstats.tbl" -#undef JITSTAT - STAT_IDS_TOTAL -}; - -static JSPropertySpec jitstats_props[] = { -#define JITSTAT(x) { #x, STAT ## x ## ID, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT }, -#include "jitstats.tbl" -#undef JITSTAT - { 0 } -}; - -static JSBool -jitstats_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) -{ - int index = -1; - - if (JSVAL_IS_STRING(id)) { - JSString* str = JSVAL_TO_STRING(id); - if (strcmp(JS_GetStringBytes(str), "HOTLOOP") == 0) { - *vp = INT_TO_JSVAL(HOTLOOP); - return JS_TRUE; - } - } - - if (JSVAL_IS_INT(id)) - index = JSVAL_TO_INT(id); - - uint64 result = 0; - switch (index) { -#define JITSTAT(x) case STAT ## x ## ID: result = jitstats.x; break; -#include "jitstats.tbl" -#undef JITSTAT - default: - *vp = JSVAL_VOID; - return JS_TRUE; - } - - if (result < JSVAL_INT_MAX) { - *vp = INT_TO_JSVAL(result); - return JS_TRUE; - } - char retstr[64]; - JS_snprintf(retstr, sizeof retstr, "%llu", result); - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, retstr)); - return JS_TRUE; -} - -JSClass jitstats_class = { - "jitstats", - 0, - JS_PropertyStub, JS_PropertyStub, - jitstats_getProperty, JS_PropertyStub, - JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, NULL, - JSCLASS_NO_OPTIONAL_MEMBERS -}; - -void -js_InitJITStatsClass(JSContext *cx, JSObject *glob) -{ - JS_InitClass(cx, glob, NULL, &jitstats_class, NULL, 0, jitstats_props, NULL, NULL, NULL); -} - -#define AUDIT(x) (jitstats.x++) -#else -#define AUDIT(x) ((void)0) -#endif /* JS_JIT_SPEW */ - -/* - * INS_CONSTPTR can be used to embed arbitrary pointers into the native code. It should not - * be used directly to embed GC thing pointers. Instead, use the INS_CONSTOBJ/FUN/STR/SPROP - * variants which ensure that the embedded pointer will be kept alive across GCs. - */ - -#define INS_CONST(c) addName(lir->insImm(c), #c) -#define INS_CONSTPTR(p) addName(lir->insImmPtr(p), #p) -#define INS_CONSTWORD(v) addName(lir->insImmPtr((void *) (v)), #v) -#define INS_CONSTVAL(v) addName(insImmVal(v), #v) -#define INS_CONSTOBJ(obj) addName(insImmObj(obj), #obj) -#define INS_CONSTFUN(fun) addName(insImmFun(fun), #fun) -#define INS_CONSTSTR(str) addName(insImmStr(str), #str) -#define INS_CONSTSPROP(sprop) addName(insImmSprop(sprop), #sprop) -#define INS_ATOM(atom) INS_CONSTSTR(ATOM_TO_STRING(atom)) -#define INS_NULL() INS_CONSTPTR(NULL) -#define INS_VOID() INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)) - -static avmplus::AvmCore s_core = avmplus::AvmCore(); -static avmplus::AvmCore* core = &s_core; - -/* Allocator SPI implementation. */ - -void* -nanojit::Allocator::allocChunk(size_t nbytes) -{ - VMAllocator *vma = (VMAllocator*)this; - JS_ASSERT(!vma->outOfMemory()); - void *p = malloc(nbytes); - if (!p) { - JS_ASSERT(nbytes < sizeof(vma->mReserve)); - vma->mOutOfMemory = true; - p = (void*) &vma->mReserve[0]; - } - vma->mSize += nbytes; - return p; -} - -void -nanojit::Allocator::freeChunk(void *p) { - VMAllocator *vma = (VMAllocator*)this; - if (p != &vma->mReserve[0]) - free(p); -} - -void -nanojit::Allocator::postReset() { - VMAllocator *vma = (VMAllocator*)this; - vma->mOutOfMemory = false; - vma->mSize = 0; -} - - -#ifdef JS_JIT_SPEW -static void -DumpPeerStability(JSTraceMonitor* tm, const void* ip, JSObject* globalObj, uint32 globalShape, uint32 argc); -#endif - -/* - * We really need a better way to configure the JIT. Shaver, where is - * my fancy JIT object? - * - * NB: this is raced on, if jstracer.cpp should ever be running MT. - * I think it's harmless tho. - */ -static bool did_we_check_processor_features = false; - -/* ------ Debug logging control ------ */ - -/* - * All the logging control stuff lives in here. It is shared between - * all threads, but I think that's OK. - */ -LogControl js_LogController; - -#ifdef JS_JIT_SPEW - -/* - * NB: this is raced on too, if jstracer.cpp should ever be running MT. - * Also harmless. - */ -static bool did_we_set_up_debug_logging = false; - -static void -InitJITLogController() -{ - char *tm, *tmf; - uint32_t bits; - - js_LogController.lcbits = 0; - - tm = getenv("TRACEMONKEY"); - if (tm) { - fflush(NULL); - printf( - "The environment variable $TRACEMONKEY has been replaced by $TMFLAGS.\n" - "Try 'TMFLAGS=help js -j' for a list of options.\n" - ); - exit(0); - } - - tmf = getenv("TMFLAGS"); - if (!tmf) return; - - /* Using strstr() is really a cheap hack as far as flag decoding goes. */ - if (strstr(tmf, "help")) { - fflush(NULL); - printf( - "usage: TMFLAGS=option,option,option,... where options can be:\n" - "\n" - " help show this message\n" - " ------ options for jstracer & jsregexp ------\n" - " minimal ultra-minimalist output; try this first\n" - " full everything except 'treevis' and 'nocodeaddrs'\n" - " tracer tracer lifetime (FIXME:better description)\n" - " recorder trace recording stuff (FIXME:better description)\n" - " abort show trace recording aborts\n" - " stats show trace recording stats\n" - " regexp show compilation & entry for regexps\n" - " treevis spew that tracevis/tree.py can parse\n" - " ------ options for Nanojit ------\n" - " fragprofile count entries and exits for each fragment\n" - " activation show activation info\n" - " liveness show LIR liveness at start of rdr pipeline\n" - " readlir show LIR as it enters the reader pipeline\n" - " aftersf show LIR after StackFilter\n" - " regalloc show regalloc details\n" - " assembly show final aggregated assembly code\n" - " nocodeaddrs don't show code addresses in assembly listings\n" - "\n" - ); - exit(0); - /*NOTREACHED*/ - } - - bits = 0; - - /* flags for jstracer.cpp */ - if (strstr(tmf, "minimal") || strstr(tmf, "full")) bits |= LC_TMMinimal; - if (strstr(tmf, "tracer") || strstr(tmf, "full")) bits |= LC_TMTracer; - if (strstr(tmf, "recorder") || strstr(tmf, "full")) bits |= LC_TMRecorder; - if (strstr(tmf, "abort") || strstr(tmf, "full")) bits |= LC_TMAbort; - if (strstr(tmf, "stats") || strstr(tmf, "full")) bits |= LC_TMStats; - if (strstr(tmf, "regexp") || strstr(tmf, "full")) bits |= LC_TMRegexp; - if (strstr(tmf, "treevis")) bits |= LC_TMTreeVis; - - /* flags for nanojit */ - if (strstr(tmf, "fragprofile")) bits |= LC_FragProfile; - if (strstr(tmf, "liveness") || strstr(tmf, "full")) bits |= LC_Liveness; - if (strstr(tmf, "activation") || strstr(tmf, "full")) bits |= LC_Activation; - if (strstr(tmf, "readlir") || strstr(tmf, "full")) bits |= LC_ReadLIR; - if (strstr(tmf, "aftersf") || strstr(tmf, "full")) bits |= LC_AfterSF; - if (strstr(tmf, "regalloc") || strstr(tmf, "full")) bits |= LC_RegAlloc; - if (strstr(tmf, "assembly") || strstr(tmf, "full")) bits |= LC_Assembly; - if (strstr(tmf, "nocodeaddrs")) bits |= LC_NoCodeAddrs; - - js_LogController.lcbits = bits; - return; - -} -#endif - -/* ------------------ Frag-level profiling support ------------------ */ - -#ifdef JS_JIT_SPEW - -/* - * All the allocations done by this profile data-collection and - * display machinery, are done in JSTraceMonitor::profAlloc. That is - * emptied out at the end of js_FinishJIT. It has a lifetime from - * js_InitJIT to js_FinishJIT, which exactly matches the span - * js_FragProfiling_init to js_FragProfiling_showResults. - */ -template -static -Seq* reverseInPlace(Seq* seq) -{ - Seq* prev = NULL; - Seq* curr = seq; - while (curr) { - Seq* next = curr->tail; - curr->tail = prev; - prev = curr; - curr = next; - } - return prev; -} - -// The number of top blocks to show in the profile -#define N_TOP_BLOCKS 50 - -// Contains profile info for a single guard -struct GuardPI { - uint32_t guardID; // identifying number - uint32_t count; // count. -}; - -struct FragPI { - uint32_t count; // entry count for this Fragment - uint32_t nStaticExits; // statically: the number of exits - size_t nCodeBytes; // statically: the number of insn bytes in the main fragment - size_t nExitBytes; // statically: the number of insn bytes in the exit paths - Seq* guards; // guards, each with its own count - uint32_t largestGuardID; // that exists in .guards -}; - -/* A mapping of Fragment.profFragID to FragPI */ -typedef HashMap FragStatsMap; - -void -js_FragProfiling_FragFinalizer(Fragment* f, JSTraceMonitor* tm) -{ - // Recover profiling data from 'f', which is logically at the end - // of its useful lifetime. - if (!(js_LogController.lcbits & LC_FragProfile)) - return; - - NanoAssert(f); - // Valid profFragIDs start at 1 - NanoAssert(f->profFragID >= 1); - // Should be called exactly once per Fragment. This will assert if - // you issue the same FragID to more than one Fragment. - NanoAssert(!tm->profTab->containsKey(f->profFragID)); - - FragPI pi = { f->profCount, - f->nStaticExits, - f->nCodeBytes, - f->nExitBytes, - NULL, 0 }; - - // Begin sanity check on the guards - SeqBuilder guardsBuilder(*tm->profAlloc); - GuardRecord* gr; - uint32_t nGs = 0; - uint32_t sumOfDynExits = 0; - for (gr = f->guardsForFrag; gr; gr = gr->nextInFrag) { - nGs++; - // Also copy the data into our auxiliary structure. - // f->guardsForFrag is in reverse order, and so this - // copy preserves that ordering (->add adds at end). - // Valid profGuardIDs start at 1. - NanoAssert(gr->profGuardID > 0); - sumOfDynExits += gr->profCount; - GuardPI gpi = { gr->profGuardID, gr->profCount }; - guardsBuilder.add(gpi); - if (gr->profGuardID > pi.largestGuardID) - pi.largestGuardID = gr->profGuardID; - } - pi.guards = guardsBuilder.get(); - // And put the guard list in forwards order - pi.guards = reverseInPlace(pi.guards); - - // Why is this so? Because nGs is the number of guards - // at the time the LIR was generated, whereas f->nStaticExits - // is the number of them observed by the time it makes it - // through to the assembler. It can be the case that LIR - // optimisation removes redundant guards; hence we expect - // nGs to always be the same or higher. - NanoAssert(nGs >= f->nStaticExits); - - // Also we can assert that the sum of the exit counts - // can't exceed the entry count. It'd be nice to assert that - // they are exactly equal, but we can't because we don't know - // how many times we got to the end of the trace. - NanoAssert(f->profCount >= sumOfDynExits); - - // End sanity check on guards - - tm->profTab->put(f->profFragID, pi); -} - -static void -js_FragProfiling_showResults(JSTraceMonitor* tm) -{ - uint32_t topFragID[N_TOP_BLOCKS]; - FragPI topPI[N_TOP_BLOCKS]; - uint64_t totCount = 0, cumulCount; - uint32_t totSE = 0; - size_t totCodeB = 0, totExitB = 0; - memset(topFragID, 0, sizeof(topFragID)); - memset(topPI, 0, sizeof(topPI)); - FragStatsMap::Iter iter(*tm->profTab); - while (iter.next()) { - uint32_t fragID = iter.key(); - FragPI pi = iter.value(); - uint32_t count = pi.count; - totCount += (uint64_t)count; - /* Find the rank for this entry, in tops */ - int r = N_TOP_BLOCKS-1; - while (true) { - if (r == -1) - break; - if (topFragID[r] == 0) { - r--; - continue; - } - if (count > topPI[r].count) { - r--; - continue; - } - break; - } - r++; - AvmAssert(r >= 0 && r <= N_TOP_BLOCKS); - /* This entry should be placed at topPI[r], and entries - at higher numbered slots moved up one. */ - if (r < N_TOP_BLOCKS) { - for (int s = N_TOP_BLOCKS-1; s > r; s--) { - topFragID[s] = topFragID[s-1]; - topPI[s] = topPI[s-1]; - } - topFragID[r] = fragID; - topPI[r] = pi; - } - } - - js_LogController.printf( - "\n----------------- Per-fragment execution counts ------------------\n"); - js_LogController.printf( - "\nTotal count = %llu\n\n", (unsigned long long int)totCount); - - js_LogController.printf( - " Entry counts Entry counts ----- Static -----\n"); - js_LogController.printf( - " ------Self------ ----Cumulative--- Exits Cbytes Xbytes FragID\n"); - js_LogController.printf("\n"); - - if (totCount == 0) - totCount = 1; /* avoid division by zero */ - cumulCount = 0; - int r; - for (r = 0; r < N_TOP_BLOCKS; r++) { - if (topFragID[r] == 0) - break; - cumulCount += (uint64_t)topPI[r].count; - js_LogController.printf("%3d: %5.2f%% %9u %6.2f%% %9llu" - " %3d %5u %5u %06u\n", - r, - (double)topPI[r].count * 100.0 / (double)totCount, - topPI[r].count, - (double)cumulCount * 100.0 / (double)totCount, - (unsigned long long int)cumulCount, - topPI[r].nStaticExits, - (unsigned int)topPI[r].nCodeBytes, - (unsigned int)topPI[r].nExitBytes, - topFragID[r]); - totSE += (uint32_t)topPI[r].nStaticExits; - totCodeB += topPI[r].nCodeBytes; - totExitB += topPI[r].nExitBytes; - } - js_LogController.printf("\nTotal displayed code bytes = %u, " - "exit bytes = %u\n" - "Total displayed static exits = %d\n\n", - (unsigned int)totCodeB, (unsigned int)totExitB, totSE); - - js_LogController.printf("Analysis by exit counts\n\n"); - - for (r = 0; r < N_TOP_BLOCKS; r++) { - if (topFragID[r] == 0) - break; - js_LogController.printf("FragID=%06u, total count %u:\n", topFragID[r], - topPI[r].count); - uint32_t madeItToEnd = topPI[r].count; - uint32_t totThisFrag = topPI[r].count; - if (totThisFrag == 0) - totThisFrag = 1; - GuardPI gpi; - // visit the guards, in forward order - for (Seq* guards = topPI[r].guards; guards; guards = guards->tail) { - gpi = (*guards).head; - if (gpi.count == 0) - continue; - madeItToEnd -= gpi.count; - js_LogController.printf(" GuardID=%03u %7u (%5.2f%%)\n", - gpi.guardID, gpi.count, - 100.0 * (double)gpi.count / (double)totThisFrag); - } - js_LogController.printf(" Looped (%03u) %7u (%5.2f%%)\n", - topPI[r].largestGuardID+1, - madeItToEnd, - 100.0 * (double)madeItToEnd / (double)totThisFrag); - NanoAssert(madeItToEnd <= topPI[r].count); // else unsigned underflow - js_LogController.printf("\n"); - } - - tm->profTab = NULL; -} - -#endif - -/* ----------------------------------------------------------------- */ - -#ifdef DEBUG -static const char* -getExitName(ExitType type) -{ - static const char* exitNames[] = - { - #define MAKE_EXIT_STRING(x) #x, - JS_TM_EXITCODES(MAKE_EXIT_STRING) - #undef MAKE_EXIT_STRING - NULL - }; - - JS_ASSERT(type < TOTAL_EXIT_TYPES); - - return exitNames[type]; -} - -static JSBool FASTCALL -PrintOnTrace(char* format, uint32 argc, double *argv) -{ - union { - struct { - uint32 lo; - uint32 hi; - } i; - double d; - char *cstr; - JSObject *o; - JSString *s; - } u; - -#define GET_ARG() JS_BEGIN_MACRO \ - if (argi >= argc) { \ - fprintf(out, "[too few args for format]"); \ - break; \ -} \ - u.d = argv[argi++]; \ - JS_END_MACRO - - FILE *out = stderr; - - uint32 argi = 0; - for (char *p = format; *p; ++p) { - if (*p != '%') { - putc(*p, out); - continue; - } - char ch = *++p; - if (!ch) { - fprintf(out, "[trailing %%]"); - continue; - } - - switch (ch) { - case 'a': - GET_ARG(); - fprintf(out, "[%u:%u 0x%x:0x%x %f]", u.i.lo, u.i.hi, u.i.lo, u.i.hi, u.d); - break; - case 'd': - GET_ARG(); - fprintf(out, "%d", u.i.lo); - break; - case 'u': - GET_ARG(); - fprintf(out, "%u", u.i.lo); - break; - case 'x': - GET_ARG(); - fprintf(out, "%x", u.i.lo); - break; - case 'f': - GET_ARG(); - fprintf(out, "%f", u.d); - break; - case 'o': - GET_ARG(); - js_DumpObject(u.o); - break; - case 's': - GET_ARG(); - { - size_t length = u.s->length(); - // protect against massive spew if u.s is a bad pointer. - if (length > 1 << 16) - length = 1 << 16; - jschar *chars = u.s->chars(); - for (unsigned i = 0; i < length; ++i) { - jschar co = chars[i]; - if (co < 128) - putc(co, out); - else if (co < 256) - fprintf(out, "\\u%02x", co); - else - fprintf(out, "\\u%04x", co); - } - } - break; - case 'S': - GET_ARG(); - fprintf(out, "%s", u.cstr); - break; - default: - fprintf(out, "[invalid %%%c]", *p); - } - } - -#undef GET_ARG - - return JS_TRUE; -} - -JS_DEFINE_CALLINFO_3(extern, BOOL, PrintOnTrace, CHARPTR, UINT32, DOUBLEPTR, 0, 0) - -// This version is not intended to be called directly: usually it is easier to -// use one of the other overloads. -void -TraceRecorder::tprint(const char *format, int count, nanojit::LIns *insa[]) -{ - size_t size = strlen(format) + 1; - char *data = (char*) lir->insSkip(size)->payload(); - memcpy(data, format, size); - - double *args = (double*) lir->insSkip(count * sizeof(double))->payload(); - for (int i = 0; i < count; ++i) { - JS_ASSERT(insa[i]); - lir->insStorei(insa[i], INS_CONSTPTR(args), sizeof(double) * i); - } - - LIns* args_ins[] = { INS_CONSTPTR(args), INS_CONST(count), INS_CONSTPTR(data) }; - LIns* call_ins = lir->insCall(&PrintOnTrace_ci, args_ins); - guard(false, lir->ins_eq0(call_ins), MISMATCH_EXIT); -} - -// Generate a 'printf'-type call from trace for debugging. -void -TraceRecorder::tprint(const char *format) -{ - LIns* insa[] = { NULL }; - tprint(format, 0, insa); -} - -void -TraceRecorder::tprint(const char *format, LIns *ins) -{ - LIns* insa[] = { ins }; - tprint(format, 1, insa); -} - -void -TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2) -{ - LIns* insa[] = { ins1, ins2 }; - tprint(format, 2, insa); -} - -void -TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3) -{ - LIns* insa[] = { ins1, ins2, ins3 }; - tprint(format, 3, insa); -} - -void -TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3, LIns *ins4) -{ - LIns* insa[] = { ins1, ins2, ins3, ins4 }; - tprint(format, 4, insa); -} - -void -TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3, LIns *ins4, - LIns *ins5) -{ - LIns* insa[] = { ins1, ins2, ins3, ins4, ins5 }; - tprint(format, 5, insa); -} - -void -TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3, LIns *ins4, - LIns *ins5, LIns *ins6) -{ - LIns* insa[] = { ins1, ins2, ins3, ins4, ins5, ins6 }; - tprint(format, 6, insa); -} -#endif - -/* - * The entire VM shares one oracle. Collisions and concurrent updates are - * tolerated and worst case cause performance regressions. - */ -static Oracle oracle; - -/* - * This confusing and mysterious expression is used for the Tracker. The - * tracker's responsibility is to map opaque, 4-byte aligned addresses to LIns - * pointers. To do this efficiently, we observe that the addresses of jsvals - * living in the interpreter tend to be aggregated close to each other - - * usually on the same page (where a tracker page doesn't have to be the same - * size as the OS page size, but it's typically similar). - * - * For every address, we split it into two values: upper bits which represent - * the "base", and lower bits which represent an offset against the base. We - * create a list of: - * struct TrackerPage { - * void* base; - * LIns* map; - * }; - * The mapping then becomes: - * page = page such that Base(address) == page->base, - * page->map[Index(address)] - * - * The size of the map is allocated as N * sizeof(LIns*), where N is - * (TRACKER_PAGE_SIZE >> 2). Since the lower two bits are 0, they are always - * discounted. - * - * TRACKER_PAGE_MASK is the "reverse" expression, with a |- 1| to get a mask - * which separates an address into the Base and Index bits. It is necessary to - * do all this work rather than use TRACKER_PAGE_SIZE - 1, because on 64-bit - * platforms the pointer width is twice as large, and only half as many - * indexes can fit into TrackerPage::map. So the "Base" grows by one bit, and - * the "Index" shrinks by one bit. - */ -#define TRACKER_PAGE_MASK (((TRACKER_PAGE_SIZE / sizeof(void*)) << 2) - 1) - -#define TRACKER_PAGE_SIZE 4096 - -Tracker::Tracker() -{ - pagelist = 0; -} - -Tracker::~Tracker() -{ - clear(); -} - -jsuword -Tracker::getTrackerPageBase(const void* v) const -{ - return jsuword(v) & ~jsuword(TRACKER_PAGE_MASK); -} - -struct Tracker::TrackerPage* -Tracker::findTrackerPage(const void* v) const -{ - jsuword base = getTrackerPageBase(v); - struct Tracker::TrackerPage* p = pagelist; - while (p) { - if (p->base == base) { - return p; - } - p = p->next; - } - return 0; -} - -struct Tracker::TrackerPage* -Tracker::addTrackerPage(const void* v) { - jsuword base = getTrackerPageBase(v); - struct Tracker::TrackerPage* p = (struct Tracker::TrackerPage*) - calloc(1, sizeof(*p) - sizeof(p->map) + (TRACKER_PAGE_SIZE >> 2) * sizeof(LIns*)); - p->base = base; - p->next = pagelist; - pagelist = p; - return p; -} - -void -Tracker::clear() -{ - while (pagelist) { - TrackerPage* p = pagelist; - pagelist = pagelist->next; - free(p); - } -} - -bool -Tracker::has(const void *v) const -{ - return get(v) != NULL; -} - -LIns* -Tracker::get(const void* v) const -{ - struct Tracker::TrackerPage* p = findTrackerPage(v); - if (!p) - return NULL; - return p->map[(jsuword(v) & TRACKER_PAGE_MASK) >> 2]; -} - -void -Tracker::set(const void* v, LIns* i) -{ - struct Tracker::TrackerPage* p = findTrackerPage(v); - if (!p) - p = addTrackerPage(v); - p->map[(jsuword(v) & TRACKER_PAGE_MASK) >> 2] = i; -} - -static inline jsuint -argSlots(JSStackFrame* fp) -{ - return JS_MAX(fp->argc, fp->fun->nargs); -} - -static inline bool -isNumber(jsval v) -{ - return JSVAL_IS_INT(v) || JSVAL_IS_DOUBLE(v); -} - -static inline jsdouble -asNumber(jsval v) -{ - JS_ASSERT(isNumber(v)); - if (JSVAL_IS_DOUBLE(v)) - return *JSVAL_TO_DOUBLE(v); - return (jsdouble)JSVAL_TO_INT(v); -} - -static inline bool -isInt32(jsval v) -{ - if (!isNumber(v)) - return false; - jsdouble d = asNumber(v); - jsint i; - return JSDOUBLE_IS_INT(d, i); -} - -static inline jsint -asInt32(jsval v) -{ - JS_ASSERT(isNumber(v)); - if (JSVAL_IS_INT(v)) - return JSVAL_TO_INT(v); -#ifdef DEBUG - jsint i; - JS_ASSERT(JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(v), i)); -#endif - return jsint(*JSVAL_TO_DOUBLE(v)); -} - -/* Return TT_DOUBLE for all numbers (int and double) and the tag otherwise. */ -static inline JSTraceType -GetPromotedType(jsval v) -{ - if (JSVAL_IS_INT(v)) - return TT_DOUBLE; - if (JSVAL_IS_OBJECT(v)) { - if (JSVAL_IS_NULL(v)) - return TT_NULL; - if (HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v))) - return TT_FUNCTION; - return TT_OBJECT; - } - uint8_t tag = JSVAL_TAG(v); - JS_ASSERT(tag == JSVAL_DOUBLE || tag == JSVAL_STRING || tag == JSVAL_SPECIAL); - JS_STATIC_ASSERT(static_cast(TT_DOUBLE) == JSVAL_DOUBLE); - JS_STATIC_ASSERT(static_cast(TT_STRING) == JSVAL_STRING); - JS_STATIC_ASSERT(static_cast(TT_PSEUDOBOOLEAN) == JSVAL_SPECIAL); - return JSTraceType(tag); -} - -/* Return TT_INT32 for all whole numbers that fit into signed 32-bit and the tag otherwise. */ -static inline JSTraceType -getCoercedType(jsval v) -{ - if (isInt32(v)) - return TT_INT32; - if (JSVAL_IS_OBJECT(v)) { - if (JSVAL_IS_NULL(v)) - return TT_NULL; - if (HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v))) - return TT_FUNCTION; - return TT_OBJECT; - } - uint8_t tag = JSVAL_TAG(v); - JS_ASSERT(tag == JSVAL_DOUBLE || tag == JSVAL_STRING || tag == JSVAL_SPECIAL); - JS_STATIC_ASSERT(static_cast(TT_DOUBLE) == JSVAL_DOUBLE); - JS_STATIC_ASSERT(static_cast(TT_STRING) == JSVAL_STRING); - JS_STATIC_ASSERT(static_cast(TT_PSEUDOBOOLEAN) == JSVAL_SPECIAL); - return JSTraceType(tag); -} - -/* Constant seed and accumulate step borrowed from the DJB hash. */ - -const uintptr_t ORACLE_MASK = ORACLE_SIZE - 1; -JS_STATIC_ASSERT((ORACLE_MASK & ORACLE_SIZE) == 0); - -const uintptr_t FRAGMENT_TABLE_MASK = FRAGMENT_TABLE_SIZE - 1; -JS_STATIC_ASSERT((FRAGMENT_TABLE_MASK & FRAGMENT_TABLE_SIZE) == 0); - -const uintptr_t HASH_SEED = 5381; - -static inline void -HashAccum(uintptr_t& h, uintptr_t i, uintptr_t mask) -{ - h = ((h << 5) + h + (mask & i)) & mask; -} - -static JS_REQUIRES_STACK inline int -StackSlotHash(JSContext* cx, unsigned slot) -{ - uintptr_t h = HASH_SEED; - HashAccum(h, uintptr_t(cx->fp->script), ORACLE_MASK); - HashAccum(h, uintptr_t(cx->fp->regs->pc), ORACLE_MASK); - HashAccum(h, uintptr_t(slot), ORACLE_MASK); - return int(h); -} - -static JS_REQUIRES_STACK inline int -GlobalSlotHash(JSContext* cx, unsigned slot) -{ - uintptr_t h = HASH_SEED; - JSStackFrame* fp = cx->fp; - - while (fp->down) - fp = fp->down; - - HashAccum(h, uintptr_t(fp->script), ORACLE_MASK); - HashAccum(h, uintptr_t(OBJ_SHAPE(JS_GetGlobalForObject(cx, fp->scopeChain))), ORACLE_MASK); - HashAccum(h, uintptr_t(slot), ORACLE_MASK); - return int(h); -} - -static inline int -PCHash(jsbytecode* pc) -{ - return int(uintptr_t(pc) & ORACLE_MASK); -} - -Oracle::Oracle() -{ - /* Grow the oracle bitsets to their (fixed) size here, once. */ - _stackDontDemote.set(ORACLE_SIZE-1); - _globalDontDemote.set(ORACLE_SIZE-1); - clear(); -} - -/* Tell the oracle that a certain global variable should not be demoted. */ -JS_REQUIRES_STACK void -Oracle::markGlobalSlotUndemotable(JSContext* cx, unsigned slot) -{ - _globalDontDemote.set(GlobalSlotHash(cx, slot)); -} - -/* Consult with the oracle whether we shouldn't demote a certain global variable. */ -JS_REQUIRES_STACK bool -Oracle::isGlobalSlotUndemotable(JSContext* cx, unsigned slot) const -{ - return _globalDontDemote.get(GlobalSlotHash(cx, slot)); -} - -/* Tell the oracle that a certain slot at a certain stack slot should not be demoted. */ -JS_REQUIRES_STACK void -Oracle::markStackSlotUndemotable(JSContext* cx, unsigned slot) -{ - _stackDontDemote.set(StackSlotHash(cx, slot)); -} - -/* Consult with the oracle whether we shouldn't demote a certain slot. */ -JS_REQUIRES_STACK bool -Oracle::isStackSlotUndemotable(JSContext* cx, unsigned slot) const -{ - return _stackDontDemote.get(StackSlotHash(cx, slot)); -} - -/* Tell the oracle that a certain slot at a certain bytecode location should not be demoted. */ -void -Oracle::markInstructionUndemotable(jsbytecode* pc) -{ - _pcDontDemote.set(PCHash(pc)); -} - -/* Consult with the oracle whether we shouldn't demote a certain bytecode location. */ -bool -Oracle::isInstructionUndemotable(jsbytecode* pc) const -{ - return _pcDontDemote.get(PCHash(pc)); -} - -void -Oracle::clearDemotability() -{ - _stackDontDemote.reset(); - _globalDontDemote.reset(); - _pcDontDemote.reset(); -} - -JS_REQUIRES_STACK static JS_INLINE void -MarkSlotUndemotable(JSContext* cx, TreeInfo* ti, unsigned slot) -{ - if (slot < ti->nStackTypes) { - oracle.markStackSlotUndemotable(cx, slot); - return; - } - - uint16* gslots = ti->globalSlots->data(); - oracle.markGlobalSlotUndemotable(cx, gslots[slot - ti->nStackTypes]); -} - -static JS_REQUIRES_STACK inline bool -IsSlotUndemotable(JSContext* cx, TreeInfo* ti, unsigned slot) -{ - if (slot < ti->nStackTypes) - return oracle.isStackSlotUndemotable(cx, slot); - - uint16* gslots = ti->globalSlots->data(); - return oracle.isGlobalSlotUndemotable(cx, gslots[slot - ti->nStackTypes]); -} - -struct PCHashEntry : public JSDHashEntryStub { - size_t count; -}; - -#define PC_HASH_COUNT 1024 - -static void -Blacklist(jsbytecode* pc) -{ - AUDIT(blacklisted); - JS_ASSERT(*pc == JSOP_TRACE || *pc == JSOP_NOP); - *pc = JSOP_NOP; -} - -static void -Backoff(JSContext *cx, jsbytecode* pc, Fragment* tree = NULL) -{ - JSDHashTable *table = &JS_TRACE_MONITOR(cx).recordAttempts; - - if (table->ops) { - PCHashEntry *entry = (PCHashEntry *) - JS_DHashTableOperate(table, pc, JS_DHASH_ADD); - - if (entry) { - if (!entry->key) { - entry->key = pc; - JS_ASSERT(entry->count == 0); - } - JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(&(entry->hdr))); - if (entry->count++ > (BL_ATTEMPTS * MAXPEERS)) { - entry->count = 0; - Blacklist(pc); - return; - } - } - } - - if (tree) { - tree->hits() -= BL_BACKOFF; - - /* - * In case there is no entry or no table (due to OOM) or some - * serious imbalance in the recording-attempt distribution on a - * multitree, give each tree another chance to blacklist here as - * well. - */ - if (++tree->recordAttempts > BL_ATTEMPTS) - Blacklist(pc); - } -} - -static void -ResetRecordingAttempts(JSContext *cx, jsbytecode* pc) -{ - JSDHashTable *table = &JS_TRACE_MONITOR(cx).recordAttempts; - if (table->ops) { - PCHashEntry *entry = (PCHashEntry *) - JS_DHashTableOperate(table, pc, JS_DHASH_LOOKUP); - - if (JS_DHASH_ENTRY_IS_FREE(&(entry->hdr))) - return; - JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(&(entry->hdr))); - entry->count = 0; - } -} - -static inline size_t -FragmentHash(const void *ip, JSObject* globalObj, uint32 globalShape, uint32 argc) -{ - uintptr_t h = HASH_SEED; - HashAccum(h, uintptr_t(ip), FRAGMENT_TABLE_MASK); - HashAccum(h, uintptr_t(globalObj), FRAGMENT_TABLE_MASK); - HashAccum(h, uintptr_t(globalShape), FRAGMENT_TABLE_MASK); - HashAccum(h, uintptr_t(argc), FRAGMENT_TABLE_MASK); - return size_t(h); -} - -/* - * argc is cx->fp->argc at the trace loop header, i.e., the number of arguments - * pushed for the innermost JS frame. This is required as part of the fragment - * key because the fragment will write those arguments back to the interpreter - * stack when it exits, using its typemap, which implicitly incorporates a - * given value of argc. Without this feature, a fragment could be called as an - * inner tree with two different values of argc, and entry type checking or - * exit frame synthesis could crash. - */ -struct VMFragment : public Fragment -{ - VMFragment(const void* _ip, JSObject* _globalObj, uint32 _globalShape, uint32 _argc - verbose_only(, uint32_t profFragID)) : - Fragment(_ip verbose_only(, profFragID)), - first(NULL), - next(NULL), - peer(NULL), - globalObj(_globalObj), - globalShape(_globalShape), - argc(_argc) - { } - - inline TreeInfo* getTreeInfo() { - return (TreeInfo*)vmprivate; - } - - VMFragment* first; - VMFragment* next; - VMFragment* peer; - JSObject* globalObj; - uint32 globalShape; - uint32 argc; -}; - -static VMFragment* -getVMFragment(JSTraceMonitor* tm, const void *ip, JSObject* globalObj, uint32 globalShape, - uint32 argc) -{ - size_t h = FragmentHash(ip, globalObj, globalShape, argc); - VMFragment* vf = tm->vmfragments[h]; - while (vf && - ! (vf->globalObj == globalObj && - vf->globalShape == globalShape && - vf->ip == ip && - vf->argc == argc)) { - vf = vf->next; - } - return vf; -} - -static VMFragment* -getLoop(JSTraceMonitor* tm, const void *ip, JSObject* globalObj, uint32 globalShape, uint32 argc) -{ - return getVMFragment(tm, ip, globalObj, globalShape, argc); -} - -static VMFragment* -getAnchor(JSTraceMonitor* tm, const void *ip, JSObject* globalObj, uint32 globalShape, uint32 argc) -{ - verbose_only( - uint32_t profFragID = (js_LogController.lcbits & LC_FragProfile) - ? (++(tm->lastFragID)) : 0; - ) - VMFragment *f = new (*tm->dataAlloc) VMFragment(ip, globalObj, globalShape, argc - verbose_only(, profFragID)); - JS_ASSERT(f); - - VMFragment *p = getVMFragment(tm, ip, globalObj, globalShape, argc); - - if (p) { - f->first = p; - /* append at the end of the peer list */ - VMFragment* next; - while ((next = p->peer) != NULL) - p = next; - p->peer = f; - } else { - /* this is the first fragment */ - f->first = f; - size_t h = FragmentHash(ip, globalObj, globalShape, argc); - f->next = tm->vmfragments[h]; - tm->vmfragments[h] = f; - } - f->root = f; - return f; -} - -#ifdef DEBUG -static void -AssertTreeIsUnique(JSTraceMonitor* tm, VMFragment* f, TreeInfo* ti) -{ - JS_ASSERT(f->root == f); - - /* - * Check for duplicate entry type maps. This is always wrong and hints at - * trace explosion since we are trying to stabilize something without - * properly connecting peer edges. - */ - TreeInfo* ti_other; - for (VMFragment* peer = getLoop(tm, f->ip, f->globalObj, f->globalShape, f->argc); - peer != NULL; - peer = peer->peer) { - if (!peer->code() || peer == f) - continue; - ti_other = (TreeInfo*)peer->vmprivate; - JS_ASSERT(ti_other); - JS_ASSERT(!ti->typeMap.matches(ti_other->typeMap)); - } -} -#endif - -static void -AttemptCompilation(JSContext *cx, JSTraceMonitor* tm, JSObject* globalObj, jsbytecode* pc, - uint32 argc) -{ - /* If we already permanently blacklisted the location, undo that. */ - JS_ASSERT(*pc == JSOP_NOP || *pc == JSOP_TRACE); - *pc = JSOP_TRACE; - ResetRecordingAttempts(cx, pc); - - /* Breathe new life into all peer fragments at the designated loop header. */ - VMFragment* f = (VMFragment*)getLoop(tm, pc, globalObj, OBJ_SHAPE(globalObj), argc); - if (!f) { - /* - * If the global object's shape changed, we can't easily find the - * corresponding loop header via a hash table lookup. In this - * we simply bail here and hope that the fragment has another - * outstanding compilation attempt. This case is extremely rare. - */ - return; - } - JS_ASSERT(f->root == f); - f = f->first; - while (f) { - JS_ASSERT(f->root == f); - --f->recordAttempts; - f->hits() = HOTLOOP; - f = f->peer; - } -} - -// Forward declarations. -JS_DEFINE_CALLINFO_1(static, DOUBLE, i2f, INT32, 1, 1) -JS_DEFINE_CALLINFO_1(static, DOUBLE, u2f, UINT32, 1, 1) - -static bool -isi2f(LIns* i) -{ - if (i->isop(LIR_i2f)) - return true; - - if (nanojit::AvmCore::config.soft_float && - i->isop(LIR_qjoin) && - i->oprnd1()->isop(LIR_pcall) && - i->oprnd2()->isop(LIR_callh)) { - if (i->oprnd1()->callInfo() == &i2f_ci) - return true; - } - - return false; -} - -static bool -isu2f(LIns* i) -{ - if (i->isop(LIR_u2f)) - return true; - - if (nanojit::AvmCore::config.soft_float && - i->isop(LIR_qjoin) && - i->oprnd1()->isop(LIR_pcall) && - i->oprnd2()->isop(LIR_callh)) { - if (i->oprnd1()->callInfo() == &u2f_ci) - return true; - } - - return false; -} - -static LIns* -iu2fArg(LIns* i) -{ - if (nanojit::AvmCore::config.soft_float && - i->isop(LIR_qjoin)) { - return i->oprnd1()->arg(0); - } - - return i->oprnd1(); -} - -static LIns* -demote(LirWriter *out, LIns* i) -{ - if (i->isCall()) - return i->callArgN(0); - if (isi2f(i) || isu2f(i)) - return iu2fArg(i); - if (i->isconst()) - return i; - JS_ASSERT(i->isconstf()); - double cf = i->imm64f(); - int32_t ci = cf > 0x7fffffff ? uint32_t(cf) : int32_t(cf); - return out->insImm(ci); -} - -static bool -isPromoteInt(LIns* i) -{ - if (isi2f(i) || i->isconst()) - return true; - if (!i->isconstf()) - return false; - jsdouble d = i->imm64f(); - return d == jsdouble(jsint(d)) && !JSDOUBLE_IS_NEGZERO(d); -} - -static bool -isPromoteUint(LIns* i) -{ - if (isu2f(i) || i->isconst()) - return true; - if (!i->isconstf()) - return false; - jsdouble d = i->imm64f(); - return d == jsdouble(jsuint(d)) && !JSDOUBLE_IS_NEGZERO(d); -} - -static bool -isPromote(LIns* i) -{ - return isPromoteInt(i) || isPromoteUint(i); -} - -static bool -IsConst(LIns* i, int32_t c) -{ - return i->isconst() && i->imm32() == c; -} - -/* - * Determine whether this operand is guaranteed to not overflow the specified - * integer operation. - */ -static bool -IsOverflowSafe(LOpcode op, LIns* i) -{ - LIns* c; - switch (op) { - case LIR_add: - case LIR_sub: - return (i->isop(LIR_and) && ((c = i->oprnd2())->isconst()) && - ((c->imm32() & 0xc0000000) == 0)) || - (i->isop(LIR_rsh) && ((c = i->oprnd2())->isconst()) && - ((c->imm32() > 0))); - default: - JS_ASSERT(op == LIR_mul); - } - return (i->isop(LIR_and) && ((c = i->oprnd2())->isconst()) && - ((c->imm32() & 0xffff0000) == 0)) || - (i->isop(LIR_ush) && ((c = i->oprnd2())->isconst()) && - ((c->imm32() >= 16))); -} - -/* soft float support */ - -static jsdouble FASTCALL -fneg(jsdouble x) -{ - return -x; -} -JS_DEFINE_CALLINFO_1(static, DOUBLE, fneg, DOUBLE, 1, 1) - -static jsdouble FASTCALL -i2f(int32 i) -{ - return i; -} - -static jsdouble FASTCALL -u2f(jsuint u) -{ - return u; -} - -static int32 FASTCALL -fcmpeq(jsdouble x, jsdouble y) -{ - return x==y; -} -JS_DEFINE_CALLINFO_2(static, INT32, fcmpeq, DOUBLE, DOUBLE, 1, 1) - -static int32 FASTCALL -fcmplt(jsdouble x, jsdouble y) -{ - return x < y; -} -JS_DEFINE_CALLINFO_2(static, INT32, fcmplt, DOUBLE, DOUBLE, 1, 1) - -static int32 FASTCALL -fcmple(jsdouble x, jsdouble y) -{ - return x <= y; -} -JS_DEFINE_CALLINFO_2(static, INT32, fcmple, DOUBLE, DOUBLE, 1, 1) - -static int32 FASTCALL -fcmpgt(jsdouble x, jsdouble y) -{ - return x > y; -} -JS_DEFINE_CALLINFO_2(static, INT32, fcmpgt, DOUBLE, DOUBLE, 1, 1) - -static int32 FASTCALL -fcmpge(jsdouble x, jsdouble y) -{ - return x >= y; -} -JS_DEFINE_CALLINFO_2(static, INT32, fcmpge, DOUBLE, DOUBLE, 1, 1) - -static jsdouble FASTCALL -fmul(jsdouble x, jsdouble y) -{ - return x * y; -} -JS_DEFINE_CALLINFO_2(static, DOUBLE, fmul, DOUBLE, DOUBLE, 1, 1) - -static jsdouble FASTCALL -fadd(jsdouble x, jsdouble y) -{ - return x + y; -} -JS_DEFINE_CALLINFO_2(static, DOUBLE, fadd, DOUBLE, DOUBLE, 1, 1) - -static jsdouble FASTCALL -fdiv(jsdouble x, jsdouble y) -{ - return x / y; -} -JS_DEFINE_CALLINFO_2(static, DOUBLE, fdiv, DOUBLE, DOUBLE, 1, 1) - -static jsdouble FASTCALL -fsub(jsdouble x, jsdouble y) -{ - return x - y; -} -JS_DEFINE_CALLINFO_2(static, DOUBLE, fsub, DOUBLE, DOUBLE, 1, 1) - -// replace fpu ops with function calls -class SoftFloatFilter: public LirWriter -{ -public: - SoftFloatFilter(LirWriter *out) : LirWriter(out) - {} - - LIns *hi(LIns *q) { - return ins1(LIR_qhi, q); - } - LIns *lo(LIns *q) { - return ins1(LIR_qlo, q); - } - - LIns *split(LIns *a) { - if (a->isQuad() && !a->isop(LIR_qjoin)) { - // all quad-sized args must be qjoin's for soft-float - a = ins2(LIR_qjoin, lo(a), hi(a)); - } - return a; - } - - LIns *split(const CallInfo *call, LInsp args[]) { - LIns *lo = out->insCall(call, args); - LIns *hi = out->ins1(LIR_callh, lo); - return out->ins2(LIR_qjoin, lo, hi); - } - - LIns *fcall1(const CallInfo *call, LIns *a) { - LIns *args[] = { split(a) }; - return split(call, args); - } - - LIns *fcall2(const CallInfo *call, LIns *a, LIns *b) { - LIns *args[] = { split(b), split(a) }; - return split(call, args); - } - - LIns *fcmp(const CallInfo *call, LIns *a, LIns *b) { - LIns *args[] = { split(b), split(a) }; - return out->ins2(LIR_eq, out->insCall(call, args), out->insImm(1)); - } - - LIns *ins1(LOpcode op, LIns *a) { - switch (op) { - case LIR_i2f: - return fcall1(&i2f_ci, a); - case LIR_u2f: - return fcall1(&u2f_ci, a); - case LIR_fneg: - return fcall1(&fneg_ci, a); - case LIR_fret: - return out->ins1(op, split(a)); - default: - return out->ins1(op, a); - } - } - - LIns *ins2(LOpcode op, LIns *a, LIns *b) { - switch (op) { - case LIR_fadd: - return fcall2(&fadd_ci, a, b); - case LIR_fsub: - return fcall2(&fsub_ci, a, b); - case LIR_fmul: - return fcall2(&fmul_ci, a, b); - case LIR_fdiv: - return fcall2(&fdiv_ci, a, b); - case LIR_feq: - return fcmp(&fcmpeq_ci, a, b); - case LIR_flt: - return fcmp(&fcmplt_ci, a, b); - case LIR_fgt: - return fcmp(&fcmpgt_ci, a, b); - case LIR_fle: - return fcmp(&fcmple_ci, a, b); - case LIR_fge: - return fcmp(&fcmpge_ci, a, b); - default: - ; - } - return out->ins2(op, a, b); - } - - LIns *insCall(const CallInfo *ci, LInsp args[]) { - uint32_t argt = ci->_argtypes; - - for (uint32_t i = 0, argsizes = argt >> ARGSIZE_SHIFT; argsizes != 0; i++, argsizes >>= ARGSIZE_SHIFT) - args[i] = split(args[i]); - - if ((argt & ARGSIZE_MASK_ANY) == ARGSIZE_F) { - // this function returns a double as two 32bit values, so replace - // call with qjoin(qhi(call), call) - return split(ci, args); - } else { - return out->insCall(ci, args); - } - } -}; - -class FuncFilter: public LirWriter -{ -public: - FuncFilter(LirWriter* out): - LirWriter(out) - { - } - - LIns* ins2(LOpcode v, LIns* s0, LIns* s1) - { - if (s0 == s1 && v == LIR_feq) { - if (isPromote(s0)) { - // double(int) and double(uint) cannot be nan - return insImm(1); - } - if (s0->isop(LIR_fmul) || s0->isop(LIR_fsub) || s0->isop(LIR_fadd)) { - LIns* lhs = s0->oprnd1(); - LIns* rhs = s0->oprnd2(); - if (isPromote(lhs) && isPromote(rhs)) { - // add/sub/mul promoted ints can't be nan - return insImm(1); - } - } - } else if (LIR_feq <= v && v <= LIR_fge) { - if (isPromoteInt(s0) && isPromoteInt(s1)) { - // demote fcmp to cmp - v = LOpcode(v + (LIR_eq - LIR_feq)); - return out->ins2(v, demote(out, s0), demote(out, s1)); - } else if (isPromoteUint(s0) && isPromoteUint(s1)) { - // uint compare - v = LOpcode(v + (LIR_eq - LIR_feq)); - if (v != LIR_eq) - v = LOpcode(v + (LIR_ult - LIR_lt)); // cmp -> ucmp - return out->ins2(v, demote(out, s0), demote(out, s1)); - } - } else if (v == LIR_or && - s0->isop(LIR_lsh) && IsConst(s0->oprnd2(), 16) && - s1->isop(LIR_and) && IsConst(s1->oprnd2(), 0xffff)) { - LIns* msw = s0->oprnd1(); - LIns* lsw = s1->oprnd1(); - LIns* x; - LIns* y; - if (lsw->isop(LIR_add) && - lsw->oprnd1()->isop(LIR_and) && - lsw->oprnd2()->isop(LIR_and) && - IsConst(lsw->oprnd1()->oprnd2(), 0xffff) && - IsConst(lsw->oprnd2()->oprnd2(), 0xffff) && - msw->isop(LIR_add) && - msw->oprnd1()->isop(LIR_add) && - msw->oprnd2()->isop(LIR_rsh) && - msw->oprnd1()->oprnd1()->isop(LIR_rsh) && - msw->oprnd1()->oprnd2()->isop(LIR_rsh) && - IsConst(msw->oprnd2()->oprnd2(), 16) && - IsConst(msw->oprnd1()->oprnd1()->oprnd2(), 16) && - IsConst(msw->oprnd1()->oprnd2()->oprnd2(), 16) && - (x = lsw->oprnd1()->oprnd1()) == msw->oprnd1()->oprnd1()->oprnd1() && - (y = lsw->oprnd2()->oprnd1()) == msw->oprnd1()->oprnd2()->oprnd1() && - lsw == msw->oprnd2()->oprnd1()) { - return out->ins2(LIR_add, x, y); - } - } - - return out->ins2(v, s0, s1); - } - - LIns* insCall(const CallInfo *ci, LIns* args[]) - { - if (ci == &js_DoubleToUint32_ci) { - LIns* s0 = args[0]; - if (s0->isconstf()) - return out->insImm(js_DoubleToECMAUint32(s0->imm64f())); - if (isi2f(s0) || isu2f(s0)) - return iu2fArg(s0); - } else if (ci == &js_DoubleToInt32_ci) { - LIns* s0 = args[0]; - if (s0->isconstf()) - return out->insImm(js_DoubleToECMAInt32(s0->imm64f())); - if (s0->isop(LIR_fadd) || s0->isop(LIR_fsub)) { - LIns* lhs = s0->oprnd1(); - LIns* rhs = s0->oprnd2(); - if (isPromote(lhs) && isPromote(rhs)) { - LOpcode op = LOpcode(s0->opcode() & ~LIR64); - return out->ins2(op, demote(out, lhs), demote(out, rhs)); - } - } - if (isi2f(s0) || isu2f(s0)) - return iu2fArg(s0); - - // XXX ARM -- check for qjoin(call(UnboxDouble),call(UnboxDouble)) - if (s0->isCall()) { - const CallInfo* ci2 = s0->callInfo(); - if (ci2 == &js_UnboxDouble_ci) { - LIns* args2[] = { s0->callArgN(0) }; - return out->insCall(&js_UnboxInt32_ci, args2); - } else if (ci2 == &js_StringToNumber_ci) { - // callArgN's ordering is that as seen by the builtin, not as stored in - // args here. True story! - LIns* args2[] = { s0->callArgN(1), s0->callArgN(0) }; - return out->insCall(&js_StringToInt32_ci, args2); - } else if (ci2 == &js_String_p_charCodeAt0_ci) { - // Use a fast path builtin for a charCodeAt that converts to an int right away. - LIns* args2[] = { s0->callArgN(0) }; - return out->insCall(&js_String_p_charCodeAt0_int_ci, args2); - } else if (ci2 == &js_String_p_charCodeAt_ci) { - LIns* idx = s0->callArgN(1); - // If the index is not already an integer, force it to be an integer. - idx = isPromote(idx) - ? demote(out, idx) - : out->insCall(&js_DoubleToInt32_ci, &idx); - LIns* args2[] = { idx, s0->callArgN(0) }; - return out->insCall(&js_String_p_charCodeAt_int_ci, args2); - } - } - } else if (ci == &js_BoxDouble_ci) { - LIns* s0 = args[0]; - JS_ASSERT(s0->isQuad()); - if (isPromoteInt(s0)) { - LIns* args2[] = { demote(out, s0), args[1] }; - return out->insCall(&js_BoxInt32_ci, args2); - } - if (s0->isCall() && s0->callInfo() == &js_UnboxDouble_ci) - return s0->callArgN(0); - } - return out->insCall(ci, args); - } -}; - -/* - * Visit the values in the given JSStackFrame that the tracer cares about. This - * visitor function is (implicitly) the primary definition of the native stack - * area layout. There are a few other independent pieces of code that must be - * maintained to assume the same layout. They are marked like this: - * - * Duplicate native stack layout computation: see VisitFrameSlots header comment. - */ -template -static JS_REQUIRES_STACK bool -VisitFrameSlots(Visitor &visitor, unsigned depth, JSStackFrame *fp, - JSStackFrame *up) -{ - if (depth > 0 && !VisitFrameSlots(visitor, depth-1, fp->down, fp)) - return false; - - if (fp->argv) { - if (depth == 0) { - visitor.setStackSlotKind("args"); - if (!visitor.visitStackSlots(&fp->argv[-2], argSlots(fp) + 2, fp)) - return false; - } - visitor.setStackSlotKind("arguments"); - if (!visitor.visitStackSlots(&fp->argsobj, 1, fp)) - return false; - visitor.setStackSlotKind("var"); - if (!visitor.visitStackSlots(fp->slots, fp->script->nfixed, fp)) - return false; - } - visitor.setStackSlotKind("stack"); - JS_ASSERT(fp->regs->sp >= StackBase(fp)); - if (!visitor.visitStackSlots(StackBase(fp), - size_t(fp->regs->sp - StackBase(fp)), - fp)) { - return false; - } - if (up) { - int missing = up->fun->nargs - up->argc; - if (missing > 0) { - visitor.setStackSlotKind("missing"); - if (!visitor.visitStackSlots(fp->regs->sp, size_t(missing), fp)) - return false; - } - } - return true; -} - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE bool -VisitStackSlots(Visitor &visitor, JSContext *cx, unsigned callDepth) -{ - return VisitFrameSlots(visitor, callDepth, cx->fp, NULL); -} - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE void -VisitGlobalSlots(Visitor &visitor, JSContext *cx, JSObject *globalObj, - unsigned ngslots, uint16 *gslots) -{ - for (unsigned n = 0; n < ngslots; ++n) { - unsigned slot = gslots[n]; - visitor.visitGlobalSlot(&STOBJ_GET_SLOT(globalObj, slot), n, slot); - } -} - -class AdjustCallerTypeVisitor; - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE void -VisitGlobalSlots(Visitor &visitor, JSContext *cx, SlotList &gslots) -{ - VisitGlobalSlots(visitor, cx, JS_GetGlobalForObject(cx, cx->fp->scopeChain), - gslots.length(), gslots.data()); -} - - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE void -VisitSlots(Visitor& visitor, JSContext* cx, JSObject* globalObj, - unsigned callDepth, unsigned ngslots, uint16* gslots) -{ - if (VisitStackSlots(visitor, cx, callDepth)) - VisitGlobalSlots(visitor, cx, globalObj, ngslots, gslots); -} - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE void -VisitSlots(Visitor& visitor, JSContext* cx, unsigned callDepth, - unsigned ngslots, uint16* gslots) -{ - VisitSlots(visitor, cx, JS_GetGlobalForObject(cx, cx->fp->scopeChain), - callDepth, ngslots, gslots); -} - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE void -VisitSlots(Visitor &visitor, JSContext *cx, JSObject *globalObj, - unsigned callDepth, const SlotList& slots) -{ - VisitSlots(visitor, cx, globalObj, callDepth, slots.length(), - slots.data()); -} - -template -static JS_REQUIRES_STACK JS_ALWAYS_INLINE void -VisitSlots(Visitor &visitor, JSContext *cx, unsigned callDepth, - const SlotList& slots) -{ - VisitSlots(visitor, cx, JS_GetGlobalForObject(cx, cx->fp->scopeChain), - callDepth, slots.length(), slots.data()); -} - - -class SlotVisitorBase { -#ifdef JS_JIT_SPEW -protected: - char const *mStackSlotKind; -public: - SlotVisitorBase() : mStackSlotKind(NULL) {} - JS_ALWAYS_INLINE const char *stackSlotKind() { return mStackSlotKind; } - JS_ALWAYS_INLINE void setStackSlotKind(char const *k) { - mStackSlotKind = k; - } -#else -public: - JS_ALWAYS_INLINE const char *stackSlotKind() { return NULL; } - JS_ALWAYS_INLINE void setStackSlotKind(char const *k) {} -#endif -}; - -struct CountSlotsVisitor : public SlotVisitorBase -{ - unsigned mCount; - bool mDone; - jsval* mStop; -public: - JS_ALWAYS_INLINE CountSlotsVisitor(jsval* stop = NULL) : - mCount(0), - mDone(false), - mStop(stop) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - if (mDone) - return false; - if (mStop && size_t(mStop - vp) < count) { - mCount += size_t(mStop - vp); - mDone = true; - return false; - } - mCount += count; - return true; - } - - JS_ALWAYS_INLINE unsigned count() { - return mCount; - } - - JS_ALWAYS_INLINE bool stopped() { - return mDone; - } -}; - -/* - * Calculate the total number of native frame slots we need from this frame all - * the way back to the entry frame, including the current stack usage. - */ -JS_REQUIRES_STACK unsigned -NativeStackSlots(JSContext *cx, unsigned callDepth) -{ - JSStackFrame* fp = cx->fp; - unsigned slots = 0; - unsigned depth = callDepth; - for (;;) { - /* - * Duplicate native stack layout computation: see VisitFrameSlots - * header comment. - */ - unsigned operands = fp->regs->sp - StackBase(fp); - slots += operands; - if (fp->argv) - slots += fp->script->nfixed + 1 /*argsobj*/; - if (depth-- == 0) { - if (fp->argv) - slots += 2/*callee,this*/ + argSlots(fp); -#ifdef DEBUG - CountSlotsVisitor visitor; - VisitStackSlots(visitor, cx, callDepth); - JS_ASSERT(visitor.count() == slots && !visitor.stopped()); -#endif - return slots; - } - JSStackFrame* fp2 = fp; - fp = fp->down; - int missing = fp2->fun->nargs - fp2->argc; - if (missing > 0) - slots += missing; - } - JS_NOT_REACHED("NativeStackSlots"); -} - -class CaptureTypesVisitor : public SlotVisitorBase -{ - JSContext* mCx; - JSTraceType* mTypeMap; - JSTraceType* mPtr; - -public: - JS_ALWAYS_INLINE CaptureTypesVisitor(JSContext* cx, JSTraceType* typeMap) : - mCx(cx), - mTypeMap(typeMap), - mPtr(typeMap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - JSTraceType type = getCoercedType(*vp); - if (type == TT_INT32 && - oracle.isGlobalSlotUndemotable(mCx, slot)) - type = TT_DOUBLE; - JS_ASSERT(type != TT_JSVAL); - debug_only_printf(LC_TMTracer, - "capture type global%d: %d=%c\n", - n, type, typeChar[type]); - *mPtr++ = type; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, int count, JSStackFrame* fp) { - for (int i = 0; i < count; ++i) { - JSTraceType type = getCoercedType(vp[i]); - if (type == TT_INT32 && - oracle.isStackSlotUndemotable(mCx, length())) - type = TT_DOUBLE; - JS_ASSERT(type != TT_JSVAL); - debug_only_printf(LC_TMTracer, - "capture type %s%d: %d=%c\n", - stackSlotKind(), i, type, typeChar[type]); - *mPtr++ = type; - } - return true; - } - - JS_ALWAYS_INLINE uintptr_t length() { - return mPtr - mTypeMap; - } -}; - -/* - * Capture the type map for the selected slots of the global object and currently pending - * stack frames. - */ -JS_REQUIRES_STACK void -TypeMap::captureTypes(JSContext* cx, JSObject* globalObj, SlotList& slots, unsigned callDepth) -{ - setLength(NativeStackSlots(cx, callDepth) + slots.length()); - CaptureTypesVisitor visitor(cx, data()); - VisitSlots(visitor, cx, globalObj, callDepth, slots); - JS_ASSERT(visitor.length() == length()); -} - -JS_REQUIRES_STACK void -TypeMap::captureMissingGlobalTypes(JSContext* cx, JSObject* globalObj, SlotList& slots, unsigned stackSlots) -{ - unsigned oldSlots = length() - stackSlots; - int diff = slots.length() - oldSlots; - JS_ASSERT(diff >= 0); - setLength(length() + diff); - CaptureTypesVisitor visitor(cx, data() + stackSlots + oldSlots); - VisitGlobalSlots(visitor, cx, globalObj, diff, slots.data() + oldSlots); -} - -/* Compare this type map to another one and see whether they match. */ -bool -TypeMap::matches(TypeMap& other) const -{ - if (length() != other.length()) - return false; - return !memcmp(data(), other.data(), length()); -} - -void -TypeMap::fromRaw(JSTraceType* other, unsigned numSlots) -{ - unsigned oldLength = length(); - setLength(length() + numSlots); - for (unsigned i = 0; i < numSlots; i++) - get(oldLength + i) = other[i]; -} - -/* - * Use the provided storage area to create a new type map that contains the - * partial type map with the rest of it filled up from the complete type - * map. - */ -static void -MergeTypeMaps(JSTraceType** partial, unsigned* plength, JSTraceType* complete, unsigned clength, JSTraceType* mem) -{ - unsigned l = *plength; - JS_ASSERT(l < clength); - memcpy(mem, *partial, l * sizeof(JSTraceType)); - memcpy(mem + l, complete + l, (clength - l) * sizeof(JSTraceType)); - *partial = mem; - *plength = clength; -} - -/* - * Specializes a tree to any specifically missing globals, including any - * dependent trees. - */ -static JS_REQUIRES_STACK void -SpecializeTreesToLateGlobals(JSContext* cx, TreeInfo* root, JSTraceType* globalTypeMap, - unsigned numGlobalSlots) -{ - TreeInfo* ti = root; - - for (unsigned i = ti->nGlobalTypes(); i < numGlobalSlots; i++) - ti->typeMap.add(globalTypeMap[i]); - - JS_ASSERT(ti->nGlobalTypes() == numGlobalSlots); - - for (unsigned i = 0; i < root->dependentTrees.length(); i++) { - ti = (TreeInfo*)root->dependentTrees[i]->vmprivate; - - /* ti can be NULL if we hit the recording tree in emitTreeCall; this is harmless. */ - if (ti && ti->nGlobalTypes() < numGlobalSlots) - SpecializeTreesToLateGlobals(cx, ti, globalTypeMap, numGlobalSlots); - } - for (unsigned i = 0; i < root->linkedTrees.length(); i++) { - ti = (TreeInfo*)root->linkedTrees[i]->vmprivate; - if (ti && ti->nGlobalTypes() < numGlobalSlots) - SpecializeTreesToLateGlobals(cx, ti, globalTypeMap, numGlobalSlots); - } -} - -/* Specializes a tree to any missing globals, including any dependent trees. */ -static JS_REQUIRES_STACK void -SpecializeTreesToMissingGlobals(JSContext* cx, JSObject* globalObj, TreeInfo* root) -{ - TreeInfo* ti = root; - - ti->typeMap.captureMissingGlobalTypes(cx, globalObj, *ti->globalSlots, ti->nStackTypes); - JS_ASSERT(ti->globalSlots->length() == ti->typeMap.length() - ti->nStackTypes); - - SpecializeTreesToLateGlobals(cx, ti, ti->globalTypeMap(), ti->nGlobalTypes()); -} - -static void -TrashTree(JSContext* cx, Fragment* f); - -JS_REQUIRES_STACK -TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _fragment, - TreeInfo* ti, unsigned stackSlots, unsigned ngslots, JSTraceType* typeMap, - VMSideExit* innermostNestedGuard, jsbytecode* outer, uint32 outerArgc) - : tempAlloc(*JS_TRACE_MONITOR(cx).tempAlloc), - whichTreesToTrash(&tempAlloc), - cfgMerges(&tempAlloc), - tempTypeMap(cx) -{ - JS_ASSERT(!_fragment->vmprivate && ti && cx->fp->regs->pc == (jsbytecode*)_fragment->ip); - /* Reset the fragment state we care about in case we got a recycled fragment. - This includes resetting any profiling data we might have accumulated. */ - _fragment->lastIns = NULL; - verbose_only( _fragment->profCount = 0; ) - verbose_only( _fragment->nStaticExits = 0; ) - verbose_only( _fragment->nCodeBytes = 0; ) - verbose_only( _fragment->nExitBytes = 0; ) - verbose_only( _fragment->guardNumberer = 1; ) - verbose_only( _fragment->guardsForFrag = NULL; ) - verbose_only( _fragment->loopLabel = NULL; ) - // don't change _fragment->profFragID, though. Once the identity of - // the Fragment is set up (for profiling purposes), we can't change it. - this->cx = cx; - this->traceMonitor = &JS_TRACE_MONITOR(cx); - this->globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); - this->lexicalBlock = cx->fp->blockChain; - this->anchor = _anchor; - this->fragment = _fragment; - this->lirbuf = _fragment->lirbuf; - this->treeInfo = ti; - this->callDepth = _anchor ? _anchor->calldepth : 0; - this->atoms = FrameAtomBase(cx, cx->fp); - this->trashSelf = false; - this->global_dslots = this->globalObj->dslots; - this->loop = true; /* default assumption is we are compiling a loop */ - this->outer = outer; - this->outerArgc = outerArgc; - this->pendingSpecializedNative = NULL; - this->newobj_ins = NULL; - this->loopLabel = NULL; - -#ifdef JS_JIT_SPEW - debug_only_print0(LC_TMMinimal, "\n"); - debug_only_printf(LC_TMMinimal, "Recording starting from %s:%u@%u (FragID=%06u)\n", - ti->treeFileName, ti->treeLineNumber, ti->treePCOffset, - _fragment->profFragID); - - debug_only_printf(LC_TMTracer, "globalObj=%p, shape=%d\n", - (void*)this->globalObj, OBJ_SHAPE(this->globalObj)); - debug_only_printf(LC_TMTreeVis, "TREEVIS RECORD FRAG=%p ANCHOR=%p\n", (void*)fragment, - (void*)anchor); -#endif - - lir = lir_buf_writer = new LirBufWriter(lirbuf); -#ifdef DEBUG - lir = sanity_filter_1 = new SanityFilter(lir); -#endif - debug_only_stmt( - if (js_LogController.lcbits & LC_TMRecorder) { - lir = verbose_filter - = new VerboseWriter (tempAlloc, lir, lirbuf->names, - &js_LogController); - } - ) - // CseFilter must be downstream of SoftFloatFilter (see bug 527754 for why). - lir = cse_filter = new CseFilter(lir, tempAlloc); - if (nanojit::AvmCore::config.soft_float) - lir = float_filter = new SoftFloatFilter(lir); - lir = expr_filter = new ExprFilter(lir); - lir = func_filter = new FuncFilter(lir); -#ifdef DEBUG - lir = sanity_filter_2 = new SanityFilter(lir); -#endif - lir->ins0(LIR_start); - - for (int i = 0; i < NumSavedRegs; ++i) - lir->insParam(i, 1); -#ifdef DEBUG - for (int i = 0; i < NumSavedRegs; ++i) - addName(lirbuf->savedRegs[i], regNames[Assembler::savedRegs[i]]); -#endif - - lirbuf->state = addName(lir->insParam(0, 0), "state"); - - if (fragment == fragment->root) - loopLabel = lir->ins0(LIR_label); - - // if profiling, drop a label, so the assembler knows to put a - // frag-entry-counter increment at this point. If there's a - // loopLabel, use that; else we'll have to make a dummy label - // especially for this purpose. - verbose_only( if (js_LogController.lcbits & LC_FragProfile) { - LIns* entryLabel = NULL; - if (fragment == fragment->root) { - entryLabel = loopLabel; - } else { - entryLabel = lir->ins0(LIR_label); - } - NanoAssert(entryLabel); - NanoAssert(!fragment->loopLabel); - fragment->loopLabel = entryLabel; - }) - - lirbuf->sp = addName(lir->insLoad(LIR_ldp, lirbuf->state, (int)offsetof(InterpState, sp)), "sp"); - lirbuf->rp = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, rp)), "rp"); - cx_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, cx)), "cx"); - eos_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, eos)), "eos"); - eor_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, eor)), "eor"); - - /* If we came from exit, we might not have enough global types. */ - if (ti->globalSlots->length() > ti->nGlobalTypes()) - SpecializeTreesToMissingGlobals(cx, globalObj, ti); - - /* read into registers all values on the stack and all globals we know so far */ - import(treeInfo, lirbuf->sp, stackSlots, ngslots, callDepth, typeMap); - - if (fragment == fragment->root) { - /* - * We poll the operation callback request flag. It is updated asynchronously whenever - * the callback is to be invoked. - */ - LIns* x = lir->insLoad(LIR_ld, cx_ins, offsetof(JSContext, operationCallbackFlag)); - guard(true, lir->ins_eq0(x), snapshot(TIMEOUT_EXIT)); - } - - /* - * If we are attached to a tree call guard, make sure the guard the inner - * tree exited from is what we expect it to be. - */ - if (_anchor && _anchor->exitType == NESTED_EXIT) { - LIns* nested_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, - offsetof(InterpState, lastTreeExitGuard)), - "lastTreeExitGuard"); - guard(true, lir->ins2(LIR_peq, nested_ins, INS_CONSTPTR(innermostNestedGuard)), NESTED_EXIT); - } -} - -TraceRecorder::~TraceRecorder() -{ - JS_ASSERT(treeInfo && fragment); - - if (trashSelf) - TrashTree(cx, fragment->root); - - for (unsigned int i = 0; i < whichTreesToTrash.length(); i++) - TrashTree(cx, whichTreesToTrash[i]); - - /* Purge the tempAlloc used during recording. */ - tempAlloc.reset(); - traceMonitor->lirbuf->clear(); - -#ifdef DEBUG - debug_only_stmt( delete verbose_filter; ) - delete sanity_filter_1; - delete sanity_filter_2; -#endif - delete cse_filter; - delete expr_filter; - delete func_filter; - delete float_filter; - delete lir_buf_writer; -} - -bool -TraceRecorder::outOfMemory() -{ - return traceMonitor->dataAlloc->outOfMemory() || tempAlloc.outOfMemory(); -} - -/* Add debug information to a LIR instruction as we emit it. */ -inline LIns* -TraceRecorder::addName(LIns* ins, const char* name) -{ -#ifdef JS_JIT_SPEW - /* - * We'll only ask for verbose Nanojit when .lcbits > 0, so there's no point - * in adding names otherwise. - */ - if (js_LogController.lcbits > 0) - lirbuf->names->addName(ins, name); -#endif - return ins; -} - -inline LIns* -TraceRecorder::insImmVal(jsval val) -{ - if (JSVAL_IS_TRACEABLE(val)) - treeInfo->gcthings.addUnique(val); - return lir->insImmWord(val); -} - -inline LIns* -TraceRecorder::insImmObj(JSObject* obj) -{ - treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(obj)); - return lir->insImmPtr((void*)obj); -} - -inline LIns* -TraceRecorder::insImmFun(JSFunction* fun) -{ - treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(FUN_OBJECT(fun))); - return lir->insImmPtr((void*)fun); -} - -inline LIns* -TraceRecorder::insImmStr(JSString* str) -{ - treeInfo->gcthings.addUnique(STRING_TO_JSVAL(str)); - return lir->insImmPtr((void*)str); -} - -inline LIns* -TraceRecorder::insImmSprop(JSScopeProperty* sprop) -{ - treeInfo->sprops.addUnique(sprop); - return lir->insImmPtr((void*)sprop); -} - -inline LIns* -TraceRecorder::p2i(nanojit::LIns* ins) -{ -#ifdef NANOJIT_64BIT - return lir->ins1(LIR_qlo, ins); -#else - return ins; -#endif -} - -/* Determine the current call depth (starting with the entry frame.) */ -unsigned -TraceRecorder::getCallDepth() const -{ - return callDepth; -} - -/* Determine the offset in the native global frame for a jsval we track. */ -ptrdiff_t -TraceRecorder::nativeGlobalOffset(jsval* p) const -{ - JS_ASSERT(isGlobal(p)); - if (size_t(p - globalObj->fslots) < JS_INITIAL_NSLOTS) - return sizeof(InterpState) + size_t(p - globalObj->fslots) * sizeof(double); - return sizeof(InterpState) + ((p - globalObj->dslots) + JS_INITIAL_NSLOTS) * sizeof(double); -} - -/* Determine whether a value is a global stack slot. */ -bool -TraceRecorder::isGlobal(jsval* p) const -{ - return ((size_t(p - globalObj->fslots) < JS_INITIAL_NSLOTS) || - (size_t(p - globalObj->dslots) < (STOBJ_NSLOTS(globalObj) - JS_INITIAL_NSLOTS))); -} - -/* - * Return the offset in the native stack for the given jsval. More formally, - * |p| must be the address of a jsval that is represented in the native stack - * area. The return value is the offset, from InterpState::stackBase, in bytes, - * where the native representation of |*p| is stored. To get the offset - * relative to InterpState::sp, subtract TreeInfo::nativeStackBase. - */ -JS_REQUIRES_STACK ptrdiff_t -TraceRecorder::nativeStackOffset(jsval* p) const -{ - CountSlotsVisitor visitor(p); - VisitStackSlots(visitor, cx, callDepth); - size_t offset = visitor.count() * sizeof(double); - - /* - * If it's not in a pending frame, it must be on the stack of the current - * frame above sp but below fp->slots + script->nslots. - */ - if (!visitor.stopped()) { - JS_ASSERT(size_t(p - cx->fp->slots) < cx->fp->script->nslots); - offset += size_t(p - cx->fp->regs->sp) * sizeof(double); - } - return offset; -} - -/* Track the maximum number of native frame slots we need during execution. */ -void -TraceRecorder::trackNativeStackUse(unsigned slots) -{ - if (slots > treeInfo->maxNativeStackSlots) - treeInfo->maxNativeStackSlots = slots; -} - -/* - * Unbox a jsval into a slot. Slots are wide enough to hold double values - * directly (instead of storing a pointer to them). We assert instead of - * type checking. The caller must ensure the types are compatible. - */ -static void -ValueToNative(JSContext* cx, jsval v, JSTraceType type, double* slot) -{ - uint8_t tag = JSVAL_TAG(v); - switch (type) { - case TT_OBJECT: - JS_ASSERT(tag == JSVAL_OBJECT); - JS_ASSERT(!JSVAL_IS_NULL(v) && !HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v))); - *(JSObject**)slot = JSVAL_TO_OBJECT(v); - debug_only_printf(LC_TMTracer, - "object<%p:%s> ", (void*)JSVAL_TO_OBJECT(v), - JSVAL_IS_NULL(v) - ? "null" - : STOBJ_GET_CLASS(JSVAL_TO_OBJECT(v))->name); - return; - - case TT_INT32: - jsint i; - if (JSVAL_IS_INT(v)) - *(jsint*)slot = JSVAL_TO_INT(v); - else if (tag == JSVAL_DOUBLE && JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(v), i)) - *(jsint*)slot = i; - else - JS_ASSERT(JSVAL_IS_INT(v)); - debug_only_printf(LC_TMTracer, "int<%d> ", *(jsint*)slot); - return; - - case TT_DOUBLE: - jsdouble d; - if (JSVAL_IS_INT(v)) - d = JSVAL_TO_INT(v); - else - d = *JSVAL_TO_DOUBLE(v); - JS_ASSERT(JSVAL_IS_INT(v) || JSVAL_IS_DOUBLE(v)); - *(jsdouble*)slot = d; - debug_only_printf(LC_TMTracer, "double<%g> ", d); - return; - - case TT_JSVAL: - JS_NOT_REACHED("found jsval type in an entry type map"); - return; - - case TT_STRING: - JS_ASSERT(tag == JSVAL_STRING); - *(JSString**)slot = JSVAL_TO_STRING(v); - debug_only_printf(LC_TMTracer, "string<%p> ", (void*)(*(JSString**)slot)); - return; - - case TT_NULL: - JS_ASSERT(tag == JSVAL_OBJECT); - *(JSObject**)slot = NULL; - debug_only_print0(LC_TMTracer, "null "); - return; - - case TT_PSEUDOBOOLEAN: - /* Watch out for pseudo-booleans. */ - JS_ASSERT(tag == JSVAL_SPECIAL); - *(JSBool*)slot = JSVAL_TO_SPECIAL(v); - debug_only_printf(LC_TMTracer, "pseudoboolean<%d> ", *(JSBool*)slot); - return; - - case TT_FUNCTION: { - JS_ASSERT(tag == JSVAL_OBJECT); - JSObject* obj = JSVAL_TO_OBJECT(v); - *(JSObject**)slot = obj; -#ifdef DEBUG - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj); - debug_only_printf(LC_TMTracer, - "function<%p:%s> ", (void*) obj, - fun->atom - ? JS_GetStringBytes(ATOM_TO_STRING(fun->atom)) - : "unnamed"); -#endif - return; - } - } - - JS_NOT_REACHED("unexpected type"); -} - -/* - * We maintain an emergency pool of doubles so we can recover safely if a trace - * runs out of memory (doubles or objects). - */ -static jsval -AllocateDoubleFromReservedPool(JSContext* cx) -{ - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - JS_ASSERT(tm->reservedDoublePoolPtr > tm->reservedDoublePool); - return *--tm->reservedDoublePoolPtr; -} - -static bool -ReplenishReservedPool(JSContext* cx, JSTraceMonitor* tm) -{ - /* We should not be called with a full pool. */ - JS_ASSERT((size_t) (tm->reservedDoublePoolPtr - tm->reservedDoublePool) < - MAX_NATIVE_STACK_SLOTS); - - /* - * When the GC runs in js_NewDoubleInRootedValue, it resets - * tm->reservedDoublePoolPtr back to tm->reservedDoublePool. - */ - JSRuntime* rt = cx->runtime; - uintN gcNumber = rt->gcNumber; - uintN lastgcNumber = gcNumber; - jsval* ptr = tm->reservedDoublePoolPtr; - while (ptr < tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) { - if (!js_NewDoubleInRootedValue(cx, 0.0, ptr)) - goto oom; - - /* Check if the last call to js_NewDoubleInRootedValue GC'd. */ - if (rt->gcNumber != lastgcNumber) { - lastgcNumber = rt->gcNumber; - ptr = tm->reservedDoublePool; - - /* - * Have we GC'd more than once? We're probably running really - * low on memory, bail now. - */ - if (uintN(rt->gcNumber - gcNumber) > uintN(1)) - goto oom; - continue; - } - ++ptr; - } - tm->reservedDoublePoolPtr = ptr; - return true; - -oom: - /* - * Already massive GC pressure, no need to hold doubles back. - * We won't run any native code anyway. - */ - tm->reservedDoublePoolPtr = tm->reservedDoublePool; - return false; -} - -void -JSTraceMonitor::flush() -{ - AUDIT(cacheFlushed); - - // recover profiling data from expiring Fragments - verbose_only( - for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { - for (VMFragment *f = vmfragments[i]; f; f = f->next) { - JS_ASSERT(f->root == f); - for (VMFragment *p = f; p; p = p->peer) - js_FragProfiling_FragFinalizer(p, this); - } - } - ) - - verbose_only( - for (Seq* f = branches; f; f = f->tail) - js_FragProfiling_FragFinalizer(f->head, this); - ) - - dataAlloc->reset(); - codeAlloc->reset(); - tempAlloc->reset(); - reTempAlloc->reset(); - - Allocator& alloc = *dataAlloc; - - for (size_t i = 0; i < MONITOR_N_GLOBAL_STATES; ++i) { - globalStates[i].globalShape = -1; - globalStates[i].globalSlots = new (alloc) SlotList(&alloc); - } - - assembler = new (alloc) Assembler(*codeAlloc, alloc, core, &js_LogController); - lirbuf = new (alloc) LirBuffer(*tempAlloc); - reLirBuf = new (alloc) LirBuffer(*reTempAlloc); - verbose_only( branches = NULL; ) - -#ifdef DEBUG - labels = new (alloc) LabelMap(alloc, &js_LogController); - reLirBuf->names = - lirbuf->names = new (alloc) LirNameMap(alloc, labels); -#endif - - memset(&vmfragments[0], 0, FRAGMENT_TABLE_SIZE * sizeof(VMFragment*)); - reFragments = new (alloc) REHashMap(alloc); - - needFlush = JS_FALSE; -} - -static inline void -MarkTreeInfo(JSTracer* trc, TreeInfo *ti) -{ - jsval* vp = ti->gcthings.data(); - unsigned len = ti->gcthings.length(); - while (len--) { - jsval v = *vp++; - JS_SET_TRACING_NAME(trc, "jitgcthing"); - JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v)); - } - JSScopeProperty** spropp = ti->sprops.data(); - len = ti->sprops.length(); - while (len--) { - JSScopeProperty* sprop = *spropp++; - sprop->trace(trc); - } -} - -void -JSTraceMonitor::mark(JSTracer* trc) -{ - if (!trc->context->runtime->gcFlushCodeCaches) { - for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { - VMFragment* f = vmfragments[i]; - while (f) { - if (TreeInfo* ti = (TreeInfo*)f->vmprivate) - MarkTreeInfo(trc, ti); - VMFragment* peer = (VMFragment*)f->peer; - while (peer) { - if (TreeInfo* ti = (TreeInfo*)peer->vmprivate) - MarkTreeInfo(trc, ti); - peer = (VMFragment*)peer->peer; - } - f = f->next; - } - } - if (recorder) - MarkTreeInfo(trc, recorder->getTreeInfo()); - } -} - -/* - * Box a value from the native stack back into the jsval format. Integers that - * are too large to fit into a jsval are automatically boxed into - * heap-allocated doubles. - */ -template -static inline bool -NativeToValueBase(JSContext* cx, jsval& v, JSTraceType type, double* slot) -{ - jsint i; - jsdouble d; - switch (type) { - case TT_OBJECT: - v = OBJECT_TO_JSVAL(*(JSObject**)slot); - JS_ASSERT(v != JSVAL_ERROR_COOKIE); /* don't leak JSVAL_ERROR_COOKIE */ - debug_only_printf(LC_TMTracer, - "object<%p:%s> ", (void*)JSVAL_TO_OBJECT(v), - JSVAL_IS_NULL(v) - ? "null" - : STOBJ_GET_CLASS(JSVAL_TO_OBJECT(v))->name); - break; - - case TT_INT32: - i = *(jsint*)slot; - debug_only_printf(LC_TMTracer, "int<%d> ", i); - store_int: - if (INT_FITS_IN_JSVAL(i)) { - v = INT_TO_JSVAL(i); - break; - } - d = (jsdouble)i; - goto store_double; - case TT_DOUBLE: - d = *slot; - debug_only_printf(LC_TMTracer, "double<%g> ", d); - if (JSDOUBLE_IS_INT(d, i)) - goto store_int; - store_double: { - /* - * It's not safe to trigger the GC here, so use an emergency heap if we - * are out of double boxes. - */ - if (cx->doubleFreeList) { -#ifdef DEBUG - JSBool ok = -#endif - js_NewDoubleInRootedValue(cx, d, &v); - JS_ASSERT(ok); - return true; - } - return E::handleDoubleOOM(cx, d, v); - } - - case TT_JSVAL: - v = *(jsval*)slot; - JS_ASSERT(v != JSVAL_ERROR_COOKIE); /* don't leak JSVAL_ERROR_COOKIE */ - debug_only_printf(LC_TMTracer, "box<%p> ", (void*)v); - break; - - case TT_STRING: - v = STRING_TO_JSVAL(*(JSString**)slot); - debug_only_printf(LC_TMTracer, "string<%p> ", (void*)(*(JSString**)slot)); - break; - - case TT_NULL: - JS_ASSERT(*(JSObject**)slot == NULL); - v = JSVAL_NULL; - debug_only_printf(LC_TMTracer, "null<%p> ", (void*)(*(JSObject**)slot)); - break; - - case TT_PSEUDOBOOLEAN: - /* Watch out for pseudo-booleans. */ - v = SPECIAL_TO_JSVAL(*(JSBool*)slot); - debug_only_printf(LC_TMTracer, "boolean<%d> ", *(JSBool*)slot); - break; - - case TT_FUNCTION: { - JS_ASSERT(HAS_FUNCTION_CLASS(*(JSObject**)slot)); - v = OBJECT_TO_JSVAL(*(JSObject**)slot); -#ifdef DEBUG - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(v)); - debug_only_printf(LC_TMTracer, - "function<%p:%s> ", (void*)JSVAL_TO_OBJECT(v), - fun->atom - ? JS_GetStringBytes(ATOM_TO_STRING(fun->atom)) - : "unnamed"); -#endif - break; - } - } - return true; -} - -struct ReserveDoubleOOMHandler { - static bool handleDoubleOOM(JSContext *cx, double d, jsval& v) { - v = AllocateDoubleFromReservedPool(cx); - JS_ASSERT(JSVAL_IS_DOUBLE(v) && *JSVAL_TO_DOUBLE(v) == 0.0); - *JSVAL_TO_DOUBLE(v) = d; - return true; - } -}; - -static void -NativeToValue(JSContext* cx, jsval& v, JSTraceType type, double* slot) -{ -#ifdef DEBUG - bool ok = -#endif - NativeToValueBase(cx, v, type, slot); - JS_ASSERT(ok); -} - -struct FailDoubleOOMHandler { - static bool handleDoubleOOM(JSContext *cx, double d, jsval& v) { - return false; - } -}; - -bool -js_NativeToValue(JSContext* cx, jsval& v, JSTraceType type, double* slot) -{ - return NativeToValueBase(cx, v, type, slot); -} - -class BuildNativeFrameVisitor : public SlotVisitorBase -{ - JSContext *mCx; - JSTraceType *mTypeMap; - double *mGlobal; - double *mStack; -public: - BuildNativeFrameVisitor(JSContext *cx, - JSTraceType *typemap, - double *global, - double *stack) : - mCx(cx), - mTypeMap(typemap), - mGlobal(global), - mStack(stack) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - debug_only_printf(LC_TMTracer, "global%d: ", n); - ValueToNative(mCx, *vp, *mTypeMap++, &mGlobal[slot]); - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, int count, JSStackFrame* fp) { - for (int i = 0; i < count; ++i) { - debug_only_printf(LC_TMTracer, "%s%d: ", stackSlotKind(), i); - ValueToNative(mCx, *vp++, *mTypeMap++, mStack++); - } - return true; - } -}; - -static JS_REQUIRES_STACK void -BuildNativeFrame(JSContext *cx, JSObject *globalObj, unsigned callDepth, - unsigned ngslots, uint16 *gslots, - JSTraceType *typeMap, double *global, double *stack) -{ - BuildNativeFrameVisitor visitor(cx, typeMap, global, stack); - VisitSlots(visitor, cx, globalObj, callDepth, ngslots, gslots); - debug_only_print0(LC_TMTracer, "\n"); -} - -class FlushNativeGlobalFrameVisitor : public SlotVisitorBase -{ - JSContext *mCx; - JSTraceType *mTypeMap; - double *mGlobal; -public: - FlushNativeGlobalFrameVisitor(JSContext *cx, - JSTraceType *typeMap, - double *global) : - mCx(cx), - mTypeMap(typeMap), - mGlobal(global) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - debug_only_printf(LC_TMTracer, "global%d=", n); - NativeToValue(mCx, *vp, *mTypeMap++, &mGlobal[slot]); - } -}; - -class FlushNativeStackFrameVisitor : public SlotVisitorBase -{ - JSContext *mCx; - JSTraceType *mTypeMap; - double *mStack; - jsval *mStop; -public: - FlushNativeStackFrameVisitor(JSContext *cx, - JSTraceType *typeMap, - double *stack, - jsval *stop) : - mCx(cx), - mTypeMap(typeMap), - mStack(stack), - mStop(stop) - {} - - JSTraceType* getTypeMap() - { - return mTypeMap; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - if (vp == mStop) - return false; - debug_only_printf(LC_TMTracer, "%s%u=", stackSlotKind(), unsigned(i)); - NativeToValue(mCx, *vp++, *mTypeMap++, mStack++); - } - return true; - } -}; - -/* Box the given native frame into a JS frame. This is infallible. */ -static JS_REQUIRES_STACK void -FlushNativeGlobalFrame(JSContext *cx, double *global, unsigned ngslots, - uint16 *gslots, JSTraceType *typemap) -{ - FlushNativeGlobalFrameVisitor visitor(cx, typemap, global); - JSObject *globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); - VisitGlobalSlots(visitor, cx, globalObj, ngslots, gslots); - debug_only_print0(LC_TMTracer, "\n"); -} - -/* - * Returns the number of values on the native stack, excluding the innermost - * frame. This walks all FrameInfos on the native frame stack and sums the - * slot usage of each frame. - */ -static int32 -StackDepthFromCallStack(InterpState* state, uint32 callDepth) -{ - int32 nativeStackFramePos = 0; - - // Duplicate native stack layout computation: see VisitFrameSlots header comment. - for (FrameInfo** fip = state->callstackBase; fip < state->rp + callDepth; fip++) - nativeStackFramePos += (*fip)->callerHeight; - return nativeStackFramePos; -} - -/* - * Generic function to read upvars on trace from slots of active frames. - * T Traits type parameter. Must provide static functions: - * interp_get(fp, slot) Read the value out of an interpreter frame. - * native_slot(argc, slot) Return the position of the desired value in the on-trace - * stack frame (with position 0 being callee). - * - * upvarLevel Static level of the function containing the upvar definition - * slot Identifies the value to get. The meaning is defined by the traits type. - * callDepth Call depth of current point relative to trace entry - */ -template -inline JSTraceType -GetUpvarOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, double* result) -{ - InterpState* state = cx->interpState; - FrameInfo** fip = state->rp + callDepth; - - /* - * First search the FrameInfo call stack for an entry containing our - * upvar, namely one with level == upvarLevel. The first FrameInfo is a - * transition from the entry frame to some callee. However, it is not - * known (from looking at the FrameInfo) whether the entry frame had a - * callee. Rather than special-case this or insert more logic into the - * loop, instead just stop before that FrameInfo (i.e. |> base| instead of - * |>= base|), and let the code after the loop handle it. - */ - int32 stackOffset = StackDepthFromCallStack(state, callDepth); - while (--fip > state->callstackBase) { - FrameInfo* fi = *fip; - - /* - * The loop starts aligned to the top of the stack, so move down to the first meaningful - * callee. Then read the callee directly from the frame. - */ - stackOffset -= fi->callerHeight; - JSObject* callee = *(JSObject**)(&state->stackBase[stackOffset]); - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, callee); - uintN calleeLevel = fun->u.i.script->staticLevel; - if (calleeLevel == upvarLevel) { - /* - * Now find the upvar's value in the native stack. stackOffset is - * the offset of the start of the activation record corresponding - * to *fip in the native stack. - */ - uint32 native_slot = T::native_slot(fi->callerArgc, slot); - *result = state->stackBase[stackOffset + native_slot]; - return fi->get_typemap()[native_slot]; - } - } - - // Next search the trace entry frame, which is not in the FrameInfo stack. - if (state->outermostTree->script->staticLevel == upvarLevel) { - uint32 argc = ((VMFragment*) state->outermostTree->fragment)->argc; - uint32 native_slot = T::native_slot(argc, slot); - *result = state->stackBase[native_slot]; - return state->callstackBase[0]->get_typemap()[native_slot]; - } - - /* - * If we did not find the upvar in the frames for the active traces, - * then we simply get the value from the interpreter state. - */ - JS_ASSERT(upvarLevel < JS_DISPLAY_SIZE); - JSStackFrame* fp = cx->display[upvarLevel]; - jsval v = T::interp_get(fp, slot); - JSTraceType type = getCoercedType(v); - ValueToNative(cx, v, type, result); - return type; -} - -// For this traits type, 'slot' is the argument index, which may be -2 for callee. -struct UpvarArgTraits { - static jsval interp_get(JSStackFrame* fp, int32 slot) { - return fp->argv[slot]; - } - - static uint32 native_slot(uint32 argc, int32 slot) { - return 2 /*callee,this*/ + slot; - } -}; - -uint32 JS_FASTCALL -GetUpvarArgOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, double* result) -{ - return GetUpvarOnTrace(cx, upvarLevel, slot, callDepth, result); -} - -// For this traits type, 'slot' is an index into the local slots array. -struct UpvarVarTraits { - static jsval interp_get(JSStackFrame* fp, int32 slot) { - return fp->slots[slot]; - } - - static uint32 native_slot(uint32 argc, int32 slot) { - return 3 /*callee,this,arguments*/ + argc + slot; - } -}; - -uint32 JS_FASTCALL -GetUpvarVarOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, double* result) -{ - return GetUpvarOnTrace(cx, upvarLevel, slot, callDepth, result); -} - -/* - * For this traits type, 'slot' is an index into the stack area (within slots, - * after nfixed) of a frame with no function. (On trace, the top-level frame is - * the only one that can have no function.) - */ -struct UpvarStackTraits { - static jsval interp_get(JSStackFrame* fp, int32 slot) { - return fp->slots[slot + fp->script->nfixed]; - } - - static uint32 native_slot(uint32 argc, int32 slot) { - /* - * Locals are not imported by the tracer when the frame has no - * function, so we do not add fp->script->nfixed. - */ - JS_ASSERT(argc == 0); - return slot; - } -}; - -uint32 JS_FASTCALL -GetUpvarStackOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, - double* result) -{ - return GetUpvarOnTrace(cx, upvarLevel, slot, callDepth, result); -} - -// Parameters needed to access a value from a closure on trace. -struct ClosureVarInfo -{ - jsid id; - uint32 slot; - uint32 callDepth; - uint32 resolveFlags; -}; - -/* - * Generic function to read upvars from Call objects of active heavyweight functions. - * call Callee Function object in which the upvar is accessed. - */ -template -inline uint32 -GetFromClosure(JSContext* cx, JSObject* call, const ClosureVarInfo* cv, double* result) -{ - JS_ASSERT(OBJ_GET_CLASS(cx, call) == &js_CallClass); - - InterpState* state = cx->interpState; - -#ifdef DEBUG - int32 stackOffset = StackDepthFromCallStack(state, cv->callDepth); - FrameInfo** fip = state->rp + cv->callDepth; - while (--fip > state->callstackBase) { - FrameInfo* fi = *fip; - JSObject* callee = *(JSObject**)(&state->stackBase[stackOffset]); - if (callee == call) { - // This is not reachable as long as JSOP_LAMBDA is not traced: - // - The upvar is found at this point only if the upvar was defined on a frame that was - // entered on this trace. - // - The upvar definition must be (dynamically, and thus on trace) before the definition - // of the function that uses the upvar. - // - Therefore, if the upvar is found at this point, the function definition JSOP_LAMBDA - // is on the trace. - JS_NOT_REACHED("JSOP_NAME variable found in outer trace"); - } - stackOffset -= fi->callerHeight; - } -#endif - - /* - * Here we specifically want to check the call object of the trace entry frame. - */ - uint32 slot = cv->slot; - VOUCH_DOES_NOT_REQUIRE_STACK(); - if (cx->fp->callobj == call) { - slot = T::adj_slot(cx->fp, slot); - *result = state->stackBase[slot]; - return state->callstackBase[0]->get_typemap()[slot]; - } - - JSStackFrame* fp = (JSStackFrame*) call->getPrivate(); - jsval v; - if (fp) { - v = T::slots(fp)[slot]; - } else { - JS_ASSERT(cv->resolveFlags != JSRESOLVE_INFER); - JSAutoResolveFlags rf(cx, cv->resolveFlags); -#ifdef DEBUG - JSBool rv = -#endif - js_GetPropertyHelper(cx, call, cv->id, JS_FALSE, &v); - JS_ASSERT(rv); - } - JSTraceType type = getCoercedType(v); - ValueToNative(cx, v, type, result); - return type; -} - -struct ArgClosureTraits -{ - // See also UpvarArgTraits. - static inline uint32 adj_slot(JSStackFrame* fp, uint32 slot) { return 2 + slot; } - - // Generate the adj_slot computation in LIR. - static inline LIns* adj_slot_lir(LirWriter* lir, LIns* fp_ins, unsigned slot) { - return lir->insImm(2 + slot); - } - - // See also UpvarArgTraits. - static inline jsval* slots(JSStackFrame* fp) { return fp->argv; } -private: - ArgClosureTraits(); -}; - -uint32 JS_FASTCALL -GetClosureArg(JSContext* cx, JSObject* callee, const ClosureVarInfo* cv, double* result) -{ - return GetFromClosure(cx, callee, cv, result); -} - -struct VarClosureTraits -{ - // See also UpvarVarTraits. - static inline uint32 adj_slot(JSStackFrame* fp, uint32 slot) { return 3 + fp->argc + slot; } - - // See also UpvarVarTraits. - static inline LIns* adj_slot_lir(LirWriter* lir, LIns* fp_ins, unsigned slot) { - LIns *argc_ins = lir->insLoad(LIR_ld, fp_ins, offsetof(JSStackFrame, argc)); - return lir->ins2(LIR_add, lir->insImm(3 + slot), argc_ins); - } - - // See also UpvarVarTraits. - static inline jsval* slots(JSStackFrame* fp) { return fp->slots; } -private: - VarClosureTraits(); -}; - -uint32 JS_FASTCALL -GetClosureVar(JSContext* cx, JSObject* callee, const ClosureVarInfo* cv, double* result) -{ - return GetFromClosure(cx, callee, cv, result); -} - -/** - * Box the given native stack frame into the virtual machine stack. This - * is infallible. - * - * @param callDepth the distance between the entry frame into our trace and - * cx->fp when we make this call. If this is not called as a - * result of a nested exit, callDepth is 0. - * @param mp an array of JSTraceTypes that indicate what the types of the things - * on the stack are. - * @param np pointer to the native stack. We want to copy values from here to - * the JS stack as needed. - * @param stopFrame if non-null, this frame and everything above it should not - * be restored. - * @return the number of things we popped off of np. - */ -static JS_REQUIRES_STACK int -FlushNativeStackFrame(JSContext* cx, unsigned callDepth, JSTraceType* mp, double* np, - JSStackFrame* stopFrame) -{ - jsval* stopAt = stopFrame ? &stopFrame->argv[-2] : NULL; - - /* Root all string and object references first (we don't need to call the GC for this). */ - FlushNativeStackFrameVisitor visitor(cx, mp, np, stopAt); - VisitStackSlots(visitor, cx, callDepth); - - // Restore thisp from the now-restored argv[-1] in each pending frame. - // Keep in mind that we didn't restore frames at stopFrame and above! - // Scope to keep |fp| from leaking into the macros we're using. - { - unsigned n = callDepth+1; // +1 to make sure we restore the entry frame - JSStackFrame* fp = cx->fp; - if (stopFrame) { - for (; fp != stopFrame; fp = fp->down) { - JS_ASSERT(n != 0); - --n; - } - - // Skip over stopFrame itself. - JS_ASSERT(n != 0); - --n; - fp = fp->down; - } - for (; n != 0; fp = fp->down) { - --n; - if (fp->argv) { - if (fp->argsobj && - js_GetArgsPrivateNative(JSVAL_TO_OBJECT(fp->argsobj))) { - JSVAL_TO_OBJECT(fp->argsobj)->setPrivate(fp); - } - - /* - * We might return from trace with a different callee object, but it still - * has to be the same JSFunction (FIXME: bug 471425, eliminate fp->callee). - */ - JS_ASSERT(JSVAL_IS_OBJECT(fp->argv[-1])); - JS_ASSERT(HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(fp->argv[-2]))); - JS_ASSERT(GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fp->argv[-2])) == - GET_FUNCTION_PRIVATE(cx, fp->callee())); - JS_ASSERT(GET_FUNCTION_PRIVATE(cx, fp->callee()) == fp->fun); - - /* - * SynthesizeFrame sets scopeChain to NULL, because we can't calculate the - * correct scope chain until we have the final callee. Calculate the real - * scope object here. - */ - if (!fp->scopeChain) { - fp->scopeChain = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fp->argv[-2])); - if (fp->fun->flags & JSFUN_HEAVYWEIGHT) { - /* - * Set hookData to null because the failure case for js_GetCallObject - * involves it calling the debugger hook. - * - * Allocating the Call object must not fail, so use an object - * previously reserved by ExecuteTree if needed. - */ - void* hookData = ((JSInlineFrame*)fp)->hookData; - ((JSInlineFrame*)fp)->hookData = NULL; - JS_ASSERT(!JS_TRACE_MONITOR(cx).useReservedObjects); - JS_TRACE_MONITOR(cx).useReservedObjects = JS_TRUE; -#ifdef DEBUG - JSObject *obj = -#endif - js_GetCallObject(cx, fp); - JS_ASSERT(obj); - JS_TRACE_MONITOR(cx).useReservedObjects = JS_FALSE; - ((JSInlineFrame*)fp)->hookData = hookData; - } - } - fp->thisp = JSVAL_TO_OBJECT(fp->argv[-1]); - if (fp->flags & JSFRAME_CONSTRUCTING) // constructors always compute 'this' - fp->flags |= JSFRAME_COMPUTED_THIS; - } - } - } - debug_only_print0(LC_TMTracer, "\n"); - return visitor.getTypeMap() - mp; -} - -/* Emit load instructions onto the trace that read the initial stack state. */ -JS_REQUIRES_STACK void -TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, JSTraceType t, - const char *prefix, uintN index, JSStackFrame *fp) -{ - LIns* ins; - if (t == TT_INT32) { /* demoted */ - JS_ASSERT(isInt32(*p)); - - /* - * Ok, we have a valid demotion attempt pending, so insert an integer - * read and promote it to double since all arithmetic operations expect - * to see doubles on entry. The first op to use this slot will emit a - * f2i cast which will cancel out the i2f we insert here. - */ - ins = lir->insLoad(LIR_ld, base, offset); - ins = lir->ins1(LIR_i2f, ins); - } else { - JS_ASSERT_IF(t != TT_JSVAL, isNumber(*p) == (t == TT_DOUBLE)); - if (t == TT_DOUBLE) { - ins = lir->insLoad(LIR_ldq, base, offset); - } else if (t == TT_PSEUDOBOOLEAN) { - ins = lir->insLoad(LIR_ld, base, offset); - } else { - ins = lir->insLoad(LIR_ldp, base, offset); - } - } - checkForGlobalObjectReallocation(); - tracker.set(p, ins); - -#ifdef DEBUG - char name[64]; - JS_ASSERT(strlen(prefix) < 10); - void* mark = NULL; - jsuword* localNames = NULL; - const char* funName = NULL; - if (*prefix == 'a' || *prefix == 'v') { - mark = JS_ARENA_MARK(&cx->tempPool); - if (fp->fun->hasLocalNames()) - localNames = js_GetLocalNameArray(cx, fp->fun, &cx->tempPool); - funName = fp->fun->atom ? js_AtomToPrintableString(cx, fp->fun->atom) : ""; - } - if (!strcmp(prefix, "argv")) { - if (index < fp->fun->nargs) { - JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[index]); - JS_snprintf(name, sizeof name, "$%s.%s", funName, js_AtomToPrintableString(cx, atom)); - } else { - JS_snprintf(name, sizeof name, "$%s.", funName, index); - } - } else if (!strcmp(prefix, "vars")) { - JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[fp->fun->nargs + index]); - JS_snprintf(name, sizeof name, "$%s.%s", funName, js_AtomToPrintableString(cx, atom)); - } else { - JS_snprintf(name, sizeof name, "$%s%d", prefix, index); - } - - if (mark) - JS_ARENA_RELEASE(&cx->tempPool, mark); - addName(ins, name); - - static const char* typestr[] = { - "object", "int", "double", "jsval", "string", "null", "boolean", "function" - }; - debug_only_printf(LC_TMTracer, "import vp=%p name=%s type=%s flags=%d\n", - (void*)p, name, typestr[t & 7], t >> 3); -#endif -} - -class ImportGlobalSlotVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - LIns *mBase; - JSTraceType *mTypemap; -public: - ImportGlobalSlotVisitor(TraceRecorder &recorder, - LIns *base, - JSTraceType *typemap) : - mRecorder(recorder), - mBase(base), - mTypemap(typemap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - JS_ASSERT(*mTypemap != TT_JSVAL); - mRecorder.import(mBase, mRecorder.nativeGlobalOffset(vp), - vp, *mTypemap++, "global", n, NULL); - } -}; - -class ImportBoxedStackSlotVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - LIns *mBase; - ptrdiff_t mStackOffset; - JSTraceType *mTypemap; - JSStackFrame *mFp; -public: - ImportBoxedStackSlotVisitor(TraceRecorder &recorder, - LIns *base, - ptrdiff_t stackOffset, - JSTraceType *typemap) : - mRecorder(recorder), - mBase(base), - mStackOffset(stackOffset), - mTypemap(typemap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - if (*mTypemap == TT_JSVAL) { - mRecorder.import(mBase, mStackOffset, vp, TT_JSVAL, - "jsval", i, fp); - LIns *vp_ins = mRecorder.unbox_jsval(*vp, mRecorder.get(vp), - mRecorder.copy(mRecorder.anchor)); - mRecorder.set(vp, vp_ins); - } - vp++; - mTypemap++; - mStackOffset += sizeof(double); - } - return true; - } -}; - -class ImportUnboxedStackSlotVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - LIns *mBase; - ptrdiff_t mStackOffset; - JSTraceType *mTypemap; - JSStackFrame *mFp; -public: - ImportUnboxedStackSlotVisitor(TraceRecorder &recorder, - LIns *base, - ptrdiff_t stackOffset, - JSTraceType *typemap) : - mRecorder(recorder), - mBase(base), - mStackOffset(stackOffset), - mTypemap(typemap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - if (*mTypemap != TT_JSVAL) { - mRecorder.import(mBase, mStackOffset, vp++, *mTypemap, - stackSlotKind(), i, fp); - } - mTypemap++; - mStackOffset += sizeof(double); - } - return true; - } -}; - -// Like ImportUnboxedStackSlotVisitor, except that this does not import -// slots past nfixed. It imports only the slots that belong totally to -// the given frame. -class ImportUnboxedFrameSlotVisitor : public ImportUnboxedStackSlotVisitor -{ -public: - ImportUnboxedFrameSlotVisitor(TraceRecorder &recorder, - LIns *base, - ptrdiff_t stackOffset, - JSTraceType *typemap) : - ImportUnboxedStackSlotVisitor(recorder, base, stackOffset, typemap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - if (vp == &fp->slots[fp->script->nfixed]) - return false; - return ImportUnboxedStackSlotVisitor::visitStackSlots(vp, count, fp); - } -}; - -JS_REQUIRES_STACK void -TraceRecorder::import(TreeInfo* treeInfo, LIns* sp, unsigned stackSlots, unsigned ngslots, - unsigned callDepth, JSTraceType* typeMap) -{ - /* - * If we get a partial list that doesn't have all the types (i.e. recording - * from a side exit that was recorded but we added more global slots - * later), merge the missing types from the entry type map. This is safe - * because at the loop edge we verify that we have compatible types for all - * globals (entry type and loop edge type match). While a different trace - * of the tree might have had a guard with a different type map for these - * slots we just filled in here (the guard we continue from didn't know - * about them), since we didn't take that particular guard the only way we - * could have ended up here is if that other trace had at its end a - * compatible type distribution with the entry map. Since that's exactly - * what we used to fill in the types our current side exit didn't provide, - * this is always safe to do. - */ - - JSTraceType* globalTypeMap = typeMap + stackSlots; - unsigned length = treeInfo->nGlobalTypes(); - - /* - * This is potentially the typemap of the side exit and thus shorter than - * the tree's global type map. - */ - if (ngslots < length) { - MergeTypeMaps(&globalTypeMap /* out param */, &ngslots /* out param */, - treeInfo->globalTypeMap(), length, - (JSTraceType*)alloca(sizeof(JSTraceType) * length)); - } - JS_ASSERT(ngslots == treeInfo->nGlobalTypes()); - ptrdiff_t offset = -treeInfo->nativeStackBase; - - /* - * Check whether there are any values on the stack we have to unbox and do - * that first before we waste any time fetching the state from the stack. - */ - ImportBoxedStackSlotVisitor boxedStackVisitor(*this, sp, offset, typeMap); - VisitStackSlots(boxedStackVisitor, cx, callDepth); - - ImportGlobalSlotVisitor globalVisitor(*this, lirbuf->state, globalTypeMap); - VisitGlobalSlots(globalVisitor, cx, globalObj, ngslots, - treeInfo->globalSlots->data()); - - ImportUnboxedStackSlotVisitor unboxedStackVisitor(*this, sp, offset, - typeMap); - VisitStackSlots(unboxedStackVisitor, cx, callDepth); -} - -JS_REQUIRES_STACK bool -TraceRecorder::isValidSlot(JSScope* scope, JSScopeProperty* sprop) -{ - uint32 setflags = (js_CodeSpec[*cx->fp->regs->pc].format & (JOF_SET | JOF_INCDEC | JOF_FOR)); - - if (setflags) { - if (!SPROP_HAS_STUB_SETTER(sprop)) - ABORT_TRACE_RV("non-stub setter", false); - if (sprop->attrs & JSPROP_READONLY) - ABORT_TRACE_RV("writing to a read-only property", false); - } - - /* This check applies even when setflags == 0. */ - if (setflags != JOF_SET && !SPROP_HAS_STUB_GETTER(sprop)) - ABORT_TRACE_RV("non-stub getter", false); - - if (!SPROP_HAS_VALID_SLOT(sprop, scope)) - ABORT_TRACE_RV("slotless obj property", false); - - return true; -} - -/* Lazily import a global slot if we don't already have it in the tracker. */ -JS_REQUIRES_STACK bool -TraceRecorder::lazilyImportGlobalSlot(unsigned slot) -{ - if (slot != uint16(slot)) /* we use a table of 16-bit ints, bail out if that's not enough */ - return false; - - /* - * If the global object grows too large, alloca in ExecuteTree might fail, - * so abort tracing on global objects with unreasonably many slots. - */ - if (STOBJ_NSLOTS(globalObj) > MAX_GLOBAL_SLOTS) - return false; - jsval* vp = &STOBJ_GET_SLOT(globalObj, slot); - if (known(vp)) - return true; /* we already have it */ - unsigned index = treeInfo->globalSlots->length(); - - /* Add the slot to the list of interned global slots. */ - JS_ASSERT(treeInfo->nGlobalTypes() == treeInfo->globalSlots->length()); - treeInfo->globalSlots->add(slot); - JSTraceType type = getCoercedType(*vp); - if (type == TT_INT32 && oracle.isGlobalSlotUndemotable(cx, slot)) - type = TT_DOUBLE; - treeInfo->typeMap.add(type); - import(lirbuf->state, sizeof(struct InterpState) + slot*sizeof(double), - vp, type, "global", index, NULL); - SpecializeTreesToMissingGlobals(cx, globalObj, treeInfo); - return true; -} - -/* Write back a value onto the stack or global frames. */ -LIns* -TraceRecorder::writeBack(LIns* i, LIns* base, ptrdiff_t offset, bool demote) -{ - /* - * Sink all type casts targeting the stack into the side exit by simply storing the original - * (uncasted) value. Each guard generates the side exit map based on the types of the - * last stores to every stack location, so it's safe to not perform them on-trace. - */ - if (demote && isPromoteInt(i)) - i = ::demote(lir, i); - return lir->insStorei(i, base, offset); -} - -/* Update the tracker, then issue a write back store. */ -JS_REQUIRES_STACK void -TraceRecorder::set(jsval* p, LIns* i, bool initializing, bool demote) -{ - JS_ASSERT(i != NULL); - JS_ASSERT(initializing || known(p)); - checkForGlobalObjectReallocation(); - tracker.set(p, i); - - /* - * If we are writing to this location for the first time, calculate the - * offset into the native frame manually. Otherwise just look up the last - * load or store associated with the same source address (p) and use the - * same offset/base. - */ - LIns* x = nativeFrameTracker.get(p); - if (!x) { - if (isGlobal(p)) - x = writeBack(i, lirbuf->state, nativeGlobalOffset(p), demote); - else - x = writeBack(i, lirbuf->sp, -treeInfo->nativeStackBase + nativeStackOffset(p), demote); - nativeFrameTracker.set(p, x); - } else { - JS_ASSERT(x->isop(LIR_sti) || x->isop(LIR_stqi)); - - int disp; - LIns *base = x->oprnd2(); -#ifdef NANOJIT_ARM - if (base->isop(LIR_piadd)) { - disp = base->oprnd2()->imm32(); - base = base->oprnd1(); - } else -#endif - disp = x->disp(); - - JS_ASSERT(base == lirbuf->sp || base == lirbuf->state); - JS_ASSERT(disp == ((base == lirbuf->sp) ? - -treeInfo->nativeStackBase + nativeStackOffset(p) : - nativeGlobalOffset(p))); - - writeBack(i, base, disp, demote); - } -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::get(jsval* p) -{ - JS_ASSERT(known(p)); - checkForGlobalObjectReallocation(); - return tracker.get(p); -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::addr(jsval* p) -{ - return isGlobal(p) - ? lir->ins2(LIR_piadd, lirbuf->state, INS_CONSTWORD(nativeGlobalOffset(p))) - : lir->ins2(LIR_piadd, lirbuf->sp, - INS_CONSTWORD(-treeInfo->nativeStackBase + nativeStackOffset(p))); -} - -JS_REQUIRES_STACK bool -TraceRecorder::known(jsval* p) -{ - checkForGlobalObjectReallocation(); - return tracker.has(p); -} - -/* - * The dslots of the global object are sometimes reallocated by the interpreter. - * This function check for that condition and re-maps the entries of the tracker - * accordingly. - */ -JS_REQUIRES_STACK void -TraceRecorder::checkForGlobalObjectReallocation() -{ - if (global_dslots != globalObj->dslots) { - debug_only_print0(LC_TMTracer, - "globalObj->dslots relocated, updating tracker\n"); - jsval* src = global_dslots; - jsval* dst = globalObj->dslots; - jsuint length = globalObj->dslots[-1] - JS_INITIAL_NSLOTS; - LIns** map = (LIns**)alloca(sizeof(LIns*) * length); - for (jsuint n = 0; n < length; ++n) { - map[n] = tracker.get(src); - tracker.set(src++, NULL); - } - for (jsuint n = 0; n < length; ++n) - tracker.set(dst++, map[n]); - global_dslots = globalObj->dslots; - } -} - -/* Determine whether the current branch is a loop edge (taken or not taken). */ -static JS_REQUIRES_STACK bool -IsLoopEdge(jsbytecode* pc, jsbytecode* header) -{ - switch (*pc) { - case JSOP_IFEQ: - case JSOP_IFNE: - return ((pc + GET_JUMP_OFFSET(pc)) == header); - case JSOP_IFEQX: - case JSOP_IFNEX: - return ((pc + GET_JUMPX_OFFSET(pc)) == header); - default: - JS_ASSERT((*pc == JSOP_AND) || (*pc == JSOP_ANDX) || - (*pc == JSOP_OR) || (*pc == JSOP_ORX)); - } - return false; -} - -class AdjustCallerGlobalTypesVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - JSContext *mCx; - nanojit::LirBuffer *mLirbuf; - nanojit::LirWriter *mLir; - JSTraceType *mTypeMap; -public: - AdjustCallerGlobalTypesVisitor(TraceRecorder &recorder, - JSTraceType *typeMap) : - mRecorder(recorder), - mCx(mRecorder.cx), - mLirbuf(mRecorder.lirbuf), - mLir(mRecorder.lir), - mTypeMap(typeMap) - {} - - JSTraceType* getTypeMap() - { - return mTypeMap; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - LIns *ins = mRecorder.get(vp); - bool isPromote = isPromoteInt(ins); - if (isPromote && *mTypeMap == TT_DOUBLE) { - mLir->insStorei(mRecorder.get(vp), mLirbuf->state, - mRecorder.nativeGlobalOffset(vp)); - - /* - * Aggressively undo speculation so the inner tree will compile - * if this fails. - */ - oracle.markGlobalSlotUndemotable(mCx, slot); - } - JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); - ++mTypeMap; - } -}; - -class AdjustCallerStackTypesVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - JSContext *mCx; - nanojit::LirBuffer *mLirbuf; - nanojit::LirWriter *mLir; - unsigned mSlotnum; - JSTraceType *mTypeMap; -public: - AdjustCallerStackTypesVisitor(TraceRecorder &recorder, - JSTraceType *typeMap) : - mRecorder(recorder), - mCx(mRecorder.cx), - mLirbuf(mRecorder.lirbuf), - mLir(mRecorder.lir), - mSlotnum(0), - mTypeMap(typeMap) - {} - - JSTraceType* getTypeMap() - { - return mTypeMap; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - LIns *ins = mRecorder.get(vp); - bool isPromote = isPromoteInt(ins); - if (isPromote && *mTypeMap == TT_DOUBLE) { - mLir->insStorei(mRecorder.get(vp), mLirbuf->sp, - -mRecorder.treeInfo->nativeStackBase + - mRecorder.nativeStackOffset(vp)); - - /* - * Aggressively undo speculation so the inner tree will compile - * if this fails. - */ - oracle.markStackSlotUndemotable(mCx, mSlotnum); - } - JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); - ++vp; - ++mTypeMap; - ++mSlotnum; - } - return true; - } -}; - -/* - * Promote slots if necessary to match the called tree's type map. This - * function is infallible and must only be called if we are certain that it is - * possible to reconcile the types for each slot in the inner and outer trees. - */ -JS_REQUIRES_STACK void -TraceRecorder::adjustCallerTypes(Fragment* f) -{ - TreeInfo* ti = (TreeInfo*)f->vmprivate; - - AdjustCallerGlobalTypesVisitor globalVisitor(*this, ti->globalTypeMap()); - VisitGlobalSlots(globalVisitor, cx, *treeInfo->globalSlots); - - AdjustCallerStackTypesVisitor stackVisitor(*this, ti->stackTypeMap()); - VisitStackSlots(stackVisitor, cx, 0); - - JS_ASSERT(f == f->root); -} - -JS_REQUIRES_STACK JSTraceType -TraceRecorder::determineSlotType(jsval* vp) -{ - JSTraceType m; - LIns* i = get(vp); - if (isNumber(*vp)) { - m = isPromoteInt(i) ? TT_INT32 : TT_DOUBLE; - } else if (JSVAL_IS_OBJECT(*vp)) { - if (JSVAL_IS_NULL(*vp)) - m = TT_NULL; - else if (HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*vp))) - m = TT_FUNCTION; - else - m = TT_OBJECT; - } else { - JS_ASSERT(JSVAL_TAG(*vp) == JSVAL_STRING || JSVAL_IS_SPECIAL(*vp)); - JS_STATIC_ASSERT(static_cast(TT_STRING) == JSVAL_STRING); - JS_STATIC_ASSERT(static_cast(TT_PSEUDOBOOLEAN) == JSVAL_SPECIAL); - m = JSTraceType(JSVAL_TAG(*vp)); - } - JS_ASSERT(m != TT_INT32 || isInt32(*vp)); - return m; -} - -class DetermineTypesVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - JSTraceType *mTypeMap; -public: - DetermineTypesVisitor(TraceRecorder &recorder, - JSTraceType *typeMap) : - mRecorder(recorder), - mTypeMap(typeMap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - *mTypeMap++ = mRecorder.determineSlotType(vp); - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) - *mTypeMap++ = mRecorder.determineSlotType(vp++); - return true; - } - - JSTraceType* getTypeMap() - { - return mTypeMap; - } -}; - -#if defined JS_JIT_SPEW -JS_REQUIRES_STACK static void -TreevisLogExit(JSContext* cx, VMSideExit* exit) -{ - debug_only_printf(LC_TMTreeVis, "TREEVIS ADDEXIT EXIT=%p TYPE=%s FRAG=%p PC=%p FILE=\"%s\"" - " LINE=%d OFFS=%d", (void*)exit, getExitName(exit->exitType), - (void*)exit->from, (void*)cx->fp->regs->pc, cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), FramePCOffset(cx->fp)); - debug_only_print0(LC_TMTreeVis, " STACK=\""); - for (unsigned i = 0; i < exit->numStackSlots; i++) - debug_only_printf(LC_TMTreeVis, "%c", typeChar[exit->stackTypeMap()[i]]); - debug_only_print0(LC_TMTreeVis, "\" GLOBALS=\""); - for (unsigned i = 0; i < exit->numGlobalSlots; i++) - debug_only_printf(LC_TMTreeVis, "%c", typeChar[exit->globalTypeMap()[i]]); - debug_only_print0(LC_TMTreeVis, "\"\n"); -} -#endif - -JS_REQUIRES_STACK VMSideExit* -TraceRecorder::snapshot(ExitType exitType) -{ - JSStackFrame* fp = cx->fp; - JSFrameRegs* regs = fp->regs; - jsbytecode* pc = regs->pc; - - /* - * Check for a return-value opcode that needs to restart at the next - * instruction. - */ - const JSCodeSpec& cs = js_CodeSpec[*pc]; - - /* - * When calling a _FAIL native, make the snapshot's pc point to the next - * instruction after the CALL or APPLY. Even on failure, a _FAIL native - * must not be called again from the interpreter. - */ - bool resumeAfter = (pendingSpecializedNative && - JSTN_ERRTYPE(pendingSpecializedNative) == FAIL_STATUS); - if (resumeAfter) { - JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || - *pc == JSOP_SETPROP || *pc == JSOP_SETNAME); - pc += cs.length; - regs->pc = pc; - MUST_FLOW_THROUGH("restore_pc"); - } - - /* - * Generate the entry map for the (possibly advanced) pc and stash it in - * the trace. - */ - unsigned stackSlots = NativeStackSlots(cx, callDepth); - - /* - * It's sufficient to track the native stack use here since all stores - * above the stack watermark defined by guards are killed. - */ - trackNativeStackUse(stackSlots + 1); - - /* Capture the type map into a temporary location. */ - unsigned ngslots = treeInfo->globalSlots->length(); - unsigned typemap_size = (stackSlots + ngslots) * sizeof(JSTraceType); - - /* Use the recorder-local temporary type map. */ - JSTraceType* typemap = NULL; - if (tempTypeMap.resize(typemap_size)) - typemap = tempTypeMap.begin(); /* crash if resize() fails. */ - - /* - * Determine the type of a store by looking at the current type of the - * actual value the interpreter is using. For numbers we have to check what - * kind of store we used last (integer or double) to figure out what the - * side exit show reflect in its typemap. - */ - DetermineTypesVisitor detVisitor(*this, typemap); - VisitSlots(detVisitor, cx, callDepth, ngslots, - treeInfo->globalSlots->data()); - JS_ASSERT(unsigned(detVisitor.getTypeMap() - typemap) == - ngslots + stackSlots); - - /* - * If this snapshot is for a side exit that leaves a boxed jsval result on - * the stack, make a note of this in the typemap. Examples include the - * builtinStatus guard after calling a _FAIL builtin, a JSFastNative, or - * GetPropertyByName; and the type guard in unbox_jsval after such a call - * (also at the beginning of a trace branched from such a type guard). - */ - if (pendingUnboxSlot || - (pendingSpecializedNative && (pendingSpecializedNative->flags & JSTN_UNBOX_AFTER))) { - unsigned pos = stackSlots - 1; - if (pendingUnboxSlot == cx->fp->regs->sp - 2) - pos = stackSlots - 2; - typemap[pos] = TT_JSVAL; - } - - /* Now restore the the original pc (after which early returns are ok). */ - if (resumeAfter) { - MUST_FLOW_LABEL(restore_pc); - regs->pc = pc - cs.length; - } else { - /* - * If we take a snapshot on a goto, advance to the target address. This - * avoids inner trees returning on a break goto, which the outer - * recorder then would confuse with a break in the outer tree. - */ - if (*pc == JSOP_GOTO) - pc += GET_JUMP_OFFSET(pc); - else if (*pc == JSOP_GOTOX) - pc += GET_JUMPX_OFFSET(pc); - } - - /* - * Check if we already have a matching side exit; if so we can return that - * side exit instead of creating a new one. - */ - VMSideExit** exits = treeInfo->sideExits.data(); - unsigned nexits = treeInfo->sideExits.length(); - if (exitType == LOOP_EXIT) { - for (unsigned n = 0; n < nexits; ++n) { - VMSideExit* e = exits[n]; - if (e->pc == pc && e->imacpc == fp->imacpc && - ngslots == e->numGlobalSlots && - !memcmp(exits[n]->fullTypeMap(), typemap, typemap_size)) { - AUDIT(mergedLoopExits); -#if defined JS_JIT_SPEW - TreevisLogExit(cx, e); -#endif - return e; - } - } - } - - if (sizeof(VMSideExit) + (stackSlots + ngslots) * sizeof(JSTraceType) > - LirBuffer::MAX_SKIP_PAYLOAD_SZB) { - /* - * ::snapshot() is infallible in the sense that callers don't - * expect errors; but this is a trace-aborting error condition. So - * mangle the request to consume zero slots, and mark the tree as - * to-be-trashed. This should be safe as the trace will be aborted - * before assembly or execution due to the call to - * trackNativeStackUse above. - */ - stackSlots = 0; - ngslots = 0; - typemap_size = 0; - trashSelf = true; - } - - /* We couldn't find a matching side exit, so create a new one. */ - VMSideExit* exit = (VMSideExit*) - traceMonitor->dataAlloc->alloc(sizeof(VMSideExit) + - (stackSlots + ngslots) * sizeof(JSTraceType)); - - /* Setup side exit structure. */ - memset(exit, 0, sizeof(VMSideExit)); - exit->from = fragment; - exit->calldepth = callDepth; - exit->numGlobalSlots = ngslots; - exit->numStackSlots = stackSlots; - exit->numStackSlotsBelowCurrentFrame = cx->fp->argv ? - nativeStackOffset(&cx->fp->argv[-2]) / sizeof(double) : - 0; - exit->exitType = exitType; - exit->block = fp->blockChain; - if (fp->blockChain) - treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(fp->blockChain)); - exit->pc = pc; - exit->imacpc = fp->imacpc; - exit->sp_adj = (stackSlots * sizeof(double)) - treeInfo->nativeStackBase; - exit->rp_adj = exit->calldepth * sizeof(FrameInfo*); - exit->nativeCalleeWord = 0; - exit->lookupFlags = js_InferFlags(cx, 0); - memcpy(exit->fullTypeMap(), typemap, typemap_size); - -#if defined JS_JIT_SPEW - TreevisLogExit(cx, exit); -#endif - return exit; -} - -JS_REQUIRES_STACK GuardRecord* -TraceRecorder::createGuardRecord(VMSideExit* exit) -{ - GuardRecord* gr = new (*traceMonitor->dataAlloc) GuardRecord(); - - memset(gr, 0, sizeof(GuardRecord)); - gr->exit = exit; - exit->addGuard(gr); - - // gr->profCount is memset'd to zero - verbose_only( - gr->profGuardID = fragment->guardNumberer++; - gr->nextInFrag = fragment->guardsForFrag; - fragment->guardsForFrag = gr; - ) - - return gr; -} - -/* - * Emit a guard for condition (cond), expecting to evaluate to boolean result - * (expected) and using the supplied side exit if the conditon doesn't hold. - */ -JS_REQUIRES_STACK void -TraceRecorder::guard(bool expected, LIns* cond, VMSideExit* exit) -{ - debug_only_printf(LC_TMRecorder, - " About to try emitting guard code for " - "SideExit=%p exitType=%s\n", - (void*)exit, getExitName(exit->exitType)); - - GuardRecord* guardRec = createGuardRecord(exit); - - /* - * BIG FAT WARNING: If compilation fails we don't reset the lirbuf, so it's - * safe to keep references to the side exits here. If we ever start - * clearing those lirbufs, we have to make sure we purge the side exits - * that then no longer will be in valid memory. - */ - if (exit->exitType == LOOP_EXIT) - treeInfo->sideExits.add(exit); - - if (!cond->isCond()) { - expected = !expected; - cond = cond->isQuad() ? lir->ins_peq0(cond) : lir->ins_eq0(cond); - } - - LIns* guardIns = - lir->insGuard(expected ? LIR_xf : LIR_xt, cond, guardRec); - if (!guardIns) { - debug_only_print0(LC_TMRecorder, - " redundant guard, eliminated, no codegen\n"); - } -} - -JS_REQUIRES_STACK VMSideExit* -TraceRecorder::copy(VMSideExit* copy) -{ - size_t typemap_size = copy->numGlobalSlots + copy->numStackSlots; - VMSideExit* exit = (VMSideExit*) - traceMonitor->dataAlloc->alloc(sizeof(VMSideExit) + - typemap_size * sizeof(JSTraceType)); - - /* Copy side exit structure. */ - memcpy(exit, copy, sizeof(VMSideExit) + typemap_size * sizeof(JSTraceType)); - exit->guards = NULL; - exit->from = fragment; - exit->target = NULL; - - /* - * BIG FAT WARNING: If compilation fails we don't reset the lirbuf, so it's - * safe to keep references to the side exits here. If we ever start - * clearing those lirbufs, we have to make sure we purge the side exits - * that then no longer will be in valid memory. - */ - if (exit->exitType == LOOP_EXIT) - treeInfo->sideExits.add(exit); -#if defined JS_JIT_SPEW - TreevisLogExit(cx, exit); -#endif - return exit; -} - -/* - * Emit a guard for condition (cond), expecting to evaluate to boolean result - * (expected) and generate a side exit with type exitType to jump to if the - * condition does not hold. - */ -JS_REQUIRES_STACK void -TraceRecorder::guard(bool expected, LIns* cond, ExitType exitType) -{ - guard(expected, cond, snapshot(exitType)); -} - -/* - * Determine whether any context associated with the same thread as cx is - * executing native code. - */ -static inline bool -ProhibitFlush(JSContext* cx) -{ - if (cx->interpState) // early out if the given is in native code - return true; - - JSCList *cl; - -#ifdef JS_THREADSAFE - JSThread* thread = cx->thread; - for (cl = thread->contextList.next; cl != &thread->contextList; cl = cl->next) - if (CX_FROM_THREAD_LINKS(cl)->interpState) - return true; -#else - JSRuntime* rt = cx->runtime; - for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) - if (js_ContextFromLinkField(cl)->interpState) - return true; -#endif - return false; -} - -static void -ResetJITImpl(JSContext* cx) -{ - if (!TRACING_ENABLED(cx)) - return; - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - debug_only_print0(LC_TMTracer, "Flushing cache.\n"); - if (tm->recorder) { - JS_ASSERT_NOT_ON_TRACE(cx); - js_AbortRecording(cx, "flush cache"); - } - if (ProhibitFlush(cx)) { - debug_only_print0(LC_TMTracer, "Deferring JIT flush due to deep bail.\n"); - tm->needFlush = JS_TRUE; - return; - } - tm->flush(); -} - -#ifdef MOZ_TRACEVIS -static JS_INLINE void -ResetJIT(JSContext* cx, TraceVisFlushReason r) -{ - js_LogTraceVisEvent(cx, S_RESET, r); - ResetJITImpl(cx); -} -#else -#define ResetJIT(cx, r) ResetJITImpl(cx) -#endif - -void -js_FlushJITCache(JSContext *cx) -{ - ResetJIT(cx, FR_OOM); -} - -/* Compile the current fragment. */ -JS_REQUIRES_STACK bool -TraceRecorder::compile(JSTraceMonitor* tm) -{ -#ifdef MOZ_TRACEVIS - TraceVisStateObj tvso(cx, S_COMPILE); -#endif - - if (tm->needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); - return false; - } - if (treeInfo->maxNativeStackSlots >= MAX_NATIVE_STACK_SLOTS) { - debug_only_print0(LC_TMTracer, "Blacklist: excessive stack use.\n"); - Blacklist((jsbytecode*) fragment->root->ip); - return false; - } - if (anchor && anchor->exitType != CASE_EXIT) - ++treeInfo->branchCount; - if (outOfMemory()) - return false; - - Assembler *assm = tm->assembler; - nanojit::compile(assm, fragment verbose_only(, tempAlloc, tm->labels)); - if (outOfMemory()) - return false; - - if (assm->error() != nanojit::None) { - debug_only_print0(LC_TMTracer, "Blacklisted: error during compilation\n"); - Blacklist((jsbytecode*) fragment->root->ip); - return false; - } - ResetRecordingAttempts(cx, (jsbytecode*) fragment->ip); - ResetRecordingAttempts(cx, (jsbytecode*) fragment->root->ip); - if (anchor) { -#ifdef NANOJIT_IA32 - if (anchor->exitType == CASE_EXIT) - assm->patch(anchor, anchor->switchInfo); - else -#endif - assm->patch(anchor); - } - JS_ASSERT(fragment->code()); - JS_ASSERT(!fragment->vmprivate); - if (fragment == fragment->root) - fragment->vmprivate = treeInfo; - - /* :TODO: windows support */ -#if defined DEBUG && !defined WIN32 - const char* filename = cx->fp->script->filename; - char* label = (char*)js_malloc((filename ? strlen(filename) : 7) + 16); - sprintf(label, "%s:%u", filename ? filename : "", - js_FramePCToLineNumber(cx, cx->fp)); - tm->labels->add(fragment, sizeof(Fragment), 0, label); - js_free(label); -#endif - AUDIT(traceCompleted); - return true; -} - -static void -JoinPeers(Assembler* assm, VMSideExit* exit, VMFragment* target) -{ - exit->target = target; - assm->patch(exit); - - debug_only_printf(LC_TMTreeVis, "TREEVIS JOIN ANCHOR=%p FRAG=%p\n", (void*)exit, (void*)target); - - if (exit->root() == target) - return; - - target->getTreeInfo()->dependentTrees.addUnique(exit->root()); - exit->root()->getTreeInfo()->linkedTrees.addUnique(target); -} - -/* Results of trying to connect an arbitrary type A with arbitrary type B */ -enum TypeCheckResult -{ - TypeCheck_Okay, /* Okay: same type */ - TypeCheck_Promote, /* Okay: Type A needs f2i() */ - TypeCheck_Demote, /* Okay: Type A needs i2f() */ - TypeCheck_Undemote, /* Bad: Slot is undemotable */ - TypeCheck_Bad /* Bad: incompatible types */ -}; - -class SlotMap : public SlotVisitorBase -{ - public: - struct SlotInfo - { - SlotInfo() - : v(0), promoteInt(false), lastCheck(TypeCheck_Bad) - {} - SlotInfo(jsval* v, bool promoteInt) - : v(v), promoteInt(promoteInt), lastCheck(TypeCheck_Bad) - {} - jsval *v; - bool promoteInt; - TypeCheckResult lastCheck; - }; - - SlotMap(TraceRecorder& rec, unsigned slotOffset) - : mRecorder(rec), - mCx(rec.cx), - slots(NULL), - slotOffset(slotOffset) - { - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) - { - addSlot(vp); - } - - JS_ALWAYS_INLINE SlotMap::SlotInfo& - operator [](unsigned i) - { - return slots[i]; - } - - JS_ALWAYS_INLINE SlotMap::SlotInfo& - get(unsigned i) - { - return slots[i]; - } - - JS_ALWAYS_INLINE unsigned - length() - { - return slots.length(); - } - - /** - * Possible return states: - * - * TypeConsensus_Okay: All types are compatible. Caller must go through slot list and handle - * promote/demotes. - * TypeConsensus_Bad: Types are not compatible. Individual type check results are undefined. - * TypeConsensus_Undemotes: Types would be compatible if slots were marked as undemotable - * before recording began. Caller can go through slot list and mark - * such slots as undemotable. - */ - JS_REQUIRES_STACK TypeConsensus - checkTypes(TreeInfo* ti) - { - if (ti->typeMap.length() < slotOffset || length() != ti->typeMap.length() - slotOffset) - return TypeConsensus_Bad; - - bool has_undemotes = false; - for (unsigned i = 0; i < length(); i++) { - TypeCheckResult result = checkType(i, ti->typeMap[i + slotOffset]); - if (result == TypeCheck_Bad) - return TypeConsensus_Bad; - if (result == TypeCheck_Undemote) - has_undemotes = true; - slots[i].lastCheck = result; - } - if (has_undemotes) - return TypeConsensus_Undemotes; - return TypeConsensus_Okay; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - addSlot(jsval* vp) - { - slots.add(SlotInfo(vp, isNumber(*vp) && isPromoteInt(mRecorder.get(vp)))); - } - - JS_REQUIRES_STACK void - markUndemotes() - { - for (unsigned i = 0; i < length(); i++) { - if (get(i).lastCheck == TypeCheck_Undemote) - MarkSlotUndemotable(mRecorder.cx, mRecorder.treeInfo, slotOffset + i); - } - } - - JS_REQUIRES_STACK virtual void - adjustTypes() - { - for (unsigned i = 0; i < length(); i++) { - SlotInfo& info = get(i); - JS_ASSERT(info.lastCheck != TypeCheck_Undemote && info.lastCheck != TypeCheck_Bad); - if (info.lastCheck == TypeCheck_Promote) { - JS_ASSERT(isNumber(*info.v)); - mRecorder.set(info.v, mRecorder.f2i(mRecorder.get(info.v))); - } else if (info.lastCheck == TypeCheck_Demote) { - JS_ASSERT(isNumber(*info.v)); - JS_ASSERT(mRecorder.get(info.v)->isQuad()); - - /* Never demote this final i2f. */ - mRecorder.set(info.v, mRecorder.get(info.v), false, false); - } - } - } - private: - TypeCheckResult - checkType(unsigned i, JSTraceType t) - { - debug_only_printf(LC_TMTracer, - "checkType slot %d: interp=%c typemap=%c isNum=%d promoteInt=%d\n", - i, - typeChar[getCoercedType(*slots[i].v)], - typeChar[t], - isNumber(*slots[i].v), - slots[i].promoteInt); - switch (t) { - case TT_INT32: - if (!isNumber(*slots[i].v)) - return TypeCheck_Bad; /* Not a number? Type mismatch. */ - /* This is always a type mismatch, we can't close a double to an int. */ - if (!slots[i].promoteInt) - return TypeCheck_Undemote; - /* Looks good, slot is an int32, the last instruction should be promotable. */ - JS_ASSERT(isInt32(*slots[i].v) && slots[i].promoteInt); - return TypeCheck_Promote; - case TT_DOUBLE: - if (!isNumber(*slots[i].v)) - return TypeCheck_Bad; /* Not a number? Type mismatch. */ - if (slots[i].promoteInt) - return TypeCheck_Demote; - return TypeCheck_Okay; - case TT_NULL: - return JSVAL_IS_NULL(*slots[i].v) ? TypeCheck_Okay : TypeCheck_Bad; - case TT_FUNCTION: - return !JSVAL_IS_PRIMITIVE(*slots[i].v) && - HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*slots[i].v)) ? - TypeCheck_Okay : TypeCheck_Bad; - case TT_OBJECT: - return !JSVAL_IS_PRIMITIVE(*slots[i].v) && - !HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*slots[i].v)) ? - TypeCheck_Okay : TypeCheck_Bad; - default: - return getCoercedType(*slots[i].v) == t ? TypeCheck_Okay : TypeCheck_Bad; - } - JS_NOT_REACHED("shouldn't fall through type check switch"); - } - protected: - TraceRecorder& mRecorder; - JSContext* mCx; - Queue slots; - unsigned slotOffset; -}; - -class DefaultSlotMap : public SlotMap -{ - public: - DefaultSlotMap(TraceRecorder& tr) : SlotMap(tr, 0) - { - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) - { - for (size_t i = 0; i < count; i++) - addSlot(&vp[i]); - return true; - } -}; - -JS_REQUIRES_STACK TypeConsensus -TraceRecorder::selfTypeStability(SlotMap& slotMap) -{ - debug_only_printf(LC_TMTracer, "Checking type stability against self=%p\n", (void*)fragment); - TypeConsensus consensus = slotMap.checkTypes(treeInfo); - - /* Best case: loop jumps back to its own header */ - if (consensus == TypeConsensus_Okay) - return TypeConsensus_Okay; - - /* If the only thing keeping this loop from being stable is undemotions, then mark relevant - * slots as undemotable. - */ - if (consensus == TypeConsensus_Undemotes) - slotMap.markUndemotes(); - - return consensus; -} - -JS_REQUIRES_STACK TypeConsensus -TraceRecorder::peerTypeStability(SlotMap& slotMap, VMFragment** pPeer) -{ - /* See if there are any peers that would make this stable */ - VMFragment* root = (VMFragment*)fragment->root; - VMFragment* peer = getLoop(traceMonitor, root->ip, root->globalObj, root->globalShape, - root->argc); - JS_ASSERT(peer != NULL); - bool onlyUndemotes = false; - for (; peer != NULL; peer = (VMFragment*)peer->peer) { - if (!peer->vmprivate || peer == fragment) - continue; - debug_only_printf(LC_TMTracer, "Checking type stability against peer=%p\n", (void*)peer); - TypeConsensus consensus = slotMap.checkTypes((TreeInfo*)peer->vmprivate); - if (consensus == TypeConsensus_Okay) { - *pPeer = peer; - /* Return this even though there will be linkage; the trace itself is not stable. - * Caller should inspect ppeer to check for a compatible peer. - */ - return TypeConsensus_Okay; - } - if (consensus == TypeConsensus_Undemotes) - onlyUndemotes = true; - } - - return onlyUndemotes ? TypeConsensus_Undemotes : TypeConsensus_Bad; -} - -JS_REQUIRES_STACK bool -TraceRecorder::closeLoop(TypeConsensus &consensus) -{ - DefaultSlotMap slotMap(*this); - VisitSlots(slotMap, cx, 0, *treeInfo->globalSlots); - return closeLoop(slotMap, snapshot(UNSTABLE_LOOP_EXIT), consensus); -} - -/* Complete and compile a trace and link it to the existing tree if appropriate. - * Returns true if something was compiled. Outparam is always set. - */ -JS_REQUIRES_STACK bool -TraceRecorder::closeLoop(SlotMap& slotMap, VMSideExit* exit, TypeConsensus& consensus) -{ - /* - * We should have arrived back at the loop header, and hence we don't want - * to be in an imacro here and the opcode should be either JSOP_TRACE or, in - * case this loop was blacklisted in the meantime, JSOP_NOP. - */ - JS_ASSERT((*cx->fp->regs->pc == JSOP_TRACE || *cx->fp->regs->pc == JSOP_NOP) && - !cx->fp->imacpc); - - if (callDepth != 0) { - debug_only_print0(LC_TMTracer, - "Blacklisted: stack depth mismatch, possible recursion.\n"); - Blacklist((jsbytecode*) fragment->root->ip); - trashSelf = true; - consensus = TypeConsensus_Bad; - return false; - } - - JS_ASSERT(exit->exitType == UNSTABLE_LOOP_EXIT); - JS_ASSERT(exit->numStackSlots == treeInfo->nStackTypes); - - VMFragment* peer = NULL; - VMFragment* root = (VMFragment*)fragment->root; - - consensus = selfTypeStability(slotMap); - if (consensus != TypeConsensus_Okay) { - TypeConsensus peerConsensus = peerTypeStability(slotMap, &peer); - /* If there was a semblance of a stable peer (even if not linkable), keep the result. */ - if (peerConsensus != TypeConsensus_Bad) - consensus = peerConsensus; - } - -#if DEBUG - if (consensus != TypeConsensus_Okay || peer) - AUDIT(unstableLoopVariable); -#endif - - JS_ASSERT(!trashSelf); - - /* This exit is indeed linkable to something now. Process any promote/demotes that - * are pending in the slot map. - */ - if (consensus == TypeConsensus_Okay) - slotMap.adjustTypes(); - - if (consensus != TypeConsensus_Okay || peer) { - fragment->lastIns = lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); - - /* If there is a peer, there must have been an "Okay" consensus. */ - JS_ASSERT_IF(peer, consensus == TypeConsensus_Okay); - - /* Compile as a type-unstable loop, and hope for a connection later. */ - if (!peer) { - /* - * If such a fragment does not exist, let's compile the loop ahead - * of time anyway. Later, if the loop becomes type stable, we will - * connect these two fragments together. - */ - debug_only_print0(LC_TMTracer, - "Trace has unstable loop variable with no stable peer, " - "compiling anyway.\n"); - UnstableExit* uexit = new (*traceMonitor->dataAlloc) UnstableExit; - uexit->fragment = fragment; - uexit->exit = exit; - uexit->next = treeInfo->unstableExits; - treeInfo->unstableExits = uexit; - } else { - JS_ASSERT(peer->code()); - exit->target = peer; - debug_only_printf(LC_TMTracer, - "Joining type-unstable trace to target fragment %p.\n", - (void*)peer); - ((TreeInfo*)peer->vmprivate)->dependentTrees.addUnique(fragment->root); - treeInfo->linkedTrees.addUnique(peer); - } - } else { - exit->exitType = LOOP_EXIT; - debug_only_printf(LC_TMTreeVis, "TREEVIS CHANGEEXIT EXIT=%p TYPE=%s\n", (void*)exit, - getExitName(LOOP_EXIT)); - - JS_ASSERT((fragment == fragment->root) == !!loopLabel); - if (loopLabel) { - lir->insBranch(LIR_j, NULL, loopLabel); - lir->ins1(LIR_live, lirbuf->state); - } - - exit->target = fragment->root; - fragment->lastIns = lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); - } - if (!compile(traceMonitor)) - return false; - - debug_only_printf(LC_TMTreeVis, "TREEVIS CLOSELOOP EXIT=%p PEER=%p\n", (void*)exit, (void*)peer); - - peer = getLoop(traceMonitor, root->ip, root->globalObj, root->globalShape, root->argc); - JS_ASSERT(peer); - joinEdgesToEntry(peer); - - debug_only_stmt(DumpPeerStability(traceMonitor, peer->ip, peer->globalObj, - peer->globalShape, peer->argc);) - - debug_only_print0(LC_TMTracer, - "updating specializations on dependent and linked trees\n"); - if (fragment->root->vmprivate) - SpecializeTreesToMissingGlobals(cx, globalObj, (TreeInfo*)fragment->root->vmprivate); - - /* - * If this is a newly formed tree, and the outer tree has not been compiled yet, we - * should try to compile the outer tree again. - */ - if (outer) - AttemptCompilation(cx, traceMonitor, globalObj, outer, outerArgc); -#ifdef JS_JIT_SPEW - debug_only_printf(LC_TMMinimal, - "Recording completed at %s:%u@%u via closeLoop (FragID=%06u)\n", - cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx->fp), - fragment->profFragID); - debug_only_print0(LC_TMMinimal, "\n"); -#endif - - return true; -} - -static void -FullMapFromExit(TypeMap& typeMap, VMSideExit* exit) -{ - typeMap.setLength(0); - typeMap.fromRaw(exit->stackTypeMap(), exit->numStackSlots); - typeMap.fromRaw(exit->globalTypeMap(), exit->numGlobalSlots); - /* Include globals that were later specialized at the root of the tree. */ - if (exit->numGlobalSlots < exit->root()->getTreeInfo()->nGlobalTypes()) { - typeMap.fromRaw(exit->root()->getTreeInfo()->globalTypeMap() + exit->numGlobalSlots, - exit->root()->getTreeInfo()->nGlobalTypes() - exit->numGlobalSlots); - } -} - -static JS_REQUIRES_STACK TypeConsensus -TypeMapLinkability(JSContext* cx, const TypeMap& typeMap, VMFragment* peer) -{ - const TypeMap& peerMap = peer->getTreeInfo()->typeMap; - unsigned minSlots = JS_MIN(typeMap.length(), peerMap.length()); - TypeConsensus consensus = TypeConsensus_Okay; - for (unsigned i = 0; i < minSlots; i++) { - if (typeMap[i] == peerMap[i]) - continue; - if (typeMap[i] == TT_INT32 && peerMap[i] == TT_DOUBLE && - IsSlotUndemotable(cx, peer->getTreeInfo(), i)) { - consensus = TypeConsensus_Undemotes; - } else { - return TypeConsensus_Bad; - } - } - return consensus; -} - -static JS_REQUIRES_STACK unsigned -FindUndemotesInTypemaps(JSContext* cx, const TypeMap& typeMap, TreeInfo* treeInfo, - Queue& undemotes) -{ - undemotes.setLength(0); - unsigned minSlots = JS_MIN(typeMap.length(), treeInfo->typeMap.length()); - for (unsigned i = 0; i < minSlots; i++) { - if (typeMap[i] == TT_INT32 && treeInfo->typeMap[i] == TT_DOUBLE) { - undemotes.add(i); - } else if (typeMap[i] != treeInfo->typeMap[i]) { - return 0; - } - } - for (unsigned i = 0; i < undemotes.length(); i++) - MarkSlotUndemotable(cx, treeInfo, undemotes[i]); - return undemotes.length(); -} - -JS_REQUIRES_STACK void -TraceRecorder::joinEdgesToEntry(VMFragment* peer_root) -{ - if (fragment->root != fragment) - return; - - TypeMap typeMap(NULL); - Queue undemotes(NULL); - - for (VMFragment* peer = peer_root; peer; peer = (VMFragment*)peer->peer) { - TreeInfo* ti = peer->getTreeInfo(); - if (!ti) - continue; - UnstableExit* uexit = ti->unstableExits; - while (uexit != NULL) { - /* Build the full typemap for this unstable exit */ - FullMapFromExit(typeMap, uexit->exit); - /* Check its compatibility against this tree */ - TypeConsensus consensus = TypeMapLinkability(cx, typeMap, (VMFragment*)fragment->root); - JS_ASSERT_IF(consensus == TypeConsensus_Okay, peer != fragment); - if (consensus == TypeConsensus_Okay) { - debug_only_printf(LC_TMTracer, - "Joining type-stable trace to target exit %p->%p.\n", - (void*)uexit->fragment, (void*)uexit->exit); - - /* - * See bug 531513. Before linking these trees, make sure the - * peer's dependency graph is up to date. - */ - TreeInfo* from = (TreeInfo*)uexit->exit->root()->vmprivate; - if (from->nGlobalTypes() < treeInfo->nGlobalTypes()) { - SpecializeTreesToLateGlobals(cx, from, treeInfo->globalTypeMap(), - treeInfo->nGlobalTypes()); - } - - /* It's okay! Link together and remove the unstable exit. */ - JoinPeers(traceMonitor->assembler, uexit->exit, (VMFragment*)fragment); - uexit = ti->removeUnstableExit(uexit->exit); - } else { - /* Check for int32->double slots that suggest trashing. */ - if (FindUndemotesInTypemaps(cx, typeMap, treeInfo, undemotes)) { - JS_ASSERT(peer == uexit->fragment->root); - if (fragment == peer) - trashSelf = true; - else - whichTreesToTrash.addUnique(uexit->fragment->root); - return; - } - uexit = uexit->next; - } - } - } -} - -JS_REQUIRES_STACK void -TraceRecorder::endLoop() -{ - endLoop(snapshot(LOOP_EXIT)); -} - -/* Emit an always-exit guard and compile the tree (used for break statements. */ -JS_REQUIRES_STACK void -TraceRecorder::endLoop(VMSideExit* exit) -{ - if (callDepth != 0) { - debug_only_print0(LC_TMTracer, "Blacklisted: stack depth mismatch, possible recursion.\n"); - Blacklist((jsbytecode*) fragment->root->ip); - trashSelf = true; - return; - } - - fragment->lastIns = - lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); - if (!compile(traceMonitor)) - return; - - debug_only_printf(LC_TMTreeVis, "TREEVIS ENDLOOP EXIT=%p\n", (void*)exit); - - VMFragment* root = (VMFragment*)fragment->root; - joinEdgesToEntry(getLoop(traceMonitor, - root->ip, - root->globalObj, - root->globalShape, - root->argc)); - debug_only_stmt(DumpPeerStability(traceMonitor, root->ip, root->globalObj, - root->globalShape, root->argc);) - - /* - * Note: this must always be done, in case we added new globals on trace - * and haven't yet propagated those to linked and dependent trees. - */ - debug_only_print0(LC_TMTracer, - "updating specializations on dependent and linked trees\n"); - if (fragment->root->vmprivate) - SpecializeTreesToMissingGlobals(cx, globalObj, (TreeInfo*)fragment->root->vmprivate); - - /* - * If this is a newly formed tree, and the outer tree has not been compiled - * yet, we should try to compile the outer tree again. - */ - if (outer) - AttemptCompilation(cx, traceMonitor, globalObj, outer, outerArgc); -#ifdef JS_JIT_SPEW - debug_only_printf(LC_TMMinimal, - "Recording completed at %s:%u@%u via endLoop (FragID=%06u)\n", - cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx->fp), - fragment->profFragID); - debug_only_print0(LC_TMTracer, "\n"); -#endif -} - -/* Emit code to adjust the stack to match the inner tree's stack expectations. */ -JS_REQUIRES_STACK void -TraceRecorder::prepareTreeCall(VMFragment* inner) -{ - TreeInfo* ti = (TreeInfo*)inner->vmprivate; - inner_sp_ins = lirbuf->sp; - VMSideExit* exit = snapshot(OOM_EXIT); - - /* - * The inner tree expects to be called from the current frame. If the outer - * tree (this trace) is currently inside a function inlining code - * (calldepth > 0), we have to advance the native stack pointer such that - * we match what the inner trace expects to see. We move it back when we - * come out of the inner tree call. - */ - if (callDepth > 0) { - /* - * Calculate the amount we have to lift the native stack pointer by to - * compensate for any outer frames that the inner tree doesn't expect - * but the outer tree has. - */ - ptrdiff_t sp_adj = nativeStackOffset(&cx->fp->argv[-2]); - - /* Calculate the amount we have to lift the call stack by. */ - ptrdiff_t rp_adj = callDepth * sizeof(FrameInfo*); - - /* - * Guard that we have enough stack space for the tree we are trying to - * call on top of the new value for sp. - */ - debug_only_printf(LC_TMTracer, - "sp_adj=%lld outer=%lld inner=%lld\n", - (long long int)sp_adj, - (long long int)treeInfo->nativeStackBase, - (long long int)ti->nativeStackBase); - ptrdiff_t sp_offset = - - treeInfo->nativeStackBase /* rebase sp to beginning of outer tree's stack */ - + sp_adj /* adjust for stack in outer frame inner tree can't see */ - + ti->maxNativeStackSlots * sizeof(double); /* plus the inner tree's stack */ - LIns* sp_top = lir->ins2(LIR_piadd, lirbuf->sp, INS_CONSTWORD(sp_offset)); - guard(true, lir->ins2(LIR_plt, sp_top, eos_ins), exit); - - /* Guard that we have enough call stack space. */ - ptrdiff_t rp_offset = rp_adj + ti->maxCallDepth * sizeof(FrameInfo*); - LIns* rp_top = lir->ins2(LIR_piadd, lirbuf->rp, INS_CONSTWORD(rp_offset)); - guard(true, lir->ins2(LIR_plt, rp_top, eor_ins), exit); - - sp_offset = - - treeInfo->nativeStackBase /* rebase sp to beginning of outer tree's stack */ - + sp_adj /* adjust for stack in outer frame inner tree can't see */ - + ti->nativeStackBase; /* plus the inner tree's stack base */ - /* We have enough space, so adjust sp and rp to their new level. */ - lir->insStorei(inner_sp_ins = lir->ins2(LIR_piadd, lirbuf->sp, INS_CONSTWORD(sp_offset)), - lirbuf->state, offsetof(InterpState, sp)); - lir->insStorei(lir->ins2(LIR_piadd, lirbuf->rp, INS_CONSTWORD(rp_adj)), - lirbuf->state, offsetof(InterpState, rp)); - } - - /* - * The inner tree will probably access stack slots. So tell nanojit not to - * discard or defer stack writes before calling js_CallTree. - * - * (The ExitType of this snapshot is nugatory. The exit can't be taken.) - */ - GuardRecord* guardRec = createGuardRecord(exit); - lir->insGuard(LIR_xbarrier, NULL, guardRec); -} - -static unsigned -BuildGlobalTypeMapFromInnerTree(Queue& typeMap, VMSideExit* inner) -{ -#if defined DEBUG - unsigned initialSlots = typeMap.length(); -#endif - /* First, use the innermost exit's global typemap. */ - typeMap.add(inner->globalTypeMap(), inner->numGlobalSlots); - - /* Add missing global types from the innermost exit's tree. */ - TreeInfo* innerTree = inner->root()->getTreeInfo(); - unsigned slots = inner->numGlobalSlots; - if (slots < innerTree->nGlobalTypes()) { - typeMap.add(innerTree->globalTypeMap() + slots, innerTree->nGlobalTypes() - slots); - slots = innerTree->nGlobalTypes(); - } - JS_ASSERT(typeMap.length() - initialSlots == slots); - return slots; -} - -/* Record a call to an inner tree. */ -JS_REQUIRES_STACK void -TraceRecorder::emitTreeCall(VMFragment* inner, VMSideExit* exit) -{ - TreeInfo* ti = (TreeInfo*)inner->vmprivate; - - /* Invoke the inner tree. */ - LIns* args[] = { INS_CONSTPTR(inner), lirbuf->state }; /* reverse order */ - LIns* ret = lir->insCall(&js_CallTree_ci, args); - - /* Read back all registers, in case the called tree changed any of them. */ -#ifdef DEBUG - JSTraceType* map; - size_t i; - map = exit->globalTypeMap(); - for (i = 0; i < exit->numGlobalSlots; i++) - JS_ASSERT(map[i] != TT_JSVAL); - map = exit->stackTypeMap(); - for (i = 0; i < exit->numStackSlots; i++) - JS_ASSERT(map[i] != TT_JSVAL); -#endif - /* - * Bug 502604 - It is illegal to extend from the outer typemap without - * first extending from the inner. Make a new typemap here. - */ - TypeMap fullMap(NULL); - fullMap.add(exit->stackTypeMap(), exit->numStackSlots); - BuildGlobalTypeMapFromInnerTree(fullMap, exit); - import(ti, inner_sp_ins, exit->numStackSlots, fullMap.length() - exit->numStackSlots, - exit->calldepth, fullMap.data()); - - /* Restore sp and rp to their original values (we still have them in a register). */ - if (callDepth > 0) { - lir->insStorei(lirbuf->sp, lirbuf->state, offsetof(InterpState, sp)); - lir->insStorei(lirbuf->rp, lirbuf->state, offsetof(InterpState, rp)); - } - - // Create snapshot now so that the following block has an updated type map. - VMSideExit* nested = snapshot(NESTED_EXIT); - - // If the outer-trace entry frame is not the same as the inner-trace entry frame, - // then we must reimport the outer trace entry frame in case the inner trace set - // upvars defined in that frame. - if (callDepth > 0) { - ptrdiff_t offset = -treeInfo->nativeStackBase; - JSStackFrame *fp = cx->fp; - for (unsigned i = 0; i < callDepth; ++i) - fp = fp->down; - ImportUnboxedFrameSlotVisitor frameVisitor(*this, lirbuf->sp, offset, - nested->stackTypeMap()); - VisitFrameSlots(frameVisitor, 0, fp, NULL); - } - - /* - * Guard that we come out of the inner tree along the same side exit we came out when - * we called the inner tree at recording time. - */ - guard(true, lir->ins2(LIR_peq, ret, INS_CONSTPTR(exit)), nested); - debug_only_printf(LC_TMTreeVis, "TREEVIS TREECALL INNER=%p EXIT=%p GUARD=%p\n", (void*)inner, - (void*)nested, (void*)exit); - - /* Register us as a dependent tree of the inner tree. */ - ((TreeInfo*)inner->vmprivate)->dependentTrees.addUnique(fragment->root); - treeInfo->linkedTrees.addUnique(inner); -} - -/* Add a if/if-else control-flow merge point to the list of known merge points. */ -JS_REQUIRES_STACK void -TraceRecorder::trackCfgMerges(jsbytecode* pc) -{ - /* If we hit the beginning of an if/if-else, then keep track of the merge point after it. */ - JS_ASSERT((*pc == JSOP_IFEQ) || (*pc == JSOP_IFEQX)); - jssrcnote* sn = js_GetSrcNote(cx->fp->script, pc); - if (sn != NULL) { - if (SN_TYPE(sn) == SRC_IF) { - cfgMerges.add((*pc == JSOP_IFEQ) - ? pc + GET_JUMP_OFFSET(pc) - : pc + GET_JUMPX_OFFSET(pc)); - } else if (SN_TYPE(sn) == SRC_IF_ELSE) - cfgMerges.add(pc + js_GetSrcNoteOffset(sn, 0)); - } -} - -/* - * Invert the direction of the guard if this is a loop edge that is not - * taken (thin loop). - */ -JS_REQUIRES_STACK void -TraceRecorder::emitIf(jsbytecode* pc, bool cond, LIns* x) -{ - ExitType exitType; - if (IsLoopEdge(pc, (jsbytecode*)fragment->root->ip)) { - exitType = LOOP_EXIT; - - /* - * If we are about to walk out of the loop, generate code for the - * inverse loop condition, pretending we recorded the case that stays - * on trace. - */ - if ((*pc == JSOP_IFEQ || *pc == JSOP_IFEQX) == cond) { - JS_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX || *pc == JSOP_IFEQ || *pc == JSOP_IFEQX); - debug_only_print0(LC_TMTracer, - "Walking out of the loop, terminating it anyway.\n"); - cond = !cond; - } - - /* - * Conditional guards do not have to be emitted if the condition is - * constant. We make a note whether the loop condition is true or false - * here, so we later know whether to emit a loop edge or a loop end. - */ - if (x->isconst()) { - loop = (x->imm32() == cond); - return; - } - } else { - exitType = BRANCH_EXIT; - } - if (!x->isconst()) - guard(cond, x, exitType); -} - -/* Emit code for a fused IFEQ/IFNE. */ -JS_REQUIRES_STACK void -TraceRecorder::fuseIf(jsbytecode* pc, bool cond, LIns* x) -{ - if (*pc == JSOP_IFEQ || *pc == JSOP_IFNE) { - emitIf(pc, cond, x); - if (*pc == JSOP_IFEQ) - trackCfgMerges(pc); - } -} - -/* Check whether we have reached the end of the trace. */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::checkTraceEnd(jsbytecode *pc) -{ - if (IsLoopEdge(pc, (jsbytecode*)fragment->root->ip)) { - /* - * If we compile a loop, the trace should have a zero stack balance at - * the loop edge. Currently we are parked on a comparison op or - * IFNE/IFEQ, so advance pc to the loop header and adjust the stack - * pointer and pretend we have reached the loop header. - */ - if (loop) { - JS_ASSERT(!cx->fp->imacpc && (pc == cx->fp->regs->pc || pc == cx->fp->regs->pc + 1)); - bool fused = pc != cx->fp->regs->pc; - JSFrameRegs orig = *cx->fp->regs; - - cx->fp->regs->pc = (jsbytecode*)fragment->root->ip; - cx->fp->regs->sp -= fused ? 2 : 1; - - TypeConsensus consensus; - closeLoop(consensus); - - *cx->fp->regs = orig; - } else { - endLoop(); - } - return JSRS_STOP; - } - return JSRS_CONTINUE; -} - -bool -TraceRecorder::hasMethod(JSObject* obj, jsid id) -{ - if (!obj) - return false; - - JSObject* pobj; - JSProperty* prop; - int protoIndex = obj->lookupProperty(cx, id, &pobj, &prop); - if (protoIndex < 0 || !prop) - return false; - - bool found = false; - if (OBJ_IS_NATIVE(pobj)) { - JSScope* scope = OBJ_SCOPE(pobj); - JSScopeProperty* sprop = (JSScopeProperty*) prop; - - if (SPROP_HAS_STUB_GETTER(sprop) && - SPROP_HAS_VALID_SLOT(sprop, scope)) { - jsval v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); - if (VALUE_IS_FUNCTION(cx, v)) { - found = true; - if (!scope->branded()) { - scope->brandingShapeChange(cx, sprop->slot, v); - scope->setBranded(); - } - } - } - } - - pobj->dropProperty(cx, prop); - return found; -} - -JS_REQUIRES_STACK bool -TraceRecorder::hasIteratorMethod(JSObject* obj) -{ - JS_ASSERT(cx->fp->regs->sp + 2 <= cx->fp->slots + cx->fp->script->nslots); - - return hasMethod(obj, ATOM_TO_JSID(cx->runtime->atomState.iteratorAtom)); -} - -void -nanojit::StackFilter::getTops(LIns* guard, int& spTop, int& rpTop) -{ - VMSideExit* e = (VMSideExit*)guard->record()->exit; - spTop = e->sp_adj; - rpTop = e->rp_adj; -} - -#if defined NJ_VERBOSE -void -nanojit::LirNameMap::formatGuard(LIns *i, char *out) -{ - VMSideExit *x; - - x = (VMSideExit *)i->record()->exit; - sprintf(out, - "%s: %s %s -> pc=%p imacpc=%p sp%+ld rp%+ld (GuardID=%03d)", - formatRef(i), - lirNames[i->opcode()], - i->oprnd1() ? formatRef(i->oprnd1()) : "", - (void *)x->pc, - (void *)x->imacpc, - (long int)x->sp_adj, - (long int)x->rp_adj, - i->record()->profGuardID); -} -#endif - -static JS_REQUIRES_STACK bool -DeleteRecorder(JSContext* cx) -{ - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - - /* Aborting and completing a trace end up here. */ - delete tm->recorder; - tm->recorder = NULL; - - /* If we ran out of memory, flush the code cache. */ - if (tm->dataAlloc->outOfMemory() || - js_OverfullJITCache(tm)) { - ResetJIT(cx, FR_OOM); - return false; - } - - return true; -} - -/* Check whether the shape of the global object has changed. */ -static JS_REQUIRES_STACK bool -CheckGlobalObjectShape(JSContext* cx, JSTraceMonitor* tm, JSObject* globalObj, - uint32 *shape = NULL, SlotList** slots = NULL) -{ - if (tm->needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); - return false; - } - - if (STOBJ_NSLOTS(globalObj) > MAX_GLOBAL_SLOTS) - return false; - - uint32 globalShape = OBJ_SHAPE(globalObj); - - if (tm->recorder) { - VMFragment* root = (VMFragment*)tm->recorder->getFragment()->root; - TreeInfo* ti = tm->recorder->getTreeInfo(); - - /* Check the global shape matches the recorder's treeinfo's shape. */ - if (globalObj != root->globalObj || globalShape != root->globalShape) { - AUDIT(globalShapeMismatchAtEntry); - debug_only_printf(LC_TMTracer, - "Global object/shape mismatch (%p/%u vs. %p/%u), flushing cache.\n", - (void*)globalObj, globalShape, (void*)root->globalObj, - root->globalShape); - Backoff(cx, (jsbytecode*) root->ip); - ResetJIT(cx, FR_GLOBAL_SHAPE_MISMATCH); - return false; - } - if (shape) - *shape = globalShape; - if (slots) - *slots = ti->globalSlots; - return true; - } - - /* No recorder, search for a tracked global-state (or allocate one). */ - for (size_t i = 0; i < MONITOR_N_GLOBAL_STATES; ++i) { - GlobalState &state = tm->globalStates[i]; - - if (state.globalShape == uint32(-1)) { - state.globalObj = globalObj; - state.globalShape = globalShape; - JS_ASSERT(state.globalSlots); - JS_ASSERT(state.globalSlots->length() == 0); - } - - if (state.globalObj == globalObj && state.globalShape == globalShape) { - if (shape) - *shape = globalShape; - if (slots) - *slots = state.globalSlots; - return true; - } - } - - /* No currently-tracked-global found and no room to allocate, abort. */ - AUDIT(globalShapeMismatchAtEntry); - debug_only_printf(LC_TMTracer, - "No global slotlist for global shape %u, flushing cache.\n", - globalShape); - ResetJIT(cx, FR_GLOBALS_FULL); - return false; -} - -static JS_REQUIRES_STACK bool -StartRecorder(JSContext* cx, VMSideExit* anchor, Fragment* f, TreeInfo* ti, - unsigned stackSlots, unsigned ngslots, JSTraceType* typeMap, - VMSideExit* expectedInnerExit, jsbytecode* outer, uint32 outerArgc) -{ - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - if (JS_TRACE_MONITOR(cx).needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); - return false; - } - - JS_ASSERT(f->root != f || !cx->fp->imacpc); - - /* Start recording if no exception during construction. */ - tm->recorder = new TraceRecorder(cx, anchor, f, ti, - stackSlots, ngslots, typeMap, - expectedInnerExit, outer, outerArgc); - - if (cx->throwing) { - js_AbortRecording(cx, "setting up recorder failed"); - return false; - } - - /* Clear any leftover error state. */ - Assembler *assm = JS_TRACE_MONITOR(cx).assembler; - assm->setError(None); - return true; -} - -static void -TrashTree(JSContext* cx, Fragment* f) -{ - JS_ASSERT((!f->code()) == (!f->vmprivate)); - JS_ASSERT(f == f->root); - debug_only_printf(LC_TMTreeVis, "TREEVIS TRASH FRAG=%p\n", (void*)f); - - if (!f->code()) - return; - AUDIT(treesTrashed); - debug_only_print0(LC_TMTracer, "Trashing tree info.\n"); - TreeInfo* ti = (TreeInfo*)f->vmprivate; - f->vmprivate = NULL; - f->setCode(NULL); - Fragment** data = ti->dependentTrees.data(); - unsigned length = ti->dependentTrees.length(); - for (unsigned n = 0; n < length; ++n) - TrashTree(cx, data[n]); - data = ti->linkedTrees.data(); - length = ti->linkedTrees.length(); - for (unsigned n = 0; n < length; ++n) - TrashTree(cx, data[n]); -} - -static int -SynthesizeFrame(JSContext* cx, const FrameInfo& fi, JSObject* callee) -{ - VOUCH_DOES_NOT_REQUIRE_STACK(); - - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, callee); - JS_ASSERT(FUN_INTERPRETED(fun)); - - /* Assert that we have a correct sp distance from cx->fp->slots in fi. */ - JSStackFrame* fp = cx->fp; - JS_ASSERT_IF(!fi.imacpc, - js_ReconstructStackDepth(cx, fp->script, fi.pc) == - uintN(fi.spdist - fp->script->nfixed)); - - uintN nframeslots = JS_HOWMANY(sizeof(JSInlineFrame), sizeof(jsval)); - JSScript* script = fun->u.i.script; - size_t nbytes = (nframeslots + script->nslots) * sizeof(jsval); - - /* Code duplicated from inline_call: case in js_Interpret (FIXME). */ - JSArena* a = cx->stackPool.current; - void* newmark = (void*) a->avail; - uintN argc = fi.get_argc(); - jsval* vp = fp->slots + fi.spdist - (2 + argc); - uintN missing = 0; - jsval* newsp; - - if (fun->nargs > argc) { - const JSFrameRegs& regs = *fp->regs; - - newsp = vp + 2 + fun->nargs; - JS_ASSERT(newsp > regs.sp); - if ((jsuword) newsp <= a->limit) { - if ((jsuword) newsp > a->avail) - a->avail = (jsuword) newsp; - jsval* argsp = newsp; - do { - *--argsp = JSVAL_VOID; - } while (argsp != regs.sp); - missing = 0; - } else { - missing = fun->nargs - argc; - nbytes += (2 + fun->nargs) * sizeof(jsval); - } - } - - /* Allocate the inline frame with its vars and operands. */ - if (a->avail + nbytes <= a->limit) { - newsp = (jsval *) a->avail; - a->avail += nbytes; - JS_ASSERT(missing == 0); - } else { - /* - * This allocation is infallible: ExecuteTree reserved enough stack. - * (But see bug 491023.) - */ - JS_ARENA_ALLOCATE_CAST(newsp, jsval *, &cx->stackPool, nbytes); - JS_ASSERT(newsp); - - /* - * Move args if the missing ones overflow arena a, then push - * undefined for the missing args. - */ - if (missing) { - memcpy(newsp, vp, (2 + argc) * sizeof(jsval)); - vp = newsp; - newsp = vp + 2 + argc; - do { - *newsp++ = JSVAL_VOID; - } while (--missing != 0); - } - } - - /* Claim space for the stack frame and initialize it. */ - JSInlineFrame* newifp = (JSInlineFrame *) newsp; - newsp += nframeslots; - - newifp->frame.callobj = NULL; - newifp->frame.argsobj = NULL; - newifp->frame.varobj = NULL; - newifp->frame.script = script; - newifp->frame.fun = fun; - - bool constructing = fi.is_constructing(); - newifp->frame.argc = argc; - newifp->callerRegs.pc = fi.pc; - newifp->callerRegs.sp = fp->slots + fi.spdist; - fp->imacpc = fi.imacpc; - -#ifdef DEBUG - if (fi.block != fp->blockChain) { - for (JSObject* obj = fi.block; obj != fp->blockChain; obj = STOBJ_GET_PARENT(obj)) - JS_ASSERT(obj); - } -#endif - fp->blockChain = fi.block; - - newifp->frame.argv = newifp->callerRegs.sp - argc; - JS_ASSERT(newifp->frame.argv); -#ifdef DEBUG - // Initialize argv[-1] to a known-bogus value so we'll catch it if - // someone forgets to initialize it later. - newifp->frame.argv[-1] = JSVAL_HOLE; -#endif - JS_ASSERT(newifp->frame.argv >= StackBase(fp) + 2); - - newifp->frame.rval = JSVAL_VOID; - newifp->frame.down = fp; - newifp->frame.annotation = NULL; - newifp->frame.scopeChain = NULL; // will be updated in FlushNativeStackFrame - newifp->frame.sharpDepth = 0; - newifp->frame.sharpArray = NULL; - newifp->frame.flags = constructing ? JSFRAME_CONSTRUCTING : 0; - newifp->frame.dormantNext = NULL; - newifp->frame.blockChain = NULL; - newifp->mark = newmark; - newifp->frame.thisp = NULL; // will be updated in FlushNativeStackFrame - - newifp->frame.regs = fp->regs; - newifp->frame.regs->pc = script->code; - newifp->frame.regs->sp = newsp + script->nfixed; - newifp->frame.imacpc = NULL; - newifp->frame.slots = newsp; - if (script->staticLevel < JS_DISPLAY_SIZE) { - JSStackFrame **disp = &cx->display[script->staticLevel]; - newifp->frame.displaySave = *disp; - *disp = &newifp->frame; - } - - /* - * Note that fp->script is still the caller's script; set the callee - * inline frame's idea of caller version from its version. - */ - newifp->callerVersion = (JSVersion) fp->script->version; - - // After this paragraph, fp and cx->fp point to the newly synthesized frame. - fp->regs = &newifp->callerRegs; - fp = cx->fp = &newifp->frame; - - /* - * If there's a call hook, invoke it to compute the hookData used by - * debuggers that cooperate with the interpreter. - */ - JSInterpreterHook hook = cx->debugHooks->callHook; - if (hook) { - newifp->hookData = hook(cx, fp, JS_TRUE, 0, cx->debugHooks->callHookData); - } else { - newifp->hookData = NULL; - } - - /* - * Duplicate native stack layout computation: see VisitFrameSlots header comment. - * - * FIXME - We must count stack slots from caller's operand stack up to (but - * not including) callee's, including missing arguments. Could we shift - * everything down to the caller's fp->slots (where vars start) and avoid - * some of the complexity? - */ - return (fi.spdist - fp->down->script->nfixed) + - ((fun->nargs > fp->argc) ? fun->nargs - fp->argc : 0) + - script->nfixed + 1/*argsobj*/; -} - -static void -SynthesizeSlowNativeFrame(InterpState& state, JSContext *cx, VMSideExit *exit) -{ - VOUCH_DOES_NOT_REQUIRE_STACK(); - - void *mark; - JSInlineFrame *ifp; - - /* This allocation is infallible: ExecuteTree reserved enough stack. */ - mark = JS_ARENA_MARK(&cx->stackPool); - JS_ARENA_ALLOCATE_CAST(ifp, JSInlineFrame *, &cx->stackPool, sizeof(JSInlineFrame)); - JS_ASSERT(ifp); - - JSStackFrame *fp = &ifp->frame; - fp->regs = NULL; - fp->imacpc = NULL; - fp->slots = NULL; - fp->callobj = NULL; - fp->argsobj = NULL; - fp->varobj = cx->fp->varobj; - fp->script = NULL; - // fp->thisp is really a jsval, so reinterpret_cast here, not JSVAL_TO_OBJECT. - fp->thisp = (JSObject *) state.nativeVp[1]; - fp->argc = state.nativeVpLen - 2; - fp->argv = state.nativeVp + 2; - fp->fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fp->argv[-2])); - fp->rval = JSVAL_VOID; - fp->down = cx->fp; - fp->annotation = NULL; - JS_ASSERT(cx->fp->scopeChain); - fp->scopeChain = cx->fp->scopeChain; - fp->blockChain = NULL; - fp->sharpDepth = 0; - fp->sharpArray = NULL; - fp->flags = exit->constructing() ? JSFRAME_CONSTRUCTING : 0; - fp->dormantNext = NULL; - fp->displaySave = NULL; - - ifp->mark = mark; - cx->fp = fp; -} - -static JS_REQUIRES_STACK bool -RecordTree(JSContext* cx, JSTraceMonitor* tm, VMFragment* f, jsbytecode* outer, - uint32 outerArgc, JSObject* globalObj, uint32 globalShape, - SlotList* globalSlots, uint32 argc) -{ - JS_ASSERT(f->root == f); - - /* save a local copy for use after JIT flush */ - const void* localRootIP = f->root->ip; - - /* Make sure the global type map didn't change on us. */ - if (!CheckGlobalObjectShape(cx, tm, globalObj)) { - Backoff(cx, (jsbytecode*) localRootIP); - return false; - } - - AUDIT(recorderStarted); - - /* Try to find an unused peer fragment, or allocate a new one. */ - while (f->code() && f->peer) - f = f->peer; - if (f->code()) - f = getAnchor(&JS_TRACE_MONITOR(cx), f->root->ip, globalObj, globalShape, argc); - - if (!f) { - ResetJIT(cx, FR_OOM); - return false; - } - - f->root = f; - f->lirbuf = tm->lirbuf; - - if (tm->dataAlloc->outOfMemory() || js_OverfullJITCache(tm)) { - Backoff(cx, (jsbytecode*) f->root->ip); - ResetJIT(cx, FR_OOM); - debug_only_print0(LC_TMTracer, - "Out of memory recording new tree, flushing cache.\n"); - return false; - } - - JS_ASSERT(!f->code() && !f->vmprivate); - - /* Set up the VM-private treeInfo structure for this fragment. */ - TreeInfo* ti = new (*tm->dataAlloc) TreeInfo(tm->dataAlloc, f, globalSlots); - - /* Capture the coerced type of each active slot in the type map. */ - ti->typeMap.captureTypes(cx, globalObj, *globalSlots, 0 /* callDepth */); - ti->nStackTypes = ti->typeMap.length() - globalSlots->length(); - -#ifdef DEBUG - AssertTreeIsUnique(tm, (VMFragment*)f, ti); - ti->treeFileName = cx->fp->script->filename; - ti->treeLineNumber = js_FramePCToLineNumber(cx, cx->fp); - ti->treePCOffset = FramePCOffset(cx->fp); -#endif -#ifdef JS_JIT_SPEW - debug_only_printf(LC_TMTreeVis, "TREEVIS CREATETREE ROOT=%p PC=%p FILE=\"%s\" LINE=%d OFFS=%d", - (void*)f, f->ip, ti->treeFileName, ti->treeLineNumber, - FramePCOffset(cx->fp)); - debug_only_print0(LC_TMTreeVis, " STACK=\""); - for (unsigned i = 0; i < ti->nStackTypes; i++) - debug_only_printf(LC_TMTreeVis, "%c", typeChar[ti->typeMap[i]]); - debug_only_print0(LC_TMTreeVis, "\" GLOBALS=\""); - for (unsigned i = 0; i < ti->nGlobalTypes(); i++) - debug_only_printf(LC_TMTreeVis, "%c", typeChar[ti->typeMap[ti->nStackTypes + i]]); - debug_only_print0(LC_TMTreeVis, "\"\n"); -#endif - - /* Determine the native frame layout at the entry point. */ - unsigned entryNativeStackSlots = ti->nStackTypes; - JS_ASSERT(entryNativeStackSlots == NativeStackSlots(cx, 0 /* callDepth */)); - ti->nativeStackBase = (entryNativeStackSlots - - (cx->fp->regs->sp - StackBase(cx->fp))) * sizeof(double); - ti->maxNativeStackSlots = entryNativeStackSlots; - ti->maxCallDepth = 0; - ti->script = cx->fp->script; - - /* Recording primary trace. */ - if (!StartRecorder(cx, NULL, f, ti, - ti->nStackTypes, - ti->globalSlots->length(), - ti->typeMap.data(), NULL, outer, outerArgc)) { - return false; - } - - return true; -} - -static JS_REQUIRES_STACK TypeConsensus -FindLoopEdgeTarget(JSContext* cx, VMSideExit* exit, VMFragment** peerp) -{ - VMFragment* from = exit->root(); - TreeInfo* from_ti = from->getTreeInfo(); - - JS_ASSERT(from->code()); - - TypeMap typeMap(NULL); - FullMapFromExit(typeMap, exit); - JS_ASSERT(typeMap.length() - exit->numStackSlots == from_ti->nGlobalTypes()); - - /* Mark all double slots as undemotable */ - for (unsigned i = 0; i < typeMap.length(); i++) { - if (typeMap[i] == TT_DOUBLE) - MarkSlotUndemotable(cx, from_ti, i); - } - - VMFragment* firstPeer = ((VMFragment*)from)->first; - for (VMFragment* peer = firstPeer; peer; peer = peer->peer) { - TreeInfo* peer_ti = peer->getTreeInfo(); - if (!peer_ti) - continue; - JS_ASSERT(peer->argc == from->argc); - JS_ASSERT(exit->numStackSlots == peer_ti->nStackTypes); - TypeConsensus consensus = TypeMapLinkability(cx, typeMap, peer); - if (consensus == TypeConsensus_Okay || consensus == TypeConsensus_Undemotes) { - *peerp = peer; - return consensus; - } - } - - return TypeConsensus_Bad; -} - -UnstableExit* -TreeInfo::removeUnstableExit(VMSideExit* exit) -{ - /* Now erase this exit from the unstable exit list. */ - UnstableExit** tail = &this->unstableExits; - for (UnstableExit* uexit = this->unstableExits; uexit != NULL; uexit = uexit->next) { - if (uexit->exit == exit) { - *tail = uexit->next; - return *tail; - } - tail = &uexit->next; - } - JS_NOT_REACHED("exit not in unstable exit list"); - return NULL; -} - -static JS_REQUIRES_STACK bool -AttemptToStabilizeTree(JSContext* cx, JSObject* globalObj, VMSideExit* exit, jsbytecode* outer, - uint32 outerArgc) -{ - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - if (tm->needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); - return false; - } - - VMFragment* from = exit->root(); - TreeInfo* from_ti = from->getTreeInfo(); - - VMFragment* peer = NULL; - TypeConsensus consensus = FindLoopEdgeTarget(cx, exit, &peer); - if (consensus == TypeConsensus_Okay) { - TreeInfo* peer_ti = peer->getTreeInfo(); - JS_ASSERT(from_ti->globalSlots == peer_ti->globalSlots); - JS_ASSERT(from_ti->nStackTypes == peer_ti->nStackTypes); - /* Patch this exit to its peer */ - JoinPeers(tm->assembler, exit, peer); - /* - * Update peer global types. The |from| fragment should already be updated because it on - * the execution path, and somehow connected to the entry trace. - */ - if (peer_ti->nGlobalTypes() < peer_ti->globalSlots->length()) - SpecializeTreesToMissingGlobals(cx, globalObj, peer_ti); - JS_ASSERT(from_ti->nGlobalTypes() == from_ti->globalSlots->length()); - /* This exit is no longer unstable, so remove it. */ - from_ti->removeUnstableExit(exit); - debug_only_stmt(DumpPeerStability(tm, peer->ip, from->globalObj, from->globalShape, from->argc);) - return false; - } else if (consensus == TypeConsensus_Undemotes) { - /* The original tree is unconnectable, so trash it. */ - TrashTree(cx, peer); - return false; - } - - return RecordTree(cx, tm, from->first, outer, outerArgc, from->globalObj, - from->globalShape, from_ti->globalSlots, cx->fp->argc); -} - -static JS_REQUIRES_STACK bool -AttemptToExtendTree(JSContext* cx, VMSideExit* anchor, VMSideExit* exitedFrom, jsbytecode* outer -#ifdef MOZ_TRACEVIS - , TraceVisStateObj* tvso = NULL -#endif - ) -{ - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - if (tm->needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); -#ifdef MOZ_TRACEVIS - if (tvso) tvso->r = R_FAIL_EXTEND_FLUSH; -#endif - return false; - } - - Fragment* f = anchor->root(); - JS_ASSERT(f->vmprivate); - TreeInfo* ti = (TreeInfo*)f->vmprivate; - - /* - * Don't grow trees above a certain size to avoid code explosion due to - * tail duplication. - */ - if (ti->branchCount >= MAX_BRANCHES) { -#ifdef MOZ_TRACEVIS - if (tvso) tvso->r = R_FAIL_EXTEND_MAX_BRANCHES; -#endif - return false; - } - - Fragment* c; - if (!(c = anchor->target)) { - JSTraceMonitor *tm = &JS_TRACE_MONITOR(cx); - Allocator& alloc = *tm->dataAlloc; - verbose_only( - uint32_t profFragID = (js_LogController.lcbits & LC_FragProfile) - ? (++(tm->lastFragID)) : 0; - ) - c = new (alloc) Fragment(cx->fp->regs->pc verbose_only(, profFragID)); - c->root = anchor->from->root; - debug_only_printf(LC_TMTreeVis, "TREEVIS CREATEBRANCH ROOT=%p FRAG=%p PC=%p FILE=\"%s\"" - " LINE=%d ANCHOR=%p OFFS=%d\n", - (void*)f, (void*)c, (void*)cx->fp->regs->pc, cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), (void*)anchor, - FramePCOffset(cx->fp)); - anchor->target = c; - c->root = f; - verbose_only( tm->branches = new (alloc) Seq(c, tm->branches); ) - } - - /* - * If we are recycling a fragment, it might have a different ip so reset it - * here. This can happen when attaching a branch to a NESTED_EXIT, which - * might extend along separate paths (i.e. after the loop edge, and after a - * return statement). - */ - c->ip = cx->fp->regs->pc; - - debug_only_printf(LC_TMTracer, - "trying to attach another branch to the tree (hits = %d)\n", c->hits()); - - int32_t& hits = c->hits(); - if (outer || (hits++ >= HOTEXIT && hits <= HOTEXIT+MAXEXIT)) { - /* start tracing secondary trace from this point */ - c->lirbuf = f->lirbuf; - unsigned stackSlots; - unsigned ngslots; - JSTraceType* typeMap; - TypeMap fullMap(NULL); - if (exitedFrom == NULL) { - /* - * If we are coming straight from a simple side exit, just use that - * exit's type map as starting point. - */ - ngslots = anchor->numGlobalSlots; - stackSlots = anchor->numStackSlots; - typeMap = anchor->fullTypeMap(); - } else { - /* - * If we side-exited on a loop exit and continue on a nesting - * guard, the nesting guard (anchor) has the type information for - * everything below the current scope, and the actual guard we - * exited from has the types for everything in the current scope - * (and whatever it inlined). We have to merge those maps here. - */ - VMSideExit* e1 = anchor; - VMSideExit* e2 = exitedFrom; - fullMap.add(e1->stackTypeMap(), e1->numStackSlotsBelowCurrentFrame); - fullMap.add(e2->stackTypeMap(), e2->numStackSlots); - stackSlots = fullMap.length(); - ngslots = BuildGlobalTypeMapFromInnerTree(fullMap, e2); - JS_ASSERT(ngslots >= e1->numGlobalSlots); // inner tree must have all globals - JS_ASSERT(ngslots == fullMap.length() - stackSlots); - typeMap = fullMap.data(); - } - JS_ASSERT(ngslots >= anchor->numGlobalSlots); - bool rv = StartRecorder(cx, anchor, c, (TreeInfo*)f->vmprivate, stackSlots, - ngslots, typeMap, exitedFrom, outer, cx->fp->argc); -#ifdef MOZ_TRACEVIS - if (!rv && tvso) - tvso->r = R_FAIL_EXTEND_START; -#endif - return rv; - } -#ifdef MOZ_TRACEVIS - if (tvso) tvso->r = R_FAIL_EXTEND_COLD; -#endif - return false; -} - -static JS_REQUIRES_STACK VMSideExit* -ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, - VMSideExit** innermostNestedGuardp); - -static JS_REQUIRES_STACK bool -RecordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCallCount) -{ -#ifdef JS_THREADSAFE - if (OBJ_SCOPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain))->title.ownercx != cx) { - js_AbortRecording(cx, "Global object not owned by this context"); - return false; /* we stay away from shared global objects */ - } -#endif - - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - - /* Process needFlush and deep abort requests. */ - if (tm->needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); - return false; - } - - JS_ASSERT(r->getFragment() && !r->getFragment()->lastIns); - VMFragment* root = (VMFragment*)r->getFragment()->root; - - /* Does this branch go to an inner loop? */ - VMFragment* first = getLoop(&JS_TRACE_MONITOR(cx), cx->fp->regs->pc, - root->globalObj, root->globalShape, cx->fp->argc); - if (!first) { - /* Not an inner loop we can call, abort trace. */ - AUDIT(returnToDifferentLoopHeader); - JS_ASSERT(!cx->fp->imacpc); - debug_only_printf(LC_TMTracer, - "loop edge to %lld, header %lld\n", - (long long int)(cx->fp->regs->pc - cx->fp->script->code), - (long long int)((jsbytecode*)r->getFragment()->root->ip - cx->fp->script->code)); - js_AbortRecording(cx, "Loop edge does not return to header"); - return false; - } - - /* Make sure inner tree call will not run into an out-of-memory condition. */ - if (tm->reservedDoublePoolPtr < (tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) && - !ReplenishReservedPool(cx, tm)) { - js_AbortRecording(cx, "Couldn't call inner tree (out of memory)"); - return false; - } - - /* - * Make sure the shape of the global object still matches (this might flush - * the JIT cache). - */ - JSObject* globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); - uint32 globalShape = -1; - SlotList* globalSlots = NULL; - if (!CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots)) - return false; - - debug_only_printf(LC_TMTracer, - "Looking for type-compatible peer (%s:%d@%d)\n", - cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx->fp)); - - // Find a matching inner tree. If none can be found, compile one. - VMFragment* f = r->findNestedCompatiblePeer(first); - if (!f || !f->code()) { - AUDIT(noCompatInnerTrees); - - VMFragment* outerFragment = (VMFragment*) tm->recorder->getFragment()->root; - jsbytecode* outer = (jsbytecode*) outerFragment->ip; - uint32 outerArgc = outerFragment->argc; - uint32 argc = cx->fp->argc; - js_AbortRecording(cx, "No compatible inner tree"); - - // Find an empty fragment we can recycle, or allocate a new one. - for (f = first; f != NULL; f = f->peer) { - if (!f->code()) - break; - } - if (!f || f->code()) { - f = getAnchor(tm, cx->fp->regs->pc, globalObj, globalShape, argc); - if (!f) { - ResetJIT(cx, FR_OOM); - return false; - } - } - return RecordTree(cx, tm, f, outer, outerArgc, globalObj, globalShape, globalSlots, argc); - } - - r->adjustCallerTypes(f); - r->prepareTreeCall(f); - - VMSideExit* innermostNestedGuard = NULL; - VMSideExit* lr = ExecuteTree(cx, f, inlineCallCount, &innermostNestedGuard); - - /* ExecuteTree can reenter the interpreter and kill |this|. */ - if (!TRACE_RECORDER(cx)) - return false; - - if (!lr) { - js_AbortRecording(cx, "Couldn't call inner tree"); - return false; - } - - VMFragment* outerFragment = (VMFragment*) tm->recorder->getFragment()->root; - jsbytecode* outer = (jsbytecode*) outerFragment->ip; - switch (lr->exitType) { - case LOOP_EXIT: - /* If the inner tree exited on an unknown loop exit, grow the tree around it. */ - if (innermostNestedGuard) { - js_AbortRecording(cx, "Inner tree took different side exit, abort current " - "recording and grow nesting tree"); - return AttemptToExtendTree(cx, innermostNestedGuard, lr, outer); - } - - /* Emit a call to the inner tree and continue recording the outer tree trace. */ - r->emitTreeCall(f, lr); - return true; - - case UNSTABLE_LOOP_EXIT: - /* Abort recording so the inner loop can become type stable. */ - js_AbortRecording(cx, "Inner tree is trying to stabilize, abort outer recording"); - return AttemptToStabilizeTree(cx, globalObj, lr, outer, outerFragment->argc); - - case OVERFLOW_EXIT: - oracle.markInstructionUndemotable(cx->fp->regs->pc); - /* FALL THROUGH */ - case BRANCH_EXIT: - case CASE_EXIT: - /* Abort recording the outer tree, extend the inner tree. */ - js_AbortRecording(cx, "Inner tree is trying to grow, abort outer recording"); - return AttemptToExtendTree(cx, lr, NULL, outer); - - default: - debug_only_printf(LC_TMTracer, "exit_type=%s\n", getExitName(lr->exitType)); - js_AbortRecording(cx, "Inner tree not suitable for calling"); - return false; - } -} - -static bool -IsEntryTypeCompatible(jsval* vp, JSTraceType* m) -{ - unsigned tag = JSVAL_TAG(*vp); - - debug_only_printf(LC_TMTracer, "%c/%c ", tagChar[tag], typeChar[*m]); - - switch (*m) { - case TT_OBJECT: - if (tag == JSVAL_OBJECT && !JSVAL_IS_NULL(*vp) && - !HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*vp))) { - return true; - } - debug_only_printf(LC_TMTracer, "object != tag%u ", tag); - return false; - case TT_INT32: - jsint i; - if (JSVAL_IS_INT(*vp)) - return true; - if (tag == JSVAL_DOUBLE && JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(*vp), i)) - return true; - debug_only_printf(LC_TMTracer, "int != tag%u(value=%lu) ", tag, (unsigned long)*vp); - return false; - case TT_DOUBLE: - if (JSVAL_IS_INT(*vp) || tag == JSVAL_DOUBLE) - return true; - debug_only_printf(LC_TMTracer, "double != tag%u ", tag); - return false; - case TT_JSVAL: - JS_NOT_REACHED("shouldn't see jsval type in entry"); - return false; - case TT_STRING: - if (tag == JSVAL_STRING) - return true; - debug_only_printf(LC_TMTracer, "string != tag%u ", tag); - return false; - case TT_NULL: - if (JSVAL_IS_NULL(*vp)) - return true; - debug_only_printf(LC_TMTracer, "null != tag%u ", tag); - return false; - case TT_PSEUDOBOOLEAN: - if (tag == JSVAL_SPECIAL) - return true; - debug_only_printf(LC_TMTracer, "bool != tag%u ", tag); - return false; - default: - JS_ASSERT(*m == TT_FUNCTION); - if (tag == JSVAL_OBJECT && !JSVAL_IS_NULL(*vp) && - HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*vp))) { - return true; - } - debug_only_printf(LC_TMTracer, "fun != tag%u ", tag); - return false; - } -} - -class TypeCompatibilityVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - JSContext *mCx; - JSTraceType *mTypeMap; - unsigned mStackSlotNum; - bool mOk; -public: - TypeCompatibilityVisitor (TraceRecorder &recorder, - JSTraceType *typeMap) : - mRecorder(recorder), - mCx(mRecorder.cx), - mTypeMap(typeMap), - mStackSlotNum(0), - mOk(true) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - debug_only_printf(LC_TMTracer, "global%d=", n); - if (!IsEntryTypeCompatible(vp, mTypeMap)) { - mOk = false; - } else if (!isPromoteInt(mRecorder.get(vp)) && *mTypeMap == TT_INT32) { - oracle.markGlobalSlotUndemotable(mCx, slot); - mOk = false; - } else if (JSVAL_IS_INT(*vp) && *mTypeMap == TT_DOUBLE) { - oracle.markGlobalSlotUndemotable(mCx, slot); - } - mTypeMap++; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - debug_only_printf(LC_TMTracer, "%s%u=", stackSlotKind(), unsigned(i)); - if (!IsEntryTypeCompatible(vp, mTypeMap)) { - mOk = false; - } else if (!isPromoteInt(mRecorder.get(vp)) && *mTypeMap == TT_INT32) { - oracle.markStackSlotUndemotable(mCx, mStackSlotNum); - mOk = false; - } else if (JSVAL_IS_INT(*vp) && *mTypeMap == TT_DOUBLE) { - oracle.markStackSlotUndemotable(mCx, mStackSlotNum); - } - vp++; - mTypeMap++; - mStackSlotNum++; - } - return true; - } - - bool isOk() { - return mOk; - } -}; - -JS_REQUIRES_STACK VMFragment* -TraceRecorder::findNestedCompatiblePeer(VMFragment* f) -{ - JSTraceMonitor* tm; - - tm = &JS_TRACE_MONITOR(cx); - unsigned int ngslots = treeInfo->globalSlots->length(); - - TreeInfo* ti; - for (; f != NULL; f = f->peer) { - if (!f->code()) - continue; - - ti = (TreeInfo*)f->vmprivate; - - debug_only_printf(LC_TMTracer, "checking nested types %p: ", (void*)f); - - if (ngslots > ti->nGlobalTypes()) - SpecializeTreesToMissingGlobals(cx, globalObj, ti); - - /* - * Determine whether the typemap of the inner tree matches the outer - * tree's current state. If the inner tree expects an integer, but the - * outer tree doesn't guarantee an integer for that slot, we mark the - * slot undemotable and mismatch here. This will force a new tree to be - * compiled that accepts a double for the slot. If the inner tree - * expects a double, but the outer tree has an integer, we can proceed, - * but we mark the location undemotable. - */ - TypeCompatibilityVisitor visitor(*this, ti->typeMap.data()); - VisitSlots(visitor, cx, 0, *treeInfo->globalSlots); - - debug_only_printf(LC_TMTracer, " %s\n", visitor.isOk() ? "match" : ""); - if (visitor.isOk()) - return f; - } - - return NULL; -} - -class CheckEntryTypeVisitor : public SlotVisitorBase -{ - bool mOk; - JSTraceType *mTypeMap; -public: - CheckEntryTypeVisitor(JSTraceType *typeMap) : - mOk(true), - mTypeMap(typeMap) - {} - - JS_ALWAYS_INLINE void checkSlot(jsval *vp, char const *name, int i) { - debug_only_printf(LC_TMTracer, "%s%d=", name, i); - JS_ASSERT(*(uint8_t*)mTypeMap != 0xCD); - mOk = IsEntryTypeCompatible(vp, mTypeMap++); - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - if (mOk) - checkSlot(vp, "global", n); - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - if (!mOk) - break; - checkSlot(vp++, stackSlotKind(), i); - } - return mOk; - } - - bool isOk() { - return mOk; - } -}; - -/** - * Check if types are usable for trace execution. - * - * @param cx Context. - * @param ti Tree info of peer we're testing. - * @return True if compatible (with or without demotions), false otherwise. - */ -static JS_REQUIRES_STACK bool -CheckEntryTypes(JSContext* cx, JSObject* globalObj, TreeInfo* ti) -{ - unsigned int ngslots = ti->globalSlots->length(); - - JS_ASSERT(ti->nStackTypes == NativeStackSlots(cx, 0)); - - if (ngslots > ti->nGlobalTypes()) - SpecializeTreesToMissingGlobals(cx, globalObj, ti); - - JS_ASSERT(ti->typeMap.length() == NativeStackSlots(cx, 0) + ngslots); - JS_ASSERT(ti->typeMap.length() == ti->nStackTypes + ngslots); - JS_ASSERT(ti->nGlobalTypes() == ngslots); - - CheckEntryTypeVisitor visitor(ti->typeMap.data()); - VisitSlots(visitor, cx, 0, *ti->globalSlots); - - debug_only_print0(LC_TMTracer, "\n"); - return visitor.isOk(); -} - -/** - * Find an acceptable entry tree given a PC. - * - * @param cx Context. - * @param globalObj Global object. - * @param f First peer fragment. - * @param nodemote If true, will try to find a peer that does not require demotion. - * @out count Number of fragments consulted. - */ -static JS_REQUIRES_STACK VMFragment* -FindVMCompatiblePeer(JSContext* cx, JSObject* globalObj, VMFragment* f, uintN& count) -{ - count = 0; - for (; f != NULL; f = f->peer) { - if (f->vmprivate == NULL) - continue; - debug_only_printf(LC_TMTracer, - "checking vm types %p (ip: %p): ", (void*)f, f->ip); - if (CheckEntryTypes(cx, globalObj, (TreeInfo*)f->vmprivate)) - return f; - ++count; - } - return NULL; -} - -static void -LeaveTree(InterpState&, VMSideExit* lr); - -static JS_REQUIRES_STACK VMSideExit* -ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, - VMSideExit** innermostNestedGuardp) -{ -#ifdef MOZ_TRACEVIS - TraceVisStateObj tvso(cx, S_EXECUTE); -#endif - - JS_ASSERT(f->root == f && f->code() && f->vmprivate); - - /* - * The JIT records and expects to execute with two scope-chain - * assumptions baked-in: - * - * 1. That the bottom of the scope chain is global, in the sense of - * JSCLASS_IS_GLOBAL. - * - * 2. That the scope chain between fp and the global is free of - * "unusual" native objects such as HTML forms or other funny - * things. - * - * #2 is checked here while following the scope-chain links, via - * js_IsCacheableNonGlobalScope, which consults a whitelist of known - * class types; once a global is found, it's checked for #1. Failing - * either check causes an early return from execution. - */ - JSObject* parent; - JSObject* child = cx->fp->scopeChain; - while ((parent = OBJ_GET_PARENT(cx, child)) != NULL) { - if (!js_IsCacheableNonGlobalScope(child)) { - debug_only_print0(LC_TMTracer,"Blacklist: non-cacheable object on scope chain.\n"); - Blacklist((jsbytecode*) f->root->ip); - return NULL; - } - child = parent; - } - JSObject* globalObj = child; - if (!(OBJ_GET_CLASS(cx, globalObj)->flags & JSCLASS_IS_GLOBAL)) { - debug_only_print0(LC_TMTracer, "Blacklist: non-global at root of scope chain.\n"); - Blacklist((jsbytecode*) f->root->ip); - return NULL; - } - - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - TreeInfo* ti = (TreeInfo*)f->vmprivate; - unsigned ngslots = ti->globalSlots->length(); - uint16* gslots = ti->globalSlots->data(); - unsigned globalFrameSize = STOBJ_NSLOTS(globalObj); - - /* Make sure the global object is sane. */ - JS_ASSERT_IF(ngslots != 0, - OBJ_SHAPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain)) == - ((VMFragment*)f)->globalShape); - - /* Make sure our caller replenished the double pool. */ - JS_ASSERT(tm->reservedDoublePoolPtr >= tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS); - - /* Reserve objects and stack space now, to make leaving the tree infallible. */ - if (!js_ReserveObjects(cx, MAX_CALL_STACK_ENTRIES)) - return NULL; - - /* Set up the interpreter state block, which is followed by the native global frame. */ - InterpState* state = (InterpState*)alloca(sizeof(InterpState) + (globalFrameSize+1)*sizeof(double)); - state->cx = cx; - state->inlineCallCountp = &inlineCallCount; - state->innermostNestedGuardp = innermostNestedGuardp; - state->outermostTree = ti; - state->lastTreeExitGuard = NULL; - state->lastTreeCallGuard = NULL; - state->rpAtLastTreeCall = NULL; - state->nativeVp = NULL; - state->builtinStatus = 0; - - /* Set up the native global frame. */ - double* global = (double*)(state+1); - - /* Set up the native stack frame. */ - double stack_buffer[MAX_NATIVE_STACK_SLOTS]; - state->stackBase = stack_buffer; - state->sp = stack_buffer + (ti->nativeStackBase/sizeof(double)); - state->eos = stack_buffer + MAX_NATIVE_STACK_SLOTS; - - /* Set up the native call stack frame. */ - FrameInfo* callstack_buffer[MAX_CALL_STACK_ENTRIES]; - state->callstackBase = callstack_buffer; - state->rp = callstack_buffer; - state->eor = callstack_buffer + MAX_CALL_STACK_ENTRIES; - - void *reserve; - state->stackMark = JS_ARENA_MARK(&cx->stackPool); - JS_ARENA_ALLOCATE(reserve, &cx->stackPool, MAX_INTERP_STACK_BYTES); - if (!reserve) - return NULL; - -#ifdef DEBUG - memset(stack_buffer, 0xCD, sizeof(stack_buffer)); - memset(global, 0xCD, (globalFrameSize+1)*sizeof(double)); - JS_ASSERT(globalFrameSize <= MAX_GLOBAL_SLOTS); -#endif - - debug_only_stmt(*(uint64*)&global[globalFrameSize] = 0xdeadbeefdeadbeefLL;) - debug_only_printf(LC_TMTracer, - "entering trace at %s:%u@%u, native stack slots: %u code: %p\n", - cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx->fp), - ti->maxNativeStackSlots, - f->code()); - - JS_ASSERT(ti->nGlobalTypes() == ngslots); - BuildNativeFrame(cx, globalObj, 0 /* callDepth */, ngslots, gslots, - ti->typeMap.data(), global, stack_buffer); - - union { NIns *code; GuardRecord* (FASTCALL *func)(InterpState*, Fragment*); } u; - u.code = f->code(); - -#ifdef EXECUTE_TREE_TIMER - state->startTime = rdtsc(); -#endif - - JS_ASSERT(!tm->tracecx); - tm->tracecx = cx; - state->prev = cx->interpState; - cx->interpState = state; - - debug_only_stmt(fflush(NULL)); - GuardRecord* rec; - - // Note that the block scoping is crucial here for TraceVis; the - // TraceVisStateObj constructors and destructors must run at the right times. - { -#ifdef MOZ_TRACEVIS - TraceVisStateObj tvso_n(cx, S_NATIVE); -#endif -#if defined(JS_NO_FASTCALL) && defined(NANOJIT_IA32) - SIMULATE_FASTCALL(rec, state, NULL, u.func); -#else - rec = u.func(state, NULL); -#endif - } - - JS_ASSERT(*(uint64*)&global[globalFrameSize] == 0xdeadbeefdeadbeefLL); - JS_ASSERT(!state->nativeVp); - - VMSideExit* lr = (VMSideExit*)rec->exit; - - AUDIT(traceTriggered); - - cx->interpState = state->prev; - - JS_ASSERT(!cx->bailExit); - JS_ASSERT(lr->exitType != LOOP_EXIT || !lr->calldepth); - tm->tracecx = NULL; - LeaveTree(*state, lr); - return state->innermost; -} - -static JS_FORCES_STACK void -LeaveTree(InterpState& state, VMSideExit* lr) -{ - VOUCH_DOES_NOT_REQUIRE_STACK(); - - JSContext* cx = state.cx; - FrameInfo** callstack = state.callstackBase; - double* stack = state.stackBase; - - /* - * Except if we find that this is a nested bailout, the guard the call - * returned is the one we have to use to adjust pc and sp. - */ - VMSideExit* innermost = lr; - - /* - * While executing a tree we do not update state.sp and state.rp even if - * they grow. Instead, guards tell us by how much sp and rp should be - * incremented in case of a side exit. When calling a nested tree, however, - * we actively adjust sp and rp. If we have such frames from outer trees on - * the stack, then rp will have been adjusted. Before we can process the - * stack of the frames of the tree we directly exited from, we have to - * first work our way through the outer frames and generate interpreter - * frames for them. Once the call stack (rp) is empty, we can process the - * final frames (which again are not directly visible and only the guard we - * exited on will tells us about). - */ - FrameInfo** rp = (FrameInfo**)state.rp; - if (lr->exitType == NESTED_EXIT) { - VMSideExit* nested = state.lastTreeCallGuard; - if (!nested) { - /* - * If lastTreeCallGuard is not set in state, we only have a single - * level of nesting in this exit, so lr itself is the innermost and - * outermost nested guard, and hence we set nested to lr. The - * calldepth of the innermost guard is not added to state.rp, so we - * do it here manually. For a nesting depth greater than 1 the - * CallTree builtin already added the innermost guard's calldepth - * to state.rpAtLastTreeCall. - */ - nested = lr; - rp += lr->calldepth; - } else { - /* - * During unwinding state.rp gets overwritten at every step and we - * restore it here to its state at the innermost nested guard. The - * builtin already added the calldepth of that innermost guard to - * rpAtLastTreeCall. - */ - rp = (FrameInfo**)state.rpAtLastTreeCall; - } - innermost = state.lastTreeExitGuard; - if (state.innermostNestedGuardp) - *state.innermostNestedGuardp = nested; - JS_ASSERT(nested); - JS_ASSERT(nested->exitType == NESTED_EXIT); - JS_ASSERT(state.lastTreeExitGuard); - JS_ASSERT(state.lastTreeExitGuard->exitType != NESTED_EXIT); - } - - int32_t bs = state.builtinStatus; - bool bailed = innermost->exitType == STATUS_EXIT && (bs & JSBUILTIN_BAILED); - if (bailed) { - /* - * Deep-bail case. - * - * A _FAIL native already called LeaveTree. We already reconstructed - * the interpreter stack, in pre-call state, with pc pointing to the - * CALL/APPLY op, for correctness. Then we continued in native code. - * - * First, if we just returned from a slow native, pop its stack frame. - */ - if (!cx->fp->script) { - JSStackFrame *fp = cx->fp; - JS_ASSERT(FUN_SLOW_NATIVE(fp->fun)); - JS_ASSERT(fp->regs == NULL); - JS_ASSERT(fp->down->regs != &((JSInlineFrame *) fp)->callerRegs); - cx->fp = fp->down; - JS_ARENA_RELEASE(&cx->stackPool, ((JSInlineFrame *) fp)->mark); - } - JS_ASSERT(cx->fp->script); - - if (!(bs & JSBUILTIN_ERROR)) { - /* - * The builtin or native deep-bailed but finished successfully - * (no exception or error). - * - * After it returned, the JIT code stored the results of the - * builtin or native at the top of the native stack and then - * immediately flunked the guard on state->builtinStatus. - * - * Now LeaveTree has been called again from the tail of - * ExecuteTree. We are about to return to the interpreter. Adjust - * the top stack frame to resume on the next op. - */ - JSFrameRegs* regs = cx->fp->regs; - JSOp op = (JSOp) *regs->pc; - JS_ASSERT(op == JSOP_CALL || op == JSOP_APPLY || op == JSOP_NEW || - op == JSOP_GETPROP || op == JSOP_GETTHISPROP || op == JSOP_GETARGPROP || - op == JSOP_GETLOCALPROP || op == JSOP_LENGTH || - op == JSOP_GETELEM || op == JSOP_CALLELEM || - op == JSOP_SETPROP || op == JSOP_SETNAME || - op == JSOP_SETELEM || op == JSOP_INITELEM || - op == JSOP_INSTANCEOF); - - /* - * JSOP_SETELEM can be coalesced with a JSOP_POP in the interpeter. - * Since this doesn't re-enter the recorder, the post-state snapshot - * is invalid. Fix it up here. - */ - if (op == JSOP_SETELEM && JSOp(regs->pc[JSOP_SETELEM_LENGTH]) == JSOP_POP) { - regs->sp -= js_CodeSpec[JSOP_SETELEM].nuses; - regs->sp += js_CodeSpec[JSOP_SETELEM].ndefs; - regs->pc += JSOP_SETELEM_LENGTH; - op = JSOP_POP; - } - - const JSCodeSpec& cs = js_CodeSpec[op]; - regs->sp -= (cs.format & JOF_INVOKE) ? GET_ARGC(regs->pc) + 2 : cs.nuses; - regs->sp += cs.ndefs; - regs->pc += cs.length; - JS_ASSERT_IF(!cx->fp->imacpc, - cx->fp->slots + cx->fp->script->nfixed + - js_ReconstructStackDepth(cx, cx->fp->script, regs->pc) == - regs->sp); - - /* - * If there's a tree call around the point that we deep exited at, - * then state.sp and state.rp were restored to their original - * values before the tree call and sp might be less than deepBailSp, - * which we sampled when we were told to deep bail. - */ - JS_ASSERT(state.deepBailSp >= state.stackBase && state.sp <= state.deepBailSp); - - /* - * As explained above, the JIT code stored a result value or values - * on the native stack. Transfer them to the interpreter stack now. - * (Some opcodes, like JSOP_CALLELEM, produce two values, hence the - * loop.) - */ - JSTraceType* typeMap = innermost->stackTypeMap(); - for (int i = 1; i <= cs.ndefs; i++) { - NativeToValue(cx, - regs->sp[-i], - typeMap[innermost->numStackSlots - i], - (jsdouble *) state.deepBailSp - + innermost->sp_adj / sizeof(jsdouble) - i); - } - } - return; - } - - JS_ARENA_RELEASE(&cx->stackPool, state.stackMark); - while (callstack < rp) { - FrameInfo* fi = *callstack; - /* Peek at the callee native slot in the not-yet-synthesized down frame. */ - JSObject* callee = *(JSObject**)&stack[fi->callerHeight]; - - /* - * Synthesize a stack frame and write out the values in it using the - * type map pointer on the native call stack. - */ - SynthesizeFrame(cx, *fi, callee); - int slots = FlushNativeStackFrame(cx, 1 /* callDepth */, (JSTraceType*)(fi + 1), - stack, cx->fp); -#ifdef DEBUG - JSStackFrame* fp = cx->fp; - debug_only_printf(LC_TMTracer, - "synthesized deep frame for %s:%u@%u, slots=%d\n", - fp->script->filename, - js_FramePCToLineNumber(cx, fp), - FramePCOffset(fp), - slots); -#endif - /* - * Keep track of the additional frames we put on the interpreter stack - * and the native stack slots we consumed. - */ - ++*state.inlineCallCountp; - ++callstack; - stack += slots; - } - - /* - * We already synthesized the frames around the innermost guard. Here we - * just deal with additional frames inside the tree we are bailing out - * from. - */ - JS_ASSERT(rp == callstack); - unsigned calldepth = innermost->calldepth; - unsigned calldepth_slots = 0; - unsigned calleeOffset = 0; - for (unsigned n = 0; n < calldepth; ++n) { - /* Peek at the callee native slot in the not-yet-synthesized down frame. */ - calleeOffset += callstack[n]->callerHeight; - JSObject* callee = *(JSObject**)&stack[calleeOffset]; - - /* Reconstruct the frame. */ - calldepth_slots += SynthesizeFrame(cx, *callstack[n], callee); - ++*state.inlineCallCountp; -#ifdef DEBUG - JSStackFrame* fp = cx->fp; - debug_only_printf(LC_TMTracer, - "synthesized shallow frame for %s:%u@%u\n", - fp->script->filename, js_FramePCToLineNumber(cx, fp), - FramePCOffset(fp)); -#endif - } - - /* - * Adjust sp and pc relative to the tree we exited from (not the tree we - * entered into). These are our final values for sp and pc since - * SynthesizeFrame has already taken care of all frames in between. But - * first we recover fp->blockChain, which comes from the side exit - * struct. - */ - JSStackFrame* fp = cx->fp; - - fp->blockChain = innermost->block; - - /* - * If we are not exiting from an inlined frame, the state->sp is spbase. - * Otherwise spbase is whatever slots frames around us consume. - */ - fp->regs->pc = innermost->pc; - fp->imacpc = innermost->imacpc; - fp->regs->sp = StackBase(fp) + (innermost->sp_adj / sizeof(double)) - calldepth_slots; - JS_ASSERT_IF(!fp->imacpc, - fp->slots + fp->script->nfixed + - js_ReconstructStackDepth(cx, fp->script, fp->regs->pc) == fp->regs->sp); - -#ifdef EXECUTE_TREE_TIMER - uint64 cycles = rdtsc() - state.startTime; -#elif defined(JS_JIT_SPEW) - uint64 cycles = 0; -#endif - - debug_only_printf(LC_TMTracer, - "leaving trace at %s:%u@%u, op=%s, lr=%p, exitType=%s, sp=%lld, " - "calldepth=%d, cycles=%llu\n", - fp->script->filename, - js_FramePCToLineNumber(cx, fp), - FramePCOffset(fp), - js_CodeName[fp->imacpc ? *fp->imacpc : *fp->regs->pc], - (void*)lr, - getExitName(lr->exitType), - (long long int)(fp->regs->sp - StackBase(fp)), - calldepth, - (unsigned long long int)cycles); - - /* - * If this trace is part of a tree, later branches might have added - * additional globals for which we don't have any type information - * available in the side exit. We merge in this information from the entry - * type-map. See also the comment in the constructor of TraceRecorder - * regarding why this is always safe to do. - */ - TreeInfo* outermostTree = state.outermostTree; - uint16* gslots = outermostTree->globalSlots->data(); - unsigned ngslots = outermostTree->globalSlots->length(); - JS_ASSERT(ngslots == outermostTree->nGlobalTypes()); - JSTraceType* globalTypeMap; - - /* Are there enough globals? */ - Queue typeMap(0); - if (innermost->numGlobalSlots == ngslots) { - /* Yes. This is the ideal fast path. */ - globalTypeMap = innermost->globalTypeMap(); - } else { - /* - * No. Merge the typemap of the innermost entry and exit together. This - * should always work because it is invalid for nested trees or linked - * trees to have incompatible types. Thus, whenever a new global type - * is lazily added into a tree, all dependent and linked trees are - * immediately specialized (see bug 476653). - */ - JS_ASSERT(innermost->root()->getTreeInfo()->nGlobalTypes() == ngslots); - JS_ASSERT(innermost->root()->getTreeInfo()->nGlobalTypes() > innermost->numGlobalSlots); - typeMap.ensure(ngslots); -#ifdef DEBUG - unsigned check_ngslots = -#endif - BuildGlobalTypeMapFromInnerTree(typeMap, innermost); - JS_ASSERT(check_ngslots == ngslots); - globalTypeMap = typeMap.data(); - } - - /* Write back the topmost native stack frame. */ -#ifdef DEBUG - int slots = -#endif - FlushNativeStackFrame(cx, innermost->calldepth, - innermost->stackTypeMap(), - stack, NULL); - JS_ASSERT(unsigned(slots) == innermost->numStackSlots); - - if (innermost->nativeCalleeWord) - SynthesizeSlowNativeFrame(state, cx, innermost); - - /* Write back interned globals. */ - double* global = (double*)(&state + 1); - FlushNativeGlobalFrame(cx, global, - ngslots, gslots, globalTypeMap); -#ifdef DEBUG - /* Verify that our state restoration worked. */ - for (JSStackFrame* fp = cx->fp; fp; fp = fp->down) { - JS_ASSERT_IF(fp->argv, JSVAL_IS_OBJECT(fp->argv[-1])); - } -#endif -#ifdef JS_JIT_SPEW - if (innermost->exitType != TIMEOUT_EXIT) - AUDIT(sideExitIntoInterpreter); - else - AUDIT(timeoutIntoInterpreter); -#endif - - state.innermost = innermost; -} - -JS_REQUIRES_STACK bool -js_MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount) -{ -#ifdef MOZ_TRACEVIS - TraceVisStateObj tvso(cx, S_MONITOR); -#endif - - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - - /* Is the recorder currently active? */ - if (tm->recorder) { - jsbytecode* innerLoopHeaderPC = cx->fp->regs->pc; - - if (RecordLoopEdge(cx, tm->recorder, inlineCallCount)) - return true; - - /* - * RecordLoopEdge will invoke an inner tree if we have a matching - * one. If we arrive here, that tree didn't run to completion and - * instead we mis-matched or the inner tree took a side exit other than - * the loop exit. We are thus no longer guaranteed to be parked on the - * same loop header js_MonitorLoopEdge was called for. In fact, this - * might not even be a loop header at all. Hence if the program counter - * no longer hovers over the inner loop header, return to the - * interpreter and do not attempt to trigger or record a new tree at - * this location. - */ - if (innerLoopHeaderPC != cx->fp->regs->pc) { -#ifdef MOZ_TRACEVIS - tvso.r = R_INNER_SIDE_EXIT; -#endif - return false; - } - } - JS_ASSERT(!tm->recorder); - - /* Check the pool of reserved doubles (this might trigger a GC). */ - if (tm->reservedDoublePoolPtr < (tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) && - !ReplenishReservedPool(cx, tm)) { -#ifdef MOZ_TRACEVIS - tvso.r = R_DOUBLES; -#endif - return false; /* Out of memory, don't try to record now. */ - } - - /* - * Make sure the shape of the global object still matches (this might flush - * the JIT cache). - */ - JSObject* globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); - uint32 globalShape = -1; - SlotList* globalSlots = NULL; - - if (!CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots)) { - Backoff(cx, cx->fp->regs->pc); - return false; - } - - /* Do not enter the JIT code with a pending operation callback. */ - if (cx->operationCallbackFlag) { -#ifdef MOZ_TRACEVIS - tvso.r = R_CALLBACK_PENDING; -#endif - return false; - } - - jsbytecode* pc = cx->fp->regs->pc; - uint32 argc = cx->fp->argc; - - VMFragment* f = getLoop(tm, pc, globalObj, globalShape, argc); - if (!f) - f = getAnchor(tm, pc, globalObj, globalShape, argc); - - if (!f) { - ResetJIT(cx, FR_OOM); -#ifdef MOZ_TRACEVIS - tvso.r = R_OOM_GETANCHOR; -#endif - return false; - } - - /* - * If we have no code in the anchor and no peers, we definitively won't be - * able to activate any trees, so start compiling. - */ - if (!f->code() && !f->peer) { - record: - if (++f->hits() < HOTLOOP) { -#ifdef MOZ_TRACEVIS - tvso.r = f->hits() < 1 ? R_BACKED_OFF : R_COLD; -#endif - return false; - } - - /* - * We can give RecordTree the root peer. If that peer is already taken, - * it will walk the peer list and find us a free slot or allocate a new - * tree if needed. - */ - bool rv = RecordTree(cx, tm, f->first, NULL, 0, globalObj, globalShape, - globalSlots, argc); -#ifdef MOZ_TRACEVIS - if (!rv) - tvso.r = R_FAIL_RECORD_TREE; -#endif - return rv; - } - - debug_only_printf(LC_TMTracer, - "Looking for compat peer %d@%d, from %p (ip: %p)\n", - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx->fp), (void*)f, f->ip); - - uintN count; - Fragment* match = FindVMCompatiblePeer(cx, globalObj, f, count); - if (!match) { - if (count < MAXPEERS) - goto record; - - /* - * If we hit the max peers ceiling, don't try to lookup fragments all - * the time. That's expensive. This must be a rather type-unstable loop. - */ - debug_only_print0(LC_TMTracer, "Blacklisted: too many peer trees.\n"); - Blacklist((jsbytecode*) f->root->ip); -#ifdef MOZ_TRACEVIS - tvso.r = R_MAX_PEERS; -#endif - return false; - } - - VMSideExit* lr = NULL; - VMSideExit* innermostNestedGuard = NULL; - - lr = ExecuteTree(cx, match, inlineCallCount, &innermostNestedGuard); - if (!lr) { -#ifdef MOZ_TRACEVIS - tvso.r = R_FAIL_EXECUTE_TREE; -#endif - return false; - } - - /* - * If we exit on a branch, or on a tree call guard, try to grow the inner - * tree (in case of a branch exit), or the tree nested around the tree we - * exited from (in case of the tree call guard). - */ - bool rv; - switch (lr->exitType) { - case UNSTABLE_LOOP_EXIT: - rv = AttemptToStabilizeTree(cx, globalObj, lr, NULL, NULL); -#ifdef MOZ_TRACEVIS - if (!rv) - tvso.r = R_FAIL_STABILIZE; -#endif - return rv; - - case OVERFLOW_EXIT: - oracle.markInstructionUndemotable(cx->fp->regs->pc); - /* FALL THROUGH */ - case BRANCH_EXIT: - case CASE_EXIT: - return AttemptToExtendTree(cx, lr, NULL, NULL -#ifdef MOZ_TRACEVIS - , &tvso -#endif - ); - - case LOOP_EXIT: - if (innermostNestedGuard) - return AttemptToExtendTree(cx, innermostNestedGuard, lr, NULL -#ifdef MOZ_TRACEVIS - , &tvso -#endif - ); -#ifdef MOZ_TRACEVIS - tvso.r = R_NO_EXTEND_OUTER; -#endif - return false; - -#ifdef MOZ_TRACEVIS - case MISMATCH_EXIT: tvso.r = R_MISMATCH_EXIT; return false; - case OOM_EXIT: tvso.r = R_OOM_EXIT; return false; - case TIMEOUT_EXIT: tvso.r = R_TIMEOUT_EXIT; return false; - case DEEP_BAIL_EXIT: tvso.r = R_DEEP_BAIL_EXIT; return false; - case STATUS_EXIT: tvso.r = R_STATUS_EXIT; return false; -#endif - - default: - /* - * No, this was an unusual exit (i.e. out of memory/GC), so just resume - * interpretation. - */ -#ifdef MOZ_TRACEVIS - tvso.r = R_OTHER_EXIT; -#endif - return false; - } -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::monitorRecording(JSContext* cx, TraceRecorder* tr, JSOp op) -{ - Assembler *assm = JS_TRACE_MONITOR(cx).assembler; - JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx); - - /* Process needFlush requests now. */ - if (localtm.needFlush) { - ResetJIT(cx, FR_DEEP_BAIL); - return JSRS_STOP; - } - JS_ASSERT(!tr->fragment->lastIns); - - /* - * Clear one-shot state used to communicate between record_JSOP_CALL and post- - * opcode-case-guts record hook (record_NativeCallComplete). - */ - tr->pendingSpecializedNative = NULL; - tr->newobj_ins = NULL; - - /* Handle one-shot request from finishGetProp or INSTANCEOF to snapshot post-op state and guard. */ - if (tr->pendingGuardCondition) { - tr->guard(true, tr->pendingGuardCondition, STATUS_EXIT); - tr->pendingGuardCondition = NULL; - } - - /* Handle one-shot request to unbox the result of a property get. */ - if (tr->pendingUnboxSlot) { - LIns* val_ins = tr->get(tr->pendingUnboxSlot); - val_ins = tr->unbox_jsval(*tr->pendingUnboxSlot, val_ins, tr->snapshot(BRANCH_EXIT)); - tr->set(tr->pendingUnboxSlot, val_ins); - tr->pendingUnboxSlot = 0; - } - - debug_only_stmt( - if (js_LogController.lcbits & LC_TMRecorder) { - js_Disassemble1(cx, cx->fp->script, cx->fp->regs->pc, - cx->fp->imacpc - ? 0 : cx->fp->regs->pc - cx->fp->script->code, - !cx->fp->imacpc, stdout); - } - ) - - /* - * If op is not a break or a return from a loop, continue recording and - * follow the trace. We check for imacro-calling bytecodes inside each - * switch case to resolve the if (JSOP_IS_IMACOP(x)) conditions at compile - * time. - */ - - JSRecordingStatus status; -#ifdef DEBUG - bool wasInImacro = (cx->fp->imacpc != NULL); -#endif - switch (op) { - default: - status = JSRS_ERROR; - goto stop_recording; -# define OPDEF(x,val,name,token,length,nuses,ndefs,prec,format) \ - case x: \ - status = tr->record_##x(); \ - if (JSOP_IS_IMACOP(x)) \ - goto imacro; \ - break; -# include "jsopcode.tbl" -# undef OPDEF - } - - /* record_JSOP_X can reenter the interpreter and kill |tr|. */ - if (!localtm.recorder) - return JSRS_STOP; - - JS_ASSERT(status != JSRS_IMACRO); - JS_ASSERT_IF(!wasInImacro, cx->fp->imacpc == NULL); - - if (assm->error()) { - js_AbortRecording(cx, "error during recording"); - return JSRS_STOP; - } - - if (tr->outOfMemory() || js_OverfullJITCache(&localtm)) { - js_AbortRecording(cx, "no more memory"); - ResetJIT(cx, FR_OOM); - return JSRS_STOP; - } - - imacro: - /* record_JSOP_X can reenter the interpreter and kill |tr|. */ - if (!localtm.recorder) - return JSRS_STOP; - - if (!STATUS_ABORTS_RECORDING(status)) - return status; - - stop_recording: - /* If we recorded the end of the trace, destroy the recorder now. */ - if (tr->fragment->lastIns) { - DeleteRecorder(cx); - return status; - } - - /* Looks like we encountered an error condition. Abort recording. */ - js_AbortRecording(cx, js_CodeName[op]); - return status; -} - -JS_REQUIRES_STACK void -js_AbortRecording(JSContext* cx, const char* reason) -{ - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - JS_ASSERT(tm->recorder != NULL); - - /* Abort the trace and blacklist its starting point. */ - Fragment* f = tm->recorder->getFragment(); - - /* - * If the recorder already had its fragment disposed, or we actually - * finished recording and this recorder merely is passing through the deep - * abort state to the next recorder on the stack, just destroy the - * recorder. There is nothing to abort. - */ - if (!f || f->lastIns) { - DeleteRecorder(cx); - return; - } - - AUDIT(recorderAborted); - - JS_ASSERT(!f->vmprivate); -#ifdef DEBUG - TreeInfo* ti = tm->recorder->getTreeInfo(); - debug_only_printf(LC_TMAbort, - "Abort recording of tree %s:%d@%d at %s:%d@%d: %s.\n", - ti->treeFileName, - ti->treeLineNumber, - ti->treePCOffset, - cx->fp->script->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx->fp), - reason); -#endif - - Backoff(cx, (jsbytecode*) f->root->ip, f->root); - - /* If DeleteRecorder flushed the code cache, we can't rely on f any more. */ - if (!DeleteRecorder(cx)) - return; - - /* - * If this is the primary trace and we didn't succeed compiling, trash the - * TreeInfo object. - */ - if (!f->code() && (f->root == f)) - TrashTree(cx, f); -} - -#if defined NANOJIT_IA32 -static bool -CheckForSSE2() -{ - char *c = getenv("X86_FORCE_SSE2"); - if (c) - return (!strcmp(c, "true") || - !strcmp(c, "1") || - !strcmp(c, "yes")); - - int features = 0; -#if defined _MSC_VER - __asm - { - pushad - mov eax, 1 - cpuid - mov features, edx - popad - } -#elif defined __GNUC__ - asm("xchg %%esi, %%ebx\n" /* we can't clobber ebx on gcc (PIC register) */ - "mov $0x01, %%eax\n" - "cpuid\n" - "mov %%edx, %0\n" - "xchg %%esi, %%ebx\n" - : "=m" (features) - : /* We have no inputs */ - : "%eax", "%esi", "%ecx", "%edx" - ); -#elif defined __SUNPRO_C || defined __SUNPRO_CC - asm("push %%ebx\n" - "mov $0x01, %%eax\n" - "cpuid\n" - "pop %%ebx\n" - : "=d" (features) - : /* We have no inputs */ - : "%eax", "%ecx" - ); -#endif - return (features & (1<<26)) != 0; -} -#endif - -#if defined(NANOJIT_ARM) - -#if defined(_MSC_VER) && defined(WINCE) - -// these come in from jswince.asm -extern "C" int js_arm_try_thumb_op(); -extern "C" int js_arm_try_armv6t2_op(); -extern "C" int js_arm_try_armv5_op(); -extern "C" int js_arm_try_armv6_op(); -extern "C" int js_arm_try_armv7_op(); -extern "C" int js_arm_try_vfp_op(); - -static bool -js_arm_check_thumb() { - bool ret = false; - __try { - js_arm_try_thumb_op(); - ret = true; - } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { - ret = false; - } - return ret; -} - -static bool -js_arm_check_thumb2() { - bool ret = false; - __try { - js_arm_try_armv6t2_op(); - ret = true; - } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { - ret = false; - } - return ret; -} - -static unsigned int -js_arm_check_arch() { - unsigned int arch = 4; - __try { - js_arm_try_armv5_op(); - arch = 5; - js_arm_try_armv6_op(); - arch = 6; - js_arm_try_armv7_op(); - arch = 7; - } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { - } - return arch; -} - -static bool -js_arm_check_vfp() { -#ifdef WINCE_WINDOWS_MOBILE - return false; -#else - bool ret = false; - __try { - js_arm_try_vfp_op(); - ret = true; - } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { - ret = false; - } - return ret; -#endif -} - -#define HAVE_ENABLE_DISABLE_DEBUGGER_EXCEPTIONS 1 - -/* See "Suppressing Exception Notifications while Debugging", at - * http://msdn.microsoft.com/en-us/library/ms924252.aspx - */ -static void -js_disable_debugger_exceptions() -{ - // 2 == TLSSLOT_KERNEL - DWORD kctrl = (DWORD) TlsGetValue(2); - // 0x12 = TLSKERN_NOFAULT | TLSKERN_NOFAULTMSG - kctrl |= 0x12; - TlsSetValue(2, (LPVOID) kctrl); -} - -static void -js_enable_debugger_exceptions() -{ - // 2 == TLSSLOT_KERNEL - DWORD kctrl = (DWORD) TlsGetValue(2); - // 0x12 = TLSKERN_NOFAULT | TLSKERN_NOFAULTMSG - kctrl &= ~0x12; - TlsSetValue(2, (LPVOID) kctrl); -} - -#elif defined(__GNUC__) && defined(AVMPLUS_LINUX) - -#include -#include -#include -#include -#include -#include -#include -#include - -// Assume ARMv4 by default. -static unsigned int arm_arch = 4; -static bool arm_has_thumb = false; -static bool arm_has_vfp = false; -static bool arm_has_neon = false; -static bool arm_has_iwmmxt = false; -static bool arm_tests_initialized = false; - -static void -arm_read_auxv() { - int fd; - Elf32_auxv_t aux; - - fd = open("/proc/self/auxv", O_RDONLY); - if (fd > 0) { - while (read(fd, &aux, sizeof(Elf32_auxv_t))) { - if (aux.a_type == AT_HWCAP) { - uint32_t hwcap = aux.a_un.a_val; - if (getenv("ARM_FORCE_HWCAP")) - hwcap = strtoul(getenv("ARM_FORCE_HWCAP"), NULL, 0); - else if (getenv("_SBOX_DIR")) - continue; // Ignore the rest, if we're running in scratchbox - // hardcode these values to avoid depending on specific versions - // of the hwcap header, e.g. HWCAP_NEON - arm_has_thumb = (hwcap & 4) != 0; - arm_has_vfp = (hwcap & 64) != 0; - arm_has_iwmmxt = (hwcap & 512) != 0; - // this flag is only present on kernel 2.6.29 - arm_has_neon = (hwcap & 4096) != 0; - } else if (aux.a_type == AT_PLATFORM) { - const char *plat = (const char*) aux.a_un.a_val; - if (getenv("ARM_FORCE_PLATFORM")) - plat = getenv("ARM_FORCE_PLATFORM"); - else if (getenv("_SBOX_DIR")) - continue; // Ignore the rest, if we're running in scratchbox - // The platform string has the form "v[0-9][lb]". The "l" or "b" indicate little- - // or big-endian variants and the digit indicates the version of the platform. - // We can only accept ARMv4 and above, but allow anything up to ARMv9 for future - // processors. Architectures newer than ARMv7 are assumed to be - // backwards-compatible with ARMv7. - if ((plat[0] == 'v') && - (plat[1] >= '4') && (plat[1] <= '9') && - ((plat[2] == 'l') || (plat[2] == 'b'))) - { - arm_arch = plat[1] - '0'; - } - } - } - close (fd); - - // if we don't have 2.6.29, we have to do this hack; set - // the env var to trust HWCAP. - if (!getenv("ARM_TRUST_HWCAP") && (arm_arch >= 7)) - arm_has_neon = true; - } - - arm_tests_initialized = true; -} - -static bool -js_arm_check_thumb() { - if (!arm_tests_initialized) - arm_read_auxv(); - - return arm_has_thumb; -} - -static bool -js_arm_check_thumb2() { - if (!arm_tests_initialized) - arm_read_auxv(); - - // ARMv6T2 also supports Thumb2, but Linux doesn't provide an easy way to test for this as - // there is no associated bit in auxv. ARMv7 always supports Thumb2, and future architectures - // are assumed to be backwards-compatible. - return (arm_arch >= 7); -} - -static unsigned int -js_arm_check_arch() { - if (!arm_tests_initialized) - arm_read_auxv(); - - return arm_arch; -} - -static bool -js_arm_check_vfp() { - if (!arm_tests_initialized) - arm_read_auxv(); - - return arm_has_vfp; -} - -#else -#warning Not sure how to check for architecture variant on your platform. Assuming ARMv4. -static bool -js_arm_check_thumb() { return false; } -static bool -js_arm_check_thumb2() { return false; } -static unsigned int -js_arm_check_arch() { return 4; } -static bool -js_arm_check_vfp() { return false; } -#endif - -#ifndef HAVE_ENABLE_DISABLE_DEBUGGER_EXCEPTIONS -static void -js_enable_debugger_exceptions() { } -static void -js_disable_debugger_exceptions() { } -#endif - -#endif /* NANOJIT_ARM */ - -#define K *1024 -#define M K K -#define G K M - -void -js_SetMaxCodeCacheBytes(JSContext* cx, uint32 bytes) -{ - JSTraceMonitor* tm = &JS_THREAD_DATA(cx)->traceMonitor; - JS_ASSERT(tm->codeAlloc && tm->dataAlloc); - if (bytes > 1 G) - bytes = 1 G; - if (bytes < 128 K) - bytes = 128 K; - tm->maxCodeCacheBytes = bytes; -} - -void -js_InitJIT(JSTraceMonitor *tm) -{ -#if defined JS_JIT_SPEW - tm->profAlloc = NULL; - /* Set up debug logging. */ - if (!did_we_set_up_debug_logging) { - InitJITLogController(); - did_we_set_up_debug_logging = true; - } - /* Set up fragprofiling, if required. */ - if (js_LogController.lcbits & LC_FragProfile) { - tm->profAlloc = new VMAllocator(); - tm->profTab = new (*tm->profAlloc) FragStatsMap(*tm->profAlloc); - } - tm->lastFragID = 0; -#else - memset(&js_LogController, 0, sizeof(js_LogController)); -#endif - - if (!did_we_check_processor_features) { -#if defined NANOJIT_IA32 - avmplus::AvmCore::config.use_cmov = - avmplus::AvmCore::config.sse2 = CheckForSSE2(); -#endif -#if defined NANOJIT_ARM - - js_disable_debugger_exceptions(); - - bool arm_vfp = js_arm_check_vfp(); - bool arm_thumb = js_arm_check_thumb(); - bool arm_thumb2 = js_arm_check_thumb2(); - unsigned int arm_arch = js_arm_check_arch(); - - js_enable_debugger_exceptions(); - - avmplus::AvmCore::config.vfp = arm_vfp; - avmplus::AvmCore::config.soft_float = !arm_vfp; - avmplus::AvmCore::config.thumb = arm_thumb; - avmplus::AvmCore::config.thumb2 = arm_thumb2; - avmplus::AvmCore::config.arch = arm_arch; - - // Sanity-check the configuration detection. - // * We don't understand architectures prior to ARMv4. - JS_ASSERT(arm_arch >= 4); - // * All architectures support Thumb with the possible exception of ARMv4. - JS_ASSERT((arm_thumb) || (arm_arch == 4)); - // * Only ARMv6T2 and ARMv7(+) support Thumb2, but ARMv6 does not. - JS_ASSERT((arm_thumb2) || (arm_arch <= 6)); - // * All architectures that support Thumb2 also support Thumb. - JS_ASSERT((arm_thumb2 && arm_thumb) || (!arm_thumb2)); -#endif - did_we_check_processor_features = true; - } - - /* Set the default size for the code cache to 16MB. */ - tm->maxCodeCacheBytes = 16 M; - - if (!tm->recordAttempts.ops) { - JS_DHashTableInit(&tm->recordAttempts, JS_DHashGetStubOps(), - NULL, sizeof(PCHashEntry), - JS_DHASH_DEFAULT_CAPACITY(PC_HASH_COUNT)); - } - - JS_ASSERT(!tm->dataAlloc && !tm->codeAlloc); - tm->dataAlloc = new VMAllocator(); - tm->tempAlloc = new VMAllocator(); - tm->reTempAlloc = new VMAllocator(); - tm->codeAlloc = new CodeAlloc(); - tm->flush(); - verbose_only( tm->branches = NULL; ) - - JS_ASSERT(!tm->reservedDoublePool); - tm->reservedDoublePoolPtr = tm->reservedDoublePool = new jsval[MAX_NATIVE_STACK_SLOTS]; - -#if !defined XP_WIN - debug_only(memset(&jitstats, 0, sizeof(jitstats))); -#endif - -#ifdef JS_JIT_SPEW - /* Architecture properties used by test cases. */ - jitstats.archIsIA32 = 0; - jitstats.archIs64BIT = 0; - jitstats.archIsARM = 0; - jitstats.archIsSPARC = 0; - jitstats.archIsPPC = 0; -#if defined NANOJIT_IA32 - jitstats.archIsIA32 = 1; -#endif -#if defined NANOJIT_64BIT - jitstats.archIs64BIT = 1; -#endif -#if defined NANOJIT_ARM - jitstats.archIsARM = 1; -#endif -#if defined NANOJIT_SPARC - jitstats.archIsSPARC = 1; -#endif -#if defined NANOJIT_PPC - jitstats.archIsPPC = 1; -#endif -#if defined NANOJIT_X64 - jitstats.archIsAMD64 = 1; -#endif -#endif -} - -void -js_FinishJIT(JSTraceMonitor *tm) -{ -#ifdef JS_JIT_SPEW - if (jitstats.recorderStarted) { - char sep = ':'; - debug_only_print0(LC_TMStats, "recorder"); -#define RECORDER_JITSTAT(_ident, _name) \ - debug_only_printf(LC_TMStats, "%c " _name "(%llu)", sep, \ - (unsigned long long int)jitstats._ident); \ - sep = ','; -#define JITSTAT(x) /* nothing */ -#include "jitstats.tbl" -#undef JITSTAT -#undef RECORDER_JITSTAT - debug_only_print0(LC_TMStats, "\n"); - - sep = ':'; - debug_only_print0(LC_TMStats, "monitor"); -#define MONITOR_JITSTAT(_ident, _name) \ - debug_only_printf(LC_TMStats, "%c " _name "(%llu)", sep, \ - (unsigned long long int)jitstats._ident); \ - sep = ','; -#define JITSTAT(x) /* nothing */ -#include "jitstats.tbl" -#undef JITSTAT -#undef MONITOR_JITSTAT - debug_only_print0(LC_TMStats, "\n"); - } -#endif - JS_ASSERT(tm->reservedDoublePool); - - if (tm->recordAttempts.ops) - JS_DHashTableFinish(&tm->recordAttempts); - -#ifdef DEBUG - // Recover profiling data from expiring Fragments, and display - // final results. - if (js_LogController.lcbits & LC_FragProfile) { - for (Seq* f = tm->branches; f; f = f->tail) { - js_FragProfiling_FragFinalizer(f->head, tm); - } - for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { - for (VMFragment *f = tm->vmfragments[i]; f; f = f->next) { - JS_ASSERT(f->root == f); - for (VMFragment *p = f; p; p = p->peer) - js_FragProfiling_FragFinalizer(p, tm); - } - } - REHashMap::Iter iter(*(tm->reFragments)); - while (iter.next()) { - nanojit::Fragment* frag = iter.value(); - js_FragProfiling_FragFinalizer(frag, tm); - } - - js_FragProfiling_showResults(tm); - delete tm->profAlloc; - - } else { - NanoAssert(!tm->profTab); - NanoAssert(!tm->profAlloc); - } -#endif - - memset(&tm->vmfragments[0], 0, FRAGMENT_TABLE_SIZE * sizeof(VMFragment*)); - - delete[] tm->reservedDoublePool; - tm->reservedDoublePool = tm->reservedDoublePoolPtr = NULL; - - if (tm->codeAlloc) { - delete tm->codeAlloc; - tm->codeAlloc = NULL; - } - - if (tm->dataAlloc) { - delete tm->dataAlloc; - tm->dataAlloc = NULL; - } - - if (tm->tempAlloc) { - delete tm->tempAlloc; - tm->tempAlloc = NULL; - } - - if (tm->reTempAlloc) { - delete tm->reTempAlloc; - tm->reTempAlloc = NULL; - } -} - -void -js_PurgeJITOracle() -{ - oracle.clear(); -} - -static JSDHashOperator -PurgeScriptRecordingAttempts(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, void *arg) -{ - PCHashEntry *e = (PCHashEntry *)hdr; - JSScript *script = (JSScript *)arg; - jsbytecode *pc = (jsbytecode *)e->key; - - if (JS_UPTRDIFF(pc, script->code) < script->length) - return JS_DHASH_REMOVE; - return JS_DHASH_NEXT; -} - - -JS_REQUIRES_STACK void -js_PurgeScriptFragments(JSContext* cx, JSScript* script) -{ - if (!TRACING_ENABLED(cx)) - return; - debug_only_printf(LC_TMTracer, - "Purging fragments for JSScript %p.\n", (void*)script); - - JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { - VMFragment** fragp = &tm->vmfragments[i]; - while (VMFragment* frag = *fragp) { - if (JS_UPTRDIFF(frag->ip, script->code) < script->length) { - /* This fragment is associated with the script. */ - debug_only_printf(LC_TMTracer, - "Disconnecting VMFragment %p " - "with ip %p, in range [%p,%p).\n", - (void*)frag, frag->ip, script->code, - script->code + script->length); - - JS_ASSERT(frag->root == frag); - *fragp = frag->next; - do { - verbose_only( js_FragProfiling_FragFinalizer(frag, tm); ) - TrashTree(cx, frag); - } while ((frag = frag->peer) != NULL); - continue; - } - fragp = &frag->next; - } - } - - JS_DHashTableEnumerate(&tm->recordAttempts, PurgeScriptRecordingAttempts, script); -} - -bool -js_OverfullJITCache(JSTraceMonitor* tm) -{ - /* - * You might imagine the outOfMemory flag on the allocator is sufficient - * to model the notion of "running out of memory", but there are actually - * two separate issues involved: - * - * 1. The process truly running out of memory: malloc() or mmap() - * failed. - * - * 2. The limit we put on the "intended size" of the tracemonkey code - * cache, in pages, has been exceeded. - * - * Condition 1 doesn't happen very often, but we're obliged to try to - * safely shut down and signal the rest of spidermonkey when it - * does. Condition 2 happens quite regularly. - * - * Presently, the code in this file doesn't check the outOfMemory condition - * often enough, and frequently misuses the unchecked results of - * lirbuffer insertions on the asssumption that it will notice the - * outOfMemory flag "soon enough" when it returns to the monitorRecording - * function. This turns out to be a false assumption if we use outOfMemory - * to signal condition 2: we regularly provoke "passing our intended - * size" and regularly fail to notice it in time to prevent writing - * over the end of an artificially self-limited LIR buffer. - * - * To mitigate, though not completely solve, this problem, we're - * modeling the two forms of memory exhaustion *separately* for the - * time being: condition 1 is handled by the outOfMemory flag inside - * nanojit, and condition 2 is being handled independently *here*. So - * we construct our allocators to use all available memory they like, - * and only report outOfMemory to us when there is literally no OS memory - * left. Merely purging our cache when we hit our highwater mark is - * handled by the (few) callers of this function. - * - */ - jsuint maxsz = tm->maxCodeCacheBytes; - VMAllocator *dataAlloc = tm->dataAlloc; - CodeAlloc *codeAlloc = tm->codeAlloc; - - return (codeAlloc->size() + dataAlloc->size() > maxsz); -} - -JS_FORCES_STACK JS_FRIEND_API(void) -js_DeepBail(JSContext *cx) -{ - JS_ASSERT(JS_ON_TRACE(cx)); - - /* - * Exactly one context on the current thread is on trace. Find out which - * one. (Most callers cannot guarantee that it's cx.) - */ - JSTraceMonitor *tm = &JS_TRACE_MONITOR(cx); - JSContext *tracecx = tm->tracecx; - - /* It's a bug if a non-FAIL_STATUS builtin gets here. */ - JS_ASSERT(tracecx->bailExit); - - tm->tracecx = NULL; - debug_only_print0(LC_TMTracer, "Deep bail.\n"); - LeaveTree(*tracecx->interpState, tracecx->bailExit); - tracecx->bailExit = NULL; - - InterpState* state = tracecx->interpState; - state->builtinStatus |= JSBUILTIN_BAILED; - state->deepBailSp = state->sp; -} - -JS_REQUIRES_STACK jsval& -TraceRecorder::argval(unsigned n) const -{ - JS_ASSERT(n < cx->fp->fun->nargs); - return cx->fp->argv[n]; -} - -JS_REQUIRES_STACK jsval& -TraceRecorder::varval(unsigned n) const -{ - JS_ASSERT(n < cx->fp->script->nslots); - return cx->fp->slots[n]; -} - -JS_REQUIRES_STACK jsval& -TraceRecorder::stackval(int n) const -{ - jsval* sp = cx->fp->regs->sp; - return sp[n]; -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::scopeChain() const -{ - return lir->insLoad(LIR_ldp, - lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp)), - offsetof(JSStackFrame, scopeChain)); -} - -/* - * Return the frame of a call object if that frame is part of the current - * trace. |depthp| is an optional outparam: if it is non-null, it will be - * filled in with the depth of the call object's frame relevant to cx->fp. - */ -JS_REQUIRES_STACK JSStackFrame* -TraceRecorder::frameIfInRange(JSObject* obj, unsigned* depthp) const -{ - JSStackFrame* ofp = (JSStackFrame*) obj->getPrivate(); - JSStackFrame* fp = cx->fp; - for (unsigned depth = 0; depth <= callDepth; ++depth) { - if (fp == ofp) { - if (depthp) - *depthp = depth; - return ofp; - } - if (!(fp = fp->down)) - break; - } - return NULL; -} - -JS_DEFINE_CALLINFO_4(extern, UINT32, GetClosureVar, CONTEXT, OBJECT, CVIPTR, DOUBLEPTR, 0, 0) -JS_DEFINE_CALLINFO_4(extern, UINT32, GetClosureArg, CONTEXT, OBJECT, CVIPTR, DOUBLEPTR, 0, 0) - -/* - * Search the scope chain for a property lookup operation at the current PC and - * generate LIR to access the given property. Return JSRS_CONTINUE on success, - * otherwise abort and return JSRS_STOP. There are 3 outparams: - * - * vp the address of the current property value - * ins LIR instruction representing the property value on trace - * NameResult describes how to look up name; see comment for NameResult in jstracer.h - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::scopeChainProp(JSObject* obj, jsval*& vp, LIns*& ins, NameResult& nr) -{ - JS_ASSERT(obj != globalObj); - - JSTraceMonitor &localtm = *traceMonitor; - - JSAtom* atom = atoms[GET_INDEX(cx->fp->regs->pc)]; - JSObject* obj2; - JSProperty* prop; - bool ok = js_FindProperty(cx, ATOM_TO_JSID(atom), &obj, &obj2, &prop); - - /* js_FindProperty can reenter the interpreter and kill |this|. */ - if (!localtm.recorder) - return JSRS_STOP; - - if (!ok) - ABORT_TRACE_ERROR("error in js_FindProperty"); - - if (!prop) - ABORT_TRACE("failed to find name in non-global scope chain"); - - if (obj == globalObj) { - // Even if the property is on the global object, we must guard against - // the creation of properties that shadow the property in the middle - // of the scope chain if we are in a function. - if (cx->fp->argv) { - LIns* obj_ins; - JSObject* parent = STOBJ_GET_PARENT(JSVAL_TO_OBJECT(cx->fp->argv[-2])); - LIns* parent_ins = stobj_get_parent(get(&cx->fp->argv[-2])); - CHECK_STATUS(traverseScopeChain(parent, parent_ins, obj, obj_ins)); - } - - JSScopeProperty* sprop = (JSScopeProperty*) prop; - - if (obj2 != obj) { - obj2->dropProperty(cx, prop); - ABORT_TRACE("prototype property"); - } - if (!isValidSlot(OBJ_SCOPE(obj), sprop)) { - obj2->dropProperty(cx, prop); - return JSRS_STOP; - } - if (!lazilyImportGlobalSlot(sprop->slot)) { - obj2->dropProperty(cx, prop); - ABORT_TRACE("lazy import of global slot failed"); - } - vp = &STOBJ_GET_SLOT(obj, sprop->slot); - ins = get(vp); - obj2->dropProperty(cx, prop); - nr.tracked = true; - return JSRS_CONTINUE; - } - - if (obj == obj2 && OBJ_GET_CLASS(cx, obj) == &js_CallClass) - return callProp(obj, obj2, prop, ATOM_TO_JSID(atom), vp, ins, nr); - - obj2->dropProperty(cx, prop); - ABORT_TRACE("fp->scopeChain is not global or active call object"); -} - -/* - * Generate LIR to access a property of a Call object. - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::callProp(JSObject* obj, JSObject* obj2, JSProperty* prop, jsid id, jsval*& vp, - LIns*& ins, NameResult& nr) -{ - JSScopeProperty *sprop = (JSScopeProperty*) prop; - - uint32 setflags = (js_CodeSpec[*cx->fp->regs->pc].format & (JOF_SET | JOF_INCDEC | JOF_FOR)); - if (setflags && (sprop->attrs & JSPROP_READONLY)) - ABORT_TRACE("writing to a read-only property"); - - uintN slot = sprop->shortid; - - vp = NULL; - uintN upvar_slot = SPROP_INVALID_SLOT; - JSStackFrame* cfp = (JSStackFrame*) obj->getPrivate(); - if (cfp) { - if (sprop->getter == js_GetCallArg) { - JS_ASSERT(slot < cfp->fun->nargs); - vp = &cfp->argv[slot]; - upvar_slot = slot; - nr.v = *vp; - } else if (sprop->getter == js_GetCallVar) { - JS_ASSERT(slot < cfp->script->nslots); - vp = &cfp->slots[slot]; - upvar_slot = cx->fp->fun->nargs + slot; - nr.v = *vp; - } else { - ABORT_TRACE("dynamic property of Call object"); - } - obj2->dropProperty(cx, prop); - - if (frameIfInRange(obj)) { - // At this point we are guaranteed to be looking at an active call oject - // whose properties are stored in the corresponding JSStackFrame. - ins = get(vp); - nr.tracked = true; - return JSRS_CONTINUE; - } - } else { -#ifdef DEBUG - JSBool rv = -#endif - js_GetPropertyHelper(cx, obj, sprop->id, JS_FALSE, &nr.v); - JS_ASSERT(rv); - obj2->dropProperty(cx, prop); - } - - LIns* obj_ins; - JSObject* parent = STOBJ_GET_PARENT(JSVAL_TO_OBJECT(cx->fp->argv[-2])); - LIns* parent_ins = stobj_get_parent(get(&cx->fp->argv[-2])); - CHECK_STATUS(traverseScopeChain(parent, parent_ins, obj, obj_ins)); - - ClosureVarInfo* cv = new (traceMonitor->dataAlloc) ClosureVarInfo(); - cv->id = id; - cv->slot = slot; - cv->callDepth = callDepth; - cv->resolveFlags = cx->resolveFlags == JSRESOLVE_INFER - ? js_InferFlags(cx, 0) - : cx->resolveFlags; - - LIns* outp = lir->insAlloc(sizeof(double)); - LIns* args[] = { - outp, - INS_CONSTPTR(cv), - obj_ins, - cx_ins - }; - const CallInfo* ci; - if (sprop->getter == js_GetCallArg) - ci = &GetClosureArg_ci; - else - ci = &GetClosureVar_ci; - - LIns* call_ins = lir->insCall(ci, args); - JSTraceType type = getCoercedType(nr.v); - guard(true, - addName(lir->ins2(LIR_eq, call_ins, lir->insImm(type)), - "guard(type-stable name access)"), - BRANCH_EXIT); - ins = stackLoad(outp, type); - nr.tracked = false; - nr.obj = obj; - nr.obj_ins = obj_ins; - nr.sprop = sprop; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::arg(unsigned n) -{ - return get(&argval(n)); -} - -JS_REQUIRES_STACK void -TraceRecorder::arg(unsigned n, LIns* i) -{ - set(&argval(n), i); -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::var(unsigned n) -{ - return get(&varval(n)); -} - -JS_REQUIRES_STACK void -TraceRecorder::var(unsigned n, LIns* i) -{ - set(&varval(n), i); -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::stack(int n) -{ - return get(&stackval(n)); -} - -JS_REQUIRES_STACK void -TraceRecorder::stack(int n, LIns* i) -{ - set(&stackval(n), i, n >= 0); -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::alu(LOpcode v, jsdouble v0, jsdouble v1, LIns* s0, LIns* s1) -{ - /* - * To even consider this operation for demotion, both operands have to be - * integers and the oracle must not give us a negative hint for the - * instruction. - */ - if (oracle.isInstructionUndemotable(cx->fp->regs->pc) || !isPromoteInt(s0) || !isPromoteInt(s1)) { - out: - if (v == LIR_fmod) { - LIns* args[] = { s1, s0 }; - return lir->insCall(&js_dmod_ci, args); - } - LIns* result = lir->ins2(v, s0, s1); - JS_ASSERT_IF(s0->isconstf() && s1->isconstf(), result->isconstf()); - return result; - } - - jsdouble r; - switch (v) { - case LIR_fadd: - r = v0 + v1; - break; - case LIR_fsub: - r = v0 - v1; - break; -#if !defined NANOJIT_ARM - case LIR_fmul: - r = v0 * v1; - if (r == 0.0) - goto out; - break; -#endif -#if defined NANOJIT_IA32 || defined NANOJIT_X64 - case LIR_fdiv: - if (v1 == 0) - goto out; - r = v0 / v1; - break; - case LIR_fmod: - if (v0 < 0 || v1 == 0 || (s1->isconstf() && v1 < 0)) - goto out; - r = js_dmod(v0, v1); - break; -#endif - default: - goto out; - } - - /* - * The result must be an integer at record time, otherwise there is no - * point in trying to demote it. - */ - if (jsint(r) != r || JSDOUBLE_IS_NEGZERO(r)) - goto out; - - LIns* d0 = ::demote(lir, s0); - LIns* d1 = ::demote(lir, s1); - - /* - * Speculatively emit an integer operation, betting that at runtime we - * will get integer results again. - */ - VMSideExit* exit; - LIns* result; - switch (v) { -#if defined NANOJIT_IA32 || defined NANOJIT_X64 - case LIR_fdiv: - if (d0->isconst() && d1->isconst()) - return lir->ins1(LIR_i2f, lir->insImm(jsint(r))); - - exit = snapshot(OVERFLOW_EXIT); - - /* - * If the divisor is greater than zero its always safe to execute - * the division. If not, we have to make sure we are not running - * into -2147483648 / -1, because it can raise an overflow exception. - */ - if (!d1->isconst()) { - LIns* gt = lir->insBranch(LIR_jt, lir->ins2i(LIR_gt, d1, 0), NULL); - guard(false, lir->ins_eq0(d1), exit); - guard(false, lir->ins2(LIR_and, - lir->ins2i(LIR_eq, d0, 0x80000000), - lir->ins2i(LIR_eq, d1, -1)), exit); - gt->setTarget(lir->ins0(LIR_label)); - } else { - if (d1->imm32() == -1) - guard(false, lir->ins2i(LIR_eq, d0, 0x80000000), exit); - } - result = lir->ins2(v = LIR_div, d0, d1); - - /* As long the modulus is zero, the result is an integer. */ - guard(true, lir->ins_eq0(lir->ins1(LIR_mod, result)), exit); - - /* Don't lose a -0. */ - guard(false, lir->ins_eq0(result), exit); - break; - - case LIR_fmod: { - if (d0->isconst() && d1->isconst()) - return lir->ins1(LIR_i2f, lir->insImm(jsint(r))); - - exit = snapshot(OVERFLOW_EXIT); - - /* Make sure we don't trigger division by zero at runtime. */ - if (!d1->isconst()) - guard(false, lir->ins_eq0(d1), exit); - result = lir->ins1(v = LIR_mod, lir->ins2(LIR_div, d0, d1)); - - /* If the result is not 0, it is always within the integer domain. */ - LIns* branch = lir->insBranch(LIR_jf, lir->ins_eq0(result), NULL); - - /* - * If the result is zero, we must exit if the lhs is negative since - * the result is -0 in this case, which is not in the integer domain. - */ - guard(false, lir->ins2i(LIR_lt, d0, 0), exit); - branch->setTarget(lir->ins0(LIR_label)); - break; - } -#endif - - default: - v = (LOpcode)((int)v & ~LIR64); - result = lir->ins2(v, d0, d1); - - /* - * If the operands guarantee that the result will be an integer (i.e. - * z = x + y with 0 <= (x|y) <= 0xffff guarantees z <= fffe0001), we - * don't have to guard against an overflow. Otherwise we emit a guard - * that will inform the oracle and cause a non-demoted trace to be - * attached that uses floating-point math for this operation. - */ - if (!result->isconst() && (!IsOverflowSafe(v, d0) || !IsOverflowSafe(v, d1))) { - exit = snapshot(OVERFLOW_EXIT); - guard(false, lir->ins1(LIR_ov, result), exit); - if (v == LIR_mul) // make sure we don't lose a -0 - guard(false, lir->ins_eq0(result), exit); - } - break; - } - JS_ASSERT_IF(d0->isconst() && d1->isconst(), - result->isconst() && result->imm32() == jsint(r)); - return lir->ins1(LIR_i2f, result); -} - -LIns* -TraceRecorder::f2i(LIns* f) -{ - return lir->insCall(&js_DoubleToInt32_ci, &f); -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::makeNumberInt32(LIns* f) -{ - JS_ASSERT(f->isQuad()); - LIns* x; - if (!isPromote(f)) { - x = f2i(f); - guard(true, lir->ins2(LIR_feq, f, lir->ins1(LIR_i2f, x)), MISMATCH_EXIT); - } else { - x = ::demote(lir, f); - } - return x; -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::stringify(jsval& v) -{ - LIns* v_ins = get(&v); - if (JSVAL_IS_STRING(v)) - return v_ins; - - LIns* args[] = { v_ins, cx_ins }; - const CallInfo* ci; - if (JSVAL_IS_NUMBER(v)) { - ci = &js_NumberToString_ci; - } else if (JSVAL_IS_SPECIAL(v)) { - ci = &js_BooleanOrUndefinedToString_ci; - } else { - /* - * Callers must deal with non-primitive (non-null object) values by - * calling an imacro. We don't try to guess about which imacro, with - * what valueOf hint, here. - */ - JS_ASSERT(JSVAL_IS_NULL(v)); - return INS_ATOM(cx->runtime->atomState.nullAtom); - } - - v_ins = lir->insCall(ci, args); - guard(false, lir->ins_peq0(v_ins), OOM_EXIT); - return v_ins; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::call_imacro(jsbytecode* imacro) -{ - JSStackFrame* fp = cx->fp; - JSFrameRegs* regs = fp->regs; - - /* We cannot nest imacros, only tail-call. */ - if (fp->imacpc) { - /* Dereference is safe since imacros are JSOP_STOP-terminated. */ - if (regs->pc[js_CodeSpec[*regs->pc].length] != JSOP_STOP) - return JSRS_STOP; - regs->pc = imacro; - return JSRS_IMACRO; - } - - fp->imacpc = regs->pc; - regs->pc = imacro; - atoms = COMMON_ATOMS_START(&cx->runtime->atomState); - return JSRS_IMACRO; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::ifop() -{ - jsval& v = stackval(-1); - LIns* v_ins = get(&v); - bool cond; - LIns* x; - - if (JSVAL_IS_NULL(v)) { - cond = false; - x = lir->insImm(0); - } else if (!JSVAL_IS_PRIMITIVE(v)) { - cond = true; - x = lir->insImm(1); - } else if (JSVAL_IS_SPECIAL(v)) { - /* Test for boolean is true, negate later if we are testing for false. */ - cond = JSVAL_TO_SPECIAL(v) == JS_TRUE; - x = lir->ins2i(LIR_eq, v_ins, 1); - } else if (isNumber(v)) { - jsdouble d = asNumber(v); - cond = !JSDOUBLE_IS_NaN(d) && d; - x = lir->ins2(LIR_and, - lir->ins2(LIR_feq, v_ins, v_ins), - lir->ins_eq0(lir->ins2(LIR_feq, v_ins, lir->insImmf(0)))); - } else if (JSVAL_IS_STRING(v)) { - cond = JSVAL_TO_STRING(v)->length() != 0; - x = lir->ins2(LIR_piand, - lir->insLoad(LIR_ldp, - v_ins, - (int)offsetof(JSString, mLength)), - INS_CONSTWORD(JSString::LENGTH_MASK)); - } else { - JS_NOT_REACHED("ifop"); - return JSRS_STOP; - } - - jsbytecode* pc = cx->fp->regs->pc; - emitIf(pc, cond, x); - return checkTraceEnd(pc); -} - -#ifdef NANOJIT_IA32 -/* - * Record LIR for a tableswitch or tableswitchx op. We record LIR only the - * "first" time we hit the op. Later, when we start traces after exiting that - * trace, we just patch. - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::tableswitch() -{ - jsval& v = stackval(-1); - - /* No need to guard if the condition can't match any of the cases. */ - if (!isNumber(v)) - return JSRS_CONTINUE; - - /* No need to guard if the condition is constant. */ - LIns* v_ins = f2i(get(&v)); - if (v_ins->isconst() || v_ins->isconstq()) - return JSRS_CONTINUE; - - jsbytecode* pc = cx->fp->regs->pc; - /* Starting a new trace after exiting a trace via switch. */ - if (anchor && - (anchor->exitType == CASE_EXIT || anchor->exitType == DEFAULT_EXIT) && - fragment->ip == pc) { - return JSRS_CONTINUE; - } - - /* Decode jsop. */ - jsint low, high; - if (*pc == JSOP_TABLESWITCH) { - pc += JUMP_OFFSET_LEN; - low = GET_JUMP_OFFSET(pc); - pc += JUMP_OFFSET_LEN; - high = GET_JUMP_OFFSET(pc); - } else { - pc += JUMPX_OFFSET_LEN; - low = GET_JUMPX_OFFSET(pc); - pc += JUMPX_OFFSET_LEN; - high = GET_JUMPX_OFFSET(pc); - } - - /* - * Really large tables won't fit in a page. This is a conservative check. - * If it matters in practice we need to go off-page. - */ - if ((high + 1 - low) * sizeof(intptr_t*) + 128 > (unsigned) LARGEST_UNDERRUN_PROT) - return switchop(); - - /* Generate switch LIR. */ - SwitchInfo* si = new (*traceMonitor->dataAlloc) SwitchInfo(); - si->count = high + 1 - low; - si->table = 0; - si->index = (uint32) -1; - LIns* diff = lir->ins2(LIR_sub, v_ins, lir->insImm(low)); - LIns* cmp = lir->ins2(LIR_ult, diff, lir->insImm(si->count)); - lir->insGuard(LIR_xf, cmp, createGuardRecord(snapshot(DEFAULT_EXIT))); - lir->insStorei(diff, lir->insImmPtr(&si->index), 0); - VMSideExit* exit = snapshot(CASE_EXIT); - exit->switchInfo = si; - LIns* guardIns = lir->insGuard(LIR_xtbl, diff, createGuardRecord(exit)); - fragment->lastIns = guardIns; - compile(&JS_TRACE_MONITOR(cx)); - return JSRS_STOP; -} -#endif - -static JS_ALWAYS_INLINE int32_t -UnboxBooleanOrUndefined(jsval v) -{ - /* Although this says 'special', we really only expect 3 special values: */ - JS_ASSERT(v == JSVAL_TRUE || v == JSVAL_FALSE || v == JSVAL_VOID); - return JSVAL_TO_SPECIAL(v); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::switchop() -{ - jsval& v = stackval(-1); - LIns* v_ins = get(&v); - - /* No need to guard if the condition is constant. */ - if (v_ins->isconst() || v_ins->isconstq()) - return JSRS_CONTINUE; - if (isNumber(v)) { - jsdouble d = asNumber(v); - guard(true, - addName(lir->ins2(LIR_feq, v_ins, lir->insImmf(d)), - "guard(switch on numeric)"), - BRANCH_EXIT); - } else if (JSVAL_IS_STRING(v)) { - LIns* args[] = { v_ins, INS_CONSTSTR(JSVAL_TO_STRING(v)) }; - guard(true, - addName(lir->ins_eq0(lir->ins_eq0(lir->insCall(&js_EqualStrings_ci, args))), - "guard(switch on string)"), - BRANCH_EXIT); - } else if (JSVAL_IS_SPECIAL(v)) { - guard(true, - addName(lir->ins2(LIR_eq, v_ins, lir->insImm(UnboxBooleanOrUndefined(v))), - "guard(switch on boolean)"), - BRANCH_EXIT); - } else { - ABORT_TRACE("switch on object or null"); - } - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::inc(jsval& v, jsint incr, bool pre) -{ - LIns* v_ins = get(&v); - CHECK_STATUS(inc(v, v_ins, incr, pre)); - set(&v, v_ins); - return JSRS_CONTINUE; -} - -/* - * On exit, v_ins is the incremented unboxed value, and the appropriate value - * (pre- or post-increment as described by pre) is stacked. - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::inc(jsval v, LIns*& v_ins, jsint incr, bool pre) -{ - LIns* v_after; - CHECK_STATUS(incHelper(v, v_ins, v_after, incr)); - - const JSCodeSpec& cs = js_CodeSpec[*cx->fp->regs->pc]; - JS_ASSERT(cs.ndefs == 1); - stack(-cs.nuses, pre ? v_after : v_ins); - v_ins = v_after; - return JSRS_CONTINUE; -} - -/* - * Do an increment operation without storing anything to the stack. - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::incHelper(jsval v, LIns* v_ins, LIns*& v_after, jsint incr) -{ - if (!isNumber(v)) - ABORT_TRACE("can only inc numbers"); - v_after = alu(LIR_fadd, asNumber(v), incr, v_ins, lir->insImmf(incr)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::incProp(jsint incr, bool pre) -{ - jsval& l = stackval(-1); - if (JSVAL_IS_PRIMITIVE(l)) - ABORT_TRACE("incProp on primitive"); - - JSObject* obj = JSVAL_TO_OBJECT(l); - LIns* obj_ins = get(&l); - - uint32 slot; - LIns* v_ins; - CHECK_STATUS(prop(obj, obj_ins, &slot, &v_ins, NULL)); - - if (slot == SPROP_INVALID_SLOT) - ABORT_TRACE("incProp on invalid slot"); - - jsval& v = STOBJ_GET_SLOT(obj, slot); - CHECK_STATUS(inc(v, v_ins, incr, pre)); - - LIns* dslots_ins = NULL; - stobj_set_slot(obj_ins, slot, dslots_ins, box_jsval(v, v_ins)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::incElem(jsint incr, bool pre) -{ - jsval& r = stackval(-1); - jsval& l = stackval(-2); - jsval* vp; - LIns* v_ins; - LIns* addr_ins; - - if (JSVAL_IS_PRIMITIVE(l) || !JSVAL_IS_INT(r) || - !guardDenseArray(JSVAL_TO_OBJECT(l), get(&l))) { - return JSRS_STOP; - } - - CHECK_STATUS(denseArrayElement(l, r, vp, v_ins, addr_ins)); - if (!addr_ins) // if we read a hole, abort - return JSRS_STOP; - CHECK_STATUS(inc(*vp, v_ins, incr, pre)); - lir->insStorei(box_jsval(*vp, v_ins), addr_ins, 0); - return JSRS_CONTINUE; -} - -static bool -EvalCmp(LOpcode op, double l, double r) -{ - bool cond; - switch (op) { - case LIR_feq: - cond = (l == r); - break; - case LIR_flt: - cond = l < r; - break; - case LIR_fgt: - cond = l > r; - break; - case LIR_fle: - cond = l <= r; - break; - case LIR_fge: - cond = l >= r; - break; - default: - JS_NOT_REACHED("unexpected comparison op"); - return false; - } - return cond; -} - -static bool -EvalCmp(LOpcode op, JSString* l, JSString* r) -{ - if (op == LIR_feq) - return js_EqualStrings(l, r); - return EvalCmp(op, js_CompareStrings(l, r), 0); -} - -JS_REQUIRES_STACK void -TraceRecorder::strictEquality(bool equal, bool cmpCase) -{ - jsval& r = stackval(-1); - jsval& l = stackval(-2); - LIns* l_ins = get(&l); - LIns* r_ins = get(&r); - LIns* x; - bool cond; - - JSTraceType ltag = GetPromotedType(l); - if (ltag != GetPromotedType(r)) { - cond = !equal; - x = lir->insImm(cond); - } else if (ltag == TT_STRING) { - LIns* args[] = { r_ins, l_ins }; - x = lir->ins2i(LIR_eq, lir->insCall(&js_EqualStrings_ci, args), equal); - cond = js_EqualStrings(JSVAL_TO_STRING(l), JSVAL_TO_STRING(r)); - } else { - LOpcode op; - if (ltag == TT_DOUBLE) - op = LIR_feq; - else if (ltag == TT_NULL || ltag == TT_OBJECT || ltag == TT_FUNCTION) - op = LIR_peq; - else - op = LIR_eq; - x = lir->ins2(op, l_ins, r_ins); - if (!equal) - x = lir->ins_eq0(x); - cond = (ltag == TT_DOUBLE) - ? asNumber(l) == asNumber(r) - : l == r; - } - cond = (cond == equal); - - if (cmpCase) { - /* Only guard if the same path may not always be taken. */ - if (!x->isconst()) - guard(cond, x, BRANCH_EXIT); - return; - } - - set(&l, x); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::equality(bool negate, bool tryBranchAfterCond) -{ - jsval& rval = stackval(-1); - jsval& lval = stackval(-2); - LIns* l_ins = get(&lval); - LIns* r_ins = get(&rval); - - return equalityHelper(lval, rval, l_ins, r_ins, negate, tryBranchAfterCond, lval); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::equalityHelper(jsval l, jsval r, LIns* l_ins, LIns* r_ins, - bool negate, bool tryBranchAfterCond, - jsval& rval) -{ - LOpcode op = LIR_eq; - bool cond; - LIns* args[] = { NULL, NULL }; - - /* - * The if chain below closely mirrors that found in 11.9.3, in general - * deviating from that ordering of ifs only to account for SpiderMonkey's - * conflation of booleans and undefined and for the possibility of - * confusing objects and null. Note carefully the spec-mandated recursion - * in the final else clause, which terminates because Number == T recurs - * only if T is Object, but that must recur again to convert Object to - * primitive, and ToPrimitive throws if the object cannot be converted to - * a primitive value (which would terminate recursion). - */ - - if (GetPromotedType(l) == GetPromotedType(r)) { - if (JSVAL_TAG(l) == JSVAL_OBJECT || JSVAL_IS_SPECIAL(l)) { - if (JSVAL_TAG(l) == JSVAL_OBJECT && l) { - JSClass *clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(l)); - if ((clasp->flags & JSCLASS_IS_EXTENDED) && ((JSExtendedClass*) clasp)->equality) - ABORT_TRACE("Can't trace extended class equality operator"); - } - if (JSVAL_TAG(l) == JSVAL_OBJECT) - op = LIR_peq; - cond = (l == r); - } else if (JSVAL_IS_STRING(l)) { - args[0] = r_ins, args[1] = l_ins; - l_ins = lir->insCall(&js_EqualStrings_ci, args); - r_ins = lir->insImm(1); - cond = js_EqualStrings(JSVAL_TO_STRING(l), JSVAL_TO_STRING(r)); - } else { - JS_ASSERT(isNumber(l) && isNumber(r)); - cond = (asNumber(l) == asNumber(r)); - op = LIR_feq; - } - } else if (JSVAL_IS_NULL(l) && JSVAL_IS_SPECIAL(r)) { - l_ins = lir->insImm(JSVAL_TO_SPECIAL(JSVAL_VOID)); - cond = (r == JSVAL_VOID); - } else if (JSVAL_IS_SPECIAL(l) && JSVAL_IS_NULL(r)) { - r_ins = lir->insImm(JSVAL_TO_SPECIAL(JSVAL_VOID)); - cond = (l == JSVAL_VOID); - } else if (isNumber(l) && JSVAL_IS_STRING(r)) { - args[0] = r_ins, args[1] = cx_ins; - r_ins = lir->insCall(&js_StringToNumber_ci, args); - cond = (asNumber(l) == js_StringToNumber(cx, JSVAL_TO_STRING(r))); - op = LIR_feq; - } else if (JSVAL_IS_STRING(l) && isNumber(r)) { - args[0] = l_ins, args[1] = cx_ins; - l_ins = lir->insCall(&js_StringToNumber_ci, args); - cond = (js_StringToNumber(cx, JSVAL_TO_STRING(l)) == asNumber(r)); - op = LIR_feq; - } else { - if (JSVAL_IS_SPECIAL(l)) { - bool isVoid = JSVAL_IS_VOID(l); - guard(isVoid, - lir->ins2(LIR_eq, l_ins, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))), - BRANCH_EXIT); - if (!isVoid) { - args[0] = l_ins, args[1] = cx_ins; - l_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); - l = (l == JSVAL_VOID) - ? DOUBLE_TO_JSVAL(cx->runtime->jsNaN) - : INT_TO_JSVAL(l == JSVAL_TRUE); - return equalityHelper(l, r, l_ins, r_ins, negate, - tryBranchAfterCond, rval); - } - } else if (JSVAL_IS_SPECIAL(r)) { - bool isVoid = JSVAL_IS_VOID(r); - guard(isVoid, - lir->ins2(LIR_eq, r_ins, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))), - BRANCH_EXIT); - if (!isVoid) { - args[0] = r_ins, args[1] = cx_ins; - r_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); - r = (r == JSVAL_VOID) - ? DOUBLE_TO_JSVAL(cx->runtime->jsNaN) - : INT_TO_JSVAL(r == JSVAL_TRUE); - return equalityHelper(l, r, l_ins, r_ins, negate, - tryBranchAfterCond, rval); - } - } else { - if ((JSVAL_IS_STRING(l) || isNumber(l)) && !JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(equality_imacros.any_obj); - } - if (!JSVAL_IS_PRIMITIVE(l) && (JSVAL_IS_STRING(r) || isNumber(r))) { - ABORT_IF_XML(l); - return call_imacro(equality_imacros.obj_any); - } - } - - l_ins = lir->insImm(0); - r_ins = lir->insImm(1); - cond = false; - } - - /* If the operands aren't numbers, compare them as integers. */ - LIns* x = lir->ins2(op, l_ins, r_ins); - if (negate) { - x = lir->ins_eq0(x); - cond = !cond; - } - - jsbytecode* pc = cx->fp->regs->pc; - - /* - * Don't guard if the same path is always taken. If it isn't, we have to - * fuse comparisons and the following branch, because the interpreter does - * that. - */ - if (tryBranchAfterCond) - fuseIf(pc + 1, cond, x); - - /* - * There is no need to write out the result of this comparison if the trace - * ends on this operation. - */ - if (pc[1] == JSOP_IFNE || pc[1] == JSOP_IFEQ) - CHECK_STATUS(checkTraceEnd(pc + 1)); - - /* - * We update the stack after the guard. This is safe since the guard bails - * out at the comparison and the interpreter will therefore re-execute the - * comparison. This way the value of the condition doesn't have to be - * calculated and saved on the stack in most cases. - */ - set(&rval, x); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::relational(LOpcode op, bool tryBranchAfterCond) -{ - jsval& r = stackval(-1); - jsval& l = stackval(-2); - LIns* x = NULL; - bool cond; - LIns* l_ins = get(&l); - LIns* r_ins = get(&r); - bool fp = false; - jsdouble lnum, rnum; - - /* - * 11.8.5 if either argument is an object with a function-valued valueOf - * property; if both arguments are objects with non-function-valued valueOf - * properties, abort. - */ - if (!JSVAL_IS_PRIMITIVE(l)) { - ABORT_IF_XML(l); - if (!JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(binary_imacros.obj_obj); - } - return call_imacro(binary_imacros.obj_any); - } - if (!JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(binary_imacros.any_obj); - } - - /* 11.8.5 steps 3, 16-21. */ - if (JSVAL_IS_STRING(l) && JSVAL_IS_STRING(r)) { - LIns* args[] = { r_ins, l_ins }; - l_ins = lir->insCall(&js_CompareStrings_ci, args); - r_ins = lir->insImm(0); - cond = EvalCmp(op, JSVAL_TO_STRING(l), JSVAL_TO_STRING(r)); - goto do_comparison; - } - - /* 11.8.5 steps 4-5. */ - if (!JSVAL_IS_NUMBER(l)) { - LIns* args[] = { l_ins, cx_ins }; - switch (JSVAL_TAG(l)) { - case JSVAL_SPECIAL: - l_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); - break; - case JSVAL_STRING: - l_ins = lir->insCall(&js_StringToNumber_ci, args); - break; - case JSVAL_OBJECT: - if (JSVAL_IS_NULL(l)) { - l_ins = lir->insImmf(0.0); - break; - } - // FALL THROUGH - case JSVAL_INT: - case JSVAL_DOUBLE: - default: - JS_NOT_REACHED("JSVAL_IS_NUMBER if int/double, objects should " - "have been handled at start of method"); - ABORT_TRACE("safety belt"); - } - } - if (!JSVAL_IS_NUMBER(r)) { - LIns* args[] = { r_ins, cx_ins }; - switch (JSVAL_TAG(r)) { - case JSVAL_SPECIAL: - r_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); - break; - case JSVAL_STRING: - r_ins = lir->insCall(&js_StringToNumber_ci, args); - break; - case JSVAL_OBJECT: - if (JSVAL_IS_NULL(r)) { - r_ins = lir->insImmf(0.0); - break; - } - // FALL THROUGH - case JSVAL_INT: - case JSVAL_DOUBLE: - default: - JS_NOT_REACHED("JSVAL_IS_NUMBER if int/double, objects should " - "have been handled at start of method"); - ABORT_TRACE("safety belt"); - } - } - { - jsval tmp = JSVAL_NULL; - JSAutoTempValueRooter tvr(cx, 1, &tmp); - - tmp = l; - lnum = js_ValueToNumber(cx, &tmp); - tmp = r; - rnum = js_ValueToNumber(cx, &tmp); - } - cond = EvalCmp(op, lnum, rnum); - fp = true; - - /* 11.8.5 steps 6-15. */ - do_comparison: - /* - * If the result is not a number or it's not a quad, we must use an integer - * compare. - */ - if (!fp) { - JS_ASSERT(op >= LIR_feq && op <= LIR_fge); - op = LOpcode(op + (LIR_eq - LIR_feq)); - } - x = lir->ins2(op, l_ins, r_ins); - - jsbytecode* pc = cx->fp->regs->pc; - - /* - * Don't guard if the same path is always taken. If it isn't, we have to - * fuse comparisons and the following branch, because the interpreter does - * that. - */ - if (tryBranchAfterCond) - fuseIf(pc + 1, cond, x); - - /* - * There is no need to write out the result of this comparison if the trace - * ends on this operation. - */ - if (pc[1] == JSOP_IFNE || pc[1] == JSOP_IFEQ) - CHECK_STATUS(checkTraceEnd(pc + 1)); - - /* - * We update the stack after the guard. This is safe since the guard bails - * out at the comparison and the interpreter will therefore re-execute the - * comparison. This way the value of the condition doesn't have to be - * calculated and saved on the stack in most cases. - */ - set(&l, x); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::unary(LOpcode op) -{ - jsval& v = stackval(-1); - bool intop = !(op & LIR64); - if (isNumber(v)) { - LIns* a = get(&v); - if (intop) - a = f2i(a); - a = lir->ins1(op, a); - if (intop) - a = lir->ins1(LIR_i2f, a); - set(&v, a); - return JSRS_CONTINUE; - } - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::binary(LOpcode op) -{ - jsval& r = stackval(-1); - jsval& l = stackval(-2); - - if (!JSVAL_IS_PRIMITIVE(l)) { - ABORT_IF_XML(l); - if (!JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(binary_imacros.obj_obj); - } - return call_imacro(binary_imacros.obj_any); - } - if (!JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(binary_imacros.any_obj); - } - - bool intop = !(op & LIR64); - LIns* a = get(&l); - LIns* b = get(&r); - - bool leftIsNumber = isNumber(l); - jsdouble lnum = leftIsNumber ? asNumber(l) : 0; - - bool rightIsNumber = isNumber(r); - jsdouble rnum = rightIsNumber ? asNumber(r) : 0; - - if ((op >= LIR_sub && op <= LIR_ush) || // sub, mul, (callh), or, xor, (not,) lsh, rsh, ush - (op >= LIR_fsub && op <= LIR_fmod)) { // fsub, fmul, fdiv, fmod - LIns* args[2]; - if (JSVAL_IS_STRING(l)) { - args[0] = a; - args[1] = cx_ins; - a = lir->insCall(&js_StringToNumber_ci, args); - lnum = js_StringToNumber(cx, JSVAL_TO_STRING(l)); - leftIsNumber = true; - } - if (JSVAL_IS_STRING(r)) { - args[0] = b; - args[1] = cx_ins; - b = lir->insCall(&js_StringToNumber_ci, args); - rnum = js_StringToNumber(cx, JSVAL_TO_STRING(r)); - rightIsNumber = true; - } - } - if (JSVAL_IS_SPECIAL(l)) { - LIns* args[] = { a, cx_ins }; - a = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); - lnum = js_BooleanOrUndefinedToNumber(cx, JSVAL_TO_SPECIAL(l)); - leftIsNumber = true; - } - if (JSVAL_IS_SPECIAL(r)) { - LIns* args[] = { b, cx_ins }; - b = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); - rnum = js_BooleanOrUndefinedToNumber(cx, JSVAL_TO_SPECIAL(r)); - rightIsNumber = true; - } - if (leftIsNumber && rightIsNumber) { - if (intop) { - LIns *args[] = { a }; - a = lir->insCall(op == LIR_ush ? &js_DoubleToUint32_ci : &js_DoubleToInt32_ci, args); - b = f2i(b); - } - a = alu(op, lnum, rnum, a, b); - if (intop) - a = lir->ins1(op == LIR_ush ? LIR_u2f : LIR_i2f, a); - set(&l, a); - return JSRS_CONTINUE; - } - return JSRS_STOP; -} - -void -TraceRecorder::guardShape(LIns* obj_ins, JSObject* obj, uint32 shape, const char* guardName, - LIns* map_ins, VMSideExit* exit) -{ - LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape"); - guard(true, - addName(lir->ins2i(LIR_eq, shape_ins, shape), guardName), - exit); -} - -JS_STATIC_ASSERT(offsetof(JSObjectOps, objectMap) == 0); - -inline LIns* -TraceRecorder::map(LIns *obj_ins) -{ - return addName(lir->insLoad(LIR_ldp, obj_ins, (int) offsetof(JSObject, map)), "map"); -} - -bool -TraceRecorder::map_is_native(JSObjectMap* map, LIns* map_ins, LIns*& ops_ins, size_t op_offset) -{ - JS_ASSERT(op_offset < sizeof(JSObjectOps)); - JS_ASSERT(op_offset % sizeof(void *) == 0); - -#define OP(ops) (*(void **) ((uint8 *) (ops) + op_offset)) - void* ptr = OP(map->ops); - if (ptr != OP(&js_ObjectOps)) - return false; -#undef OP - - ops_ins = addName(lir->insLoad(LIR_ldcp, map_ins, int(offsetof(JSObjectMap, ops))), "ops"); - LIns* n = lir->insLoad(LIR_ldcp, ops_ins, op_offset); - guard(true, - addName(lir->ins2(LIR_peq, n, INS_CONSTPTR(ptr)), "guard(native-map)"), - BRANCH_EXIT); - - return true; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::guardNativePropertyOp(JSObject* aobj, LIns* map_ins) -{ - /* - * Interpreter calls to PROPERTY_CACHE_TEST guard on native object ops - * which is required to use native objects (those whose maps are scopes), - * or even more narrow conditions required because the cache miss case - * will call a particular object-op (js_GetProperty, js_SetProperty). - * - * We parameterize using offsetof and guard on match against the hook at - * the given offset in js_ObjectOps. TraceRecorder::record_JSOP_SETPROP - * guards the js_SetProperty case. - */ - uint32 format = js_CodeSpec[*cx->fp->regs->pc].format; - uint32 mode = JOF_MODE(format); - - // No need to guard native-ness of global object. - JS_ASSERT(OBJ_IS_NATIVE(globalObj)); - if (aobj != globalObj) { - size_t op_offset = offsetof(JSObjectOps, objectMap); - if (mode == JOF_PROP || mode == JOF_VARPROP) { - op_offset = (format & JOF_SET) - ? offsetof(JSObjectOps, setProperty) - : offsetof(JSObjectOps, getProperty); - } else { - JS_ASSERT(mode == JOF_NAME); - } - - LIns* ops_ins; - if (!map_is_native(aobj->map, map_ins, ops_ins, op_offset)) - ABORT_TRACE("non-native map"); - } - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2, jsuword& pcval) -{ - jsbytecode* pc = cx->fp->regs->pc; - JS_ASSERT(*pc != JSOP_INITPROP && *pc != JSOP_SETNAME && *pc != JSOP_SETPROP); - - // Mimic the interpreter's special case for dense arrays by skipping up one - // hop along the proto chain when accessing a named (not indexed) property, - // typically to find Array.prototype methods. - JSObject* aobj = obj; - if (OBJ_IS_DENSE_ARRAY(cx, obj)) { - guardDenseArray(obj, obj_ins, BRANCH_EXIT); - aobj = OBJ_GET_PROTO(cx, obj); - obj_ins = stobj_get_proto(obj_ins); - } - - if (!OBJ_IS_NATIVE(obj)) - ABORT_TRACE("non-native object"); - - LIns* map_ins = map(obj_ins); - - CHECK_STATUS(guardNativePropertyOp(aobj, map_ins)); - - JSAtom* atom; - JSPropCacheEntry* entry; - PROPERTY_CACHE_TEST(cx, pc, aobj, obj2, entry, atom); - if (!atom) { - // Null atom means that obj2 is locked and must now be unlocked. - JS_UNLOCK_OBJ(cx, obj2); - } else { - // Miss: pre-fill the cache for the interpreter, as well as for our needs. - jsid id = ATOM_TO_JSID(atom); - JSProperty* prop; - if (JOF_OPMODE(*pc) == JOF_NAME) { - JS_ASSERT(aobj == obj); - - JSTraceMonitor &localtm = *traceMonitor; - entry = js_FindPropertyHelper(cx, id, true, &obj, &obj2, &prop); - - /* js_FindPropertyHelper can reenter the interpreter and kill |this|. */ - if (!localtm.recorder) - return JSRS_STOP; - - if (!entry) - ABORT_TRACE_ERROR("error in js_FindPropertyHelper"); - if (entry == JS_NO_PROP_CACHE_FILL) - ABORT_TRACE("cannot cache name"); - } else { - JSTraceMonitor &localtm = *traceMonitor; - JSContext *localcx = cx; - int protoIndex = js_LookupPropertyWithFlags(cx, aobj, id, - cx->resolveFlags, - &obj2, &prop); - - /* js_LookupPropertyWithFlags can reenter the interpreter and kill |this|. */ - if (!localtm.recorder) { - if (prop) - obj2->dropProperty(localcx, prop); - return JSRS_STOP; - } - - if (protoIndex < 0) - ABORT_TRACE_ERROR("error in js_LookupPropertyWithFlags"); - - if (prop) { - if (!OBJ_IS_NATIVE(obj2)) { - obj2->dropProperty(cx, prop); - ABORT_TRACE("property found on non-native object"); - } - entry = js_FillPropertyCache(cx, aobj, 0, protoIndex, obj2, - (JSScopeProperty*) prop, false); - JS_ASSERT(entry); - if (entry == JS_NO_PROP_CACHE_FILL) - entry = NULL; - } - - } - - if (!prop) { - // Propagate obj from js_FindPropertyHelper to record_JSOP_BINDNAME - // via our obj2 out-parameter. If we are recording JSOP_SETNAME and - // the global it's assigning does not yet exist, create it. - obj2 = obj; - - // Use PCVAL_NULL to return "no such property" to our caller. - pcval = PCVAL_NULL; - return JSRS_CONTINUE; - } - - obj2->dropProperty(cx, prop); - if (!entry) - ABORT_TRACE("failed to fill property cache"); - } - -#ifdef JS_THREADSAFE - // There's a potential race in any JS_THREADSAFE embedding that's nuts - // enough to share mutable objects on the scope or proto chain, but we - // don't care about such insane embeddings. Anyway, the (scope, proto) - // entry->vcap coordinates must reach obj2 from aobj at this point. - JS_ASSERT(cx->requestDepth); -#endif - - return guardPropertyCacheHit(obj_ins, map_ins, aobj, obj2, entry, pcval); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::guardPropertyCacheHit(LIns* obj_ins, - LIns* map_ins, - JSObject* aobj, - JSObject* obj2, - JSPropCacheEntry* entry, - jsuword& pcval) -{ - VMSideExit* exit = snapshot(BRANCH_EXIT); - - uint32 vshape = PCVCAP_SHAPE(entry->vcap); - - // Check for first-level cache hit and guard on kshape if possible. - // Otherwise guard on key object exact match. - if (PCVCAP_TAG(entry->vcap) <= 1) { - if (aobj != globalObj) - guardShape(obj_ins, aobj, entry->kshape, "guard_kshape", map_ins, exit); - - if (entry->adding()) { - if (aobj == globalObj) - ABORT_TRACE("adding a property to the global object"); - - LIns *vshape_ins = addName( - lir->insLoad(LIR_ld, - addName(lir->insLoad(LIR_ldcp, cx_ins, offsetof(JSContext, runtime)), - "runtime"), - offsetof(JSRuntime, protoHazardShape)), - "protoHazardShape"); - guard(true, - addName(lir->ins2i(LIR_eq, vshape_ins, vshape), "guard_protoHazardShape"), - MISMATCH_EXIT); - } - } else { -#ifdef DEBUG - JSOp op = js_GetOpcode(cx, cx->fp->script, cx->fp->regs->pc); - JSAtom *pcatom; - if (op == JSOP_LENGTH) { - pcatom = cx->runtime->atomState.lengthAtom; - } else { - ptrdiff_t pcoff = (JOF_TYPE(js_CodeSpec[op].format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; - GET_ATOM_FROM_BYTECODE(cx->fp->script, cx->fp->regs->pc, pcoff, pcatom); - } - JS_ASSERT(entry->kpc == (jsbytecode *) pcatom); - JS_ASSERT(entry->kshape == jsuword(aobj)); -#endif - if (aobj != globalObj && !obj_ins->isconstp()) { - guard(true, - addName(lir->ins2(LIR_peq, obj_ins, INS_CONSTOBJ(aobj)), "guard_kobj"), - exit); - } - } - - // For any hit that goes up the scope and/or proto chains, we will need to - // guard on the shape of the object containing the property. - if (PCVCAP_TAG(entry->vcap) >= 1) { - JS_ASSERT(OBJ_SHAPE(obj2) == vshape); - - LIns* obj2_ins; - if (PCVCAP_TAG(entry->vcap) == 1) { - // Duplicate the special case in PROPERTY_CACHE_TEST. - obj2_ins = addName(stobj_get_proto(obj_ins), "proto"); - guard(false, lir->ins_peq0(obj2_ins), exit); - } else { - obj2_ins = INS_CONSTOBJ(obj2); - } - guardShape(obj2_ins, obj2, vshape, "guard_vshape", map(obj2_ins), exit); - } - - pcval = entry->vword; - return JSRS_CONTINUE; -} - -void -TraceRecorder::stobj_set_fslot(LIns *obj_ins, unsigned slot, LIns* v_ins) -{ - lir->insStorei(v_ins, obj_ins, offsetof(JSObject, fslots) + slot * sizeof(jsval)); -} - -void -TraceRecorder::stobj_set_dslot(LIns *obj_ins, unsigned slot, LIns*& dslots_ins, LIns* v_ins) -{ - if (!dslots_ins) - dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); - lir->insStorei(v_ins, dslots_ins, slot * sizeof(jsval)); -} - -void -TraceRecorder::stobj_set_slot(LIns* obj_ins, unsigned slot, LIns*& dslots_ins, LIns* v_ins) -{ - if (slot < JS_INITIAL_NSLOTS) { - stobj_set_fslot(obj_ins, slot, v_ins); - } else { - stobj_set_dslot(obj_ins, slot - JS_INITIAL_NSLOTS, dslots_ins, v_ins); - } -} - -LIns* -TraceRecorder::stobj_get_fslot(LIns* obj_ins, unsigned slot) -{ - JS_ASSERT(slot < JS_INITIAL_NSLOTS); - return lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, fslots) + slot * sizeof(jsval)); -} - -LIns* -TraceRecorder::stobj_get_dslot(LIns* obj_ins, unsigned index, LIns*& dslots_ins) -{ - if (!dslots_ins) - dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); - return lir->insLoad(LIR_ldp, dslots_ins, index * sizeof(jsval)); -} - -LIns* -TraceRecorder::stobj_get_slot(LIns* obj_ins, unsigned slot, LIns*& dslots_ins) -{ - if (slot < JS_INITIAL_NSLOTS) - return stobj_get_fslot(obj_ins, slot); - return stobj_get_dslot(obj_ins, slot - JS_INITIAL_NSLOTS, dslots_ins); -} - -JSRecordingStatus -TraceRecorder::native_get(LIns* obj_ins, LIns* pobj_ins, JSScopeProperty* sprop, - LIns*& dslots_ins, LIns*& v_ins) -{ - if (!SPROP_HAS_STUB_GETTER(sprop)) - return JSRS_STOP; - - if (sprop->slot != SPROP_INVALID_SLOT) - v_ins = stobj_get_slot(pobj_ins, sprop->slot, dslots_ins); - else - v_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::box_jsval(jsval v, LIns* v_ins) -{ - if (isNumber(v)) { - LIns* args[] = { v_ins, cx_ins }; - v_ins = lir->insCall(&js_BoxDouble_ci, args); - guard(false, lir->ins2(LIR_peq, v_ins, INS_CONSTWORD(JSVAL_ERROR_COOKIE)), - OOM_EXIT); - return v_ins; - } - switch (JSVAL_TAG(v)) { - case JSVAL_SPECIAL: - return lir->ins2(LIR_pior, lir->ins2i(LIR_pilsh, lir->ins_u2p(v_ins), JSVAL_TAGBITS), - INS_CONSTWORD(JSVAL_SPECIAL)); - case JSVAL_OBJECT: - return v_ins; - default: - JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING); - return lir->ins2(LIR_pior, v_ins, INS_CONSTWORD(JSVAL_STRING)); - } -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::unbox_jsval(jsval v, LIns* v_ins, VMSideExit* exit) -{ - if (isNumber(v)) { - // JSVAL_IS_NUMBER(v) - guard(false, - lir->ins_eq0(lir->ins2(LIR_or, - p2i(lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_INT))), - lir->ins2(LIR_peq, - lir->ins2(LIR_piand, v_ins, - INS_CONSTWORD(JSVAL_TAGMASK)), - INS_CONSTWORD(JSVAL_DOUBLE)))), - exit); - LIns* args[] = { v_ins }; - return lir->insCall(&js_UnboxDouble_ci, args); - } - switch (JSVAL_TAG(v)) { - case JSVAL_SPECIAL: - guard(true, - lir->ins2(LIR_peq, - lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK)), - INS_CONSTWORD(JSVAL_SPECIAL)), - exit); - return p2i(lir->ins2i(LIR_pursh, v_ins, JSVAL_TAGBITS)); - - case JSVAL_OBJECT: - if (JSVAL_IS_NULL(v)) { - // JSVAL_NULL maps to type TT_NULL, so insist that v_ins == 0 here. - guard(true, lir->ins_peq0(v_ins), exit); - } else { - guard(false, lir->ins_peq0(v_ins), exit); - guard(true, - lir->ins2(LIR_peq, - lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK)), - INS_CONSTWORD(JSVAL_OBJECT)), - exit); - - /* - * LIR_ldcp is ok to use here even though Array classword can - * change, because no object's classword can ever change from - * &js_ArrayClass to &js_FunctionClass. - */ - guard(HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v)), - lir->ins2(LIR_peq, - lir->ins2(LIR_piand, - lir->insLoad(LIR_ldcp, v_ins, offsetof(JSObject, classword)), - INS_CONSTWORD(~JSSLOT_CLASS_MASK_BITS)), - INS_CONSTPTR(&js_FunctionClass)), - exit); - } - return v_ins; - - default: - JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING); - guard(true, - lir->ins2(LIR_peq, - lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK)), - INS_CONSTWORD(JSVAL_STRING)), - exit); - return lir->ins2(LIR_piand, v_ins, addName(lir->insImmWord(~JSVAL_TAGMASK), - "~JSVAL_TAGMASK")); - } -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getThis(LIns*& this_ins) -{ - /* - * js_ComputeThisForFrame updates cx->fp->argv[-1], so sample it into 'original' first. - */ - jsval original = JSVAL_NULL; - if (cx->fp->argv) { - original = cx->fp->argv[-1]; - if (!JSVAL_IS_PRIMITIVE(original) && - guardClass(JSVAL_TO_OBJECT(original), get(&cx->fp->argv[-1]), &js_WithClass, snapshot(MISMATCH_EXIT))) { - ABORT_TRACE("can't trace getThis on With object"); - } - } - - JSObject* thisObj = js_ComputeThisForFrame(cx, cx->fp); - if (!thisObj) - ABORT_TRACE_ERROR("js_ComputeThisForName failed"); - - /* In global code, bake in the global object as 'this' object. */ - if (!cx->fp->callee()) { - JS_ASSERT(callDepth == 0); - this_ins = INS_CONSTOBJ(thisObj); - - /* - * We don't have argv[-1] in global code, so we don't update the - * tracker here. - */ - return JSRS_CONTINUE; - } - - jsval& thisv = cx->fp->argv[-1]; - JS_ASSERT(JSVAL_IS_OBJECT(thisv)); - - /* - * Traces type-specialize between null and objects, so if we currently see - * a null value in argv[-1], this trace will only match if we see null at - * runtime as well. Bake in the global object as 'this' object, updating - * the tracker as well. We can only detect this condition prior to calling - * js_ComputeThisForFrame, since it updates the interpreter's copy of - * argv[-1]. - */ - JSClass* clasp = NULL;; - if (JSVAL_IS_NULL(original) || - (((clasp = STOBJ_GET_CLASS(JSVAL_TO_OBJECT(original))) == &js_CallClass) || - (clasp == &js_BlockClass))) { - if (clasp) - guardClass(JSVAL_TO_OBJECT(original), get(&thisv), clasp, snapshot(BRANCH_EXIT)); - JS_ASSERT(!JSVAL_IS_PRIMITIVE(thisv)); - if (thisObj != globalObj) - ABORT_TRACE("global object was wrapped while recording"); - this_ins = INS_CONSTOBJ(thisObj); - set(&thisv, this_ins); - return JSRS_CONTINUE; - } - - this_ins = get(&thisv); - - JSObject* wrappedGlobal = globalObj->thisObject(cx); - if (!wrappedGlobal) - ABORT_TRACE_ERROR("globalObj->thisObject hook threw in getThis"); - - /* - * The only unwrapped object that needs to be wrapped that we can get here - * is the global object obtained throught the scope chain. - */ - this_ins = lir->ins_choose(lir->ins_peq0(stobj_get_parent(this_ins)), - INS_CONSTOBJ(wrappedGlobal), - this_ins); - return JSRS_CONTINUE; -} - - -LIns* -TraceRecorder::getStringLength(LIns* str_ins) -{ - LIns* len_ins = lir->insLoad(LIR_ldp, str_ins, (int)offsetof(JSString, mLength)); - - LIns* masked_len_ins = lir->ins2(LIR_piand, - len_ins, - INS_CONSTWORD(JSString::LENGTH_MASK)); - - LIns* real_len = - lir->ins_choose(lir->ins_peq0(lir->ins2(LIR_piand, - len_ins, - INS_CONSTWORD(JSString::DEPENDENT))), - masked_len_ins, - lir->ins_choose(lir->ins_peq0(lir->ins2(LIR_piand, - len_ins, - INS_CONSTWORD(JSString::PREFIX))), - lir->ins2(LIR_piand, - len_ins, - INS_CONSTWORD(JSString::DEPENDENT_LENGTH_MASK)), - masked_len_ins)); - return p2i(real_len); -} - -JS_REQUIRES_STACK bool -TraceRecorder::guardClass(JSObject* obj, LIns* obj_ins, JSClass* clasp, VMSideExit* exit) -{ - bool cond = STOBJ_GET_CLASS(obj) == clasp; - - LIns* class_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, classword)); - class_ins = lir->ins2(LIR_piand, class_ins, INS_CONSTWORD(~JSSLOT_CLASS_MASK_BITS)); - - char namebuf[32]; - JS_snprintf(namebuf, sizeof namebuf, "guard(class is %s)", clasp->name); - guard(cond, addName(lir->ins2(LIR_peq, class_ins, INS_CONSTPTR(clasp)), namebuf), exit); - return cond; -} - -JS_REQUIRES_STACK bool -TraceRecorder::guardDenseArray(JSObject* obj, LIns* obj_ins, ExitType exitType) -{ - return guardClass(obj, obj_ins, &js_ArrayClass, snapshot(exitType)); -} - -JS_REQUIRES_STACK bool -TraceRecorder::guardDenseArray(JSObject* obj, LIns* obj_ins, VMSideExit* exit) -{ - return guardClass(obj, obj_ins, &js_ArrayClass, exit); -} - -JS_REQUIRES_STACK bool -TraceRecorder::guardHasPrototype(JSObject* obj, LIns* obj_ins, - JSObject** pobj, LIns** pobj_ins, - VMSideExit* exit) -{ - *pobj = obj->getProto(); - *pobj_ins = stobj_get_proto(obj_ins); - - bool cond = *pobj == NULL; - guard(cond, addName(lir->ins_peq0(*pobj_ins), "guard(proto-not-null)"), exit); - return !cond; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::guardPrototypeHasNoIndexedProperties(JSObject* obj, LIns* obj_ins, ExitType exitType) -{ - /* - * Guard that no object along the prototype chain has any indexed - * properties which might become visible through holes in the array. - */ - VMSideExit* exit = snapshot(exitType); - - if (js_PrototypeHasIndexedProperties(cx, obj)) - return JSRS_STOP; - - while (guardHasPrototype(obj, obj_ins, &obj, &obj_ins, exit)) - guardShape(obj_ins, obj, OBJ_SHAPE(obj), "guard(shape)", map(obj_ins), exit); - return JSRS_CONTINUE; -} - -JSRecordingStatus -TraceRecorder::guardNotGlobalObject(JSObject* obj, LIns* obj_ins) -{ - if (obj == globalObj) - ABORT_TRACE("reference aliases global object"); - guard(false, lir->ins2(LIR_peq, obj_ins, INS_CONSTOBJ(globalObj)), MISMATCH_EXIT); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK void -TraceRecorder::clearFrameSlotsFromCache() -{ - /* - * Clear out all slots of this frame in the nativeFrameTracker. Different - * locations on the VM stack might map to different locations on the native - * stack depending on the number of arguments (i.e.) of the next call, so - * we have to make sure we map those in to the cache with the right - * offsets. - */ - JSStackFrame* fp = cx->fp; - jsval* vp; - jsval* vpstop; - - /* - * Duplicate native stack layout computation: see VisitFrameSlots header comment. - * This doesn't do layout arithmetic, but it must clear out all the slots defined as - * imported by VisitFrameSlots. - */ - if (fp->argv) { - vp = &fp->argv[-2]; - vpstop = &fp->argv[argSlots(fp)]; - while (vp < vpstop) - nativeFrameTracker.set(vp++, (LIns*)0); - nativeFrameTracker.set(&fp->argsobj, (LIns*)0); - } - vp = &fp->slots[0]; - vpstop = &fp->slots[fp->script->nslots]; - while (vp < vpstop) - nativeFrameTracker.set(vp++, (LIns*)0); -} - -/* - * If we have created an |arguments| object for the frame, we must copy the - * argument values into the object as properties in case it is used after - * this frame returns. - */ -JS_REQUIRES_STACK void -TraceRecorder::putArguments() -{ - if (cx->fp->argsobj && cx->fp->argc) { - LIns* argsobj_ins = get(&cx->fp->argsobj); - LIns* args_ins = lir->insAlloc(sizeof(jsval) * cx->fp->argc); - for (uintN i = 0; i < cx->fp->argc; ++i) { - LIns* arg_ins = box_jsval(cx->fp->argv[i], get(&cx->fp->argv[i])); - lir->insStorei(arg_ins, args_ins, i * sizeof(jsval)); - } - LIns* args[] = { args_ins, argsobj_ins, cx_ins }; - lir->insCall(&js_PutArguments_ci, args); - } -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_EnterFrame() -{ - JSStackFrame* fp = cx->fp; - - if (++callDepth >= MAX_CALLDEPTH) - ABORT_TRACE("exceeded maximum call depth"); - - // FIXME: Allow and attempt to inline a single level of recursion until we compile - // recursive calls as independent trees (459301). - if (fp->script == fp->down->script && fp->down->down && fp->down->down->script == fp->script) - ABORT_TRACE("recursive call"); - - debug_only_printf(LC_TMTracer, "EnterFrame %s, callDepth=%d\n", - js_AtomToPrintableString(cx, cx->fp->fun->atom), - callDepth); - debug_only_stmt( - if (js_LogController.lcbits & LC_TMRecorder) { - js_Disassemble(cx, cx->fp->script, JS_TRUE, stdout); - debug_only_print0(LC_TMTracer, "----\n"); - } - ) - LIns* void_ins = INS_VOID(); - - // Duplicate native stack layout computation: see VisitFrameSlots header comment. - // This doesn't do layout arithmetic, but it must initialize in the tracker all the - // slots defined as imported by VisitFrameSlots. - jsval* vp = &fp->argv[fp->argc]; - jsval* vpstop = vp + ptrdiff_t(fp->fun->nargs) - ptrdiff_t(fp->argc); - while (vp < vpstop) { - if (vp >= fp->down->regs->sp) - nativeFrameTracker.set(vp, (LIns*)0); - set(vp++, void_ins, true); - } - - vp = &fp->slots[0]; - vpstop = vp + fp->script->nfixed; - while (vp < vpstop) - set(vp++, void_ins, true); - set(&fp->argsobj, INS_NULL(), true); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_LeaveFrame() -{ - debug_only_stmt( - if (cx->fp->fun) - debug_only_printf(LC_TMTracer, - "LeaveFrame (back to %s), callDepth=%d\n", - js_AtomToPrintableString(cx, cx->fp->fun->atom), - callDepth); - ); - if (callDepth-- <= 0) - ABORT_TRACE("returned out of a loop we started tracing"); - - // LeaveFrame gets called after the interpreter popped the frame and - // stored rval, so cx->fp not cx->fp->down, and -1 not 0. - atoms = FrameAtomBase(cx, cx->fp); - set(&stackval(-1), rval_ins, true); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_PUSH() -{ - stack(0, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_POPV() -{ - jsval& rval = stackval(-1); - LIns *rval_ins = box_jsval(rval, get(&rval)); - - // Store it in cx->fp->rval. NB: Tricky dependencies. cx->fp is the right - // frame because POPV appears only in global and eval code and we don't - // trace JSOP_EVAL or leaving the frame where tracing started. - LIns *fp_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp)); - lir->insStorei(rval_ins, fp_ins, offsetof(JSStackFrame, rval)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENTERWITH() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LEAVEWITH() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_RETURN() -{ - /* A return from callDepth 0 terminates the current loop. */ - if (callDepth == 0) { - AUDIT(returnLoopExits); - endLoop(); - return JSRS_STOP; - } - - putArguments(); - - /* If we inlined this function call, make the return value available to the caller code. */ - jsval& rval = stackval(-1); - JSStackFrame *fp = cx->fp; - if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && JSVAL_IS_PRIMITIVE(rval)) { - JS_ASSERT(OBJECT_TO_JSVAL(fp->thisp) == fp->argv[-1]); - rval_ins = get(&fp->argv[-1]); - } else { - rval_ins = get(&rval); - } - debug_only_printf(LC_TMTracer, - "returning from %s\n", - js_AtomToPrintableString(cx, cx->fp->fun->atom)); - clearFrameSlotsFromCache(); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GOTO() -{ - /* - * If we hit a break or a continue to an outer loop, end the loop and - * generate an always-taken loop exit guard. For other downward gotos - * (like if/else) continue recording. - */ - jssrcnote* sn = js_GetSrcNote(cx->fp->script, cx->fp->regs->pc); - - if (sn && (SN_TYPE(sn) == SRC_BREAK || SN_TYPE(sn) == SRC_CONT2LABEL)) { - AUDIT(breakLoopExits); - endLoop(); - return JSRS_STOP; - } - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IFEQ() -{ - trackCfgMerges(cx->fp->regs->pc); - return ifop(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IFNE() -{ - return ifop(); -} - -LIns* -TraceRecorder::newArguments() -{ - LIns* global_ins = INS_CONSTOBJ(globalObj); - LIns* argc_ins = INS_CONST(cx->fp->argc); - LIns* callee_ins = get(&cx->fp->argv[-2]); - LIns* argv_ins = cx->fp->argc - ? lir->ins2(LIR_piadd, lirbuf->sp, - lir->insImmWord(-treeInfo->nativeStackBase + nativeStackOffset(&cx->fp->argv[0]))) - : INS_CONSTPTR((void *) 2); - js_ArgsPrivateNative *apn = js_ArgsPrivateNative::create(*traceMonitor->dataAlloc, cx->fp->argc); - for (uintN i = 0; i < cx->fp->argc; ++i) { - apn->typemap()[i] = determineSlotType(&cx->fp->argv[i]); - } - - LIns* args[] = { INS_CONSTPTR(apn), argv_ins, callee_ins, argc_ins, global_ins, cx_ins }; - LIns* call_ins = lir->insCall(&js_Arguments_ci, args); - guard(false, lir->ins_peq0(call_ins), OOM_EXIT); - return call_ins; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ARGUMENTS() -{ - if (cx->fp->flags & JSFRAME_OVERRIDE_ARGS) - ABORT_TRACE("Can't trace |arguments| if |arguments| is assigned to"); - - LIns* a_ins = get(&cx->fp->argsobj); - LIns* args_ins; - if (a_ins->opcode() == LIR_int) { - // |arguments| is set to 0 by EnterFrame on this trace, so call to create it. - args_ins = newArguments(); - } else { - // Generate LIR to create arguments only if it has not already been created. - - LIns* mem_ins = lir->insAlloc(sizeof(jsval)); - - LIns* br1 = lir->insBranch(LIR_jt, lir->ins_peq0(a_ins), NULL); - lir->insStorei(a_ins, mem_ins, 0); - LIns* br2 = lir->insBranch(LIR_j, NULL, NULL); - - LIns* label1 = lir->ins0(LIR_label); - br1->setTarget(label1); - - LIns* call_ins = newArguments(); - lir->insStorei(call_ins, mem_ins, 0); - - LIns* label2 = lir->ins0(LIR_label); - br2->setTarget(label2); - - args_ins = lir->insLoad(LIR_ldp, mem_ins, 0); - } - - stack(0, args_ins); - set(&cx->fp->argsobj, args_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DUP() -{ - stack(0, get(&stackval(-1))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DUP2() -{ - stack(0, get(&stackval(-2))); - stack(1, get(&stackval(-1))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SWAP() -{ - jsval& l = stackval(-2); - jsval& r = stackval(-1); - LIns* l_ins = get(&l); - LIns* r_ins = get(&r); - set(&r, l_ins); - set(&l, r_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_PICK() -{ - jsval* sp = cx->fp->regs->sp; - jsint n = cx->fp->regs->pc[1]; - JS_ASSERT(sp - (n+1) >= StackBase(cx->fp)); - LIns* top = get(sp - (n+1)); - for (jsint i = 0; i < n; ++i) - set(sp - (n+1) + i, get(sp - n + i)); - set(&sp[-1], top); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETCONST() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BITOR() -{ - return binary(LIR_or); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BITXOR() -{ - return binary(LIR_xor); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BITAND() -{ - return binary(LIR_and); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_EQ() -{ - return equality(false, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NE() -{ - return equality(true, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LT() -{ - return relational(LIR_flt, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LE() -{ - return relational(LIR_fle, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GT() -{ - return relational(LIR_fgt, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GE() -{ - return relational(LIR_fge, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LSH() -{ - return binary(LIR_lsh); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_RSH() -{ - return binary(LIR_rsh); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_URSH() -{ - return binary(LIR_ush); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ADD() -{ - jsval& r = stackval(-1); - jsval& l = stackval(-2); - - if (!JSVAL_IS_PRIMITIVE(l)) { - ABORT_IF_XML(l); - if (!JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(add_imacros.obj_obj); - } - return call_imacro(add_imacros.obj_any); - } - if (!JSVAL_IS_PRIMITIVE(r)) { - ABORT_IF_XML(r); - return call_imacro(add_imacros.any_obj); - } - - if (JSVAL_IS_STRING(l) || JSVAL_IS_STRING(r)) { - LIns* args[] = { stringify(r), stringify(l), cx_ins }; - LIns* concat = lir->insCall(&js_ConcatStrings_ci, args); - guard(false, lir->ins_peq0(concat), OOM_EXIT); - set(&l, concat); - return JSRS_CONTINUE; - } - - return binary(LIR_fadd); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SUB() -{ - return binary(LIR_fsub); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_MUL() -{ - return binary(LIR_fmul); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DIV() -{ - return binary(LIR_fdiv); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_MOD() -{ - return binary(LIR_fmod); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NOT() -{ - jsval& v = stackval(-1); - if (JSVAL_IS_SPECIAL(v)) { - set(&v, lir->ins_eq0(lir->ins2i(LIR_eq, get(&v), 1))); - return JSRS_CONTINUE; - } - if (isNumber(v)) { - LIns* v_ins = get(&v); - set(&v, lir->ins2(LIR_or, lir->ins2(LIR_feq, v_ins, lir->insImmf(0)), - lir->ins_eq0(lir->ins2(LIR_feq, v_ins, v_ins)))); - return JSRS_CONTINUE; - } - if (JSVAL_TAG(v) == JSVAL_OBJECT) { - set(&v, lir->ins_peq0(get(&v))); - return JSRS_CONTINUE; - } - JS_ASSERT(JSVAL_IS_STRING(v)); - set(&v, lir->ins_peq0(lir->ins2(LIR_piand, - lir->insLoad(LIR_ldp, get(&v), (int)offsetof(JSString, mLength)), - INS_CONSTWORD(JSString::LENGTH_MASK)))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BITNOT() -{ - return unary(LIR_not); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NEG() -{ - jsval& v = stackval(-1); - - if (!JSVAL_IS_PRIMITIVE(v)) { - ABORT_IF_XML(v); - return call_imacro(unary_imacros.sign); - } - - if (isNumber(v)) { - LIns* a = get(&v); - - /* - * If we're a promoted integer, we have to watch out for 0s since -0 is - * a double. Only follow this path if we're not an integer that's 0 and - * we're not a double that's zero. - */ - if (!oracle.isInstructionUndemotable(cx->fp->regs->pc) && - isPromoteInt(a) && - (!JSVAL_IS_INT(v) || JSVAL_TO_INT(v) != 0) && - (!JSVAL_IS_DOUBLE(v) || !JSDOUBLE_IS_NEGZERO(*JSVAL_TO_DOUBLE(v))) && - -asNumber(v) == (int)-asNumber(v)) { - a = lir->ins1(LIR_neg, ::demote(lir, a)); - if (!a->isconst()) { - VMSideExit* exit = snapshot(OVERFLOW_EXIT); - guard(false, lir->ins1(LIR_ov, a), exit); - guard(false, lir->ins2i(LIR_eq, a, 0), exit); - } - a = lir->ins1(LIR_i2f, a); - } else { - a = lir->ins1(LIR_fneg, a); - } - - set(&v, a); - return JSRS_CONTINUE; - } - - if (JSVAL_IS_NULL(v)) { - set(&v, lir->insImmf(-0.0)); - return JSRS_CONTINUE; - } - - JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING || JSVAL_IS_SPECIAL(v)); - - LIns* args[] = { get(&v), cx_ins }; - set(&v, lir->ins1(LIR_fneg, - lir->insCall(JSVAL_IS_STRING(v) - ? &js_StringToNumber_ci - : &js_BooleanOrUndefinedToNumber_ci, - args))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_POS() -{ - jsval& v = stackval(-1); - - if (!JSVAL_IS_PRIMITIVE(v)) { - ABORT_IF_XML(v); - return call_imacro(unary_imacros.sign); - } - - if (isNumber(v)) - return JSRS_CONTINUE; - - if (JSVAL_IS_NULL(v)) { - set(&v, lir->insImmf(0)); - return JSRS_CONTINUE; - } - - JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING || JSVAL_IS_SPECIAL(v)); - - LIns* args[] = { get(&v), cx_ins }; - set(&v, lir->insCall(JSVAL_IS_STRING(v) - ? &js_StringToNumber_ci - : &js_BooleanOrUndefinedToNumber_ci, - args)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_PRIMTOP() -{ - // Either this opcode does nothing or we couldn't have traced here, because - // we'd have thrown an exception -- so do nothing if we actually hit this. - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_OBJTOP() -{ - jsval& v = stackval(-1); - ABORT_IF_XML(v); - return JSRS_CONTINUE; -} - -JSRecordingStatus -TraceRecorder::getClassPrototype(JSObject* ctor, LIns*& proto_ins) -{ - // ctor must be a function created via js_InitClass. -#ifdef DEBUG - JSClass *clasp = FUN_CLASP(GET_FUNCTION_PRIVATE(cx, ctor)); - JS_ASSERT(clasp); - - JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx); -#endif - - jsval pval; - if (!ctor->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &pval)) - ABORT_TRACE_ERROR("error getting prototype from constructor"); - - // ctor.prototype is a permanent data property, so this lookup cannot have - // deep-aborted. - JS_ASSERT(localtm.recorder); - -#ifdef DEBUG - JSBool ok, found; - uintN attrs; - ok = JS_GetPropertyAttributes(cx, ctor, js_class_prototype_str, &attrs, &found); - JS_ASSERT(ok); - JS_ASSERT(found); - JS_ASSERT((~attrs & (JSPROP_READONLY | JSPROP_PERMANENT)) == 0); -#endif - - // Since ctor was built by js_InitClass, we can assert (rather than check) - // that pval is usable. - JS_ASSERT(!JSVAL_IS_PRIMITIVE(pval)); - JSObject *proto = JSVAL_TO_OBJECT(pval); - JS_ASSERT_IF(clasp != &js_ArrayClass, OBJ_SCOPE(proto)->emptyScope->clasp == clasp); - - proto_ins = INS_CONSTOBJ(proto); - return JSRS_CONTINUE; -} - -JSRecordingStatus -TraceRecorder::getClassPrototype(JSProtoKey key, LIns*& proto_ins) -{ -#ifdef DEBUG - JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx); -#endif - - JSObject* proto; - if (!js_GetClassPrototype(cx, globalObj, INT_TO_JSID(key), &proto)) - ABORT_TRACE_ERROR("error in js_GetClassPrototype"); - - // This should not have reentered. - JS_ASSERT(localtm.recorder); - - // If we might end up passing the proto to JSObject::initSharingEmptyScope, - // we must check here that proto has a matching emptyScope. We skip the - // check for Array.prototype because new arrays, being non-native, are - // never initialized using initSharingEmptyScope. - if (key != JSProto_Array) { - if (!OBJ_IS_NATIVE(proto)) { - //non-native class prototype - return JSRS_STOP; - } - JSEmptyScope *emptyScope = OBJ_SCOPE(proto)->emptyScope; - if (!emptyScope || JSCLASS_CACHED_PROTO_KEY(emptyScope->clasp) != key) { - // class prototype is not the standard one - return JSRS_STOP; - } - } - - proto_ins = INS_CONSTOBJ(proto); - return JSRS_CONTINUE; -} - -#define IGNORE_NATIVE_CALL_COMPLETE_CALLBACK ((JSSpecializedNative*)1) - -JSRecordingStatus -TraceRecorder::newString(JSObject* ctor, uint32 argc, jsval* argv, jsval* rval) -{ - JS_ASSERT(argc == 1); - - if (!JSVAL_IS_PRIMITIVE(argv[0])) { - ABORT_IF_XML(argv[0]); - return call_imacro(new_imacros.String); - } - - LIns* proto_ins; - CHECK_STATUS(getClassPrototype(ctor, proto_ins)); - - LIns* args[] = { stringify(argv[0]), proto_ins, cx_ins }; - LIns* obj_ins = lir->insCall(&js_String_tn_ci, args); - guard(false, lir->ins_peq0(obj_ins), OOM_EXIT); - - set(rval, obj_ins); - pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; - return JSRS_CONTINUE; -} - -JSRecordingStatus -TraceRecorder::newArray(JSObject* ctor, uint32 argc, jsval* argv, jsval* rval) -{ - LIns *proto_ins; - CHECK_STATUS(getClassPrototype(ctor, proto_ins)); - - LIns *arr_ins; - if (argc == 0) { - // arr_ins = js_NewEmptyArray(cx, Array.prototype) - LIns *args[] = { proto_ins, cx_ins }; - arr_ins = lir->insCall(&js_NewEmptyArray_ci, args); - guard(false, lir->ins_peq0(arr_ins), OOM_EXIT); - } else if (argc == 1 && JSVAL_IS_NUMBER(argv[0])) { - // arr_ins = js_NewEmptyArray(cx, Array.prototype, length) - LIns *args[] = { f2i(get(argv)), proto_ins, cx_ins }; // FIXME: is this 64-bit safe? - arr_ins = lir->insCall(&js_NewEmptyArrayWithLength_ci, args); - guard(false, lir->ins_peq0(arr_ins), OOM_EXIT); - } else { - // arr_ins = js_NewUninitializedArray(cx, Array.prototype, argc) - LIns *args[] = { INS_CONST(argc), proto_ins, cx_ins }; - arr_ins = lir->insCall(&js_NewUninitializedArray_ci, args); - guard(false, lir->ins_peq0(arr_ins), OOM_EXIT); - - // arr->dslots[i] = box_jsval(vp[i]); for i in 0..argc - LIns *dslots_ins = NULL; - for (uint32 i = 0; i < argc && !outOfMemory(); i++) { - LIns *elt_ins = box_jsval(argv[i], get(&argv[i])); - stobj_set_dslot(arr_ins, i, dslots_ins, elt_ins); - } - - if (argc > 0) - stobj_set_fslot(arr_ins, JSSLOT_ARRAY_COUNT, INS_CONST(argc)); - } - - set(rval, arr_ins); - pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK void -TraceRecorder::propagateFailureToBuiltinStatus(LIns* ok_ins, LIns*& status_ins) -{ - /* - * Check the boolean return value (ok_ins) of a native JSNative, - * JSFastNative, or JSPropertyOp hook for failure. On failure, set the - * JSBUILTIN_ERROR bit of cx->builtinStatus. - * - * If the return value (ok_ins) is true, status' == status. Otherwise - * status' = status | JSBUILTIN_ERROR. We calculate (rval&1)^1, which is 1 - * if rval is JS_FALSE (error), and then shift that by 1, which is the log2 - * of JSBUILTIN_ERROR. - */ - JS_STATIC_ASSERT(((JS_TRUE & 1) ^ 1) << 1 == 0); - JS_STATIC_ASSERT(((JS_FALSE & 1) ^ 1) << 1 == JSBUILTIN_ERROR); - status_ins = lir->ins2(LIR_or, - status_ins, - lir->ins2i(LIR_lsh, - lir->ins2i(LIR_xor, - lir->ins2i(LIR_and, ok_ins, 1), - 1), - 1)); - lir->insStorei(status_ins, lirbuf->state, (int) offsetof(InterpState, builtinStatus)); -} - -JS_REQUIRES_STACK void -TraceRecorder::emitNativePropertyOp(JSScope* scope, JSScopeProperty* sprop, LIns* obj_ins, - bool setflag, LIns* boxed_ins) -{ - JS_ASSERT(!(sprop->attrs & (setflag ? JSPROP_SETTER : JSPROP_GETTER))); - JS_ASSERT(setflag ? !SPROP_HAS_STUB_SETTER(sprop) : !SPROP_HAS_STUB_GETTER(sprop)); - - enterDeepBailCall(); - - // It is unsafe to pass the address of an object slot as the out parameter, - // because the getter or setter could end up resizing the object's dslots. - // Instead, use a word of stack and root it in nativeVp. - LIns* vp_ins = lir->insAlloc(sizeof(jsval)); - lir->insStorei(vp_ins, lirbuf->state, offsetof(InterpState, nativeVp)); - lir->insStorei(INS_CONST(1), lirbuf->state, offsetof(InterpState, nativeVpLen)); - if (setflag) - lir->insStorei(boxed_ins, vp_ins, 0); - - CallInfo* ci = new (*traceMonitor->dataAlloc) CallInfo(); - ci->_address = uintptr_t(setflag ? sprop->setter : sprop->getter); - ci->_argtypes = ARGSIZE_I << (0*ARGSIZE_SHIFT) | - ARGSIZE_P << (1*ARGSIZE_SHIFT) | - ARGSIZE_P << (2*ARGSIZE_SHIFT) | - ARGSIZE_P << (3*ARGSIZE_SHIFT) | - ARGSIZE_P << (4*ARGSIZE_SHIFT); - ci->_cse = ci->_fold = 0; - ci->_abi = ABI_CDECL; -#ifdef DEBUG - ci->_name = "JSPropertyOp"; -#endif - LIns* args[] = { vp_ins, INS_CONSTVAL(SPROP_USERID(sprop)), obj_ins, cx_ins }; - LIns* ok_ins = lir->insCall(ci, args); - - // Cleanup. Immediately clear nativeVp before we might deep bail. - lir->insStorei(INS_NULL(), lirbuf->state, offsetof(InterpState, nativeVp)); - leaveDeepBailCall(); - - // Guard that the call succeeded and builtinStatus is still 0. - // If the native op succeeds but we deep-bail here, the result value is - // lost! Therefore this can only be used for setters of shared properties. - // In that case we ignore the result value anyway. - LIns* status_ins = lir->insLoad(LIR_ld, - lirbuf->state, - (int) offsetof(InterpState, builtinStatus)); - propagateFailureToBuiltinStatus(ok_ins, status_ins); - guard(true, lir->ins_eq0(status_ins), STATUS_EXIT); - - // Re-load the value--but this is currently unused, so commented out. - //boxed_ins = lir->insLoad(LIR_ldp, vp_ins, 0); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::emitNativeCall(JSSpecializedNative* sn, uintN argc, LIns* args[], bool rooted) -{ - bool constructing = sn->flags & JSTN_CONSTRUCTOR; - - if (JSTN_ERRTYPE(sn) == FAIL_STATUS) { - // This needs to capture the pre-call state of the stack. So do not set - // pendingSpecializedNative before taking this snapshot. - JS_ASSERT(!pendingSpecializedNative); - - // Take snapshot for js_DeepBail and store it in cx->bailExit. - // If we are calling a slow native, add information to the side exit - // for SynthesizeSlowNativeFrame. - VMSideExit* exit = enterDeepBailCall(); - JSObject* funobj = JSVAL_TO_OBJECT(stackval(0 - (2 + argc))); - if (FUN_SLOW_NATIVE(GET_FUNCTION_PRIVATE(cx, funobj))) { - exit->setNativeCallee(funobj, constructing); - treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(funobj)); - } - } - - LIns* res_ins = lir->insCall(sn->builtin, args); - - // Immediately unroot the vp as soon we return since we might deep bail next. - if (rooted) - lir->insStorei(INS_NULL(), lirbuf->state, offsetof(InterpState, nativeVp)); - - rval_ins = res_ins; - switch (JSTN_ERRTYPE(sn)) { - case FAIL_NULL: - guard(false, lir->ins_peq0(res_ins), OOM_EXIT); - break; - case FAIL_NEG: - res_ins = lir->ins1(LIR_i2f, res_ins); - guard(false, lir->ins2(LIR_flt, res_ins, lir->insImmf(0)), OOM_EXIT); - break; - case FAIL_VOID: - guard(false, lir->ins2i(LIR_eq, res_ins, JSVAL_TO_SPECIAL(JSVAL_VOID)), OOM_EXIT); - break; - case FAIL_COOKIE: - guard(false, lir->ins2(LIR_peq, res_ins, INS_CONSTWORD(JSVAL_ERROR_COOKIE)), OOM_EXIT); - break; - default:; - } - - set(&stackval(0 - (2 + argc)), res_ins); - - /* - * The return value will be processed by NativeCallComplete since - * we have to know the actual return value type for calls that return - * jsval (like Array_p_pop). - */ - pendingSpecializedNative = sn; - - return JSRS_CONTINUE; -} - -/* - * Check whether we have a specialized implementation for this native - * invocation. - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::callSpecializedNative(JSNativeTraceInfo *trcinfo, uintN argc, - bool constructing) -{ - JSStackFrame* fp = cx->fp; - jsbytecode *pc = fp->regs->pc; - - jsval& fval = stackval(0 - (2 + argc)); - jsval& tval = stackval(0 - (1 + argc)); - - LIns* this_ins = get(&tval); - - LIns* args[nanojit::MAXARGS]; - JSSpecializedNative *sn = trcinfo->specializations; - JS_ASSERT(sn); - do { - if (((sn->flags & JSTN_CONSTRUCTOR) != 0) != constructing) - continue; - - uintN knownargc = strlen(sn->argtypes); - if (argc != knownargc) - continue; - - intN prefixc = strlen(sn->prefix); - JS_ASSERT(prefixc <= 3); - LIns** argp = &args[argc + prefixc - 1]; - char argtype; - -#if defined DEBUG - memset(args, 0xCD, sizeof(args)); -#endif - - uintN i; - for (i = prefixc; i--; ) { - argtype = sn->prefix[i]; - if (argtype == 'C') { - *argp = cx_ins; - } else if (argtype == 'T') { /* this, as an object */ - if (JSVAL_IS_PRIMITIVE(tval)) - goto next_specialization; - *argp = this_ins; - } else if (argtype == 'S') { /* this, as a string */ - if (!JSVAL_IS_STRING(tval)) - goto next_specialization; - *argp = this_ins; - } else if (argtype == 'f') { - *argp = INS_CONSTOBJ(JSVAL_TO_OBJECT(fval)); - } else if (argtype == 'p') { - CHECK_STATUS(getClassPrototype(JSVAL_TO_OBJECT(fval), *argp)); - } else if (argtype == 'R') { - *argp = INS_CONSTPTR(cx->runtime); - } else if (argtype == 'P') { - // FIXME: Set pc to imacpc when recording JSOP_CALL inside the - // JSOP_GETELEM imacro (bug 476559). - if (*pc == JSOP_CALL && fp->imacpc && *fp->imacpc == JSOP_GETELEM) - *argp = INS_CONSTPTR(fp->imacpc); - else - *argp = INS_CONSTPTR(pc); - } else if (argtype == 'D') { /* this, as a number */ - if (!isNumber(tval)) - goto next_specialization; - *argp = this_ins; - } else { - JS_NOT_REACHED("unknown prefix arg type"); - } - argp--; - } - - for (i = knownargc; i--; ) { - jsval& arg = stackval(0 - (i + 1)); - *argp = get(&arg); - - argtype = sn->argtypes[i]; - if (argtype == 'd' || argtype == 'i') { - if (!isNumber(arg)) - goto next_specialization; - if (argtype == 'i') - *argp = f2i(*argp); - } else if (argtype == 'o') { - if (JSVAL_IS_PRIMITIVE(arg)) - goto next_specialization; - } else if (argtype == 's') { - if (!JSVAL_IS_STRING(arg)) - goto next_specialization; - } else if (argtype == 'r') { - if (!VALUE_IS_REGEXP(cx, arg)) - goto next_specialization; - } else if (argtype == 'f') { - if (!VALUE_IS_FUNCTION(cx, arg)) - goto next_specialization; - } else if (argtype == 'v') { - *argp = box_jsval(arg, *argp); - } else { - goto next_specialization; - } - argp--; - } -#if defined DEBUG - JS_ASSERT(args[0] != (LIns *)0xcdcdcdcd); -#endif - return emitNativeCall(sn, argc, args, false); - -next_specialization:; - } while ((sn++)->flags & JSTN_MORE); - - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::callNative(uintN argc, JSOp mode) -{ - LIns* args[5]; - - JS_ASSERT(mode == JSOP_CALL || mode == JSOP_NEW || mode == JSOP_APPLY); - - jsval* vp = &stackval(0 - (2 + argc)); - JSObject* funobj = JSVAL_TO_OBJECT(vp[0]); - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, funobj); - JSFastNative native = (JSFastNative)fun->u.n.native; - - switch (argc) { - case 1: - if (isNumber(vp[2]) && - (native == js_math_ceil || native == js_math_floor || native == js_math_round)) { - LIns* a = get(&vp[2]); - if (isPromote(a)) { - set(&vp[0], a); - pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; - return JSRS_CONTINUE; - } - } - break; - case 2: - if (isNumber(vp[2]) && isNumber(vp[3]) && - (native == js_math_min || native == js_math_max)) { - LIns* a = get(&vp[2]); - LIns* b = get(&vp[3]); - if (isPromote(a) && isPromote(b)) { - a = ::demote(lir, a); - b = ::demote(lir, b); - set(&vp[0], - lir->ins1(LIR_i2f, - lir->ins_choose(lir->ins2((native == js_math_min) - ? LIR_lt - : LIR_gt, a, b), - a, b))); - pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; - return JSRS_CONTINUE; - } - } - break; - } - - if (fun->flags & JSFUN_TRCINFO) { - JSNativeTraceInfo *trcinfo = FUN_TRCINFO(fun); - JS_ASSERT(trcinfo && (JSFastNative)fun->u.n.native == trcinfo->native); - - /* Try to call a type specialized version of the native. */ - if (trcinfo->specializations) { - JSRecordingStatus status = callSpecializedNative(trcinfo, argc, mode == JSOP_NEW); - if (status != JSRS_STOP) - return status; - } - } - - if (native == js_fun_apply || native == js_fun_call) - ABORT_TRACE("trying to call native apply or call"); - - // Allocate the vp vector and emit code to root it. - uintN vplen = 2 + JS_MAX(argc, FUN_MINARGS(fun)) + fun->u.n.extra; - if (!(fun->flags & JSFUN_FAST_NATIVE)) - vplen++; // slow native return value slot - LIns* invokevp_ins = lir->insAlloc(vplen * sizeof(jsval)); - - // vp[0] is the callee. - lir->insStorei(INS_CONSTVAL(OBJECT_TO_JSVAL(funobj)), invokevp_ins, 0); - - // Calculate |this|. - LIns* this_ins; - if (mode == JSOP_NEW) { - JSClass* clasp = fun->u.n.clasp; - JS_ASSERT(clasp != &js_SlowArrayClass); - if (!clasp) - clasp = &js_ObjectClass; - JS_ASSERT(((jsuword) clasp & 3) == 0); - - // Abort on |new Function|. js_NewInstance would allocate a regular- - // sized JSObject, not a Function-sized one. (The Function ctor would - // deep-bail anyway but let's not go there.) - if (clasp == &js_FunctionClass) - ABORT_TRACE("new Function"); - - if (clasp->getObjectOps) - ABORT_TRACE("new with non-native ops"); - - args[0] = INS_CONSTOBJ(funobj); - args[1] = INS_CONSTPTR(clasp); - args[2] = cx_ins; - newobj_ins = lir->insCall(&js_NewInstance_ci, args); - guard(false, lir->ins_peq0(newobj_ins), OOM_EXIT); - this_ins = newobj_ins; /* boxing an object is a no-op */ - } else if (JSFUN_BOUND_METHOD_TEST(fun->flags)) { - /* |funobj| was rooted above already. */ - this_ins = INS_CONSTWORD(OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, funobj))); - } else { - this_ins = get(&vp[1]); - - /* - * For fast natives, 'null' or primitives are fine as as 'this' value. - * For slow natives we have to ensure the object is substituted for the - * appropriate global object or boxed object value. JSOP_NEW allocates its - * own object so it's guaranteed to have a valid 'this' value. - */ - if (!(fun->flags & JSFUN_FAST_NATIVE)) { - if (JSVAL_IS_NULL(vp[1])) { - JSObject* thisObj = js_ComputeThis(cx, JS_FALSE, vp + 2); - if (!thisObj) - ABORT_TRACE_ERROR("error in js_ComputeGlobalThis"); - this_ins = INS_CONSTOBJ(thisObj); - } else if (!JSVAL_IS_OBJECT(vp[1])) { - ABORT_TRACE("slow native(primitive, args)"); - } else { - if (guardClass(JSVAL_TO_OBJECT(vp[1]), this_ins, &js_WithClass, snapshot(MISMATCH_EXIT))) - ABORT_TRACE("can't trace slow native invocation on With object"); - - this_ins = lir->ins_choose(lir->ins_peq0(stobj_get_parent(this_ins)), - INS_CONSTOBJ(globalObj), - this_ins); - } - } - this_ins = box_jsval(vp[1], this_ins); - } - set(&vp[1], this_ins); - lir->insStorei(this_ins, invokevp_ins, 1 * sizeof(jsval)); - - // Populate argv. - for (uintN n = 2; n < 2 + argc; n++) { - LIns* i = box_jsval(vp[n], get(&vp[n])); - lir->insStorei(i, invokevp_ins, n * sizeof(jsval)); - - // For a very long argument list we might run out of LIR space, so - // check inside the loop. - if (outOfMemory()) - ABORT_TRACE("out of memory in argument list"); - } - - // Populate extra slots, including the return value slot for a slow native. - if (2 + argc < vplen) { - LIns* undef_ins = INS_CONSTWORD(JSVAL_VOID); - for (uintN n = 2 + argc; n < vplen; n++) { - lir->insStorei(undef_ins, invokevp_ins, n * sizeof(jsval)); - - if (outOfMemory()) - ABORT_TRACE("out of memory in extra slots"); - } - } - - // Set up arguments for the JSNative or JSFastNative. - uint32 types; - if (fun->flags & JSFUN_FAST_NATIVE) { - if (mode == JSOP_NEW) - ABORT_TRACE("untraceable fast native constructor"); - native_rval_ins = invokevp_ins; - args[0] = invokevp_ins; - args[1] = lir->insImm(argc); - args[2] = cx_ins; - types = ARGSIZE_I << (0*ARGSIZE_SHIFT) | - ARGSIZE_P << (1*ARGSIZE_SHIFT) | - ARGSIZE_I << (2*ARGSIZE_SHIFT) | - ARGSIZE_P << (3*ARGSIZE_SHIFT); - } else { - int32_t offset = (vplen - 1) * sizeof(jsval); - native_rval_ins = lir->ins2(LIR_piadd, invokevp_ins, INS_CONSTWORD(offset)); - args[0] = native_rval_ins; - args[1] = lir->ins2(LIR_piadd, invokevp_ins, INS_CONSTWORD(2 * sizeof(jsval))); - args[2] = lir->insImm(argc); - args[3] = this_ins; - args[4] = cx_ins; - types = ARGSIZE_I << (0*ARGSIZE_SHIFT) | - ARGSIZE_P << (1*ARGSIZE_SHIFT) | - ARGSIZE_P << (2*ARGSIZE_SHIFT) | - ARGSIZE_I << (3*ARGSIZE_SHIFT) | - ARGSIZE_P << (4*ARGSIZE_SHIFT) | - ARGSIZE_P << (5*ARGSIZE_SHIFT); - } - - // Generate CallInfo and a JSSpecializedNative structure on the fly. - // Do not use JSTN_UNBOX_AFTER for mode JSOP_NEW because - // record_NativeCallComplete unboxes the result specially. - - CallInfo* ci = new (*traceMonitor->dataAlloc) CallInfo(); - ci->_address = uintptr_t(fun->u.n.native); - ci->_cse = ci->_fold = 0; - ci->_abi = ABI_CDECL; - ci->_argtypes = types; -#ifdef DEBUG - ci->_name = JS_GetFunctionName(fun); - #endif - - // Generate a JSSpecializedNative structure on the fly. - generatedSpecializedNative.builtin = ci; - generatedSpecializedNative.flags = FAIL_STATUS | ((mode == JSOP_NEW) - ? JSTN_CONSTRUCTOR - : JSTN_UNBOX_AFTER); - generatedSpecializedNative.prefix = NULL; - generatedSpecializedNative.argtypes = NULL; - - // We only have to ensure that the values we wrote into the stack buffer - // are rooted if we actually make it to the call, so only set nativeVp and - // nativeVpLen immediately before emitting the call code. This way we avoid - // leaving trace with a bogus nativeVp because we fall off trace while unboxing - // values into the stack buffer. - lir->insStorei(INS_CONST(vplen), lirbuf->state, offsetof(InterpState, nativeVpLen)); - lir->insStorei(invokevp_ins, lirbuf->state, offsetof(InterpState, nativeVp)); - - // argc is the original argc here. It is used to calculate where to place - // the return value. - return emitNativeCall(&generatedSpecializedNative, argc, args, true); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::functionCall(uintN argc, JSOp mode) -{ - jsval& fval = stackval(0 - (2 + argc)); - JS_ASSERT(&fval >= StackBase(cx->fp)); - - if (!VALUE_IS_FUNCTION(cx, fval)) - ABORT_TRACE("callee is not a function"); - - jsval& tval = stackval(0 - (1 + argc)); - - /* - * If callee is not constant, it's a shapeless call and we have to guard - * explicitly that we will get this callee again at runtime. - */ - if (!get(&fval)->isconstp()) - CHECK_STATUS(guardCallee(fval)); - - /* - * Require that the callee be a function object, to avoid guarding on its - * class here. We know if the callee and this were pushed by JSOP_CALLNAME - * or JSOP_CALLPROP that callee is a *particular* function, since these hit - * the property cache and guard on the object (this) in which the callee - * was found. So it's sufficient to test here that the particular function - * is interpreted, not guard on that condition. - * - * Bytecode sequences that push shapeless callees must guard on the callee - * class being Function and the function being interpreted. - */ - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fval)); - - if (FUN_INTERPRETED(fun)) { - if (mode == JSOP_NEW) { - LIns* args[] = { get(&fval), INS_CONSTPTR(&js_ObjectClass), cx_ins }; - LIns* tv_ins = lir->insCall(&js_NewInstance_ci, args); - guard(false, lir->ins_peq0(tv_ins), OOM_EXIT); - set(&tval, tv_ins); - } - return interpretedFunctionCall(fval, fun, argc, mode == JSOP_NEW); - } - - if (FUN_SLOW_NATIVE(fun)) { - JSNative native = fun->u.n.native; - jsval* argv = &tval + 1; - if (native == js_Array) - return newArray(JSVAL_TO_OBJECT(fval), argc, argv, &fval); - if (native == js_String && argc == 1) { - if (mode == JSOP_NEW) - return newString(JSVAL_TO_OBJECT(fval), 1, argv, &fval); - if (!JSVAL_IS_PRIMITIVE(argv[0])) { - ABORT_IF_XML(argv[0]); - return call_imacro(call_imacros.String); - } - set(&fval, stringify(argv[0])); - pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; - return JSRS_CONTINUE; - } - } - - return callNative(argc, mode); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NEW() -{ - uintN argc = GET_ARGC(cx->fp->regs->pc); - cx->fp->assertValidStackDepth(argc + 2); - return functionCall(argc, JSOP_NEW); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DELNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DELPROP() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DELELEM() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TYPEOF() -{ - jsval& r = stackval(-1); - LIns* type; - if (JSVAL_IS_STRING(r)) { - type = INS_ATOM(cx->runtime->atomState.typeAtoms[JSTYPE_STRING]); - } else if (isNumber(r)) { - type = INS_ATOM(cx->runtime->atomState.typeAtoms[JSTYPE_NUMBER]); - } else if (VALUE_IS_FUNCTION(cx, r)) { - type = INS_ATOM(cx->runtime->atomState.typeAtoms[JSTYPE_FUNCTION]); - } else { - LIns* args[] = { get(&r), cx_ins }; - if (JSVAL_IS_SPECIAL(r)) { - // We specialize identically for boolean and undefined. We must not have a hole here. - // Pass the unboxed type here, since TypeOfBoolean knows how to handle it. - JS_ASSERT(r == JSVAL_TRUE || r == JSVAL_FALSE || r == JSVAL_VOID); - type = lir->insCall(&js_TypeOfBoolean_ci, args); - } else { - JS_ASSERT(JSVAL_TAG(r) == JSVAL_OBJECT); - type = lir->insCall(&js_TypeOfObject_ci, args); - } - } - set(&r, type); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_VOID() -{ - stack(-1, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INCNAME() -{ - return incName(1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INCPROP() -{ - return incProp(1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INCELEM() -{ - return incElem(1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DECNAME() -{ - return incName(-1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DECPROP() -{ - return incProp(-1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DECELEM() -{ - return incElem(-1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::incName(jsint incr, bool pre) -{ - jsval* vp; - LIns* v_ins; - LIns* v_after; - NameResult nr; - - CHECK_STATUS(name(vp, v_ins, nr)); - jsval v = nr.tracked ? *vp : nr.v; - CHECK_STATUS(incHelper(v, v_ins, v_after, incr)); - LIns* v_result = pre ? v_after : v_ins; - if (nr.tracked) { - set(vp, v_after); - stack(0, v_result); - return JSRS_CONTINUE; - } - - if (OBJ_GET_CLASS(cx, nr.obj) != &js_CallClass) - ABORT_TRACE("incName on unsupported object class"); - - CHECK_STATUS(setCallProp(nr.obj, nr.obj_ins, nr.sprop, v_after, v)); - stack(0, v_result); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NAMEINC() -{ - return incName(1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_PROPINC() -{ - return incProp(1, false); -} - -// XXX consolidate with record_JSOP_GETELEM code... -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ELEMINC() -{ - return incElem(1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NAMEDEC() -{ - return incName(-1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_PROPDEC() -{ - return incProp(-1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ELEMDEC() -{ - return incElem(-1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETPROP() -{ - return getProp(stackval(-1)); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETPROP() -{ - jsval& l = stackval(-2); - if (JSVAL_IS_PRIMITIVE(l)) - ABORT_TRACE("primitive this for SETPROP"); - - JSObject* obj = JSVAL_TO_OBJECT(l); - if (obj->map->ops->setProperty != js_SetProperty) - ABORT_TRACE("non-native JSObjectOps::setProperty"); - return JSRS_CONTINUE; -} - -/* Emit a specialized, inlined copy of js_NativeSet. */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop, - jsval v, LIns* v_ins) -{ - JSScope* scope = OBJ_SCOPE(obj); - uint32 slot = sprop->slot; - - /* - * We do not trace assignment to properties that have both a nonstub setter - * and a slot, for several reasons. - * - * First, that would require sampling rt->propertyRemovals before and after - * (see js_NativeSet), and even more code to handle the case where the two - * samples differ. A mere guard is not enough, because you can't just bail - * off trace in the middle of a property assignment without storing the - * value and making the stack right. - * - * If obj is the global object, there are two additional problems. We would - * have to emit still more code to store the result in the object (not the - * native global frame) if the setter returned successfully after - * deep-bailing. And we would have to cope if the run-time type of the - * setter's return value differed from the record-time type of v, in which - * case unboxing would fail and, having called a native setter, we could - * not just retry the instruction in the interpreter. - */ - JS_ASSERT(SPROP_HAS_STUB_SETTER(sprop) || slot == SPROP_INVALID_SLOT); - - // Box the value to be stored, if necessary. - LIns* boxed_ins = NULL; - if (!SPROP_HAS_STUB_SETTER(sprop) || (slot != SPROP_INVALID_SLOT && obj != globalObj)) - boxed_ins = box_jsval(v, v_ins); - - // Call the setter, if any. - if (!SPROP_HAS_STUB_SETTER(sprop)) - emitNativePropertyOp(scope, sprop, obj_ins, true, boxed_ins); - - // Store the value, if this property has a slot. - if (slot != SPROP_INVALID_SLOT) { - JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope)); - JS_ASSERT(!(sprop->attrs & JSPROP_SHARED)); - if (obj == globalObj) { - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - set(&STOBJ_GET_SLOT(obj, slot), v_ins); - } else { - LIns* dslots_ins = NULL; - stobj_set_slot(obj_ins, slot, dslots_ins, boxed_ins); - } - } - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::setProp(jsval &l, JSPropCacheEntry* entry, JSScopeProperty* sprop, - jsval &v, LIns*& v_ins) -{ - if (entry == JS_NO_PROP_CACHE_FILL) - ABORT_TRACE("can't trace uncacheable property set"); - JS_ASSERT_IF(PCVCAP_TAG(entry->vcap) >= 1, sprop->attrs & JSPROP_SHARED); - if (!SPROP_HAS_STUB_SETTER(sprop) && sprop->slot != SPROP_INVALID_SLOT) - ABORT_TRACE("can't trace set of property with setter and slot"); - if (sprop->attrs & JSPROP_SETTER) - ABORT_TRACE("can't trace JavaScript function setter"); - - // These two cases are errors and can't be traced. - if (sprop->attrs & JSPROP_GETTER) - ABORT_TRACE("can't assign to property with script getter but no setter"); - if (sprop->attrs & JSPROP_READONLY) - ABORT_TRACE("can't assign to readonly property"); - - JS_ASSERT(!JSVAL_IS_PRIMITIVE(l)); - JSObject* obj = JSVAL_TO_OBJECT(l); - LIns* obj_ins = get(&l); - JSScope* scope = OBJ_SCOPE(obj); - - JS_ASSERT_IF(entry->vcap == PCVCAP_MAKE(entry->kshape, 0, 0), scope->has(sprop)); - - // Fast path for CallClass. This is about 20% faster than the general case. - if (OBJ_GET_CLASS(cx, obj) == &js_CallClass) { - v_ins = get(&v); - return setCallProp(obj, obj_ins, sprop, v_ins, v); - } - - /* - * Setting a function-valued property might need to rebrand the object; we - * don't trace that case. There's no need to guard on that, though, because - * separating functions into the trace-time type TT_FUNCTION will save the - * day! - */ - if (scope->branded() && VALUE_IS_FUNCTION(cx, v)) - ABORT_TRACE("can't trace function-valued property set in branded scope"); - - // Find obj2. If entry->adding(), the TAG bits are all 0. - JSObject* obj2 = obj; - for (jsuword i = PCVCAP_TAG(entry->vcap) >> PCVCAP_PROTOBITS; i; i--) - obj2 = OBJ_GET_PARENT(cx, obj2); - for (jsuword j = PCVCAP_TAG(entry->vcap) & PCVCAP_PROTOMASK; j; j--) - obj2 = OBJ_GET_PROTO(cx, obj2); - scope = OBJ_SCOPE(obj2); - JS_ASSERT_IF(entry->adding(), obj2 == obj); - - // Guard before anything else. - LIns* map_ins = map(obj_ins); - CHECK_STATUS(guardNativePropertyOp(obj, map_ins)); - jsuword pcval; - CHECK_STATUS(guardPropertyCacheHit(obj_ins, map_ins, obj, obj2, entry, pcval)); - JS_ASSERT(scope->object == obj2); - JS_ASSERT(scope->has(sprop)); - JS_ASSERT_IF(obj2 != obj, sprop->attrs & JSPROP_SHARED); - - // Add a property to the object if necessary. - if (entry->adding()) { - JS_ASSERT(!(sprop->attrs & JSPROP_SHARED)); - if (obj == globalObj) - ABORT_TRACE("adding a property to the global object"); - - LIns* args[] = { INS_CONSTSPROP(sprop), obj_ins, cx_ins }; - LIns* ok_ins = lir->insCall(&js_AddProperty_ci, args); - guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); - } - - v_ins = get(&v); - return nativeSet(obj, obj_ins, sprop, v, v_ins); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::setUpwardTrackedVar(jsval* stackVp, jsval v, LIns* v_ins) -{ - JSTraceType stackT = determineSlotType(stackVp); - JSTraceType otherT = getCoercedType(v); - - bool promote = true; - - if (stackT != otherT) { - if (stackT == TT_DOUBLE && otherT == TT_INT32 && isPromoteInt(v_ins)) - promote = false; - else - ABORT_TRACE("can't trace this upvar mutation"); - } - - set(stackVp, v_ins, promote); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::setCallProp(JSObject *callobj, LIns *callobj_ins, JSScopeProperty *sprop, - LIns *v_ins, jsval v) -{ - // Set variables in on-trace-stack call objects by updating the tracker. - JSStackFrame *fp = frameIfInRange(callobj); - if (fp) { - jsint slot = JSVAL_TO_INT(SPROP_USERID(sprop)); - if (sprop->setter == SetCallArg) { - jsval *vp2 = &fp->argv[slot]; - CHECK_STATUS(setUpwardTrackedVar(vp2, v, v_ins)); - return JSRS_CONTINUE; - } - if (sprop->setter == SetCallVar) { - jsval *vp2 = &fp->slots[slot]; - CHECK_STATUS(setUpwardTrackedVar(vp2, v, v_ins)); - return JSRS_CONTINUE; - } - ABORT_TRACE("can't trace special CallClass setter"); - } - - // Set variables in off-trace-stack call objects by calling standard builtins. - const CallInfo* ci = NULL; - if (sprop->setter == SetCallArg) - ci = &js_SetCallArg_ci; - else if (sprop->setter == SetCallVar) - ci = &js_SetCallVar_ci; - else - ABORT_TRACE("can't trace special CallClass setter"); - - // Even though the frame is out of range, later we might be called as an - // inner trace such that the target variable is defined in the outer trace - // entry frame. In that case, we must store to the native stack area for - // that frame. - - LIns *fp_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp)); - LIns *fpcallobj_ins = lir->insLoad(LIR_ldp, fp_ins, offsetof(JSStackFrame, callobj)); - LIns *br1 = lir->insBranch(LIR_jf, lir->ins2(LIR_peq, fpcallobj_ins, callobj_ins), NULL); - - // Case 1: storing to native stack area. - - // Compute native stack slot and address offset we are storing to. - unsigned slot = uint16(sprop->shortid); - LIns *slot_ins; - if (sprop->setter == SetCallArg) - slot_ins = ArgClosureTraits::adj_slot_lir(lir, fp_ins, slot); - else - slot_ins = VarClosureTraits::adj_slot_lir(lir, fp_ins, slot); - LIns *offset_ins = lir->ins2(LIR_mul, slot_ins, INS_CONST(sizeof(double))); - - // Guard that we are not changing the type of the slot we are storing to. - LIns *callstackBase_ins = lir->insLoad(LIR_ldp, lirbuf->state, - offsetof(InterpState, callstackBase)); - LIns *frameInfo_ins = lir->insLoad(LIR_ldp, callstackBase_ins, 0); - LIns *typemap_ins = lir->ins2(LIR_addp, frameInfo_ins, INS_CONSTWORD(sizeof(FrameInfo))); - LIns *type_ins = lir->insLoad(LIR_ldcb, - lir->ins2(LIR_addp, typemap_ins, lir->ins_u2p(slot_ins)), 0); - JSTraceType type = getCoercedType(v); - if (type == TT_INT32 && !isPromoteInt(v_ins)) - type = TT_DOUBLE; - guard(true, - addName(lir->ins2(LIR_eq, type_ins, lir->insImm(type)), - "guard(type-stable set upvar)"), - BRANCH_EXIT); - - // Store to the native stack slot. - LIns *stackBase_ins = lir->insLoad(LIR_ldp, lirbuf->state, - offsetof(InterpState, stackBase)); - LIns *storeValue_ins = isPromoteInt(v_ins) ? demote(lir, v_ins) : v_ins; - lir->insStorei(storeValue_ins, - lir->ins2(LIR_addp, stackBase_ins, lir->ins_u2p(offset_ins)), 0); - LIns *br2 = lir->insBranch(LIR_j, NULL, NULL); - - // Case 2: calling builtin. - LIns *label1 = lir->ins0(LIR_label); - br1->setTarget(label1); - LIns* args[] = { - box_jsval(v, v_ins), - INS_CONST(SPROP_USERID(sprop)), - callobj_ins, - cx_ins - }; - LIns* call_ins = lir->insCall(ci, args); - guard(false, addName(lir->ins_eq0(call_ins), "guard(set upvar)"), STATUS_EXIT); - - LIns *label2 = lir->ins0(LIR_label); - br2->setTarget(label2); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_SetPropHit(JSPropCacheEntry* entry, JSScopeProperty* sprop) -{ - jsval& r = stackval(-1); - jsval& l = stackval(-2); - LIns* v_ins; - CHECK_STATUS(setProp(l, entry, sprop, r, v_ins)); - - jsbytecode* pc = cx->fp->regs->pc; - if (*pc != JSOP_INITPROP && pc[JSOP_SETPROP_LENGTH] != JSOP_POP) - set(&l, v_ins); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK VMSideExit* -TraceRecorder::enterDeepBailCall() -{ - // Take snapshot for js_DeepBail and store it in cx->bailExit. - VMSideExit* exit = snapshot(DEEP_BAIL_EXIT); - lir->insStorei(INS_CONSTPTR(exit), cx_ins, offsetof(JSContext, bailExit)); - - // Tell nanojit not to discard or defer stack writes before this call. - GuardRecord* guardRec = createGuardRecord(exit); - lir->insGuard(LIR_xbarrier, NULL, guardRec); - return exit; -} - -JS_REQUIRES_STACK void -TraceRecorder::leaveDeepBailCall() -{ - // Keep cx->bailExit null when it's invalid. - lir->insStorei(INS_NULL(), cx_ins, offsetof(JSContext, bailExit)); -} - -JS_REQUIRES_STACK void -TraceRecorder::finishGetProp(LIns* obj_ins, LIns* vp_ins, LIns* ok_ins, jsval* outp) -{ - // Store the boxed result (and this-object, if JOF_CALLOP) before the - // guard. The deep-bail case requires this. If the property get fails, - // these slots will be ignored anyway. - LIns* result_ins = lir->insLoad(LIR_ldp, vp_ins, 0); - set(outp, result_ins, true); - if (js_CodeSpec[*cx->fp->regs->pc].format & JOF_CALLOP) - set(outp + 1, obj_ins, true); - - // We need to guard on ok_ins, but this requires a snapshot of the state - // after this op. monitorRecording will do it for us. - pendingGuardCondition = ok_ins; - - // Note there is a boxed result sitting on the stack. The caller must leave - // it there for the time being, since the return type is not yet - // known. monitorRecording will emit the code to unbox it. - pendingUnboxSlot = outp; -} - -static inline bool -RootedStringToId(JSContext* cx, JSString** namep, jsid* idp) -{ - JSString* name = *namep; - if (name->isAtomized()) { - *idp = ATOM_TO_JSID((JSAtom*) STRING_TO_JSVAL(name)); - return true; - } - - JSAtom* atom = js_AtomizeString(cx, name, 0); - if (!atom) - return false; - *namep = ATOM_TO_STRING(atom); /* write back to GC root */ - *idp = ATOM_TO_JSID(atom); - return true; -} - -static JSBool FASTCALL -GetPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval* vp) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - - jsid id; - if (!RootedStringToId(cx, namep, &id) || !obj->getProperty(cx, id, vp)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyByName, CONTEXT, OBJECT, STRINGPTR, JSVALPTR, - 0, 0) - -// Convert the value in a slot to a string and store the resulting string back -// in the slot (typically in order to root it). -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::primitiveToStringInPlace(jsval* vp) -{ - jsval v = *vp; - JS_ASSERT(JSVAL_IS_PRIMITIVE(v)); - - if (!JSVAL_IS_STRING(v)) { - // v is not a string. Turn it into one. js_ValueToString is safe - // because v is not an object. - JSString *str = js_ValueToString(cx, v); - if (!str) - ABORT_TRACE_ERROR("failed to stringify element id"); - v = STRING_TO_JSVAL(str); - set(vp, stringify(*vp)); - - // Write the string back to the stack to save the interpreter some work - // and to ensure snapshots get the correct type for this slot. - *vp = v; - } - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getPropertyByName(LIns* obj_ins, jsval* idvalp, jsval* outp) -{ - CHECK_STATUS(primitiveToStringInPlace(idvalp)); - enterDeepBailCall(); - - // Call GetPropertyByName. The vp parameter points to stack because this is - // what the interpreter currently does. obj and id are rooted on the - // interpreter stack, but the slot at vp is not a root. - LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); - LIns* idvalp_ins = addName(addr(idvalp), "idvalp"); - LIns* args[] = {vp_ins, idvalp_ins, obj_ins, cx_ins}; - LIns* ok_ins = lir->insCall(&GetPropertyByName_ci, args); - - // GetPropertyByName can assign to *idvalp, so the tracker has an incorrect - // entry for that address. Correct it. (If the value in the address is - // never used again, the usual case, Nanojit will kill this load.) - tracker.set(idvalp, lir->insLoad(LIR_ldp, idvalp_ins, 0)); - - finishGetProp(obj_ins, vp_ins, ok_ins, outp); - leaveDeepBailCall(); - return JSRS_CONTINUE; -} - -static JSBool FASTCALL -GetPropertyByIndex(JSContext* cx, JSObject* obj, int32 index, jsval* vp) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - - JSAutoTempIdRooter idr(cx); - if (!js_Int32ToId(cx, index, idr.addr()) || !obj->getProperty(cx, idr.id(), vp)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyByIndex, CONTEXT, OBJECT, INT32, JSVALPTR, 0, 0) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getPropertyByIndex(LIns* obj_ins, LIns* index_ins, jsval* outp) -{ - index_ins = makeNumberInt32(index_ins); - - // See note in getPropertyByName about vp. - enterDeepBailCall(); - LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); - LIns* args[] = {vp_ins, index_ins, obj_ins, cx_ins}; - LIns* ok_ins = lir->insCall(&GetPropertyByIndex_ci, args); - finishGetProp(obj_ins, vp_ins, ok_ins, outp); - leaveDeepBailCall(); - return JSRS_CONTINUE; -} - -static JSBool FASTCALL -GetPropertyById(JSContext* cx, JSObject* obj, jsid id, jsval* vp) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - if (!obj->getProperty(cx, id, vp)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyById, - CONTEXT, OBJECT, JSVAL, JSVALPTR, 0, 0) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getPropertyById(LIns* obj_ins, jsval* outp) -{ - // Find the atom. - JSAtom* atom; - jsbytecode* pc = cx->fp->regs->pc; - const JSCodeSpec& cs = js_CodeSpec[*pc]; - if (*pc == JSOP_LENGTH) { - atom = cx->runtime->atomState.lengthAtom; - } else if (JOF_TYPE(cs.format) == JOF_ATOM) { - atom = atoms[GET_INDEX(pc)]; - } else { - JS_ASSERT(JOF_TYPE(cs.format) == JOF_SLOTATOM); - atom = atoms[GET_INDEX(pc + SLOTNO_LEN)]; - } - - // Call GetPropertyById. See note in getPropertyByName about vp. - enterDeepBailCall(); - jsid id = ATOM_TO_JSID(atom); - LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); - LIns* args[] = {vp_ins, INS_CONSTWORD(id), obj_ins, cx_ins}; - LIns* ok_ins = lir->insCall(&GetPropertyById_ci, args); - finishGetProp(obj_ins, vp_ins, ok_ins, outp); - leaveDeepBailCall(); - return JSRS_CONTINUE; -} - -/* Manually inlined, specialized copy of js_NativeGet. */ -static JSBool FASTCALL -GetPropertyWithNativeGetter(JSContext* cx, JSObject* obj, JSScopeProperty* sprop, jsval* vp) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - -#ifdef DEBUG - JSProperty* prop; - JSObject* pobj; - JS_ASSERT(obj->lookupProperty(cx, sprop->id, &pobj, &prop)); - JS_ASSERT(prop == (JSProperty*) sprop); - pobj->dropProperty(cx, prop); -#endif - - // js_GetSprop contains a special case for With objects. We can elide it - // here because With objects are, we claim, never on the operand stack. - JS_ASSERT(STOBJ_GET_CLASS(obj) != &js_WithClass); - - *vp = JSVAL_VOID; - if (!sprop->getter(cx, obj, SPROP_USERID(sprop), vp)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyWithNativeGetter, - CONTEXT, OBJECT, SCOPEPROP, JSVALPTR, 0, 0) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getPropertyWithNativeGetter(LIns* obj_ins, JSScopeProperty* sprop, jsval* outp) -{ - JS_ASSERT(!(sprop->attrs & JSPROP_GETTER)); - JS_ASSERT(sprop->slot == SPROP_INVALID_SLOT); - JS_ASSERT(!SPROP_HAS_STUB_GETTER(sprop)); - - // Call GetPropertyWithNativeGetter. See note in getPropertyByName about vp. - // FIXME - We should call the getter directly. Using a builtin function for - // now because it buys some extra asserts. See bug 508310. - enterDeepBailCall(); - LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); - LIns* args[] = {vp_ins, INS_CONSTPTR(sprop), obj_ins, cx_ins}; - LIns* ok_ins = lir->insCall(&GetPropertyWithNativeGetter_ci, args); - finishGetProp(obj_ins, vp_ins, ok_ins, outp); - leaveDeepBailCall(); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETELEM() -{ - bool call = *cx->fp->regs->pc == JSOP_CALLELEM; - - jsval& idx = stackval(-1); - jsval& lval = stackval(-2); - - LIns* obj_ins = get(&lval); - LIns* idx_ins = get(&idx); - - // Special case for array-like access of strings. - if (JSVAL_IS_STRING(lval) && isInt32(idx)) { - if (call) - ABORT_TRACE("JSOP_CALLELEM on a string"); - int i = asInt32(idx); - if (size_t(i) >= JSVAL_TO_STRING(lval)->length()) - ABORT_TRACE("Invalid string index in JSOP_GETELEM"); - idx_ins = makeNumberInt32(idx_ins); - LIns* args[] = { idx_ins, obj_ins, cx_ins }; - LIns* unitstr_ins = lir->insCall(&js_String_getelem_ci, args); - guard(false, lir->ins_peq0(unitstr_ins), MISMATCH_EXIT); - set(&lval, unitstr_ins); - return JSRS_CONTINUE; - } - - if (JSVAL_IS_PRIMITIVE(lval)) - ABORT_TRACE("JSOP_GETLEM on a primitive"); - ABORT_IF_XML(lval); - - JSObject* obj = JSVAL_TO_OBJECT(lval); - if (obj == globalObj) - ABORT_TRACE("JSOP_GETELEM on global"); - LIns* v_ins; - - /* Property access using a string name or something we have to stringify. */ - if (!JSVAL_IS_INT(idx)) { - if (!JSVAL_IS_PRIMITIVE(idx)) - ABORT_TRACE("object used as index"); - - return getPropertyByName(obj_ins, &idx, &lval); - } - - if (STOBJ_GET_CLASS(obj) == &js_ArgumentsClass) { - unsigned depth; - JSStackFrame *afp = guardArguments(obj, obj_ins, &depth); - if (afp) { - uintN int_idx = JSVAL_TO_INT(idx); - jsval* vp = &afp->argv[int_idx]; - if (idx_ins->isconstf()) { - if (int_idx >= 0 && int_idx < afp->argc) - v_ins = get(vp); - else - v_ins = INS_VOID(); - } else { - // If the index is not a constant expression, we generate LIR to load the value from - // the native stack area. The guard on js_ArgumentClass above ensures the up-to-date - // value has been written back to the native stack area. - idx_ins = makeNumberInt32(idx_ins); - - if (int_idx < 0 || int_idx >= afp->argc) - ABORT_TRACE("cannot trace arguments with out of range index"); - - guard(true, - addName(lir->ins2(LIR_ge, idx_ins, INS_CONST(0)), - "guard(index >= 0)"), - MISMATCH_EXIT); - guard(true, - addName(lir->ins2(LIR_lt, idx_ins, INS_CONST(afp->argc)), - "guard(index < argc)"), - MISMATCH_EXIT); - - JSTraceType type = getCoercedType(*vp); - - // Guard that the argument has the same type on trace as during recording. - LIns* typemap_ins; - if (depth == 0) { - // In this case, we are in the same frame where the arguments object was created. - // The entry type map is not necessarily up-to-date, so we capture a new type map - // for this point in the code. - unsigned stackSlots = NativeStackSlots(cx, 0 /* callDepth */); - if (stackSlots * sizeof(JSTraceType) > LirBuffer::MAX_SKIP_PAYLOAD_SZB) - ABORT_TRACE("|arguments| requires saving too much stack"); - JSTraceType* typemap = new (*traceMonitor->dataAlloc) JSTraceType[stackSlots]; - DetermineTypesVisitor detVisitor(*this, typemap); - VisitStackSlots(detVisitor, cx, 0); - typemap_ins = INS_CONSTPTR(typemap + 2 /* callee, this */); - } else { - // In this case, we are in a deeper frame from where the arguments object was - // created. The type map at the point of the call out from the creation frame - // is accurate. - // Note: this relies on the assumption that we abort on setting an element of - // an arguments object in any deeper frame. - LIns* fip_ins = lir->insLoad(LIR_ldp, lirbuf->rp, (callDepth-depth)*sizeof(FrameInfo*)); - typemap_ins = lir->ins2(LIR_add, fip_ins, INS_CONST(sizeof(FrameInfo) + 2/*callee,this*/ * sizeof(JSTraceType))); - } - - LIns* typep_ins = lir->ins2(LIR_piadd, typemap_ins, - lir->ins_u2p(lir->ins2(LIR_mul, - idx_ins, - INS_CONST(sizeof(JSTraceType))))); - LIns* type_ins = lir->insLoad(LIR_ldcb, typep_ins, 0); - guard(true, - addName(lir->ins2(LIR_eq, type_ins, lir->insImm(type)), - "guard(type-stable upvar)"), - BRANCH_EXIT); - - // Read the value out of the native stack area. - guard(true, lir->ins2(LIR_ult, idx_ins, INS_CONST(afp->argc)), - snapshot(BRANCH_EXIT)); - size_t stackOffset = -treeInfo->nativeStackBase + nativeStackOffset(&afp->argv[0]); - LIns* args_addr_ins = lir->ins2(LIR_piadd, lirbuf->sp, INS_CONSTWORD(stackOffset)); - LIns* argi_addr_ins = lir->ins2(LIR_piadd, - args_addr_ins, - lir->ins_u2p(lir->ins2(LIR_mul, - idx_ins, - INS_CONST(sizeof(double))))); - v_ins = stackLoad(argi_addr_ins, type); - } - JS_ASSERT(v_ins); - set(&lval, v_ins); - if (call) - set(&idx, obj_ins); - return JSRS_CONTINUE; - } - ABORT_TRACE("can't reach arguments object's frame"); - } - if (js_IsDenseArray(obj)) { - // Fast path for dense arrays accessed with a integer index. - jsval* vp; - LIns* addr_ins; - - guardDenseArray(obj, obj_ins, BRANCH_EXIT); - CHECK_STATUS(denseArrayElement(lval, idx, vp, v_ins, addr_ins)); - set(&lval, v_ins); - if (call) - set(&idx, obj_ins); - return JSRS_CONTINUE; - } - - return getPropertyByIndex(obj_ins, idx_ins, &lval); -} - -/* Functions used by JSOP_SETELEM */ - -static JSBool FASTCALL -SetPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval* vp) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - - jsid id; - if (!RootedStringToId(cx, namep, &id) || !obj->setProperty(cx, id, vp)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, SetPropertyByName, CONTEXT, OBJECT, STRINGPTR, JSVALPTR, - 0, 0) - -static JSBool FASTCALL -InitPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval val) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - - jsid id; - if (!RootedStringToId(cx, namep, &id) || - !obj->defineProperty(cx, id, val, NULL, NULL, JSPROP_ENUMERATE)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, InitPropertyByName, CONTEXT, OBJECT, STRINGPTR, JSVAL, - 0, 0) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::initOrSetPropertyByName(LIns* obj_ins, jsval* idvalp, jsval* rvalp, bool init) -{ - CHECK_STATUS(primitiveToStringInPlace(idvalp)); - - LIns* rval_ins = box_jsval(*rvalp, get(rvalp)); - - enterDeepBailCall(); - - LIns* ok_ins; - LIns* idvalp_ins = addName(addr(idvalp), "idvalp"); - if (init) { - LIns* args[] = {rval_ins, idvalp_ins, obj_ins, cx_ins}; - ok_ins = lir->insCall(&InitPropertyByName_ci, args); - } else { - // See note in getPropertyByName about vp. - LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); - lir->insStorei(rval_ins, vp_ins, 0); - LIns* args[] = {vp_ins, idvalp_ins, obj_ins, cx_ins}; - ok_ins = lir->insCall(&SetPropertyByName_ci, args); - } - pendingGuardCondition = ok_ins; - - leaveDeepBailCall(); - return JSRS_CONTINUE; -} - -static JSBool FASTCALL -SetPropertyByIndex(JSContext* cx, JSObject* obj, int32 index, jsval* vp) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - - JSAutoTempIdRooter idr(cx); - if (!js_Int32ToId(cx, index, idr.addr()) || !obj->setProperty(cx, idr.id(), vp)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, SetPropertyByIndex, CONTEXT, OBJECT, INT32, JSVALPTR, 0, 0) - -static JSBool FASTCALL -InitPropertyByIndex(JSContext* cx, JSObject* obj, int32 index, jsval val) -{ - js_LeaveTraceIfGlobalObject(cx, obj); - - JSAutoTempIdRooter idr(cx); - if (!js_Int32ToId(cx, index, idr.addr()) || - !obj->defineProperty(cx, idr.id(), val, NULL, NULL, JSPROP_ENUMERATE)) { - js_SetBuiltinError(cx); - return JS_FALSE; - } - return cx->interpState->builtinStatus == 0; -} -JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, InitPropertyByIndex, CONTEXT, OBJECT, INT32, JSVAL, 0, 0) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::initOrSetPropertyByIndex(LIns* obj_ins, LIns* index_ins, jsval* rvalp, bool init) -{ - index_ins = makeNumberInt32(index_ins); - - LIns* rval_ins = box_jsval(*rvalp, get(rvalp)); - - enterDeepBailCall(); - - LIns* ok_ins; - if (init) { - LIns* args[] = {rval_ins, index_ins, obj_ins, cx_ins}; - ok_ins = lir->insCall(&InitPropertyByIndex_ci, args); - } else { - // See note in getPropertyByName about vp. - LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); - lir->insStorei(rval_ins, vp_ins, 0); - LIns* args[] = {vp_ins, index_ins, obj_ins, cx_ins}; - ok_ins = lir->insCall(&SetPropertyByIndex_ci, args); - } - pendingGuardCondition = ok_ins; - - leaveDeepBailCall(); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETELEM() -{ - jsval& v = stackval(-1); - jsval& idx = stackval(-2); - jsval& lval = stackval(-3); - - if (JSVAL_IS_PRIMITIVE(lval)) - ABORT_TRACE("left JSOP_SETELEM operand is not an object"); - ABORT_IF_XML(lval); - - JSObject* obj = JSVAL_TO_OBJECT(lval); - LIns* obj_ins = get(&lval); - LIns* idx_ins = get(&idx); - LIns* v_ins = get(&v); - - if (JS_InstanceOf(cx, obj, &js_ArgumentsClass, NULL)) - ABORT_TRACE("can't trace setting elements of the |arguments| object"); - - if (obj == globalObj) - ABORT_TRACE("can't trace setting elements on the global object"); - - if (!JSVAL_IS_INT(idx)) { - if (!JSVAL_IS_PRIMITIVE(idx)) - ABORT_TRACE("non-primitive index"); - CHECK_STATUS(initOrSetPropertyByName(obj_ins, &idx, &v, - *cx->fp->regs->pc == JSOP_INITELEM)); - } else if (JSVAL_TO_INT(idx) < 0 || !OBJ_IS_DENSE_ARRAY(cx, obj)) { - CHECK_STATUS(initOrSetPropertyByIndex(obj_ins, idx_ins, &v, - *cx->fp->regs->pc == JSOP_INITELEM)); - } else { - // Fast path: assigning to element of dense array. - - // Make sure the array is actually dense. - if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) - return JSRS_STOP; - - // The index was on the stack and is therefore a LIR float. Force it to - // be an integer. - idx_ins = makeNumberInt32(idx_ins); - - // Box the value so we can use one builtin instead of having to add one - // builtin for every storage type. Special case for integers though, - // since they are so common. - LIns* res_ins; - LIns* args[] = { NULL, idx_ins, obj_ins, cx_ins }; - if (isNumber(v)) { - if (isPromoteInt(v_ins)) { - args[0] = ::demote(lir, v_ins); - res_ins = lir->insCall(&js_Array_dense_setelem_int_ci, args); - } else { - args[0] = v_ins; - res_ins = lir->insCall(&js_Array_dense_setelem_double_ci, args); - } - } else { - LIns* args[] = { box_jsval(v, v_ins), idx_ins, obj_ins, cx_ins }; - res_ins = lir->insCall(&js_Array_dense_setelem_ci, args); - } - guard(false, lir->ins_eq0(res_ins), MISMATCH_EXIT); - } - - jsbytecode* pc = cx->fp->regs->pc; - if (*pc == JSOP_SETELEM && pc[JSOP_SETELEM_LENGTH] != JSOP_POP) - set(&lval, v_ins); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLNAME() -{ - JSObject* obj = cx->fp->scopeChain; - if (obj != globalObj) { - jsval* vp; - LIns* ins; - NameResult nr; - CHECK_STATUS(scopeChainProp(obj, vp, ins, nr)); - stack(0, ins); - stack(1, INS_CONSTOBJ(globalObj)); - return JSRS_CONTINUE; - } - - LIns* obj_ins = scopeChain(); - JSObject* obj2; - jsuword pcval; - - CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); - - if (PCVAL_IS_NULL(pcval) || !PCVAL_IS_OBJECT(pcval)) - ABORT_TRACE("callee is not an object"); - - JS_ASSERT(HAS_FUNCTION_CLASS(PCVAL_TO_OBJECT(pcval))); - - stack(0, INS_CONSTOBJ(PCVAL_TO_OBJECT(pcval))); - stack(1, obj_ins); - return JSRS_CONTINUE; -} - -JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarArgOnTrace, CONTEXT, UINT32, INT32, UINT32, - DOUBLEPTR, 0, 0) -JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarVarOnTrace, CONTEXT, UINT32, INT32, UINT32, - DOUBLEPTR, 0, 0) -JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarStackOnTrace, CONTEXT, UINT32, INT32, UINT32, - DOUBLEPTR, 0, 0) - -/* - * Record LIR to get the given upvar. Return the LIR instruction for the upvar - * value. NULL is returned only on a can't-happen condition with an invalid - * typemap. The value of the upvar is returned as v. - */ -JS_REQUIRES_STACK LIns* -TraceRecorder::upvar(JSScript* script, JSUpvarArray* uva, uintN index, jsval& v) -{ - /* - * Try to find the upvar in the current trace's tracker. For &vr to be - * the address of the jsval found in js_GetUpvar, we must initialize - * vr directly with the result, so it is a reference to the same location. - * It does not work to assign the result to v, because v is an already - * existing reference that points to something else. - */ - uint32 cookie = uva->vector[index]; - jsval& vr = js_GetUpvar(cx, script->staticLevel, cookie); - v = vr; - - if (known(&vr)) - return get(&vr); - - /* - * The upvar is not in the current trace, so get the upvar value exactly as - * the interpreter does and unbox. - */ - uint32 level = script->staticLevel - UPVAR_FRAME_SKIP(cookie); - uint32 cookieSlot = UPVAR_FRAME_SLOT(cookie); - JSStackFrame* fp = cx->display[level]; - const CallInfo* ci; - int32 slot; - if (!fp->fun) { - ci = &GetUpvarStackOnTrace_ci; - slot = cookieSlot; - } else if (cookieSlot < fp->fun->nargs) { - ci = &GetUpvarArgOnTrace_ci; - slot = cookieSlot; - } else if (cookieSlot == CALLEE_UPVAR_SLOT) { - ci = &GetUpvarArgOnTrace_ci; - slot = -2; - } else { - ci = &GetUpvarVarOnTrace_ci; - slot = cookieSlot - fp->fun->nargs; - } - - LIns* outp = lir->insAlloc(sizeof(double)); - LIns* args[] = { - outp, - INS_CONST(callDepth), - INS_CONST(slot), - INS_CONST(level), - cx_ins - }; - LIns* call_ins = lir->insCall(ci, args); - JSTraceType type = getCoercedType(v); - guard(true, - addName(lir->ins2(LIR_eq, call_ins, lir->insImm(type)), - "guard(type-stable upvar)"), - BRANCH_EXIT); - return stackLoad(outp, type); -} - -/* - * Generate LIR to load a value from the native stack. This method ensures that - * the correct LIR load operator is used. - */ -LIns* TraceRecorder::stackLoad(LIns* base, uint8 type) -{ - LOpcode loadOp; - switch (type) { - case TT_DOUBLE: - loadOp = LIR_ldq; - break; - case TT_OBJECT: - case TT_STRING: - case TT_FUNCTION: - case TT_NULL: - loadOp = LIR_ldp; - break; - case TT_INT32: - case TT_PSEUDOBOOLEAN: - loadOp = LIR_ld; - break; - case TT_JSVAL: - default: - JS_NOT_REACHED("found jsval type in an upvar type map entry"); - return NULL; - } - - LIns* result = lir->insLoad(loadOp, base, 0); - if (type == TT_INT32) - result = lir->ins1(LIR_i2f, result); - return result; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETUPVAR() -{ - uintN index = GET_UINT16(cx->fp->regs->pc); - JSScript *script = cx->fp->script; - JSUpvarArray* uva = script->upvars(); - JS_ASSERT(index < uva->length); - - jsval v; - LIns* upvar_ins = upvar(script, uva, index, v); - if (!upvar_ins) - return JSRS_STOP; - stack(0, upvar_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLUPVAR() -{ - CHECK_STATUS(record_JSOP_GETUPVAR()); - stack(1, INS_NULL()); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETDSLOT() -{ - JSObject* callee = JSVAL_TO_OBJECT(cx->fp->argv[-2]); - LIns* callee_ins = get(&cx->fp->argv[-2]); - - unsigned index = GET_UINT16(cx->fp->regs->pc); - LIns* dslots_ins = NULL; - LIns* v_ins = stobj_get_dslot(callee_ins, index, dslots_ins); - - stack(0, unbox_jsval(callee->dslots[index], v_ins, snapshot(BRANCH_EXIT))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLDSLOT() -{ - CHECK_STATUS(record_JSOP_GETDSLOT()); - stack(1, INS_NULL()); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::guardCallee(jsval& callee) -{ - JS_ASSERT(VALUE_IS_FUNCTION(cx, callee)); - - VMSideExit* branchExit = snapshot(BRANCH_EXIT); - JSObject* callee_obj = JSVAL_TO_OBJECT(callee); - LIns* callee_ins = get(&callee); - - treeInfo->gcthings.addUnique(callee); - guard(true, - lir->ins2(LIR_peq, - stobj_get_private(callee_ins), - INS_CONSTPTR(callee_obj->getPrivate())), - branchExit); - guard(true, - lir->ins2(LIR_peq, - stobj_get_parent(callee_ins), - INS_CONSTOBJ(OBJ_GET_PARENT(cx, callee_obj))), - branchExit); - return JSRS_CONTINUE; -} - -/* - * Prepare the given |arguments| object to be accessed on trace. If the return - * value is non-NULL, then the given |arguments| object refers to a frame on - * the current trace and is guaranteed to refer to the same frame on trace for - * all later executions. - */ -JS_REQUIRES_STACK JSStackFrame * -TraceRecorder::guardArguments(JSObject *obj, LIns* obj_ins, unsigned *depthp) -{ - JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_ArgumentsClass); - - JSStackFrame *afp = frameIfInRange(obj, depthp); - if (!afp) - return NULL; - - VMSideExit *exit = snapshot(MISMATCH_EXIT); - guardClass(obj, obj_ins, &js_ArgumentsClass, exit); - - LIns* args_ins = get(&afp->argsobj); - LIns* cmp = lir->ins2(LIR_peq, args_ins, obj_ins); - lir->insGuard(LIR_xf, cmp, createGuardRecord(exit)); - return afp; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc, bool constructing) -{ - if (JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(fval)) != globalObj) - ABORT_TRACE("JSOP_CALL or JSOP_NEW crosses global scopes"); - - JSStackFrame* fp = cx->fp; - - // TODO: track the copying via the tracker... - if (argc < fun->nargs && - jsuword(fp->regs->sp + (fun->nargs - argc)) > cx->stackPool.current->limit) { - ABORT_TRACE("can't trace calls with too few args requiring argv move"); - } - - // Generate a type map for the outgoing frame and stash it in the LIR - unsigned stackSlots = NativeStackSlots(cx, 0 /* callDepth */); - if (sizeof(FrameInfo) + stackSlots * sizeof(JSTraceType) > LirBuffer::MAX_SKIP_PAYLOAD_SZB) - ABORT_TRACE("interpreted function call requires saving too much stack"); - FrameInfo* fi = (FrameInfo*) - traceMonitor->dataAlloc->alloc(sizeof(FrameInfo) + - stackSlots * sizeof(JSTraceType)); - JSTraceType* typemap = reinterpret_cast(fi + 1); - - DetermineTypesVisitor detVisitor(*this, typemap); - VisitStackSlots(detVisitor, cx, 0); - - JS_ASSERT(argc < FrameInfo::CONSTRUCTING_FLAG); - - treeInfo->gcthings.addUnique(fval); - fi->block = fp->blockChain; - if (fp->blockChain) - treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(fp->blockChain)); - fi->pc = fp->regs->pc; - fi->imacpc = fp->imacpc; - fi->spdist = fp->regs->sp - fp->slots; - fi->set_argc(argc, constructing); - fi->callerHeight = NativeStackSlots(cx, 0) - (2 + argc); - fi->callerArgc = fp->argc; - - unsigned callDepth = getCallDepth(); - if (callDepth >= treeInfo->maxCallDepth) - treeInfo->maxCallDepth = callDepth + 1; - - lir->insStorei(INS_CONSTPTR(fi), lirbuf->rp, callDepth * sizeof(FrameInfo*)); - - atoms = fun->u.i.script->atomMap.vector; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALL() -{ - uintN argc = GET_ARGC(cx->fp->regs->pc); - cx->fp->assertValidStackDepth(argc + 2); - return functionCall(argc, - (cx->fp->imacpc && *cx->fp->imacpc == JSOP_APPLY) - ? JSOP_APPLY - : JSOP_CALL); -} - -static jsbytecode* apply_imacro_table[] = { - apply_imacros.apply0, - apply_imacros.apply1, - apply_imacros.apply2, - apply_imacros.apply3, - apply_imacros.apply4, - apply_imacros.apply5, - apply_imacros.apply6, - apply_imacros.apply7, - apply_imacros.apply8 -}; - -static jsbytecode* call_imacro_table[] = { - apply_imacros.call0, - apply_imacros.call1, - apply_imacros.call2, - apply_imacros.call3, - apply_imacros.call4, - apply_imacros.call5, - apply_imacros.call6, - apply_imacros.call7, - apply_imacros.call8 -}; - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_APPLY() -{ - JSStackFrame* fp = cx->fp; - jsbytecode *pc = fp->regs->pc; - uintN argc = GET_ARGC(pc); - cx->fp->assertValidStackDepth(argc + 2); - - jsval* vp = fp->regs->sp - (argc + 2); - jsuint length = 0; - JSObject* aobj = NULL; - LIns* aobj_ins = NULL; - - JS_ASSERT(!fp->imacpc); - - if (!VALUE_IS_FUNCTION(cx, vp[0])) - return record_JSOP_CALL(); - ABORT_IF_XML(vp[0]); - - JSObject* obj = JSVAL_TO_OBJECT(vp[0]); - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj); - if (FUN_INTERPRETED(fun)) - return record_JSOP_CALL(); - - bool apply = (JSFastNative)fun->u.n.native == js_fun_apply; - if (!apply && (JSFastNative)fun->u.n.native != js_fun_call) - return record_JSOP_CALL(); - - /* - * We don't trace apply and call with a primitive 'this', which is the - * first positional parameter. - */ - if (argc > 0 && !JSVAL_IS_OBJECT(vp[2])) - return record_JSOP_CALL(); - - /* - * Guard on the identity of this, which is the function we are applying. - */ - if (!VALUE_IS_FUNCTION(cx, vp[1])) - ABORT_TRACE("callee is not a function"); - CHECK_STATUS(guardCallee(vp[1])); - - if (apply && argc >= 2) { - if (argc != 2) - ABORT_TRACE("apply with excess arguments"); - if (JSVAL_IS_PRIMITIVE(vp[3])) - ABORT_TRACE("arguments parameter of apply is primitive"); - aobj = JSVAL_TO_OBJECT(vp[3]); - aobj_ins = get(&vp[3]); - - /* - * We trace dense arrays and arguments objects. The code we generate - * for apply uses imacros to handle a specific number of arguments. - */ - if (OBJ_IS_DENSE_ARRAY(cx, aobj)) { - guardDenseArray(aobj, aobj_ins); - length = jsuint(aobj->fslots[JSSLOT_ARRAY_LENGTH]); - guard(true, - lir->ins2i(LIR_eq, - p2i(stobj_get_fslot(aobj_ins, JSSLOT_ARRAY_LENGTH)), - length), - BRANCH_EXIT); - } else if (OBJ_GET_CLASS(cx, aobj) == &js_ArgumentsClass) { - unsigned depth; - JSStackFrame *afp = guardArguments(aobj, aobj_ins, &depth); - if (!afp) - ABORT_TRACE("can't reach arguments object's frame"); - length = afp->argc; - } else { - ABORT_TRACE("arguments parameter of apply is not a dense array or argments object"); - } - - if (length >= JS_ARRAY_LENGTH(apply_imacro_table)) - ABORT_TRACE("too many arguments to apply"); - - return call_imacro(apply_imacro_table[length]); - } - - if (argc >= JS_ARRAY_LENGTH(call_imacro_table)) - ABORT_TRACE("too many arguments to call"); - - return call_imacro(call_imacro_table[argc]); -} - -static JSBool FASTCALL -CatchStopIteration_tn(JSContext* cx, JSBool ok, jsval* vp) -{ - if (!ok && cx->throwing && js_ValueIsStopIteration(cx->exception)) { - cx->throwing = JS_FALSE; - cx->exception = JSVAL_VOID; - *vp = JSVAL_HOLE; - return JS_TRUE; - } - return ok; -} - -JS_DEFINE_TRCINFO_1(CatchStopIteration_tn, - (3, (static, BOOL, CatchStopIteration_tn, CONTEXT, BOOL, JSVALPTR, 0, 0))) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_NativeCallComplete() -{ - if (pendingSpecializedNative == IGNORE_NATIVE_CALL_COMPLETE_CALLBACK) - return JSRS_CONTINUE; - - jsbytecode* pc = cx->fp->regs->pc; - - JS_ASSERT(pendingSpecializedNative); - JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || *pc == JSOP_SETPROP); - - jsval& v = stackval(-1); - LIns* v_ins = get(&v); - - /* - * At this point the generated code has already called the native function - * and we can no longer fail back to the original pc location (JSOP_CALL) - * because that would cause the interpreter to re-execute the native - * function, which might have side effects. - * - * Instead, the snapshot() call below sees that we are currently parked on - * a traceable native's JSOP_CALL instruction, and it will advance the pc - * to restore by the length of the current opcode. If the native's return - * type is jsval, snapshot() will also indicate in the type map that the - * element on top of the stack is a boxed value which doesn't need to be - * boxed if the type guard generated by unbox_jsval() fails. - */ - - if (JSTN_ERRTYPE(pendingSpecializedNative) == FAIL_STATUS) { - /* Keep cx->bailExit null when it's invalid. */ - lir->insStorei(INS_NULL(), cx_ins, (int) offsetof(JSContext, bailExit)); - - LIns* status = lir->insLoad(LIR_ld, lirbuf->state, (int) offsetof(InterpState, builtinStatus)); - if (pendingSpecializedNative == &generatedSpecializedNative) { - LIns* ok_ins = v_ins; - - /* - * Custom implementations of Iterator.next() throw a StopIteration exception. - * Catch and clear it and set the return value to JSVAL_HOLE in this case. - */ - if (uintptr_t(pc - nextiter_imacros.custom_iter_next) < - sizeof(nextiter_imacros.custom_iter_next)) { - LIns* args[] = { native_rval_ins, ok_ins, cx_ins }; /* reverse order */ - ok_ins = lir->insCall(&CatchStopIteration_tn_ci, args); - } - - /* - * If we run a generic traceable native, the return value is in the argument - * vector for native function calls. The actual return value of the native is a JSBool - * indicating the error status. - */ - v_ins = lir->insLoad(LIR_ldp, native_rval_ins, 0); - if (*pc == JSOP_NEW) { - LIns* x = lir->ins_peq0(lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK))); - x = lir->ins_choose(x, v_ins, INS_CONSTWORD(0)); - v_ins = lir->ins_choose(lir->ins_peq0(x), newobj_ins, x); - } - set(&v, v_ins); - - propagateFailureToBuiltinStatus(ok_ins, status); - } - guard(true, lir->ins_eq0(status), STATUS_EXIT); - } - - JSRecordingStatus ok = JSRS_CONTINUE; - if (pendingSpecializedNative->flags & JSTN_UNBOX_AFTER) { - /* - * If we side exit on the unboxing code due to a type change, make sure that the boxed - * value is actually currently associated with that location, and that we are talking - * about the top of the stack here, which is where we expected boxed values. - */ - JS_ASSERT(&v == &cx->fp->regs->sp[-1] && get(&v) == v_ins); - set(&v, unbox_jsval(v, v_ins, snapshot(BRANCH_EXIT))); - } else if (JSTN_ERRTYPE(pendingSpecializedNative) == FAIL_NEG) { - /* Already added i2f in functionCall. */ - JS_ASSERT(JSVAL_IS_NUMBER(v)); - } else { - /* Convert the result to double if the builtin returns int32. */ - if (JSVAL_IS_NUMBER(v) && - (pendingSpecializedNative->builtin->_argtypes & ARGSIZE_MASK_ANY) == ARGSIZE_I) { - set(&v, lir->ins1(LIR_i2f, v_ins)); - } - } - - // We'll null pendingSpecializedNative in monitorRecording, on the next op - // cycle. There must be a next op since the stack is non-empty. - return ok; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::name(jsval*& vp, LIns*& ins, NameResult& nr) -{ - JSObject* obj = cx->fp->scopeChain; - if (obj != globalObj) - return scopeChainProp(obj, vp, ins, nr); - - /* Can't use prop here, because we don't want unboxing from global slots. */ - LIns* obj_ins = scopeChain(); - uint32 slot; - - JSObject* obj2; - jsuword pcval; - - /* - * Property cache ensures that we are dealing with an existing property, - * and guards the shape for us. - */ - CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); - - /* Abort if property doesn't exist (interpreter will report an error.) */ - if (PCVAL_IS_NULL(pcval)) - ABORT_TRACE("named property not found"); - - /* Insist on obj being the directly addressed object. */ - if (obj2 != obj) - ABORT_TRACE("name() hit prototype chain"); - - /* Don't trace getter or setter calls, our caller wants a direct slot. */ - if (PCVAL_IS_SPROP(pcval)) { - JSScopeProperty* sprop = PCVAL_TO_SPROP(pcval); - if (!isValidSlot(OBJ_SCOPE(obj), sprop)) - ABORT_TRACE("name() not accessing a valid slot"); - slot = sprop->slot; - } else { - if (!PCVAL_IS_SLOT(pcval)) - ABORT_TRACE("PCE is not a slot"); - slot = PCVAL_TO_SLOT(pcval); - } - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - vp = &STOBJ_GET_SLOT(obj, slot); - ins = get(vp); - nr.tracked = true; - return JSRS_CONTINUE; -} - -/* - * Get a property. The current opcode has JOF_ATOM. - * - * There are two modes. The caller must pass nonnull pointers for either outp - * or both slotp and v_insp. In the latter case, we require a plain old - * property with a slot; if the property turns out to be anything else, abort - * tracing (rather than emit a call to a native getter or GetAnyProperty). - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, jsval *outp) -{ - JS_ASSERT((slotp && v_insp && !outp) || (!slotp && !v_insp && outp)); - - /* - * Can't specialize to assert obj != global, must guard to avoid aliasing - * stale homes of stacked global variables. - */ - CHECK_STATUS(guardNotGlobalObject(obj, obj_ins)); - - /* - * Property cache ensures that we are dealing with an existing property, - * and guards the shape for us. - */ - JSObject* obj2; - jsuword pcval; - CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); - - /* Check for non-existent property reference, which results in undefined. */ - const JSCodeSpec& cs = js_CodeSpec[*cx->fp->regs->pc]; - if (PCVAL_IS_NULL(pcval)) { - if (slotp) - ABORT_TRACE("property not found"); - - /* - * We could specialize to guard on just JSClass.getProperty, but a mere - * class guard is simpler and slightly faster. - */ - if (OBJ_GET_CLASS(cx, obj)->getProperty != JS_PropertyStub) { - ABORT_TRACE("can't trace through access to undefined property if " - "JSClass.getProperty hook isn't stubbed"); - } - guardClass(obj, obj_ins, OBJ_GET_CLASS(cx, obj), snapshot(MISMATCH_EXIT)); - - /* - * This trace will be valid as long as neither the object nor any object - * on its prototype chain changes shape. - * - * FIXME: This loop can become a single shape guard once bug 497789 has - * been fixed. - */ - VMSideExit* exit = snapshot(BRANCH_EXIT); - do { - LIns* map_ins = map(obj_ins); - LIns* ops_ins; - if (map_is_native(obj->map, map_ins, ops_ins)) { - guardShape(obj_ins, obj, OBJ_SHAPE(obj), "guard(shape)", map_ins, exit); - } else if (!guardDenseArray(obj, obj_ins, exit)) { - ABORT_TRACE("non-native object involved in undefined property access"); - } - } while (guardHasPrototype(obj, obj_ins, &obj, &obj_ins, exit)); - - set(outp, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)), true); - return JSRS_CONTINUE; - } - - uint32 setflags = (cs.format & (JOF_INCDEC | JOF_FOR)); - JS_ASSERT(!(cs.format & JOF_SET)); - - uint32 slot; - if (PCVAL_IS_SPROP(pcval)) { - JSScopeProperty* sprop = PCVAL_TO_SPROP(pcval); - - if (setflags && !SPROP_HAS_STUB_SETTER(sprop)) - ABORT_TRACE("non-stub setter"); - if (setflags && (sprop->attrs & JSPROP_READONLY)) - ABORT_TRACE("writing to a readonly property"); - if (!SPROP_HAS_STUB_GETTER(sprop)) { - if (slotp) - ABORT_TRACE("can't trace non-stub getter for this opcode"); - if (sprop->attrs & JSPROP_GETTER) - ABORT_TRACE("script getter"); - if (sprop->slot == SPROP_INVALID_SLOT) - return getPropertyWithNativeGetter(obj_ins, sprop, outp); - return getPropertyById(obj_ins, outp); - } - if (!SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj2))) - ABORT_TRACE("no valid slot"); - slot = sprop->slot; - } else { - if (!PCVAL_IS_SLOT(pcval)) - ABORT_TRACE("PCE is not a slot"); - slot = PCVAL_TO_SLOT(pcval); - } - - /* We have a slot. */ - if (obj2 != obj) { - if (setflags) - ABORT_TRACE("JOF_INCDEC|JOF_FOR opcode hit prototype chain"); - - /* - * We're getting a proto-property. Walk up the prototype chain emitting - * proto slot loads, updating obj as we go, leaving obj set to obj2 with - * obj_ins the last proto-load. - */ - while (obj != obj2) { - obj_ins = stobj_get_proto(obj_ins); - obj = STOBJ_GET_PROTO(obj); - } - } - - LIns* dslots_ins = NULL; - LIns* v_ins = unbox_jsval(STOBJ_GET_SLOT(obj, slot), - stobj_get_slot(obj_ins, slot, dslots_ins), - snapshot(BRANCH_EXIT)); - - if (slotp) { - *slotp = slot; - *v_insp = v_ins; - } - if (outp) - set(outp, v_ins, true); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::denseArrayElement(jsval& oval, jsval& ival, jsval*& vp, LIns*& v_ins, - LIns*& addr_ins) -{ - JS_ASSERT(JSVAL_IS_OBJECT(oval) && JSVAL_IS_INT(ival)); - - JSObject* obj = JSVAL_TO_OBJECT(oval); - LIns* obj_ins = get(&oval); - jsint idx = JSVAL_TO_INT(ival); - LIns* idx_ins = makeNumberInt32(get(&ival)); - LIns* pidx_ins = lir->ins_u2p(idx_ins); - - VMSideExit* exit = snapshot(BRANCH_EXIT); - - /* check that the index is within bounds */ - LIns* dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); - jsuint capacity = js_DenseArrayCapacity(obj); - bool within = (jsuint(idx) < jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH]) && jsuint(idx) < capacity); - if (!within) { - /* If idx < 0, stay on trace (and read value as undefined, since this is a dense array). */ - LIns* br1 = NULL; - if (MAX_DSLOTS_LENGTH > MAX_DSLOTS_LENGTH32 && !idx_ins->isconst()) { - /* Only 64-bit machines support large enough arrays for this. */ - JS_ASSERT(sizeof(jsval) == 8); - br1 = lir->insBranch(LIR_jt, - lir->ins2i(LIR_lt, idx_ins, 0), - NULL); - } - - /* If not idx < length, stay on trace (and read value as undefined). */ - LIns* br2 = lir->insBranch(LIR_jf, - lir->ins2(LIR_pult, - pidx_ins, - stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)), - NULL); - - /* If dslots is NULL, stay on trace (and read value as undefined). */ - LIns* br3 = lir->insBranch(LIR_jt, lir->ins_peq0(dslots_ins), NULL); - - /* If not idx < capacity, stay on trace (and read value as undefined). */ - LIns* br4 = lir->insBranch(LIR_jf, - lir->ins2(LIR_pult, - pidx_ins, - lir->insLoad(LIR_ldp, - dslots_ins, - -(int)sizeof(jsval))), - NULL); - lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); - LIns* label = lir->ins0(LIR_label); - if (br1) - br1->setTarget(label); - br2->setTarget(label); - br3->setTarget(label); - br4->setTarget(label); - - CHECK_STATUS(guardPrototypeHasNoIndexedProperties(obj, obj_ins, MISMATCH_EXIT)); - - // Return undefined and indicate that we didn't actually read this (addr_ins). - v_ins = lir->insImm(JSVAL_TO_SPECIAL(JSVAL_VOID)); - addr_ins = NULL; - return JSRS_CONTINUE; - } - - /* Guard against negative index */ - if (MAX_DSLOTS_LENGTH > MAX_DSLOTS_LENGTH32 && !idx_ins->isconst()) { - /* Only 64-bit machines support large enough arrays for this. */ - JS_ASSERT(sizeof(jsval) == 8); - guard(false, - lir->ins2i(LIR_lt, idx_ins, 0), - exit); - } - - /* Guard array length */ - guard(true, - lir->ins2(LIR_pult, pidx_ins, stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)), - exit); - - /* dslots must not be NULL */ - guard(false, - lir->ins_peq0(dslots_ins), - exit); - - /* Guard array capacity */ - guard(true, - lir->ins2(LIR_pult, - pidx_ins, - lir->insLoad(LIR_ldp, dslots_ins, 0 - (int)sizeof(jsval))), - exit); - - /* Load the value and guard on its type to unbox it. */ - vp = &obj->dslots[jsuint(idx)]; - addr_ins = lir->ins2(LIR_piadd, dslots_ins, - lir->ins2i(LIR_pilsh, pidx_ins, (sizeof(jsval) == 4) ? 2 : 3)); - v_ins = unbox_jsval(*vp, lir->insLoad(LIR_ldp, addr_ins, 0), exit); - - if (JSVAL_IS_SPECIAL(*vp)) { - /* - * If we read a hole from the array, convert it to undefined and guard - * that there are no indexed properties along the prototype chain. - */ - LIns* br = lir->insBranch(LIR_jf, - lir->ins2i(LIR_eq, v_ins, JSVAL_TO_SPECIAL(JSVAL_HOLE)), - NULL); - CHECK_STATUS(guardPrototypeHasNoIndexedProperties(obj, obj_ins, MISMATCH_EXIT)); - br->setTarget(lir->ins0(LIR_label)); - - /* Don't let the hole value escape. Turn it into an undefined. */ - v_ins = lir->ins2i(LIR_and, v_ins, ~(JSVAL_HOLE_FLAG >> JSVAL_TAGBITS)); - } - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getProp(JSObject* obj, LIns* obj_ins) -{ - const JSCodeSpec& cs = js_CodeSpec[*cx->fp->regs->pc]; - JS_ASSERT(cs.ndefs == 1); - return prop(obj, obj_ins, NULL, NULL, &stackval(-cs.nuses)); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::getProp(jsval& v) -{ - if (JSVAL_IS_PRIMITIVE(v)) - ABORT_TRACE("primitive lhs"); - - return getProp(JSVAL_TO_OBJECT(v), get(&v)); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NAME() -{ - jsval* vp; - LIns* v_ins; - NameResult nr; - CHECK_STATUS(name(vp, v_ins, nr)); - stack(0, v_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DOUBLE() -{ - jsval v = jsval(atoms[GET_INDEX(cx->fp->regs->pc)]); - stack(0, lir->insImmf(*JSVAL_TO_DOUBLE(v))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_STRING() -{ - JSAtom* atom = atoms[GET_INDEX(cx->fp->regs->pc)]; - JS_ASSERT(ATOM_IS_STRING(atom)); - stack(0, INS_ATOM(atom)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ZERO() -{ - stack(0, lir->insImmf(0)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ONE() -{ - stack(0, lir->insImmf(1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NULL() -{ - stack(0, INS_NULL()); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_THIS() -{ - LIns* this_ins; - CHECK_STATUS(getThis(this_ins)); - stack(0, this_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FALSE() -{ - stack(0, lir->insImm(0)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TRUE() -{ - stack(0, lir->insImm(1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_OR() -{ - return ifop(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_AND() -{ - return ifop(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TABLESWITCH() -{ -#ifdef NANOJIT_IA32 - /* Handle tableswitches specially -- prepare a jump table if needed. */ - return tableswitch(); -#else - return switchop(); -#endif -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LOOKUPSWITCH() -{ - return switchop(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_STRICTEQ() -{ - strictEquality(true, false); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_STRICTNE() -{ - strictEquality(false, false); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_OBJECT() -{ - JSStackFrame* fp = cx->fp; - JSScript* script = fp->script; - unsigned index = atoms - script->atomMap.vector + GET_INDEX(fp->regs->pc); - - JSObject* obj; - obj = script->getObject(index); - stack(0, INS_CONSTOBJ(obj)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_POP() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TRAP() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETARG() -{ - stack(0, arg(GET_ARGNO(cx->fp->regs->pc))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETARG() -{ - arg(GET_ARGNO(cx->fp->regs->pc), stack(-1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETLOCAL() -{ - stack(0, var(GET_SLOTNO(cx->fp->regs->pc))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETLOCAL() -{ - var(GET_SLOTNO(cx->fp->regs->pc), stack(-1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_UINT16() -{ - stack(0, lir->insImmf(GET_UINT16(cx->fp->regs->pc))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NEWINIT() -{ - JSProtoKey key = JSProtoKey(GET_INT8(cx->fp->regs->pc)); - LIns *proto_ins; - CHECK_STATUS(getClassPrototype(key, proto_ins)); - - LIns* args[] = { proto_ins, cx_ins }; - const CallInfo *ci = (key == JSProto_Array) ? &js_NewEmptyArray_ci : &js_Object_tn_ci; - LIns* v_ins = lir->insCall(ci, args); - guard(false, lir->ins_peq0(v_ins), OOM_EXIT); - stack(0, v_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENDINIT() -{ -#ifdef DEBUG - jsval& v = stackval(-1); - JS_ASSERT(!JSVAL_IS_PRIMITIVE(v)); -#endif - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INITPROP() -{ - // All the action is in record_SetPropHit. - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INITELEM() -{ - return record_JSOP_SETELEM(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFSHARP() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_USESHARP() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INCARG() -{ - return inc(argval(GET_ARGNO(cx->fp->regs->pc)), 1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INCLOCAL() -{ - return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), 1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DECARG() -{ - return inc(argval(GET_ARGNO(cx->fp->regs->pc)), -1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DECLOCAL() -{ - return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), -1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ARGINC() -{ - return inc(argval(GET_ARGNO(cx->fp->regs->pc)), 1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LOCALINC() -{ - return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), 1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ARGDEC() -{ - return inc(argval(GET_ARGNO(cx->fp->regs->pc)), -1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LOCALDEC() -{ - return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), -1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IMACOP() -{ - JS_ASSERT(cx->fp->imacpc); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ITER() -{ - jsval& v = stackval(-1); - if (JSVAL_IS_PRIMITIVE(v)) - ABORT_TRACE("for-in on a primitive value"); - ABORT_IF_XML(v); - - jsuint flags = cx->fp->regs->pc[1]; - - if (hasIteratorMethod(JSVAL_TO_OBJECT(v))) { - if (flags == JSITER_ENUMERATE) - return call_imacro(iter_imacros.for_in); - if (flags == (JSITER_ENUMERATE | JSITER_FOREACH)) - return call_imacro(iter_imacros.for_each); - } else { - if (flags == JSITER_ENUMERATE) - return call_imacro(iter_imacros.for_in_native); - if (flags == (JSITER_ENUMERATE | JSITER_FOREACH)) - return call_imacro(iter_imacros.for_each_native); - } - ABORT_TRACE("unimplemented JSITER_* flags"); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NEXTITER() -{ - jsval& iterobj_val = stackval(-2); - if (JSVAL_IS_PRIMITIVE(iterobj_val)) - ABORT_TRACE("for-in on a primitive value"); - ABORT_IF_XML(iterobj_val); - JSObject* iterobj = JSVAL_TO_OBJECT(iterobj_val); - JSClass* clasp = STOBJ_GET_CLASS(iterobj); - LIns* iterobj_ins = get(&iterobj_val); - guardClass(iterobj, iterobj_ins, clasp, snapshot(BRANCH_EXIT)); - if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass) - return call_imacro(nextiter_imacros.native_iter_next); - return call_imacro(nextiter_imacros.custom_iter_next); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENDITER() -{ - LIns* args[] = { stack(-2), cx_ins }; - LIns* ok_ins = lir->insCall(&js_CloseIterator_ci, args); - guard(false, lir->ins_eq0(ok_ins), MISMATCH_EXIT); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FORNAME() -{ - jsval* vp; - LIns* x_ins; - NameResult nr; - CHECK_STATUS(name(vp, x_ins, nr)); - if (!nr.tracked) - ABORT_TRACE("forname on non-tracked value not supported"); - set(vp, stack(-1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FORPROP() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FORELEM() -{ - return record_JSOP_DUP(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FORARG() -{ - return record_JSOP_SETARG(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FORLOCAL() -{ - return record_JSOP_SETLOCAL(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_POPN() -{ - return JSRS_CONTINUE; -} - -/* - * Generate LIR to reach |obj2| from |obj| by traversing the scope chain. The - * generated code also ensures that any call objects found have not changed shape. - * - * obj starting object - * obj_ins LIR instruction representing obj - * targetObj end object for traversal - * targetIns [out] LIR instruction representing obj2 - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::traverseScopeChain(JSObject *obj, LIns *obj_ins, JSObject *targetObj, - LIns *&targetIns) -{ - VMSideExit* exit = NULL; - - /* - * Scope chains are often left "incomplete", and reified lazily when - * necessary, since doing so is expensive. When creating null and flat - * closures on trace (the only kinds supported), the global object is - * hardcoded as the parent, since reifying the scope chain on trace - * would be extremely difficult. This is because block objects need frame - * pointers, which do not exist on trace, and thus would require magic - * similar to arguments objects or reification of stack frames. Luckily, - * for null and flat closures, these blocks are unnecessary. - * - * The problem, as exposed by bug 523793, is that this means creating a - * fixed traversal on trace can be inconsistent with the shorter scope - * chain used when executing a trace. To address this, perform an initial - * sweep of the scope chain to make sure that if there is a heavyweight - * function with a call object, and there is also a block object, the - * trace is safely aborted. - * - * If there is no call object, we must have arrived at the global object, - * and can bypass the scope chain traversal completely. - */ - bool foundCallObj = false; - bool foundBlockObj = false; - JSObject* searchObj = obj; - - for (;;) { - if (searchObj != globalObj) { - JSClass* cls = STOBJ_GET_CLASS(searchObj); - if (cls == &js_BlockClass) { - foundBlockObj = true; - } else if (cls == &js_CallClass) { - // If the function that owns this call object is not heavyweight, then - // we can't be sure it will always be there, which means the scope chain - // does not have a definite length, so abort. - if (JSFUN_HEAVYWEIGHT_TEST(js_GetCallObjectFunction(searchObj)->flags)) - foundCallObj = true; - else - ABORT_TRACE("found call object for non-heavyweight function on scope chain"); - } - } - - if (searchObj == targetObj) - break; - - searchObj = STOBJ_GET_PARENT(searchObj); - if (!searchObj) - ABORT_TRACE("cannot traverse this scope chain on trace"); - } - - if (!foundCallObj) { - JS_ASSERT(targetObj == globalObj); - targetIns = INS_CONSTPTR(globalObj); - return JSRS_CONTINUE; - } - - if (foundBlockObj) - ABORT_TRACE("cannot traverse this scope chain on trace"); - - /* There was a call object, or should be a call object now. */ - for (;;) { - if (obj != globalObj) { - if (!js_IsCacheableNonGlobalScope(obj)) - ABORT_TRACE("scope chain lookup crosses non-cacheable object"); - - // We must guard on the shape of all call objects for heavyweight functions - // that we traverse on the scope chain: if the shape changes, a variable with - // the same name may have been inserted in the scope chain. - if (STOBJ_GET_CLASS(obj) == &js_CallClass && - JSFUN_HEAVYWEIGHT_TEST(js_GetCallObjectFunction(obj)->flags)) { - LIns* map_ins = map(obj_ins); - LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), - "obj_shape"); - if (!exit) - exit = snapshot(BRANCH_EXIT); - guard(true, - addName(lir->ins2i(LIR_eq, shape_ins, OBJ_SHAPE(obj)), "guard_shape"), - exit); - } - } - - JS_ASSERT(STOBJ_GET_CLASS(obj) != &js_BlockClass); - - if (obj == targetObj) - break; - - obj = STOBJ_GET_PARENT(obj); - obj_ins = stobj_get_parent(obj_ins); - } - - targetIns = obj_ins; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BINDNAME() -{ - JSStackFrame *fp = cx->fp; - JSObject *obj; - - if (!fp->fun) { - obj = fp->scopeChain; - - // In global code, fp->scopeChain can only contain blocks whose values - // are still on the stack. We never use BINDNAME to refer to these. - while (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) { - // The block's values are still on the stack. - JS_ASSERT(obj->getPrivate() == fp); - obj = OBJ_GET_PARENT(cx, obj); - // Blocks always have parents. - JS_ASSERT(obj); - } - - if (obj != globalObj) - ABORT_TRACE("BINDNAME in global code resolved to non-global object"); - - /* - * The trace is specialized to this global object. Furthermore, we know it - * is the sole 'global' object on the scope chain: we set globalObj to the - * scope chain element with no parent, and we reached it starting from the - * function closure or the current scopeChain, so there is nothing inner to - * it. Therefore this must be the right base object. - */ - stack(0, INS_CONSTOBJ(obj)); - return JSRS_CONTINUE; - } - - // We can't trace BINDNAME in functions that contain direct calls to eval, - // as they might add bindings which previously-traced references would have - // to see. - if (JSFUN_HEAVYWEIGHT_TEST(fp->fun->flags)) - ABORT_TRACE("BINDNAME in heavyweight function."); - - // We don't have the scope chain on trace, so instead we get a start object - // that is on the scope chain and doesn't skip the target object (the one - // that contains the property). - jsval *callee = &cx->fp->argv[-2]; - obj = STOBJ_GET_PARENT(JSVAL_TO_OBJECT(*callee)); - if (obj == globalObj) { - stack(0, INS_CONSTOBJ(obj)); - return JSRS_CONTINUE; - } - LIns *obj_ins = stobj_get_parent(get(callee)); - - // Find the target object. - JSAtom *atom = atoms[GET_INDEX(cx->fp->regs->pc)]; - jsid id = ATOM_TO_JSID(atom); - JSContext *localCx = cx; - JSObject *obj2 = js_FindIdentifierBase(cx, fp->scopeChain, id); - if (!obj2) - ABORT_TRACE_ERROR("js_FindIdentifierBase failed"); - if (!TRACE_RECORDER(localCx)) - return JSRS_STOP; - if (obj2 != globalObj && STOBJ_GET_CLASS(obj2) != &js_CallClass) - ABORT_TRACE("BINDNAME on non-global, non-call object"); - - // Generate LIR to get to the target object from the start object. - LIns *obj2_ins; - CHECK_STATUS(traverseScopeChain(obj, obj_ins, obj2, obj2_ins)); - - // If |obj2| is the global object, we can refer to it directly instead of walking up - // the scope chain. There may still be guards on intervening call objects. - stack(0, obj2 == globalObj ? INS_CONSTOBJ(obj2) : obj2_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETNAME() -{ - jsval& l = stackval(-2); - JS_ASSERT(!JSVAL_IS_PRIMITIVE(l)); - - /* - * Trace only cases that are global code, in lightweight functions - * scoped by the global object only, or in call objects. - */ - JSObject* obj = JSVAL_TO_OBJECT(l); - if (OBJ_GET_CLASS(cx, obj) == &js_CallClass) - return JSRS_CONTINUE; - if (obj != cx->fp->scopeChain || obj != globalObj) - ABORT_TRACE("JSOP_SETNAME left operand is not the global object"); - - // The rest of the work is in record_SetPropHit. - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_THROW() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IN() -{ - jsval& rval = stackval(-1); - jsval& lval = stackval(-2); - - if (JSVAL_IS_PRIMITIVE(rval)) - ABORT_TRACE("JSOP_IN on non-object right operand"); - JSObject* obj = JSVAL_TO_OBJECT(rval); - LIns* obj_ins = get(&rval); - - jsid id; - LIns* x; - if (JSVAL_IS_INT(lval)) { - id = INT_JSVAL_TO_JSID(lval); - LIns* args[] = { makeNumberInt32(get(&lval)), obj_ins, cx_ins }; - x = lir->insCall(&js_HasNamedPropertyInt32_ci, args); - } else if (JSVAL_IS_STRING(lval)) { - if (!js_ValueToStringId(cx, lval, &id)) - ABORT_TRACE_ERROR("left operand of JSOP_IN didn't convert to a string-id"); - LIns* args[] = { get(&lval), obj_ins, cx_ins }; - x = lir->insCall(&js_HasNamedProperty_ci, args); - } else { - ABORT_TRACE("string or integer expected"); - } - - guard(false, lir->ins2i(LIR_eq, x, JSVAL_TO_SPECIAL(JSVAL_VOID)), OOM_EXIT); - x = lir->ins2i(LIR_eq, x, 1); - - JSTraceMonitor &localtm = *traceMonitor; - JSContext *localcx = cx; - - JSObject* obj2; - JSProperty* prop; - bool ok = obj->lookupProperty(cx, id, &obj2, &prop); - - /* lookupProperty can reenter the interpreter and kill |this|. */ - if (!localtm.recorder) { - if (prop) - obj2->dropProperty(localcx, prop); - return JSRS_STOP; - } - - if (!ok) - ABORT_TRACE_ERROR("obj->lookupProperty failed in JSOP_IN"); - bool cond = prop != NULL; - if (prop) - obj2->dropProperty(cx, prop); - - /* - * The interpreter fuses comparisons and the following branch, so we have - * to do that here as well. - */ - fuseIf(cx->fp->regs->pc + 1, cond, x); - - /* - * We update the stack after the guard. This is safe since the guard bails - * out at the comparison and the interpreter will therefore re-execute the - * comparison. This way the value of the condition doesn't have to be - * calculated and saved on the stack in most cases. - */ - set(&lval, x); - return JSRS_CONTINUE; -} - -static JSBool FASTCALL -HasInstance(JSContext* cx, JSObject* ctor, jsval val) -{ - JSBool result = JS_FALSE; - if (!ctor->map->ops->hasInstance(cx, ctor, val, &result)) - js_SetBuiltinError(cx); - return result; -} -JS_DEFINE_CALLINFO_3(static, BOOL_FAIL, HasInstance, CONTEXT, OBJECT, JSVAL, 0, 0) - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INSTANCEOF() -{ - // If the rhs isn't an object, we are headed for a TypeError. - jsval& ctor = stackval(-1); - if (JSVAL_IS_PRIMITIVE(ctor)) - ABORT_TRACE("non-object on rhs of instanceof"); - - jsval& val = stackval(-2); - LIns* val_ins = box_jsval(val, get(&val)); - - enterDeepBailCall(); - LIns* args[] = {val_ins, get(&ctor), cx_ins}; - stack(-2, lir->insCall(&HasInstance_ci, args)); - LIns* status_ins = lir->insLoad(LIR_ld, - lirbuf->state, - (int) offsetof(InterpState, builtinStatus)); - pendingGuardCondition = lir->ins_eq0(status_ins); - leaveDeepBailCall(); - - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEBUGGER() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GOSUB() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_RETSUB() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_EXCEPTION() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LINENO() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CONDSWITCH() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CASE() -{ - strictEquality(true, true); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFAULT() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_EVAL() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENUMELEM() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETTER() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETTER() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFFUN() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFFUN_FC() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFCONST() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFVAR() -{ - return JSRS_STOP; -} - -jsatomid -TraceRecorder::getFullIndex(ptrdiff_t pcoff) -{ - jsatomid index = GET_INDEX(cx->fp->regs->pc + pcoff); - index += atoms - cx->fp->script->atomMap.vector; - return index; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LAMBDA() -{ - JSFunction* fun; - fun = cx->fp->script->getFunction(getFullIndex()); - - if (FUN_NULL_CLOSURE(fun) && OBJ_GET_PARENT(cx, FUN_OBJECT(fun)) == globalObj) { - LIns *proto_ins; - CHECK_STATUS(getClassPrototype(JSProto_Function, proto_ins)); - - LIns* args[] = { INS_CONSTOBJ(globalObj), proto_ins, INS_CONSTFUN(fun), cx_ins }; - LIns* x = lir->insCall(&js_NewNullClosure_ci, args); - stack(0, x); - return JSRS_CONTINUE; - } - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LAMBDA_FC() -{ - JSFunction* fun; - fun = cx->fp->script->getFunction(getFullIndex()); - - LIns* scopeChain_ins = get(&cx->fp->argv[-2]); - JS_ASSERT(scopeChain_ins); - - LIns* args[] = { - scopeChain_ins, - INS_CONSTFUN(fun), - cx_ins - }; - LIns* call_ins = lir->insCall(&js_AllocFlatClosure_ci, args); - guard(false, - addName(lir->ins2(LIR_peq, call_ins, INS_NULL()), - "guard(js_AllocFlatClosure)"), - OOM_EXIT); - - if (fun->u.i.nupvars) { - JSUpvarArray *uva = fun->u.i.script->upvars(); - for (uint32 i = 0, n = uva->length; i < n; i++) { - jsval v; - LIns* upvar_ins = upvar(fun->u.i.script, uva, i, v); - if (!upvar_ins) - return JSRS_STOP; - LIns* dslots_ins = NULL; - stobj_set_dslot(call_ins, i, dslots_ins, box_jsval(v, upvar_ins)); - } - } - - stack(0, call_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLEE() -{ - stack(0, get(&cx->fp->argv[-2])); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETLOCALPOP() -{ - var(GET_SLOTNO(cx->fp->regs->pc), stack(-1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IFPRIMTOP() -{ - // Traces are type-specialized, including null vs. object, so we need do - // nothing here. The upstream unbox_jsval called after valueOf or toString - // from an imacro (e.g.) will fork the trace for us, allowing us to just - // follow along mindlessly :-). - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETCALL() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TRY() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FINALLY() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NOP() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ARGSUB() -{ - JSStackFrame* fp = cx->fp; - if (!(fp->fun->flags & JSFUN_HEAVYWEIGHT)) { - uintN slot = GET_ARGNO(fp->regs->pc); - if (slot >= fp->argc) - ABORT_TRACE("can't trace out-of-range arguments"); - stack(0, get(&cx->fp->argv[slot])); - return JSRS_CONTINUE; - } - ABORT_TRACE("can't trace JSOP_ARGSUB hard case"); -} - -JS_REQUIRES_STACK LIns* -TraceRecorder::guardArgsLengthNotAssigned(LIns* argsobj_ins) -{ - // The following implements js_IsOverriddenArgsLength on trace. - // The '2' bit is set if length was overridden. - LIns *len_ins = stobj_get_fslot(argsobj_ins, JSSLOT_ARGS_LENGTH); - LIns *ovr_ins = lir->ins2(LIR_piand, len_ins, INS_CONSTWORD(2)); - guard(true, lir->ins_peq0(ovr_ins), snapshot(BRANCH_EXIT)); - return len_ins; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ARGCNT() -{ - if (cx->fp->fun->flags & JSFUN_HEAVYWEIGHT) - ABORT_TRACE("can't trace heavyweight JSOP_ARGCNT"); - - // argc is fixed on trace, so ideally we would simply generate LIR for - // constant argc. But the user can mutate arguments.length in the - // interpreter, so we have to check for that in the trace entry frame. - // We also have to check that arguments.length has not been mutated - // at record time, because if so we will generate incorrect constant - // LIR, which will assert in alu(). - if (cx->fp->argsobj && js_IsOverriddenArgsLength(JSVAL_TO_OBJECT(cx->fp->argsobj))) - ABORT_TRACE("can't trace JSOP_ARGCNT if arguments.length has been modified"); - LIns *a_ins = get(&cx->fp->argsobj); - if (callDepth == 0) { - LIns *br = lir->insBranch(LIR_jt, lir->ins_peq0(a_ins), NULL); - guardArgsLengthNotAssigned(a_ins); - LIns *label = lir->ins0(LIR_label); - br->setTarget(label); - } - stack(0, lir->insImmf(cx->fp->argc)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_DefLocalFunSetSlot(uint32 slot, JSObject* obj) -{ - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj); - - if (FUN_NULL_CLOSURE(fun) && OBJ_GET_PARENT(cx, FUN_OBJECT(fun)) == globalObj) { - LIns *proto_ins; - CHECK_STATUS(getClassPrototype(JSProto_Function, proto_ins)); - - LIns* args[] = { INS_CONSTOBJ(globalObj), proto_ins, INS_CONSTFUN(fun), cx_ins }; - LIns* x = lir->insCall(&js_NewNullClosure_ci, args); - var(slot, x); - return JSRS_CONTINUE; - } - - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFLOCALFUN() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFLOCALFUN_FC() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GOTOX() -{ - return record_JSOP_GOTO(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IFEQX() -{ - return record_JSOP_IFEQ(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_IFNEX() -{ - return record_JSOP_IFNE(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ORX() -{ - return record_JSOP_OR(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ANDX() -{ - return record_JSOP_AND(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GOSUBX() -{ - return record_JSOP_GOSUB(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CASEX() -{ - strictEquality(true, true); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFAULTX() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TABLESWITCHX() -{ - return record_JSOP_TABLESWITCH(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LOOKUPSWITCHX() -{ - return switchop(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BACKPATCH() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BACKPATCH_POP() -{ - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_THROWING() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETRVAL() -{ - // If we implement this, we need to update JSOP_STOP. - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_RETRVAL() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETGVAR() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - return JSRS_CONTINUE; // We will see JSOP_NAME from the interpreter's jump, so no-op here. - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - stack(0, get(&STOBJ_GET_SLOT(globalObj, slot))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETGVAR() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - return JSRS_CONTINUE; // We will see JSOP_NAME from the interpreter's jump, so no-op here. - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - set(&STOBJ_GET_SLOT(globalObj, slot), stack(-1)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INCGVAR() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. - return JSRS_CONTINUE; - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - return inc(STOBJ_GET_SLOT(globalObj, slot), 1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DECGVAR() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. - return JSRS_CONTINUE; - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - return inc(STOBJ_GET_SLOT(globalObj, slot), -1); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GVARINC() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. - return JSRS_CONTINUE; - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - return inc(STOBJ_GET_SLOT(globalObj, slot), 1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GVARDEC() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. - return JSRS_CONTINUE; - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - return inc(STOBJ_GET_SLOT(globalObj, slot), -1, false); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_REGEXP() -{ - return JSRS_STOP; -} - -// begin JS_HAS_XML_SUPPORT - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DEFXMLNS() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ANYNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_QNAMEPART() -{ - return record_JSOP_STRING(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_QNAMECONST() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_QNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TOATTRNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TOATTRVAL() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ADDATTRNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ADDATTRVAL() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_BINDXMLNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_SETXMLNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DESCENDANTS() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_FILTER() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENDFILTER() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TOXML() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TOXMLLIST() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLTAGEXPR() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLELTEXPR() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLOBJECT() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLCDATA() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLCOMMENT() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_XMLPI() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETFUNNS() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_STARTXML() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_STARTXMLEXPR() -{ - return JSRS_STOP; -} - -// end JS_HAS_XML_SUPPORT - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLPROP() -{ - jsval& l = stackval(-1); - JSObject* obj; - LIns* obj_ins; - LIns* this_ins; - if (!JSVAL_IS_PRIMITIVE(l)) { - obj = JSVAL_TO_OBJECT(l); - obj_ins = get(&l); - this_ins = obj_ins; // |this| for subsequent call - } else { - jsint i; - debug_only_stmt(const char* protoname = NULL;) - if (JSVAL_IS_STRING(l)) { - i = JSProto_String; - debug_only_stmt(protoname = "String.prototype";) - } else if (JSVAL_IS_NUMBER(l)) { - i = JSProto_Number; - debug_only_stmt(protoname = "Number.prototype";) - } else if (JSVAL_IS_SPECIAL(l)) { - if (l == JSVAL_VOID) - ABORT_TRACE("callprop on void"); - guard(false, lir->ins2i(LIR_eq, get(&l), JSVAL_TO_SPECIAL(JSVAL_VOID)), MISMATCH_EXIT); - i = JSProto_Boolean; - debug_only_stmt(protoname = "Boolean.prototype";) - } else { - JS_ASSERT(JSVAL_IS_NULL(l) || JSVAL_IS_VOID(l)); - ABORT_TRACE("callprop on null or void"); - } - - if (!js_GetClassPrototype(cx, NULL, INT_TO_JSID(i), &obj)) - ABORT_TRACE_ERROR("GetClassPrototype failed!"); - - obj_ins = INS_CONSTOBJ(obj); - debug_only_stmt(obj_ins = addName(obj_ins, protoname);) - this_ins = get(&l); // use primitive as |this| - } - - JSObject* obj2; - jsuword pcval; - CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); - - if (PCVAL_IS_NULL(pcval) || !PCVAL_IS_OBJECT(pcval)) - ABORT_TRACE("callee is not an object"); - JS_ASSERT(HAS_FUNCTION_CLASS(PCVAL_TO_OBJECT(pcval))); - - if (JSVAL_IS_PRIMITIVE(l)) { - JSFunction* fun = GET_FUNCTION_PRIVATE(cx, PCVAL_TO_OBJECT(pcval)); - if (!PRIMITIVE_THIS_TEST(fun, l)) - ABORT_TRACE("callee does not accept primitive |this|"); - } - - stack(0, this_ins); - stack(-1, INS_CONSTOBJ(PCVAL_TO_OBJECT(pcval))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_DELDESC() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_UINT24() -{ - stack(0, lir->insImmf(GET_UINT24(cx->fp->regs->pc))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INDEXBASE() -{ - atoms += GET_INDEXBASE(cx->fp->regs->pc); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_RESETBASE() -{ - atoms = cx->fp->script->atomMap.vector; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_RESETBASE0() -{ - atoms = cx->fp->script->atomMap.vector; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLELEM() -{ - return record_JSOP_GETELEM(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_STOP() -{ - JSStackFrame *fp = cx->fp; - - if (fp->imacpc) { - /* - * End of imacro, so return true to the interpreter immediately. The - * interpreter's JSOP_STOP case will return from the imacro, back to - * the pc after the calling op, still in the same JSStackFrame. - */ - atoms = fp->script->atomMap.vector; - return JSRS_CONTINUE; - } - - putArguments(); - - /* - * We know falling off the end of a constructor returns the new object that - * was passed in via fp->argv[-1], while falling off the end of a function - * returns undefined. - * - * NB: we do not support script rval (eval, API users who want the result - * of the last expression-statement, debugger API calls). - */ - if (fp->flags & JSFRAME_CONSTRUCTING) { - JS_ASSERT(OBJECT_TO_JSVAL(fp->thisp) == fp->argv[-1]); - rval_ins = get(&fp->argv[-1]); - } else { - rval_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); - } - clearFrameSlotsFromCache(); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETXPROP() -{ - jsval& l = stackval(-1); - if (JSVAL_IS_PRIMITIVE(l)) - ABORT_TRACE("primitive-this for GETXPROP?"); - - jsval* vp; - LIns* v_ins; - NameResult nr; - CHECK_STATUS(name(vp, v_ins, nr)); - stack(-1, v_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLXMLNAME() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_TYPEOFEXPR() -{ - return record_JSOP_TYPEOF(); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENTERBLOCK() -{ - JSObject* obj; - obj = cx->fp->script->getObject(getFullIndex(0)); - - LIns* void_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); - for (int i = 0, n = OBJ_BLOCK_COUNT(cx, obj); i < n; i++) - stack(i, void_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LEAVEBLOCK() -{ - /* We mustn't exit the lexical block we began recording in. */ - if (cx->fp->blockChain != lexicalBlock) - return JSRS_CONTINUE; - else - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GENERATOR() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_YIELD() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ARRAYPUSH() -{ - uint32_t slot = GET_UINT16(cx->fp->regs->pc); - JS_ASSERT(cx->fp->script->nfixed <= slot); - JS_ASSERT(cx->fp->slots + slot < cx->fp->regs->sp - 1); - jsval &arrayval = cx->fp->slots[slot]; - JS_ASSERT(JSVAL_IS_OBJECT(arrayval)); - JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, JSVAL_TO_OBJECT(arrayval))); - LIns *array_ins = get(&arrayval); - jsval &elt = stackval(-1); - LIns *elt_ins = box_jsval(elt, get(&elt)); - - LIns *args[] = { elt_ins, array_ins, cx_ins }; - LIns *ok_ins = lir->insCall(&js_ArrayCompPush_ci, args); - guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_ENUMCONSTELEM() -{ - return JSRS_STOP; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LEAVEBLOCKEXPR() -{ - LIns* v_ins = stack(-1); - int n = -1 - GET_UINT16(cx->fp->regs->pc); - stack(n, v_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETTHISPROP() -{ - LIns* this_ins; - - CHECK_STATUS(getThis(this_ins)); - - /* - * It's safe to just use cx->fp->thisp here because getThis() returns - * JSRS_STOP if thisp is not available. - */ - CHECK_STATUS(getProp(cx->fp->thisp, this_ins)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETARGPROP() -{ - return getProp(argval(GET_ARGNO(cx->fp->regs->pc))); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_GETLOCALPROP() -{ - return getProp(varval(GET_SLOTNO(cx->fp->regs->pc))); -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INDEXBASE1() -{ - atoms += 1 << 16; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INDEXBASE2() -{ - atoms += 2 << 16; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INDEXBASE3() -{ - atoms += 3 << 16; - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLGVAR() -{ - jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; - if (JSVAL_IS_NULL(slotval)) - // We will see JSOP_CALLNAME from the interpreter's jump, so no-op here. - return JSRS_CONTINUE; - - uint32 slot = JSVAL_TO_INT(slotval); - - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); - - jsval& v = STOBJ_GET_SLOT(globalObj, slot); - stack(0, get(&v)); - stack(1, INS_NULL()); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLLOCAL() -{ - uintN slot = GET_SLOTNO(cx->fp->regs->pc); - stack(0, var(slot)); - stack(1, INS_NULL()); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLARG() -{ - uintN slot = GET_ARGNO(cx->fp->regs->pc); - stack(0, arg(slot)); - stack(1, INS_NULL()); - return JSRS_CONTINUE; -} - -/* Functions for use with JSOP_CALLBUILTIN. */ - -static JSBool -ObjectToIterator(JSContext *cx, uintN argc, jsval *vp) -{ - jsval *argv = JS_ARGV(cx, vp); - JS_ASSERT(JSVAL_IS_INT(argv[0])); - JS_SET_RVAL(cx, vp, JS_THIS(cx, vp)); - return js_ValueToIterator(cx, JSVAL_TO_INT(argv[0]), &JS_RVAL(cx, vp)); -} - -static JSObject* FASTCALL -ObjectToIterator_tn(JSContext* cx, jsbytecode* pc, JSObject *obj, int32 flags) -{ - jsval v = OBJECT_TO_JSVAL(obj); - JSBool ok = js_ValueToIterator(cx, flags, &v); - - if (!ok) { - js_SetBuiltinError(cx); - return NULL; - } - return JSVAL_TO_OBJECT(v); -} - -static JSBool -CallIteratorNext(JSContext *cx, uintN argc, jsval *vp) -{ - return js_CallIteratorNext(cx, JS_THIS_OBJECT(cx, vp), &JS_RVAL(cx, vp)); -} - -static jsval FASTCALL -CallIteratorNext_tn(JSContext* cx, jsbytecode* pc, JSObject* iterobj) -{ - JSAutoTempValueRooter tvr(cx); - JSBool ok = js_CallIteratorNext(cx, iterobj, tvr.addr()); - - if (!ok) { - js_SetBuiltinError(cx); - return JSVAL_ERROR_COOKIE; - } - return tvr.value(); -} - -JS_DEFINE_TRCINFO_1(ObjectToIterator, - (4, (static, OBJECT_FAIL, ObjectToIterator_tn, CONTEXT, PC, THIS, INT32, 0, 0))) -JS_DEFINE_TRCINFO_1(CallIteratorNext, - (3, (static, JSVAL_FAIL, CallIteratorNext_tn, CONTEXT, PC, THIS, 0, 0))) - -static const struct BuiltinFunctionInfo { - JSNativeTraceInfo *ti; - int nargs; -} builtinFunctionInfo[JSBUILTIN_LIMIT] = { - {&ObjectToIterator_trcinfo, 1}, - {&CallIteratorNext_trcinfo, 0}, -}; - -JSObject * -js_GetBuiltinFunction(JSContext *cx, uintN index) -{ - JSRuntime *rt = cx->runtime; - JSObject *funobj = rt->builtinFunctions[index]; - - if (!funobj) { - /* Use NULL parent and atom. Builtin functions never escape to scripts. */ - JS_ASSERT(index < JS_ARRAY_LENGTH(builtinFunctionInfo)); - const BuiltinFunctionInfo *bfi = &builtinFunctionInfo[index]; - JSFunction *fun = js_NewFunction(cx, - NULL, - JS_DATA_TO_FUNC_PTR(JSNative, bfi->ti), - bfi->nargs, - JSFUN_FAST_NATIVE | JSFUN_TRCINFO, - NULL, - NULL); - if (fun) { - funobj = FUN_OBJECT(fun); - STOBJ_CLEAR_PROTO(funobj); - STOBJ_CLEAR_PARENT(funobj); - - JS_LOCK_GC(rt); - if (!rt->builtinFunctions[index]) /* retest now that the lock is held */ - rt->builtinFunctions[index] = funobj; - else - funobj = rt->builtinFunctions[index]; - JS_UNLOCK_GC(rt); - } - } - return funobj; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CALLBUILTIN() -{ - JSObject *obj = js_GetBuiltinFunction(cx, GET_INDEX(cx->fp->regs->pc)); - if (!obj) - ABORT_TRACE_ERROR("error in js_GetBuiltinFunction"); - - stack(0, get(&stackval(-1))); - stack(-1, INS_CONSTOBJ(obj)); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INT8() -{ - stack(0, lir->insImmf(GET_INT8(cx->fp->regs->pc))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_INT32() -{ - stack(0, lir->insImmf(GET_INT32(cx->fp->regs->pc))); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_LENGTH() -{ - jsval& l = stackval(-1); - if (JSVAL_IS_PRIMITIVE(l)) { - if (!JSVAL_IS_STRING(l)) - ABORT_TRACE("non-string primitive JSOP_LENGTH unsupported"); - set(&l, lir->ins1(LIR_i2f, getStringLength(get(&l)))); - return JSRS_CONTINUE; - } - - JSObject* obj = JSVAL_TO_OBJECT(l); - LIns* obj_ins = get(&l); - - if (STOBJ_GET_CLASS(obj) == &js_ArgumentsClass) { - unsigned depth; - JSStackFrame *afp = guardArguments(obj, obj_ins, &depth); - if (!afp) - ABORT_TRACE("can't reach arguments object's frame"); - - // We must both check at record time and guard at run time that - // arguments.length has not been reassigned, redefined or deleted. - if (js_IsOverriddenArgsLength(obj)) - ABORT_TRACE("can't trace JSOP_ARGCNT if arguments.length has been modified"); - LIns* slot_ins = guardArgsLengthNotAssigned(obj_ins); - - // slot_ins is the value from the slot; right-shift by 2 bits to get - // the length (see GetArgsLength in jsfun.cpp). - LIns* v_ins = lir->ins1(LIR_i2f, lir->ins2i(LIR_rsh, slot_ins, 2)); - set(&l, v_ins); - return JSRS_CONTINUE; - } - - LIns* v_ins; - if (OBJ_IS_ARRAY(cx, obj)) { - if (OBJ_IS_DENSE_ARRAY(cx, obj)) { - if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) { - JS_NOT_REACHED("OBJ_IS_DENSE_ARRAY but not?!?"); - return JSRS_STOP; - } - } else { - if (!guardClass(obj, obj_ins, &js_SlowArrayClass, snapshot(BRANCH_EXIT))) - ABORT_TRACE("can't trace length property access on non-array"); - } - v_ins = lir->ins1(LIR_i2f, p2i(stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH))); - } else { - if (!OBJ_IS_NATIVE(obj)) - ABORT_TRACE("can't trace length property access on non-array, non-native object"); - return getProp(obj, obj_ins); - } - set(&l, v_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_NEWARRAY() -{ - LIns *proto_ins; - CHECK_STATUS(getClassPrototype(JSProto_Array, proto_ins)); - - uint32 len = GET_UINT16(cx->fp->regs->pc); - cx->fp->assertValidStackDepth(len); - - LIns* args[] = { lir->insImm(len), proto_ins, cx_ins }; - LIns* v_ins = lir->insCall(&js_NewUninitializedArray_ci, args); - guard(false, lir->ins_peq0(v_ins), OOM_EXIT); - - LIns* dslots_ins = NULL; - uint32 count = 0; - for (uint32 i = 0; i < len; i++) { - jsval& v = stackval(int(i) - int(len)); - if (v != JSVAL_HOLE) - count++; - LIns* elt_ins = box_jsval(v, get(&v)); - stobj_set_dslot(v_ins, i, dslots_ins, elt_ins); - } - - if (count > 0) - stobj_set_fslot(v_ins, JSSLOT_ARRAY_COUNT, INS_CONST(count)); - - stack(-int(len), v_ins); - return JSRS_CONTINUE; -} - -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_HOLE() -{ - stack(0, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_HOLE))); - return JSRS_CONTINUE; -} - -JSRecordingStatus -TraceRecorder::record_JSOP_TRACE() -{ - return JSRS_CONTINUE; -} - -static const uint32 sMaxConcatNSize = 32; - -/* - * Copy the result of defvalue.string back into concatn's arguments, clean the - * stack, and return a pointer to the argument that was just overwritten. - */ -JS_REQUIRES_STACK jsval * -js_ConcatPostImacroStackCleanup(uint32 argc, JSFrameRegs ®s, - TraceRecorder *recorder) -{ - JS_ASSERT(*regs.pc == JSOP_IMACOP); - - /* Pop the argument offset and imacro return value. */ - jsint offset = JSVAL_TO_INT(*--regs.sp); - jsval *imacroResult = --regs.sp; - - /* Replace non-primitive argument with new primitive argument. */ - jsval *vp = regs.sp - offset; - JS_ASSERT(regs.sp - argc <= vp && vp < regs.sp); - if (recorder) - recorder->set(vp, recorder->get(imacroResult)); - *vp = *imacroResult; - - return vp; -} - -/* - * Initially, concatn takes N arguments on the stack, where N is immediate - * operand. To convert these arguments to primitives, we must repeatedly call - * the defvalue.string imacro. To achieve this iteration, defvalue.string ends - * with imacop. Hence, this function is called multiple times, each time with - * one less non-primitive. To keep track of where we are in the loop, we must - * push an additional index value on the stack. Hence, on all subsequent - * entries, the stack is organized as follows (bottom to top): - * - * prim[1] - * ... - * prim[i-1] - * nonprim[i] argument to imacro - * arg[i+1] - * ... - * arg[N] - * primarg[i] nonprim[i] converted to primitive - * i - * - * Hence, the stack setup on entry to this function (and JSOP_CONCATN in the - * interpreter, on trace abort) is dependent on whether an imacro is in - * progress. When all of concatn's arguments are primitive, it emits a builtin - * call and allows the actual JSOP_CONCATN to be executed by the interpreter. - */ -JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_JSOP_CONCATN() -{ - JSStackFrame *fp = cx->fp; - JSFrameRegs ®s = *fp->regs; - - /* - * If we are in an imacro, we must have just finished a call to - * defvalue.string. Continue where we left off last time. - */ - uint32 argc; - jsval *loopStart; - if (fp->imacpc) { - JS_ASSERT(*fp->imacpc == JSOP_CONCATN); - argc = GET_ARGC(fp->imacpc); - loopStart = js_ConcatPostImacroStackCleanup(argc, regs, this) + 1; - } else { - argc = GET_ARGC(regs.pc); - JS_ASSERT(argc > 0); - loopStart = regs.sp - argc; - - /* Prevent code/alloca explosion. */ - if (argc > sMaxConcatNSize) - return JSRS_STOP; - } - - /* Convert non-primitives to primitives using defvalue.string. */ - for (jsval *vp = loopStart; vp != regs.sp; ++vp) { - if (!JSVAL_IS_PRIMITIVE(*vp)) { - /* - * In addition to the jsval we want the imacro to convert to - * primitive, pass through the offset of the argument on the stack. - */ - jsint offset = regs.sp - vp; - - /* Push the non-primitive to convert. */ - set(regs.sp, get(vp), true); - *regs.sp++ = *vp; - - /* Push the argument index. */ - set(regs.sp, lir->insImm(offset), true); - *regs.sp++ = INT_TO_JSVAL(offset); - - /* Nested imacro call OK because this is a tail call. */ - return call_imacro(defvalue_imacros.string); - } - } - - /* Build an array of the stringified primitives. */ - int32_t bufSize = argc * sizeof(JSString *); - LIns *buf_ins = lir->insAlloc(bufSize); - int32_t d = 0; - for (jsval *vp = regs.sp - argc; vp != regs.sp; ++vp, d += sizeof(void *)) - lir->insStorei(stringify(*vp), buf_ins, d); - - /* Perform concatenation using a builtin. */ - LIns *args[] = { lir->insImm(argc), buf_ins, cx_ins }; - LIns *concat = lir->insCall(&js_ConcatN_ci, args); - guard(false, lir->ins_peq0(concat), OOM_EXIT); - - /* Update tracker with result. */ - jsval *afterPop = regs.sp - (argc - 1); - set(afterPop - 1, concat); - - return JSRS_CONTINUE; -} - -#define DBG_STUB(OP) \ - JS_REQUIRES_STACK JSRecordingStatus \ - TraceRecorder::record_##OP() \ - { \ - ABORT_TRACE("can't trace " #OP); \ - } - -DBG_STUB(JSOP_GETUPVAR_DBG) -DBG_STUB(JSOP_CALLUPVAR_DBG) -DBG_STUB(JSOP_DEFFUN_DBGFC) -DBG_STUB(JSOP_DEFLOCALFUN_DBGFC) -DBG_STUB(JSOP_LAMBDA_DBGFC) - -#ifdef JS_JIT_SPEW -/* - * Print information about entry typemaps and unstable exits for all peers - * at a PC. - */ -void -DumpPeerStability(JSTraceMonitor* tm, const void* ip, JSObject* globalObj, uint32 globalShape, - uint32 argc) -{ - VMFragment* f; - TreeInfo* ti; - bool looped = false; - unsigned length = 0; - - for (f = getLoop(tm, ip, globalObj, globalShape, argc); f != NULL; f = f->peer) { - if (!f->vmprivate) - continue; - debug_only_printf(LC_TMRecorder, "Stability of fragment %p:\nENTRY STACK=", (void*)f); - ti = (TreeInfo*)f->vmprivate; - if (looped) - JS_ASSERT(ti->nStackTypes == length); - for (unsigned i = 0; i < ti->nStackTypes; i++) - debug_only_printf(LC_TMRecorder, "%c", typeChar[ti->stackTypeMap()[i]]); - debug_only_print0(LC_TMRecorder, " GLOBALS="); - for (unsigned i = 0; i < ti->nGlobalTypes(); i++) - debug_only_printf(LC_TMRecorder, "%c", typeChar[ti->globalTypeMap()[i]]); - debug_only_print0(LC_TMRecorder, "\n"); - UnstableExit* uexit = ti->unstableExits; - while (uexit != NULL) { - debug_only_print0(LC_TMRecorder, "EXIT "); - JSTraceType* m = uexit->exit->fullTypeMap(); - debug_only_print0(LC_TMRecorder, "STACK="); - for (unsigned i = 0; i < uexit->exit->numStackSlots; i++) - debug_only_printf(LC_TMRecorder, "%c", typeChar[m[i]]); - debug_only_print0(LC_TMRecorder, " GLOBALS="); - for (unsigned i = 0; i < uexit->exit->numGlobalSlots; i++) { - debug_only_printf(LC_TMRecorder, "%c", - typeChar[m[uexit->exit->numStackSlots + i]]); - } - debug_only_print0(LC_TMRecorder, "\n"); - uexit = uexit->next; - } - length = ti->nStackTypes; - looped = true; - } -} -#endif - -#ifdef MOZ_TRACEVIS - -FILE* traceVisLogFile = NULL; -JSHashTable *traceVisScriptTable = NULL; - -JS_FRIEND_API(bool) -JS_StartTraceVis(const char* filename = "tracevis.dat") -{ - if (traceVisLogFile) { - // If we're currently recording, first we must stop. - JS_StopTraceVis(); - } - - traceVisLogFile = fopen(filename, "wb"); - if (!traceVisLogFile) - return false; - - return true; -} - -JS_FRIEND_API(JSBool) -js_StartTraceVis(JSContext *cx, JSObject *obj, - uintN argc, jsval *argv, jsval *rval) -{ - JSBool ok; - - if (argc > 0 && JSVAL_IS_STRING(argv[0])) { - JSString *str = JSVAL_TO_STRING(argv[0]); - char *filename = js_DeflateString(cx, str->chars(), str->length()); - if (!filename) - goto error; - ok = JS_StartTraceVis(filename); - cx->free(filename); - } else { - ok = JS_StartTraceVis(); - } - - if (ok) { - fprintf(stderr, "started TraceVis recording\n"); - return JS_TRUE; - } - - error: - JS_ReportError(cx, "failed to start TraceVis recording"); - return JS_FALSE; -} - -JS_FRIEND_API(bool) -JS_StopTraceVis() -{ - if (!traceVisLogFile) - return false; - - fclose(traceVisLogFile); // not worth checking the result - traceVisLogFile = NULL; - - return true; -} - -JS_FRIEND_API(JSBool) -js_StopTraceVis(JSContext *cx, JSObject *obj, - uintN argc, jsval *argv, jsval *rval) -{ - JSBool ok = JS_StopTraceVis(); - - if (ok) - fprintf(stderr, "stopped TraceVis recording\n"); - else - JS_ReportError(cx, "TraceVis isn't running"); - - return ok; -} - -#endif /* MOZ_TRACEVIS */ - -#define UNUSED(n) \ - JS_REQUIRES_STACK bool \ - TraceRecorder::record_JSOP_UNUSED##n() { \ - JS_NOT_REACHED("JSOP_UNUSED" # n); \ - return false; \ - } diff --git a/tests/cpp/jstracer_part.cpp b/tests/cpp/jstracer_part.cpp deleted file mode 100644 index 30b829b..0000000 --- a/tests/cpp/jstracer_part.cpp +++ /dev/null @@ -1,195 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=4 sw=4 et tw=99: - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released - * May 28, 2008. - * - * The Initial Developer of the Original Code is - * Brendan Eich - * - * Contributor(s): - * Andreas Gal - * Mike Shaver - * David Anderson - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - - -#include "nanojit/nanojit.h" - -using namespace nanojit; - - -void* -nanojit::Allocator::allocChunk(size_t nbytes) -{ - VMAllocator *vma = (VMAllocator*)this; - JS_ASSERT(!vma->outOfMemory()); - void *p = malloc(nbytes); - if (!p) { - JS_ASSERT(nbytes < sizeof(vma->mReserve)); - vma->mOutOfMemory = true; - p = (void*) &vma->mReserve[0]; - } - vma->mSize += nbytes; - return p; -} - -void -nanojit::Allocator::freeChunk(void *p) { - VMAllocator *vma = (VMAllocator*)this; - if (p != &vma->mReserve[0]) - free(p); -} - -void -nanojit::Allocator::postReset() { - VMAllocator *vma = (VMAllocator*)this; - vma->mOutOfMemory = false; - vma->mSize = 0; -} - - -void -nanojit::StackFilter::getTops(LIns* guard, int& spTop, int& rpTop) -{ - VMSideExit* e = (VMSideExit*)guard->record()->exit; - spTop = e->sp_adj; - rpTop = e->rp_adj; -} - -class AdjustCallerGlobalTypesVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - JSContext *mCx; - nanojit::LirBuffer *mLirbuf; - nanojit::LirWriter *mLir; - JSTraceType *mTypeMap; -public: - AdjustCallerGlobalTypesVisitor(TraceRecorder &recorder, - JSTraceType *typeMap) : - mRecorder(recorder), - mCx(mRecorder.cx), - mLirbuf(mRecorder.lirbuf), - mLir(mRecorder.lir), - mTypeMap(typeMap) - {} - - JSTraceType* getTypeMap() - { - return mTypeMap; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE void - visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { - LIns *ins = mRecorder.get(vp); - bool isPromote = isPromoteInt(ins); - if (isPromote && *mTypeMap == TT_DOUBLE) { - mLir->insStorei(mRecorder.get(vp), mLirbuf->state, - mRecorder.nativeGlobalOffset(vp)); - - /* - * Aggressively undo speculation so the inner tree will compile - * if this fails. - */ - oracle.markGlobalSlotUndemotable(mCx, slot); - } - JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); - ++mTypeMap; - } -}; - -class AdjustCallerStackTypesVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - JSContext *mCx; - nanojit::LirBuffer *mLirbuf; - nanojit::LirWriter *mLir; - unsigned mSlotnum; - JSTraceType *mTypeMap; -public: - AdjustCallerStackTypesVisitor(TraceRecorder &recorder, - JSTraceType *typeMap) : - mRecorder(recorder), - mCx(mRecorder.cx), - mLirbuf(mRecorder.lirbuf), - mLir(mRecorder.lir), - mSlotnum(0), - mTypeMap(typeMap) - {} - - JSTraceType* getTypeMap() - { - return mTypeMap; - } - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - LIns *ins = mRecorder.get(vp); - bool isPromote = isPromoteInt(ins); - if (isPromote && *mTypeMap == TT_DOUBLE) { - mLir->insStorei(mRecorder.get(vp), mLirbuf->sp, - -mRecorder.treeInfo->nativeStackBase + - mRecorder.nativeStackOffset(vp)); - - /* - * Aggressively undo speculation so the inner tree will compile - * if this fails. - */ - oracle.markStackSlotUndemotable(mCx, mSlotnum); - } - JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); - ++vp; - ++mTypeMap; - ++mSlotnum; - } - return true; - } -}; - -#if defined NJ_VERBOSE -void -nanojit::LirNameMap::formatGuard(LIns *i, char *out) -{ - VMSideExit *x; - - x = (VMSideExit *)i->record()->exit; - sprintf(out, - "%s: %s %s -> pc=%p imacpc=%p sp%+ld rp%+ld (GuardID=%03d)", - formatRef(i), - lirNames[i->opcode()], - i->oprnd1() ? formatRef(i->oprnd1()) : "", - (void *)x->pc, - (void *)x->imacpc, - (long int)x->sp_adj, - (long int)x->rp_adj, - i->record()->profGuardID); -} -#endif - diff --git a/tests/cpp/mlprototype.cpp b/tests/cpp/mlprototype.cpp deleted file mode 100644 index 628e630..0000000 --- a/tests/cpp/mlprototype.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ - - NS_IMETHODIMP - nsThreadClassInfo::GetClassDescription( - char **result, - int foo, - bool blah - ) - { - *result = nsnull; - return NS_OK; - } - -int foo; diff --git a/tests/cpp/multiline.cpp b/tests/cpp/multiline.cpp deleted file mode 100644 index 4160fd5..0000000 --- a/tests/cpp/multiline.cpp +++ /dev/null @@ -1,6 +0,0 @@ -void* -Foo::bar(int i, - const char const * const * p) -{ - return; -} diff --git a/tests/cpp/nsCycleCollector.cpp b/tests/cpp/nsCycleCollector.cpp deleted file mode 100644 index f263041..0000000 --- a/tests/cpp/nsCycleCollector.cpp +++ /dev/null @@ -1,3728 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* vim: set cindent tabstop=4 expandtab shiftwidth=4: */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * L. David Baron , Mozilla Corporation - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -// -// This file implements a garbage-cycle collector based on the paper -// -// Concurrent Cycle Collection in Reference Counted Systems -// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072 -// -// We are not using the concurrent or acyclic cases of that paper; so -// the green, red and orange colors are not used. -// -// The collector is based on tracking pointers of four colors: -// -// Black nodes are definitely live. If we ever determine a node is -// black, it's ok to forget about, drop from our records. -// -// White nodes are definitely garbage cycles. Once we finish with our -// scanning, we unlink all the white nodes and expect that by -// unlinking them they will self-destruct (since a garbage cycle is -// only keeping itself alive with internal links, by definition). -// -// Grey nodes are being scanned. Nodes that turn grey will turn -// either black if we determine that they're live, or white if we -// determine that they're a garbage cycle. After the main collection -// algorithm there should be no grey nodes. -// -// Purple nodes are *candidates* for being scanned. They are nodes we -// haven't begun scanning yet because they're not old enough, or we're -// still partway through the algorithm. -// -// XPCOM objects participating in garbage-cycle collection are obliged -// to inform us when they ought to turn purple; that is, when their -// refcount transitions from N+1 -> N, for nonzero N. Furthermore we -// require that *after* an XPCOM object has informed us of turning -// purple, they will tell us when they either transition back to being -// black (incremented refcount) or are ultimately deleted. - - -// Safety: -// -// An XPCOM object is either scan-safe or scan-unsafe, purple-safe or -// purple-unsafe. -// -// An object is scan-safe if: -// -// - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though this -// operation loses ISupports identity (like nsIClassInfo). -// - The operation |traverse| on the resulting -// nsXPCOMCycleCollectionParticipant does not cause *any* refcount -// adjustment to occur (no AddRef / Release calls). -// -// An object is purple-safe if it satisfies the following properties: -// -// - The object is scan-safe. -// - If the object calls |nsCycleCollector::suspect(this)|, -// it will eventually call |nsCycleCollector::forget(this)|, -// exactly once per call to |suspect|, before being destroyed. -// -// When we receive a pointer |ptr| via -// |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We -// can check the scan-safety, but have no way to ensure the -// purple-safety; objects must obey, or else the entire system falls -// apart. Don't involve an object in this scheme if you can't -// guarantee its purple-safety. -// -// When we have a scannable set of purple nodes ready, we begin -// our walks. During the walks, the nodes we |traverse| should only -// feed us more scan-safe nodes, and should not adjust the refcounts -// of those nodes. -// -// We do not |AddRef| or |Release| any objects during scanning. We -// rely on purple-safety of the roots that call |suspect| and -// |forget| to hold, such that we will forget about a purple pointer -// before it is destroyed. The pointers that are merely scan-safe, -// we hold only for the duration of scanning, and there should be no -// objects released from the scan-safe set during the scan (there -// should be no threads involved). -// -// We *do* call |AddRef| and |Release| on every white object, on -// either side of the calls to |Unlink|. This keeps the set of white -// objects alive during the unlinking. -// - -#if !defined(__MINGW32__) -#ifdef WIN32 -#include -#include -#endif -#endif - -#include "base/process_util.h" - -/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ -#include "mozilla/Util.h" - -#include "nsCycleCollectionParticipant.h" -#include "nsCycleCollectorUtils.h" -#include "nsIProgrammingLanguage.h" -#include "nsBaseHashtable.h" -#include "nsHashKeys.h" -#include "nsDeque.h" -#include "nsCycleCollector.h" -#include "nsThreadUtils.h" -#include "prenv.h" -#include "prprf.h" -#include "plstr.h" -#include "prtime.h" -#include "nsPrintfCString.h" -#include "nsTArray.h" -#include "mozilla/FunctionTimer.h" -#include "nsIObserverService.h" -#include "nsIConsoleService.h" -#include "nsServiceManagerUtils.h" -#include "nsThreadUtils.h" -#include "nsTArray.h" -#include "mozilla/Services.h" -#include "nsICycleCollectorListener.h" -#include "nsIXPConnect.h" -#include "nsIJSRuntimeService.h" -#include "nsIMemoryReporter.h" -#include "xpcpublic.h" -#include -#include -#ifdef WIN32 -#include -#include -#endif - -#ifdef XP_WIN -#include -#endif - -#include "mozilla/Mutex.h" -#include "mozilla/CondVar.h" -#include "mozilla/Telemetry.h" - -using namespace mozilla; - -//#define COLLECT_TIME_DEBUG - -#ifdef DEBUG_CC -#define IF_DEBUG_CC_PARAM(_p) , _p -#define IF_DEBUG_CC_ONLY_PARAM(_p) _p -#else -#define IF_DEBUG_CC_PARAM(_p) -#define IF_DEBUG_CC_ONLY_PARAM(_p) -#endif - -#define DEFAULT_SHUTDOWN_COLLECTIONS 5 -#ifdef DEBUG_CC -#define SHUTDOWN_COLLECTIONS(params) params.mShutdownCollections -#else -#define SHUTDOWN_COLLECTIONS(params) DEFAULT_SHUTDOWN_COLLECTIONS -#endif - -#if defined(XP_WIN) -// Defined in nsThreadManager.cpp. -extern DWORD gTLSThreadIDIndex; -#elif defined(NS_TLS) -// Defined in nsThreadManager.cpp. -extern NS_TLS mozilla::threads::ID gTLSThreadID; -#else -PRThread* gCycleCollectorThread = nsnull; -#endif - -// If true, always log cycle collector graphs. -const bool gAlwaysLogCCGraphs = false; - -// Various parameters of this collector can be tuned using environment -// variables. - -struct nsCycleCollectorParams -{ - bool mDoNothing; - bool mLogGraphs; -#ifdef DEBUG_CC - bool mReportStats; - bool mHookMalloc; - bool mFaultIsFatal; - bool mLogPointers; - PRUint32 mShutdownCollections; -#endif - - nsCycleCollectorParams() : -#ifdef DEBUG_CC - mDoNothing (PR_GetEnv("XPCOM_CC_DO_NOTHING") != NULL), - mLogGraphs (gAlwaysLogCCGraphs || - PR_GetEnv("XPCOM_CC_DRAW_GRAPHS") != NULL), - mReportStats (PR_GetEnv("XPCOM_CC_REPORT_STATS") != NULL), - mHookMalloc (PR_GetEnv("XPCOM_CC_HOOK_MALLOC") != NULL), - mFaultIsFatal (PR_GetEnv("XPCOM_CC_FAULT_IS_FATAL") != NULL), - mLogPointers (PR_GetEnv("XPCOM_CC_LOG_POINTERS") != NULL), - - mShutdownCollections(DEFAULT_SHUTDOWN_COLLECTIONS) -#else - mDoNothing (false), - mLogGraphs (gAlwaysLogCCGraphs) -#endif - { -#ifdef DEBUG_CC - char *s = PR_GetEnv("XPCOM_CC_SHUTDOWN_COLLECTIONS"); - if (s) - PR_sscanf(s, "%d", &mShutdownCollections); -#endif - } -}; - -#ifdef DEBUG_CC -// Various operations involving the collector are recorded in a -// statistics table. These are for diagnostics. - -struct nsCycleCollectorStats -{ - PRUint32 mFailedQI; - PRUint32 mSuccessfulQI; - - PRUint32 mVisitedNode; - PRUint32 mWalkedGraph; - PRUint32 mCollectedBytes; - PRUint32 mFreeCalls; - PRUint32 mFreedBytes; - - PRUint32 mSetColorGrey; - PRUint32 mSetColorBlack; - PRUint32 mSetColorWhite; - - PRUint32 mFailedUnlink; - PRUint32 mCollectedNode; - - PRUint32 mSuspectNode; - PRUint32 mForgetNode; - PRUint32 mFreedWhilePurple; - - PRUint32 mCollection; - - nsCycleCollectorStats() - { - memset(this, 0, sizeof(nsCycleCollectorStats)); - } - - void Dump() - { - fprintf(stderr, "\f\n"); -#define DUMP(entry) fprintf(stderr, "%30.30s: %-20.20d\n", #entry, entry) - DUMP(mFailedQI); - DUMP(mSuccessfulQI); - - DUMP(mVisitedNode); - DUMP(mWalkedGraph); - DUMP(mCollectedBytes); - DUMP(mFreeCalls); - DUMP(mFreedBytes); - - DUMP(mSetColorGrey); - DUMP(mSetColorBlack); - DUMP(mSetColorWhite); - - DUMP(mFailedUnlink); - DUMP(mCollectedNode); - - DUMP(mSuspectNode); - DUMP(mForgetNode); - DUMP(mFreedWhilePurple); - - DUMP(mCollection); -#undef DUMP - } -}; -#endif - -#ifdef DEBUG_CC -static bool nsCycleCollector_shouldSuppress(nsISupports *s); -static void InitMemHook(void); -#endif - -//////////////////////////////////////////////////////////////////////// -// Base types -//////////////////////////////////////////////////////////////////////// - -struct PtrInfo; - -class EdgePool -{ -public: - // EdgePool allocates arrays of void*, primarily to hold PtrInfo*. - // However, at the end of a block, the last two pointers are a null - // and then a void** pointing to the next block. This allows - // EdgePool::Iterators to be a single word but still capable of crossing - // block boundaries. - - EdgePool() - { - mSentinelAndBlocks[0].block = nsnull; - mSentinelAndBlocks[1].block = nsnull; - mNumBlocks = 0; - } - - ~EdgePool() - { - NS_ASSERTION(!mSentinelAndBlocks[0].block && - !mSentinelAndBlocks[1].block, - "Didn't call Clear()?"); - } - - void Clear() - { - Block *b = Blocks(); - while (b) { - Block *next = b->Next(); - delete b; - NS_ASSERTION(mNumBlocks > 0, - "Expected EdgePool mNumBlocks to be positive."); - mNumBlocks--; - b = next; - } - - mSentinelAndBlocks[0].block = nsnull; - mSentinelAndBlocks[1].block = nsnull; - } - -private: - struct Block; - union PtrInfoOrBlock { - // Use a union to avoid reinterpret_cast and the ensuing - // potential aliasing bugs. - PtrInfo *ptrInfo; - Block *block; - }; - struct Block { - enum { BlockSize = 16 * 1024 }; - - PtrInfoOrBlock mPointers[BlockSize]; - Block() { - mPointers[BlockSize - 2].block = nsnull; // sentinel - mPointers[BlockSize - 1].block = nsnull; // next block pointer - } - Block*& Next() - { return mPointers[BlockSize - 1].block; } - PtrInfoOrBlock* Start() - { return &mPointers[0]; } - PtrInfoOrBlock* End() - { return &mPointers[BlockSize - 2]; } - }; - - // Store the null sentinel so that we can have valid iterators - // before adding any edges and without adding any blocks. - PtrInfoOrBlock mSentinelAndBlocks[2]; - PRUint32 mNumBlocks; - - Block*& Blocks() { return mSentinelAndBlocks[1].block; } - -public: - class Iterator - { - public: - Iterator() : mPointer(nsnull) {} - Iterator(PtrInfoOrBlock *aPointer) : mPointer(aPointer) {} - Iterator(const Iterator& aOther) : mPointer(aOther.mPointer) {} - - Iterator& operator++() - { - if (mPointer->ptrInfo == nsnull) { - // Null pointer is a sentinel for link to the next block. - mPointer = (mPointer + 1)->block->mPointers; - } - ++mPointer; - return *this; - } - - PtrInfo* operator*() const - { - if (mPointer->ptrInfo == nsnull) { - // Null pointer is a sentinel for link to the next block. - return (mPointer + 1)->block->mPointers->ptrInfo; - } - return mPointer->ptrInfo; - } - bool operator==(const Iterator& aOther) const - { return mPointer == aOther.mPointer; } - bool operator!=(const Iterator& aOther) const - { return mPointer != aOther.mPointer; } - - private: - PtrInfoOrBlock *mPointer; - }; - - class Builder; - friend class Builder; - class Builder { - public: - Builder(EdgePool &aPool) - : mCurrent(&aPool.mSentinelAndBlocks[0]), - mBlockEnd(&aPool.mSentinelAndBlocks[0]), - mNextBlockPtr(&aPool.Blocks()), - mNumBlocks(aPool.mNumBlocks) - { - } - - Iterator Mark() { return Iterator(mCurrent); } - - void Add(PtrInfo* aEdge) { - if (mCurrent == mBlockEnd) { - Block *b = new Block(); - if (!b) { - // This means we just won't collect (some) cycles. - NS_NOTREACHED("out of memory, ignoring edges"); - return; - } - *mNextBlockPtr = b; - mCurrent = b->Start(); - mBlockEnd = b->End(); - mNextBlockPtr = &b->Next(); - mNumBlocks++; - } - (mCurrent++)->ptrInfo = aEdge; - } - private: - // mBlockEnd points to space for null sentinel - PtrInfoOrBlock *mCurrent, *mBlockEnd; - Block **mNextBlockPtr; - PRUint32 &mNumBlocks; - }; - - size_t BlocksSize() const { - return sizeof(Block) * mNumBlocks; - } - -}; - -#ifdef DEBUG_CC - -struct ReversedEdge { - PtrInfo *mTarget; - nsCString *mEdgeName; - ReversedEdge *mNext; -}; - -#endif - - -enum NodeColor { black, white, grey }; - -// This structure should be kept as small as possible; we may expect -// hundreds of thousands of them to be allocated and touched -// repeatedly during each cycle collection. - -struct PtrInfo -{ - void *mPointer; - nsCycleCollectionParticipant *mParticipant; - PRUint32 mColor : 2; - PRUint32 mInternalRefs : 30; - PRUint32 mRefCount; -private: - EdgePool::Iterator mFirstChild; - -public: -#ifdef DEBUG_CC - size_t mBytes; - char *mName; - PRUint32 mLangID; - - // For finding roots in ExplainLiveExpectedGarbage (when there are - // missing calls to suspect or failures to unlink). - PRUint32 mSCCIndex; // strongly connected component - - // For finding roots in ExplainLiveExpectedGarbage (when nodes - // expected to be garbage are black). - ReversedEdge* mReversedEdges; // linked list - PtrInfo* mShortestPathToExpectedGarbage; - nsCString* mShortestPathToExpectedGarbageEdgeName; - - nsTArray mEdgeNames; -#endif - - PtrInfo(void *aPointer, nsCycleCollectionParticipant *aParticipant - IF_DEBUG_CC_PARAM(PRUint32 aLangID) - ) - : mPointer(aPointer), - mParticipant(aParticipant), - mColor(grey), - mInternalRefs(0), - mRefCount(0), - mFirstChild() -#ifdef DEBUG_CC - , mBytes(0), - mName(nsnull), - mLangID(aLangID), - mSCCIndex(0), - mReversedEdges(nsnull), - mShortestPathToExpectedGarbage(nsnull), - mShortestPathToExpectedGarbageEdgeName(nsnull) -#endif - { - } - -#ifdef DEBUG_CC - void Destroy() { - PL_strfree(mName); - mEdgeNames.~nsTArray(); - } -#endif - - // Allow NodePool::Block's constructor to compile. - PtrInfo() { - NS_NOTREACHED("should never be called"); - } - - EdgePool::Iterator FirstChild() - { - return mFirstChild; - } - - // this PtrInfo must be part of a NodePool - EdgePool::Iterator LastChild() - { - return (this + 1)->mFirstChild; - } - - void SetFirstChild(EdgePool::Iterator aFirstChild) - { - mFirstChild = aFirstChild; - } - - // this PtrInfo must be part of a NodePool - void SetLastChild(EdgePool::Iterator aLastChild) - { - (this + 1)->mFirstChild = aLastChild; - } -}; - -/** - * A structure designed to be used like a linked list of PtrInfo, except - * that allocates the PtrInfo 32K-at-a-time. - */ -class NodePool -{ -private: - enum { BlockSize = 8 * 1024 }; // could be int template parameter - - struct Block { - // We create and destroy Block using NS_Alloc/NS_Free rather - // than new and delete to avoid calling its constructor and - // destructor. - Block() { NS_NOTREACHED("should never be called"); } - ~Block() { NS_NOTREACHED("should never be called"); } - - Block* mNext; - PtrInfo mEntries[BlockSize + 1]; // +1 to store last child of last node - }; - -public: - NodePool() - : mBlocks(nsnull), - mLast(nsnull), - mNumBlocks(0) - { - } - - ~NodePool() - { - NS_ASSERTION(!mBlocks, "Didn't call Clear()?"); - } - - void Clear() - { -#ifdef DEBUG_CC - { - Enumerator queue(*this); - while (!queue.IsDone()) { - queue.GetNext()->Destroy(); - } - } -#endif - Block *b = mBlocks; - while (b) { - Block *n = b->mNext; - NS_Free(b); - NS_ASSERTION(mNumBlocks > 0, - "Expected NodePool mNumBlocks to be positive."); - mNumBlocks--; - b = n; - } - - mBlocks = nsnull; - mLast = nsnull; - } - - class Builder; - friend class Builder; - class Builder { - public: - Builder(NodePool& aPool) - : mNextBlock(&aPool.mBlocks), - mNext(aPool.mLast), - mBlockEnd(nsnull), - mNumBlocks(aPool.mNumBlocks) - { - NS_ASSERTION(aPool.mBlocks == nsnull && aPool.mLast == nsnull, - "pool not empty"); - } - PtrInfo *Add(void *aPointer, nsCycleCollectionParticipant *aParticipant - IF_DEBUG_CC_PARAM(PRUint32 aLangID) - ) - { - if (mNext == mBlockEnd) { - Block *block; - if (!(*mNextBlock = block = - static_cast(NS_Alloc(sizeof(Block))))) - return nsnull; - mNext = block->mEntries; - mBlockEnd = block->mEntries + BlockSize; - block->mNext = nsnull; - mNextBlock = &block->mNext; - mNumBlocks++; - } - return new (mNext++) PtrInfo(aPointer, aParticipant - IF_DEBUG_CC_PARAM(aLangID) - ); - } - private: - Block **mNextBlock; - PtrInfo *&mNext; - PtrInfo *mBlockEnd; - PRUint32 &mNumBlocks; - }; - - class Enumerator; - friend class Enumerator; - class Enumerator { - public: - Enumerator(NodePool& aPool) - : mFirstBlock(aPool.mBlocks), - mCurBlock(nsnull), - mNext(nsnull), - mBlockEnd(nsnull), - mLast(aPool.mLast) - { - } - - bool IsDone() const - { - return mNext == mLast; - } - - bool AtBlockEnd() const - { - return mNext == mBlockEnd; - } - - PtrInfo* GetNext() - { - NS_ASSERTION(!IsDone(), "calling GetNext when done"); - if (mNext == mBlockEnd) { - Block *nextBlock = mCurBlock ? mCurBlock->mNext : mFirstBlock; - mNext = nextBlock->mEntries; - mBlockEnd = mNext + BlockSize; - mCurBlock = nextBlock; - } - return mNext++; - } - private: - Block *mFirstBlock, *mCurBlock; - // mNext is the next value we want to return, unless mNext == mBlockEnd - // NB: mLast is a reference to allow enumerating while building! - PtrInfo *mNext, *mBlockEnd, *&mLast; - }; - - size_t BlocksSize() const { - return sizeof(Block) * mNumBlocks; - } - -private: - Block *mBlocks; - PtrInfo *mLast; - PRUint32 mNumBlocks; -}; - - -struct WeakMapping -{ - // map and key will be null if the corresponding objects are GC marked - PtrInfo *mMap; - PtrInfo *mKey; - PtrInfo *mVal; -}; - -class GCGraphBuilder; - -struct GCGraph -{ - NodePool mNodes; - EdgePool mEdges; - nsTArray mWeakMaps; - PRUint32 mRootCount; -#ifdef DEBUG_CC - ReversedEdge *mReversedEdges; -#endif - - GCGraph() : mRootCount(0) { - } - ~GCGraph() { - } - - size_t BlocksSize() const { - return mNodes.BlocksSize() + mEdges.BlocksSize(); - } - -}; - -// XXX Would be nice to have an nsHashSet API that has -// Add/Remove/Has rather than PutEntry/RemoveEntry/GetEntry. -typedef nsTHashtable PointerSet; - -static inline void -ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp); - -struct nsPurpleBuffer -{ -private: - struct Block { - Block *mNext; - nsPurpleBufferEntry mEntries[255]; - - Block() : mNext(nsnull) {} - }; -public: - // This class wraps a linked list of the elements in the purple - // buffer. - - nsCycleCollectorParams &mParams; - PRUint32 mNumBlocksAlloced; - PRUint32 mCount; - Block mFirstBlock; - nsPurpleBufferEntry *mFreeList; - - // For objects compiled against Gecko 1.9 and 1.9.1. - PointerSet mCompatObjects; -#ifdef DEBUG_CC - PointerSet mNormalObjects; // duplicates our blocks - nsCycleCollectorStats &mStats; -#endif - -#ifdef DEBUG_CC - nsPurpleBuffer(nsCycleCollectorParams ¶ms, - nsCycleCollectorStats &stats) - : mParams(params), - mStats(stats) - { - InitBlocks(); - mNormalObjects.Init(); - mCompatObjects.Init(); - } -#else - nsPurpleBuffer(nsCycleCollectorParams ¶ms) - : mParams(params) - { - InitBlocks(); - mCompatObjects.Init(); - } -#endif - - ~nsPurpleBuffer() - { - FreeBlocks(); - } - - void InitBlocks() - { - mNumBlocksAlloced = 0; - mCount = 0; - mFreeList = nsnull; - StartBlock(&mFirstBlock); - } - - void StartBlock(Block *aBlock) - { - NS_ABORT_IF_FALSE(!mFreeList, "should not have free list"); - - // Put all the entries in the block on the free list. - nsPurpleBufferEntry *entries = aBlock->mEntries; - mFreeList = entries; - for (PRUint32 i = 1; i < ArrayLength(aBlock->mEntries); ++i) { - entries[i - 1].mNextInFreeList = - (nsPurpleBufferEntry*)(PRUword(entries + i) | 1); - } - entries[ArrayLength(aBlock->mEntries) - 1].mNextInFreeList = - (nsPurpleBufferEntry*)1; - } - - void FreeBlocks() - { - if (mCount > 0) - UnmarkRemainingPurple(&mFirstBlock); - Block *b = mFirstBlock.mNext; - while (b) { - if (mCount > 0) - UnmarkRemainingPurple(b); - Block *next = b->mNext; - delete b; - b = next; - NS_ASSERTION(mNumBlocksAlloced > 0, - "Expected positive mNumBlocksAlloced."); - mNumBlocksAlloced--; - } - mFirstBlock.mNext = nsnull; - } - - void UnmarkRemainingPurple(Block *b) - { - for (nsPurpleBufferEntry *e = b->mEntries, - *eEnd = ArrayEnd(b->mEntries); - e != eEnd; ++e) { - if (!(PRUword(e->mObject) & PRUword(1))) { - // This is a real entry (rather than something on the - // free list). - if (e->mObject) { - nsXPCOMCycleCollectionParticipant *cp; - ToParticipant(e->mObject, &cp); - - cp->UnmarkPurple(e->mObject); - } - - if (--mCount == 0) - break; - } - } - } - - void SelectPointers(GCGraphBuilder &builder); - -#ifdef DEBUG_CC - void NoteAll(GCGraphBuilder &builder); - - bool Exists(void *p) const - { - return mNormalObjects.GetEntry(p) || mCompatObjects.GetEntry(p); - } -#endif - - nsPurpleBufferEntry* NewEntry() - { - if (!mFreeList) { - Block *b = new Block; - if (!b) { - return nsnull; - } - mNumBlocksAlloced++; - StartBlock(b); - - // Add the new block as the second block in the list. - b->mNext = mFirstBlock.mNext; - mFirstBlock.mNext = b; - } - - nsPurpleBufferEntry *e = mFreeList; - mFreeList = (nsPurpleBufferEntry*) - (PRUword(mFreeList->mNextInFreeList) & ~PRUword(1)); - return e; - } - - nsPurpleBufferEntry* Put(nsISupports *p) - { - nsPurpleBufferEntry *e = NewEntry(); - if (!e) { - return nsnull; - } - - ++mCount; - - e->mObject = p; - -#ifdef DEBUG_CC - mNormalObjects.PutEntry(p); -#endif - - // Caller is responsible for filling in result's mRefCnt. - return e; - } - - void Remove(nsPurpleBufferEntry *e) - { - NS_ASSERTION(mCount != 0, "must have entries"); - -#ifdef DEBUG_CC - mNormalObjects.RemoveEntry(e->mObject); -#endif - - e->mNextInFreeList = - (nsPurpleBufferEntry*)(PRUword(mFreeList) | PRUword(1)); - mFreeList = e; - - --mCount; - } - - bool PutCompatObject(nsISupports *p) - { - ++mCount; - return !!mCompatObjects.PutEntry(p); - } - - void RemoveCompatObject(nsISupports *p) - { - --mCount; - mCompatObjects.RemoveEntry(p); - } - - PRUint32 Count() const - { - return mCount; - } - - size_t BlocksSize() const - { - return sizeof(Block) * mNumBlocksAlloced; - } - -}; - -struct CallbackClosure -{ - CallbackClosure(nsPurpleBuffer *aPurpleBuffer, GCGraphBuilder &aBuilder) - : mPurpleBuffer(aPurpleBuffer), - mBuilder(aBuilder) - { - } - nsPurpleBuffer *mPurpleBuffer; - GCGraphBuilder &mBuilder; -}; - -static bool -AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root); - -static PLDHashOperator -selectionCallback(nsVoidPtrHashKey* key, void* userArg) -{ - CallbackClosure *closure = static_cast(userArg); - if (AddPurpleRoot(closure->mBuilder, - static_cast( - const_cast(key->GetKey())))) - return PL_DHASH_REMOVE; - - return PL_DHASH_NEXT; -} - -void -nsPurpleBuffer::SelectPointers(GCGraphBuilder &aBuilder) -{ -#ifdef DEBUG_CC - NS_ABORT_IF_FALSE(mCompatObjects.Count() + mNormalObjects.Count() == - mCount, - "count out of sync"); -#endif - - if (mCompatObjects.Count()) { - mCount -= mCompatObjects.Count(); - CallbackClosure closure(this, aBuilder); - mCompatObjects.EnumerateEntries(selectionCallback, &closure); - mCount += mCompatObjects.Count(); // in case of allocation failure - } - - // Walk through all the blocks. - for (Block *b = &mFirstBlock; b; b = b->mNext) { - for (nsPurpleBufferEntry *e = b->mEntries, - *eEnd = ArrayEnd(b->mEntries); - e != eEnd; ++e) { - if (!(PRUword(e->mObject) & PRUword(1))) { - // This is a real entry (rather than something on the - // free list). - if (!e->mObject || AddPurpleRoot(aBuilder, e->mObject)) { -#ifdef DEBUG_CC - mNormalObjects.RemoveEntry(e->mObject); -#endif - --mCount; - // Put this entry on the free list in case some - // call to AddPurpleRoot fails and we don't rebuild - // the free list below. - e->mNextInFreeList = (nsPurpleBufferEntry*) - (PRUword(mFreeList) | PRUword(1)); - mFreeList = e; - } - } - } - } - - NS_WARN_IF_FALSE(mCount == 0, "AddPurpleRoot failed"); - if (mCount == 0) { - FreeBlocks(); - InitBlocks(); - } -} - - - -//////////////////////////////////////////////////////////////////////// -// Implement the LanguageRuntime interface for C++/XPCOM -//////////////////////////////////////////////////////////////////////// - - -struct nsCycleCollectionXPCOMRuntime : - public nsCycleCollectionLanguageRuntime -{ - nsresult BeginCycleCollection(nsCycleCollectionTraversalCallback &cb, - bool explainLiveExpectedGarbage) - { - return NS_OK; - } - - nsresult FinishTraverse() - { - return NS_OK; - } - - nsresult FinishCycleCollection() - { - return NS_OK; - } - - inline nsCycleCollectionParticipant *ToParticipant(void *p); - -#ifdef DEBUG_CC - virtual void PrintAllReferencesTo(void *p) {} -#endif -}; - -struct nsCycleCollector -{ - bool mCollectionInProgress; - bool mScanInProgress; - bool mFollowupCollection; - PRUint32 mCollectedObjects; - TimeStamp mCollectionStart; - - nsCycleCollectionLanguageRuntime *mRuntimes[nsIProgrammingLanguage::MAX+1]; - nsCycleCollectionXPCOMRuntime mXPCOMRuntime; - - GCGraph mGraph; - - nsCycleCollectorParams mParams; - - nsTArray *mWhiteNodes; - PRUint32 mWhiteNodeCount; - - // mVisitedRefCounted and mVisitedGCed are only used for telemetry - PRUint32 mVisitedRefCounted; - PRUint32 mVisitedGCed; - - nsPurpleBuffer mPurpleBuf; - - void RegisterRuntime(PRUint32 langID, - nsCycleCollectionLanguageRuntime *rt); - nsCycleCollectionLanguageRuntime * GetRuntime(PRUint32 langID); - void ForgetRuntime(PRUint32 langID); - - void SelectPurple(GCGraphBuilder &builder); - void MarkRoots(GCGraphBuilder &builder); - void ScanRoots(); - void ScanWeakMaps(); - - // returns whether anything was collected - bool CollectWhite(nsICycleCollectorListener *aListener); - - nsCycleCollector(); - ~nsCycleCollector(); - - // The first pair of Suspect and Forget functions are only used by - // old XPCOM binary components. - bool Suspect(nsISupports *n); - bool Forget(nsISupports *n); - nsPurpleBufferEntry* Suspect2(nsISupports *n); - bool Forget2(nsPurpleBufferEntry *e); - - PRUint32 Collect(PRUint32 aTryCollections, - nsICycleCollectorListener *aListener); - - // Prepare for and cleanup after one or more collection(s). - bool PrepareForCollection(nsTArray *aWhiteNodes); - void GCIfNeeded(bool aForceGC); - void CleanupAfterCollection(); - - // Start and finish an individual collection. - bool BeginCollection(nsICycleCollectorListener *aListener); - bool FinishCollection(nsICycleCollectorListener *aListener); - - PRUint32 SuspectedCount(); - void Shutdown(); - - void ClearGraph() - { - mGraph.mNodes.Clear(); - mGraph.mEdges.Clear(); - mGraph.mWeakMaps.Clear(); - mGraph.mRootCount = 0; - } - -#ifdef DEBUG_CC - nsCycleCollectorStats mStats; - - FILE *mPtrLog; - - void Allocated(void *n, size_t sz); - void Freed(void *n); - - void ExplainLiveExpectedGarbage(); - bool CreateReversedEdges(); - void DestroyReversedEdges(); - void ShouldBeFreed(nsISupports *n); - void WasFreed(nsISupports *n); - PointerSet mExpectedGarbage; -#endif -}; - - -/** - * GraphWalker is templatized over a Visitor class that must provide - * the following two methods: - * - * bool ShouldVisitNode(PtrInfo const *pi); - * void VisitNode(PtrInfo *pi); - */ -template -class GraphWalker -{ -private: - Visitor mVisitor; - - void DoWalk(nsDeque &aQueue); - -public: - void Walk(PtrInfo *s0); - void WalkFromRoots(GCGraph &aGraph); - // copy-constructing the visitor should be cheap, and less - // indirection than using a reference - GraphWalker(const Visitor aVisitor) : mVisitor(aVisitor) {} -}; - - -//////////////////////////////////////////////////////////////////////// -// The static collector object -//////////////////////////////////////////////////////////////////////// - - -static nsCycleCollector *sCollector = nsnull; - - -//////////////////////////////////////////////////////////////////////// -// Utility functions -//////////////////////////////////////////////////////////////////////// - -class CCRunnableFaultReport : public nsRunnable { -public: - CCRunnableFaultReport(const nsCString& report) - { - CopyUTF8toUTF16(report, mReport); - } - - NS_IMETHOD Run() { - nsCOMPtr obs = - do_GetService(NS_OBSERVERSERVICE_CONTRACTID); - if (obs) { - obs->NotifyObservers(nsnull, "cycle-collector-fault", - mReport.get()); - } - - nsCOMPtr cons = - do_GetService(NS_CONSOLESERVICE_CONTRACTID); - if (cons) { - cons->LogStringMessage(mReport.get()); - } - return NS_OK; - } - -private: - nsString mReport; -}; - -static void -Fault(const char *msg, const void *ptr=nsnull) -{ -#ifdef DEBUG_CC - // This should be nearly impossible, but just in case. - if (!sCollector) - return; - - if (sCollector->mParams.mFaultIsFatal) { - - if (ptr) - printf("Fatal fault in cycle collector: %s (ptr: %p)\n", msg, ptr); - else - printf("Fatal fault in cycle collector: %s\n", msg); - - exit(1); - } -#endif - - nsPrintfCString str(256, "Fault in cycle collector: %s (ptr: %p)\n", - msg, ptr); - NS_NOTREACHED(str.get()); - - // When faults are not fatal, we assume we're running in a - // production environment and we therefore want to disable the - // collector on a fault. This will unfortunately cause the browser - // to leak pretty fast wherever creates cyclical garbage, but it's - // probably a better user experience than crashing. Besides, we - // *should* never hit a fault. - - sCollector->mParams.mDoNothing = true; - - // Report to observers off an event so we don't run JS under GC - // (which is where we might be right now). - nsCOMPtr ev = new CCRunnableFaultReport(str); - NS_DispatchToMainThread(ev); -} - -#ifdef DEBUG_CC -static void -Fault(const char *msg, PtrInfo *pi) -{ - printf("Fault in cycle collector: %s\n" - " while operating on pointer %p %s\n", - msg, pi->mPointer, pi->mName); - if (pi->mInternalRefs) { - printf(" which has internal references from:\n"); - NodePool::Enumerator queue(sCollector->mGraph.mNodes); - while (!queue.IsDone()) { - PtrInfo *ppi = queue.GetNext(); - for (EdgePool::Iterator e = ppi->FirstChild(), - e_end = ppi->LastChild(); - e != e_end; ++e) { - if (*e == pi) { - printf(" %p %s\n", ppi->mPointer, ppi->mName); - } - } - } - } - - Fault(msg, pi->mPointer); -} -#else -inline void -Fault(const char *msg, PtrInfo *pi) -{ - Fault(msg, pi->mPointer); -} -#endif - -static inline void -AbortIfOffMainThreadIfCheckFast() -{ -#if defined(XP_WIN) || defined(NS_TLS) - if (!NS_IsMainThread() && !NS_IsCycleCollectorThread()) { - NS_RUNTIMEABORT("Main-thread-only object used off the main thread"); - } -#endif -} - -static nsISupports * -canonicalize(nsISupports *in) -{ - nsISupports* child; - in->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), - reinterpret_cast(&child)); - return child; -} - -static inline void -ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp) -{ - // We use QI to move from an nsISupports to an - // nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper - // object that implements traversal and unlinking logic for the nsISupports - // in question. - CallQueryInterface(s, cp); -#ifdef DEBUG_CC - if (cp) - ++sCollector->mStats.mSuccessfulQI; - else - ++sCollector->mStats.mFailedQI; -#endif -} - -nsCycleCollectionParticipant * -nsCycleCollectionXPCOMRuntime::ToParticipant(void *p) -{ - nsXPCOMCycleCollectionParticipant *cp; - ::ToParticipant(static_cast(p), &cp); - return cp; -} - - -template -void -GraphWalker::Walk(PtrInfo *s0) -{ - nsDeque queue; - queue.Push(s0); - DoWalk(queue); -} - -template -void -GraphWalker::WalkFromRoots(GCGraph& aGraph) -{ - nsDeque queue; - NodePool::Enumerator etor(aGraph.mNodes); - for (PRUint32 i = 0; i < aGraph.mRootCount; ++i) { - queue.Push(etor.GetNext()); - } - DoWalk(queue); -} - -template -void -GraphWalker::DoWalk(nsDeque &aQueue) -{ - // Use a aQueue to match the breadth-first traversal used when we - // built the graph, for hopefully-better locality. - while (aQueue.GetSize() > 0) { - PtrInfo *pi = static_cast(aQueue.PopFront()); - - if (mVisitor.ShouldVisitNode(pi)) { - mVisitor.VisitNode(pi); - for (EdgePool::Iterator child = pi->FirstChild(), - child_end = pi->LastChild(); - child != child_end; ++child) { - aQueue.Push(*child); - } - } - }; - -#ifdef DEBUG_CC - sCollector->mStats.mWalkedGraph++; -#endif -} - - -class nsCycleCollectorLogger : public nsICycleCollectorListener -{ -public: - nsCycleCollectorLogger() : mStream(nsnull) - { - } - ~nsCycleCollectorLogger() - { - if (mStream) { - fclose(mStream); - } - } - NS_DECL_ISUPPORTS - - NS_IMETHOD Begin() - { - char name[255]; - sprintf(name, "cc-edges-%d.%d.log", ++gLogCounter, base::GetCurrentProcId()); - mStream = fopen(name, "w"); - - return mStream ? NS_OK : NS_ERROR_FAILURE; - } - NS_IMETHOD NoteRefCountedObject(PRUint64 aAddress, PRUint32 refCount, - const char *aObjectDescription) - { - fprintf(mStream, "%p [rc=%u] %s\n", (void*)aAddress, refCount, - aObjectDescription); - - return NS_OK; - } - NS_IMETHOD NoteGCedObject(PRUint64 aAddress, bool aMarked, - const char *aObjectDescription) - { - fprintf(mStream, "%p [gc%s] %s\n", (void*)aAddress, - aMarked ? ".marked" : "", aObjectDescription); - - return NS_OK; - } - NS_IMETHOD NoteEdge(PRUint64 aToAddress, const char *aEdgeName) - { - fprintf(mStream, "> %p %s\n", (void*)aToAddress, aEdgeName); - - return NS_OK; - } - NS_IMETHOD BeginResults() - { - fputs("==========\n", mStream); - - return NS_OK; - } - NS_IMETHOD DescribeRoot(PRUint64 aAddress, PRUint32 aKnownEdges) - { - fprintf(mStream, "%p [known=%u]\n", (void*)aAddress, aKnownEdges); - - return NS_OK; - } - NS_IMETHOD DescribeGarbage(PRUint64 aAddress) - { - fprintf(mStream, "%p [garbage]\n", (void*)aAddress); - - return NS_OK; - } - NS_IMETHOD End() - { - fclose(mStream); - mStream = nsnull; - - return NS_OK; - } - -private: - FILE *mStream; - - static PRUint32 gLogCounter; -}; - -NS_IMPL_ISUPPORTS1(nsCycleCollectorLogger, nsICycleCollectorListener) - -PRUint32 nsCycleCollectorLogger::gLogCounter = 0; - -nsresult -nsCycleCollectorLoggerConstructor(nsISupports* aOuter, - const nsIID& aIID, - void* *aInstancePtr) -{ - NS_ENSURE_TRUE(!aOuter, NS_ERROR_NO_AGGREGATION); - - nsISupports *logger = new nsCycleCollectorLogger(); - - return logger->QueryInterface(aIID, aInstancePtr); -} - -//////////////////////////////////////////////////////////////////////// -// Bacon & Rajan's |MarkRoots| routine. -//////////////////////////////////////////////////////////////////////// - -struct PtrToNodeEntry : public PLDHashEntryHdr -{ - // The key is mNode->mPointer - PtrInfo *mNode; -}; - -static bool -PtrToNodeMatchEntry(PLDHashTable *table, - const PLDHashEntryHdr *entry, - const void *key) -{ - const PtrToNodeEntry *n = static_cast(entry); - return n->mNode->mPointer == key; -} - -static PLDHashTableOps PtrNodeOps = { - PL_DHashAllocTable, - PL_DHashFreeTable, - PL_DHashVoidPtrKeyStub, - PtrToNodeMatchEntry, - PL_DHashMoveEntryStub, - PL_DHashClearEntryStub, - PL_DHashFinalizeStub, - nsnull -}; - -class GCGraphBuilder : public nsCycleCollectionTraversalCallback -{ -private: - NodePool::Builder mNodeBuilder; - EdgePool::Builder mEdgeBuilder; - nsTArray &mWeakMaps; - PLDHashTable mPtrToNodeMap; - PtrInfo *mCurrPi; - nsCycleCollectionLanguageRuntime **mRuntimes; // weak, from nsCycleCollector - nsCString mNextEdgeName; - nsICycleCollectorListener *mListener; - -public: - GCGraphBuilder(GCGraph &aGraph, - nsCycleCollectionLanguageRuntime **aRuntimes, - nsICycleCollectorListener *aListener); - ~GCGraphBuilder(); - bool Initialized(); - - PRUint32 Count() const { return mPtrToNodeMap.entryCount; } - -#ifdef DEBUG_CC - PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant, - PRUint32 aLangID); -#else - PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant); - PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant, - PRUint32 aLangID) - { - return AddNode(s, aParticipant); - } -#endif - PtrInfo* AddWeakMapNode(void* node); - void Traverse(PtrInfo* aPtrInfo); - void SetLastChild(); - - // nsCycleCollectionTraversalCallback methods. - NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports *root); - -private: - void DescribeNode(PRUint32 refCount, - size_t objSz, - const char *objName) - { - mCurrPi->mRefCount = refCount; -#ifdef DEBUG_CC - mCurrPi->mBytes = objSz; - mCurrPi->mName = PL_strdup(objName); - sCollector->mStats.mVisitedNode++; -#endif - } - - NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refCount, size_t objSz, - const char *objName); - NS_IMETHOD_(void) DescribeGCedNode(bool isMarked, size_t objSz, - const char *objName); - NS_IMETHOD_(void) NoteRoot(PRUint32 langID, void *child, - nsCycleCollectionParticipant* participant); - NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child); - NS_IMETHOD_(void) NoteNativeChild(void *child, - nsCycleCollectionParticipant *participant); - NS_IMETHOD_(void) NoteScriptChild(PRUint32 langID, void *child); - NS_IMETHOD_(void) NoteNextEdgeName(const char* name); - NS_IMETHOD_(void) NoteWeakMapping(void *map, void *key, void *val); -}; - -GCGraphBuilder::GCGraphBuilder(GCGraph &aGraph, - nsCycleCollectionLanguageRuntime **aRuntimes, - nsICycleCollectorListener *aListener) - : mNodeBuilder(aGraph.mNodes), - mEdgeBuilder(aGraph.mEdges), - mWeakMaps(aGraph.mWeakMaps), - mRuntimes(aRuntimes), - mListener(aListener) -{ - if (!PL_DHashTableInit(&mPtrToNodeMap, &PtrNodeOps, nsnull, - sizeof(PtrToNodeEntry), 32768)) - mPtrToNodeMap.ops = nsnull; - // We want all edges and all info if DEBUG_CC is set or if we have a - // listener. Do we want them all the time? -#ifndef DEBUG_CC - if (mListener) -#endif - { - mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO | - nsCycleCollectionTraversalCallback::WANT_ALL_TRACES; - } -} - -GCGraphBuilder::~GCGraphBuilder() -{ - if (mPtrToNodeMap.ops) - PL_DHashTableFinish(&mPtrToNodeMap); -} - -bool -GCGraphBuilder::Initialized() -{ - return !!mPtrToNodeMap.ops; -} - -PtrInfo* -GCGraphBuilder::AddNode(void *s, nsCycleCollectionParticipant *aParticipant - IF_DEBUG_CC_PARAM(PRUint32 aLangID) - ) -{ - PtrToNodeEntry *e = static_cast(PL_DHashTableOperate(&mPtrToNodeMap, s, PL_DHASH_ADD)); - if (!e) - return nsnull; - - PtrInfo *result; - if (!e->mNode) { - // New entry. - result = mNodeBuilder.Add(s, aParticipant - IF_DEBUG_CC_PARAM(aLangID) - ); - if (!result) { - PL_DHashTableRawRemove(&mPtrToNodeMap, e); - return nsnull; - } - e->mNode = result; - } else { - result = e->mNode; - NS_ASSERTION(result->mParticipant == aParticipant, - "nsCycleCollectionParticipant shouldn't change!"); - } - return result; -} - -void -GCGraphBuilder::Traverse(PtrInfo* aPtrInfo) -{ - mCurrPi = aPtrInfo; - -#ifdef DEBUG_CC - if (!mCurrPi->mParticipant) { - Fault("unknown pointer during walk", aPtrInfo); - return; - } -#endif - - mCurrPi->SetFirstChild(mEdgeBuilder.Mark()); - - nsresult rv = aPtrInfo->mParticipant->Traverse(aPtrInfo->mPointer, *this); - if (NS_FAILED(rv)) { - Fault("script pointer traversal failed", aPtrInfo); - } -} - -void -GCGraphBuilder::SetLastChild() -{ - mCurrPi->SetLastChild(mEdgeBuilder.Mark()); -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteXPCOMRoot(nsISupports *root) -{ - root = canonicalize(root); - NS_ASSERTION(root, - "Don't add objects that don't participate in collection!"); - -#ifdef DEBUG_CC - if (nsCycleCollector_shouldSuppress(root)) - return; -#endif - - nsXPCOMCycleCollectionParticipant *cp; - ToParticipant(root, &cp); - - NoteRoot(nsIProgrammingLanguage::CPLUSPLUS, root, cp); -} - - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteRoot(PRUint32 langID, void *root, - nsCycleCollectionParticipant* participant) -{ - NS_ASSERTION(root, "Don't add a null root!"); - - if (langID > nsIProgrammingLanguage::MAX || !mRuntimes[langID]) { - Fault("adding root for unregistered language", root); - return; - } - - AddNode(root, participant, langID); -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::DescribeRefCountedNode(nsrefcnt refCount, size_t objSz, - const char *objName) -{ - if (refCount == 0) - Fault("zero refcount", mCurrPi); - if (refCount == PR_UINT32_MAX) - Fault("overflowing refcount", mCurrPi); - sCollector->mVisitedRefCounted++; - - if (mListener) { - mListener->NoteRefCountedObject((PRUint64)mCurrPi->mPointer, refCount, - objName); - } - - DescribeNode(refCount, objSz, objName); -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::DescribeGCedNode(bool isMarked, size_t objSz, - const char *objName) -{ - PRUint32 refCount = isMarked ? PR_UINT32_MAX : 0; - sCollector->mVisitedGCed++; - - if (mListener) { - mListener->NoteGCedObject((PRUint64)mCurrPi->mPointer, isMarked, - objName); - } - - DescribeNode(refCount, objSz, objName); -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteXPCOMChild(nsISupports *child) -{ - nsCString edgeName; - if (WantDebugInfo()) { - edgeName.Assign(mNextEdgeName); - mNextEdgeName.Truncate(); - } - if (!child || !(child = canonicalize(child))) - return; - -#ifdef DEBUG_CC - if (nsCycleCollector_shouldSuppress(child)) - return; -#endif - - nsXPCOMCycleCollectionParticipant *cp; - ToParticipant(child, &cp); - if (cp) { - PtrInfo *childPi = AddNode(child, cp, nsIProgrammingLanguage::CPLUSPLUS); - if (!childPi) - return; - mEdgeBuilder.Add(childPi); -#ifdef DEBUG_CC - mCurrPi->mEdgeNames.AppendElement(edgeName); -#endif - if (mListener) { - mListener->NoteEdge((PRUint64)child, edgeName.get()); - } - ++childPi->mInternalRefs; - } -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteNativeChild(void *child, - nsCycleCollectionParticipant *participant) -{ - nsCString edgeName; - if (WantDebugInfo()) { - edgeName.Assign(mNextEdgeName); - mNextEdgeName.Truncate(); - } - if (!child) - return; - - NS_ASSERTION(participant, "Need a nsCycleCollectionParticipant!"); - - PtrInfo *childPi = AddNode(child, participant, nsIProgrammingLanguage::CPLUSPLUS); - if (!childPi) - return; - mEdgeBuilder.Add(childPi); -#ifdef DEBUG_CC - mCurrPi->mEdgeNames.AppendElement(edgeName); -#endif - if (mListener) { - mListener->NoteEdge((PRUint64)child, edgeName.get()); - } - ++childPi->mInternalRefs; -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteScriptChild(PRUint32 langID, void *child) -{ - nsCString edgeName; - if (WantDebugInfo()) { - edgeName.Assign(mNextEdgeName); - mNextEdgeName.Truncate(); - } - if (!child) - return; - - if (langID > nsIProgrammingLanguage::MAX) { - Fault("traversing pointer for unknown language", child); - return; - } - - if (!mRuntimes[langID]) { - NS_WARNING("Not collecting cycles involving objects for scripting " - "languages that don't participate in cycle collection."); - return; - } - - // skip over non-grey JS children - if (langID == nsIProgrammingLanguage::JAVASCRIPT && - !xpc_GCThingIsGrayCCThing(child) && !WantAllTraces()) { - return; - } - - nsCycleCollectionParticipant *cp = mRuntimes[langID]->ToParticipant(child); - if (!cp) - return; - - PtrInfo *childPi = AddNode(child, cp, langID); - if (!childPi) - return; - mEdgeBuilder.Add(childPi); -#ifdef DEBUG_CC - mCurrPi->mEdgeNames.AppendElement(edgeName); -#endif - if (mListener) { - mListener->NoteEdge((PRUint64)child, edgeName.get()); - } - ++childPi->mInternalRefs; -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteNextEdgeName(const char* name) -{ - if (WantDebugInfo()) { - mNextEdgeName = name; - } -} - -PtrInfo* -GCGraphBuilder::AddWeakMapNode(void *node) -{ - nsCycleCollectionParticipant *cp; - NS_ASSERTION(node, "Weak map node should be non-null."); - - if (!xpc_GCThingIsGrayCCThing(node) && !WantAllTraces()) - return nsnull; - - cp = mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]->ToParticipant(node); - NS_ASSERTION(cp, "Javascript runtime participant should be non-null."); - return AddNode(node, cp); -} - -NS_IMETHODIMP_(void) -GCGraphBuilder::NoteWeakMapping(void *map, void *key, void *val) -{ - PtrInfo *valNode = AddWeakMapNode(val); - - if (!valNode) - return; - - WeakMapping *mapping = mWeakMaps.AppendElement(); - mapping->mMap = map ? AddWeakMapNode(map) : nsnull; - mapping->mKey = key ? AddWeakMapNode(key) : nsnull; - mapping->mVal = valNode; -} - -static bool -AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root) -{ - root = canonicalize(root); - NS_ASSERTION(root, - "Don't add objects that don't participate in collection!"); - - nsXPCOMCycleCollectionParticipant *cp; - ToParticipant(root, &cp); - - PtrInfo *pinfo = builder.AddNode(root, cp, - nsIProgrammingLanguage::CPLUSPLUS); - if (!pinfo) { - return false; - } - - cp->UnmarkPurple(root); - - return true; -} - -#ifdef DEBUG_CC -static PLDHashOperator -noteAllCallback(nsVoidPtrHashKey* key, void* userArg) -{ - GCGraphBuilder *builder = static_cast(userArg); - builder->NoteXPCOMRoot( - static_cast(const_cast(key->GetKey()))); - return PL_DHASH_NEXT; -} - -void -nsPurpleBuffer::NoteAll(GCGraphBuilder &builder) -{ - mCompatObjects.EnumerateEntries(noteAllCallback, &builder); - - for (Block *b = &mFirstBlock; b; b = b->mNext) { - for (nsPurpleBufferEntry *e = b->mEntries, - *eEnd = ArrayEnd(b->mEntries); - e != eEnd; ++e) { - if (!(PRUword(e->mObject) & PRUword(1)) && e->mObject) { - builder.NoteXPCOMRoot(e->mObject); - } - } - } -} -#endif - -void -nsCycleCollector::SelectPurple(GCGraphBuilder &builder) -{ - mPurpleBuf.SelectPointers(builder); -} - -void -nsCycleCollector::MarkRoots(GCGraphBuilder &builder) -{ - mGraph.mRootCount = builder.Count(); - - // read the PtrInfo out of the graph that we are building - NodePool::Enumerator queue(mGraph.mNodes); - while (!queue.IsDone()) { - PtrInfo *pi = queue.GetNext(); - builder.Traverse(pi); - if (queue.AtBlockEnd()) - builder.SetLastChild(); - } - if (mGraph.mRootCount > 0) - builder.SetLastChild(); -} - - -//////////////////////////////////////////////////////////////////////// -// Bacon & Rajan's |ScanRoots| routine. -//////////////////////////////////////////////////////////////////////// - - -struct ScanBlackVisitor -{ - ScanBlackVisitor(PRUint32 &aWhiteNodeCount) - : mWhiteNodeCount(aWhiteNodeCount) - { - } - - bool ShouldVisitNode(PtrInfo const *pi) - { - return pi->mColor != black; - } - - void VisitNode(PtrInfo *pi) - { - if (pi->mColor == white) - --mWhiteNodeCount; - pi->mColor = black; -#ifdef DEBUG_CC - sCollector->mStats.mSetColorBlack++; -#endif - } - - PRUint32 &mWhiteNodeCount; -}; - - -struct scanVisitor -{ - scanVisitor(PRUint32 &aWhiteNodeCount) : mWhiteNodeCount(aWhiteNodeCount) - { - } - - bool ShouldVisitNode(PtrInfo const *pi) - { - return pi->mColor == grey; - } - - void VisitNode(PtrInfo *pi) - { - if (pi->mInternalRefs > pi->mRefCount && pi->mRefCount > 0) - Fault("traversed refs exceed refcount", pi); - - if (pi->mInternalRefs == pi->mRefCount || pi->mRefCount == 0) { - pi->mColor = white; - ++mWhiteNodeCount; -#ifdef DEBUG_CC - sCollector->mStats.mSetColorWhite++; -#endif - } else { - GraphWalker(ScanBlackVisitor(mWhiteNodeCount)).Walk(pi); - NS_ASSERTION(pi->mColor == black, - "Why didn't ScanBlackVisitor make pi black?"); - } - } - - PRUint32 &mWhiteNodeCount; -}; - -// Iterate over the WeakMaps. If we mark anything while iterating -// over the WeakMaps, we must iterate over all of the WeakMaps again. -void -nsCycleCollector::ScanWeakMaps() -{ - bool anyChanged; - do { - anyChanged = false; - for (PRUint32 i = 0; i < mGraph.mWeakMaps.Length(); i++) { - WeakMapping *wm = &mGraph.mWeakMaps[i]; - - // If mMap or mKey are null, the original object was marked black. - uint32 mColor = wm->mMap ? wm->mMap->mColor : black; - uint32 kColor = wm->mKey ? wm->mKey->mColor : black; - PtrInfo *v = wm->mVal; - - // All non-null weak mapping maps, keys and values are - // roots (in the sense of WalkFromRoots) in the cycle - // collector graph, and thus should have been colored - // either black or white in ScanRoots(). - NS_ASSERTION(mColor != grey, "Uncolored weak map"); - NS_ASSERTION(kColor != grey, "Uncolored weak map key"); - NS_ASSERTION(v->mColor != grey, "Uncolored weak map value"); - - if (mColor == black && kColor == black && v->mColor != black) { - GraphWalker(ScanBlackVisitor(mWhiteNodeCount)).Walk(v); - anyChanged = true; - } - } - } while (anyChanged); -} - -void -nsCycleCollector::ScanRoots() -{ - mWhiteNodeCount = 0; - - // On the assumption that most nodes will be black, it's - // probably faster to use a GraphWalker than a - // NodePool::Enumerator. - GraphWalker(scanVisitor(mWhiteNodeCount)).WalkFromRoots(mGraph); - - ScanWeakMaps(); - -#ifdef DEBUG_CC - // Sanity check: scan should have colored all grey nodes black or - // white. So we ensure we have no grey nodes at this point. - NodePool::Enumerator etor(mGraph.mNodes); - while (!etor.IsDone()) - { - PtrInfo *pinfo = etor.GetNext(); - if (pinfo->mColor == grey) { - Fault("valid grey node after scanning", pinfo); - } - } -#endif -} - - -//////////////////////////////////////////////////////////////////////// -// Bacon & Rajan's |CollectWhite| routine, somewhat modified. -//////////////////////////////////////////////////////////////////////// - -bool -nsCycleCollector::CollectWhite(nsICycleCollectorListener *aListener) -{ - // Explanation of "somewhat modified": we have no way to collect the - // set of whites "all at once", we have to ask each of them to drop - // their outgoing links and assume this will cause the garbage cycle - // to *mostly* self-destruct (except for the reference we continue - // to hold). - // - // To do this "safely" we must make sure that the white nodes we're - // operating on are stable for the duration of our operation. So we - // make 3 sets of calls to language runtimes: - // - // - Root(whites), which should pin the whites in memory. - // - Unlink(whites), which drops outgoing links on each white. - // - Unroot(whites), which returns the whites to normal GC. - - nsresult rv; - - NS_ASSERTION(mWhiteNodes->IsEmpty(), - "FinishCollection wasn't called?"); - - mWhiteNodes->SetCapacity(mWhiteNodeCount); - - NodePool::Enumerator etor(mGraph.mNodes); - while (!etor.IsDone()) - { - PtrInfo *pinfo = etor.GetNext(); - if (pinfo->mColor == white && mWhiteNodes->AppendElement(pinfo)) { - rv = pinfo->mParticipant->Root(pinfo->mPointer); - if (NS_FAILED(rv)) { - Fault("Failed root call while unlinking", pinfo); - mWhiteNodes->RemoveElementAt(mWhiteNodes->Length() - 1); - } - } - } - -#if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32) - struct _CrtMemState ms1, ms2; - _CrtMemCheckpoint(&ms1); -#endif - - PRUint32 i, count = mWhiteNodes->Length(); - - if (aListener) { - for (i = 0; i < count; ++i) { - PtrInfo *pinfo = mWhiteNodes->ElementAt(i); - aListener->DescribeGarbage((PRUint64)pinfo->mPointer); - } - aListener->End(); - } - - for (i = 0; i < count; ++i) { - PtrInfo *pinfo = mWhiteNodes->ElementAt(i); - rv = pinfo->mParticipant->Unlink(pinfo->mPointer); - if (NS_FAILED(rv)) { - Fault("Failed unlink call while unlinking", pinfo); -#ifdef DEBUG_CC - mStats.mFailedUnlink++; -#endif - } - else { -#ifdef DEBUG_CC - ++mStats.mCollectedNode; -#endif - } - } - - for (i = 0; i < count; ++i) { - PtrInfo *pinfo = mWhiteNodes->ElementAt(i); - rv = pinfo->mParticipant->Unroot(pinfo->mPointer); - if (NS_FAILED(rv)) - Fault("Failed unroot call while unlinking", pinfo); - } - -#if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32) - _CrtMemCheckpoint(&ms2); - if (ms2.lTotalCount < ms1.lTotalCount) - mStats.mFreedBytes += (ms1.lTotalCount - ms2.lTotalCount); -#endif - - mCollectedObjects += count; - return count > 0; -} - - -#ifdef DEBUG_CC -//////////////////////////////////////////////////////////////////////// -// Memory-hooking stuff -// When debugging wild pointers, it sometimes helps to hook malloc and -// free. This stuff is disabled unless you set an environment variable. -//////////////////////////////////////////////////////////////////////// - -static bool hookedMalloc = false; - -#if defined(__GLIBC__) && !defined(__UCLIBC__) -#include - -static void* (*old_memalign_hook)(size_t, size_t, const void *); -static void* (*old_realloc_hook)(void *, size_t, const void *); -static void* (*old_malloc_hook)(size_t, const void *); -static void (*old_free_hook)(void *, const void *); - -static void* my_memalign_hook(size_t, size_t, const void *); -static void* my_realloc_hook(void *, size_t, const void *); -static void* my_malloc_hook(size_t, const void *); -static void my_free_hook(void *, const void *); - -static inline void -install_old_hooks() -{ - __memalign_hook = old_memalign_hook; - __realloc_hook = old_realloc_hook; - __malloc_hook = old_malloc_hook; - __free_hook = old_free_hook; -} - -static inline void -save_old_hooks() -{ - // Glibc docs recommend re-saving old hooks on - // return from recursive calls. Strangely when - // we do this, we find ourselves in infinite - // recursion. - - // old_memalign_hook = __memalign_hook; - // old_realloc_hook = __realloc_hook; - // old_malloc_hook = __malloc_hook; - // old_free_hook = __free_hook; -} - -static inline void -install_new_hooks() -{ - __memalign_hook = my_memalign_hook; - __realloc_hook = my_realloc_hook; - __malloc_hook = my_malloc_hook; - __free_hook = my_free_hook; -} - -static void* -my_realloc_hook(void *ptr, size_t size, const void *caller) -{ - void *result; - - install_old_hooks(); - result = realloc(ptr, size); - save_old_hooks(); - - if (sCollector) { - sCollector->Freed(ptr); - sCollector->Allocated(result, size); - } - - install_new_hooks(); - - return result; -} - - -static void* -my_memalign_hook(size_t size, size_t alignment, const void *caller) -{ - void *result; - - install_old_hooks(); - result = memalign(size, alignment); - save_old_hooks(); - - if (sCollector) - sCollector->Allocated(result, size); - - install_new_hooks(); - - return result; -} - - -static void -my_free_hook (void *ptr, const void *caller) -{ - install_old_hooks(); - free(ptr); - save_old_hooks(); - - if (sCollector) - sCollector->Freed(ptr); - - install_new_hooks(); -} - - -static void* -my_malloc_hook (size_t size, const void *caller) -{ - void *result; - - install_old_hooks(); - result = malloc (size); - save_old_hooks(); - - if (sCollector) - sCollector->Allocated(result, size); - - install_new_hooks(); - - return result; -} - - -static void -InitMemHook(void) -{ - if (!hookedMalloc) { - save_old_hooks(); - install_new_hooks(); - hookedMalloc = true; - } -} - -#elif defined(WIN32) -#ifndef __MINGW32__ - -static int -AllocHook(int allocType, void *userData, size_t size, int - blockType, long requestNumber, const unsigned char *filename, int - lineNumber) -{ - if (allocType == _HOOK_FREE) - sCollector->Freed(userData); - return 1; -} - - -static void InitMemHook(void) -{ - if (!hookedMalloc) { - _CrtSetAllocHook (AllocHook); - hookedMalloc = true; - } -} -#endif // __MINGW32__ - -#elif 0 // defined(XP_MACOSX) - -#include - -static void (*old_free)(struct _malloc_zone_t *zone, void *ptr); - -static void -freehook(struct _malloc_zone_t *zone, void *ptr) -{ - if (sCollector) - sCollector->Freed(ptr); - old_free(zone, ptr); -} - - -static void -InitMemHook(void) -{ - if (!hookedMalloc) { - malloc_zone_t *default_zone = malloc_default_zone(); - old_free = default_zone->free; - default_zone->free = freehook; - hookedMalloc = true; - } -} - - -#else - -static void -InitMemHook(void) -{ -} - -#endif // GLIBC / WIN32 / OSX -#endif // DEBUG_CC - -//////////////////////////////////////////////////////////////////////// -// Collector implementation -//////////////////////////////////////////////////////////////////////// - -nsCycleCollector::nsCycleCollector() : - mCollectionInProgress(false), - mScanInProgress(false), - mCollectedObjects(0), - mWhiteNodes(nsnull), - mWhiteNodeCount(0), - mVisitedRefCounted(0), - mVisitedGCed(0), -#ifdef DEBUG_CC - mPurpleBuf(mParams, mStats), - mPtrLog(nsnull) -#else - mPurpleBuf(mParams) -#endif -{ -#ifdef DEBUG_CC - mExpectedGarbage.Init(); -#endif - - memset(mRuntimes, 0, sizeof(mRuntimes)); - mRuntimes[nsIProgrammingLanguage::CPLUSPLUS] = &mXPCOMRuntime; -} - - -nsCycleCollector::~nsCycleCollector() -{ -} - - -void -nsCycleCollector::RegisterRuntime(PRUint32 langID, - nsCycleCollectionLanguageRuntime *rt) -{ - if (mParams.mDoNothing) - return; - - if (langID > nsIProgrammingLanguage::MAX) - Fault("unknown language runtime in registration"); - - if (mRuntimes[langID]) - Fault("multiple registrations of language runtime", rt); - - mRuntimes[langID] = rt; -} - -nsCycleCollectionLanguageRuntime * -nsCycleCollector::GetRuntime(PRUint32 langID) -{ - if (langID > nsIProgrammingLanguage::MAX) - return nsnull; - - return mRuntimes[langID]; -} - -void -nsCycleCollector::ForgetRuntime(PRUint32 langID) -{ - if (mParams.mDoNothing) - return; - - if (langID > nsIProgrammingLanguage::MAX) - Fault("unknown language runtime in deregistration"); - - if (! mRuntimes[langID]) - Fault("forgetting non-registered language runtime"); - - mRuntimes[langID] = nsnull; -} - -#ifdef DEBUG_CC - -class Suppressor : - public nsCycleCollectionTraversalCallback -{ -protected: - static char *sSuppressionList; - static bool sInitialized; - bool mSuppressThisNode; -public: - Suppressor() - { - } - - bool shouldSuppress(nsISupports *s) - { - if (!sInitialized) { - sSuppressionList = PR_GetEnv("XPCOM_CC_SUPPRESS"); - sInitialized = true; - } - if (sSuppressionList == nsnull) { - mSuppressThisNode = false; - } else { - nsresult rv; - nsXPCOMCycleCollectionParticipant *cp; - rv = CallQueryInterface(s, &cp); - if (NS_FAILED(rv)) { - Fault("checking suppression on wrong type of pointer", s); - return true; - } - cp->Traverse(s, *this); - } - return mSuppressThisNode; - } - - NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refCount, size_t objSz, - const char *objName) - { - mSuppressThisNode = (PL_strstr(sSuppressionList, objName) != nsnull); - } - - NS_IMETHOD_(void) DescribeGCedNode(bool isMarked, size_t objSz, - const char *objName) - { - mSuppressThisNode = (PL_strstr(sSuppressionList, objName) != nsnull); - } - - NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports *root) {}; - NS_IMETHOD_(void) NoteRoot(PRUint32 langID, void *root, - nsCycleCollectionParticipant* participant) {}; - NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child) {} - NS_IMETHOD_(void) NoteScriptChild(PRUint32 langID, void *child) {} - NS_IMETHOD_(void) NoteNativeChild(void *child, - nsCycleCollectionParticipant *participant) {} - NS_IMETHOD_(void) NoteNextEdgeName(const char* name) {} - NS_IMETHOD_(void) NoteWeakMapping(void *map, void *key, void *val) {} -}; - -char *Suppressor::sSuppressionList = nsnull; -bool Suppressor::sInitialized = false; - -static bool -nsCycleCollector_shouldSuppress(nsISupports *s) -{ - Suppressor supp; - return supp.shouldSuppress(s); -} -#endif - -#ifdef DEBUG -static bool -nsCycleCollector_isScanSafe(nsISupports *s) -{ - if (!s) - return false; - - nsXPCOMCycleCollectionParticipant *cp; - ToParticipant(s, &cp); - - return cp != nsnull; -} -#endif - -bool -nsCycleCollector::Suspect(nsISupports *n) -{ - AbortIfOffMainThreadIfCheckFast(); - - // Re-entering ::Suspect during collection used to be a fault, but - // we are canonicalizing nsISupports pointers using QI, so we will - // see some spurious refcount traffic here. - - if (mScanInProgress) - return false; - - NS_ASSERTION(nsCycleCollector_isScanSafe(n), - "suspected a non-scansafe pointer"); - - if (mParams.mDoNothing) - return false; - -#ifdef DEBUG_CC - mStats.mSuspectNode++; - - if (nsCycleCollector_shouldSuppress(n)) - return false; - -#ifndef __MINGW32__ - if (mParams.mHookMalloc) - InitMemHook(); -#endif - - if (mParams.mLogPointers) { - if (!mPtrLog) - mPtrLog = fopen("pointer_log", "w"); - fprintf(mPtrLog, "S %p\n", static_cast(n)); - } -#endif - - return mPurpleBuf.PutCompatObject(n); -} - - -bool -nsCycleCollector::Forget(nsISupports *n) -{ - AbortIfOffMainThreadIfCheckFast(); - - // Re-entering ::Forget during collection used to be a fault, but - // we are canonicalizing nsISupports pointers using QI, so we will - // see some spurious refcount traffic here. - - if (mScanInProgress) - return false; - - if (mParams.mDoNothing) - return true; // it's as good as forgotten - -#ifdef DEBUG_CC - mStats.mForgetNode++; - -#ifndef __MINGW32__ - if (mParams.mHookMalloc) - InitMemHook(); -#endif - - if (mParams.mLogPointers) { - if (!mPtrLog) - mPtrLog = fopen("pointer_log", "w"); - fprintf(mPtrLog, "F %p\n", static_cast(n)); - } -#endif - - mPurpleBuf.RemoveCompatObject(n); - return true; -} - -nsPurpleBufferEntry* -nsCycleCollector::Suspect2(nsISupports *n) -{ - AbortIfOffMainThreadIfCheckFast(); - - // Re-entering ::Suspect during collection used to be a fault, but - // we are canonicalizing nsISupports pointers using QI, so we will - // see some spurious refcount traffic here. - - if (mScanInProgress) - return nsnull; - - NS_ASSERTION(nsCycleCollector_isScanSafe(n), - "suspected a non-scansafe pointer"); - - if (mParams.mDoNothing) - return nsnull; - -#ifdef DEBUG_CC - mStats.mSuspectNode++; - - if (nsCycleCollector_shouldSuppress(n)) - return nsnull; - -#ifndef __MINGW32__ - if (mParams.mHookMalloc) - InitMemHook(); -#endif - - if (mParams.mLogPointers) { - if (!mPtrLog) - mPtrLog = fopen("pointer_log", "w"); - fprintf(mPtrLog, "S %p\n", static_cast(n)); - } -#endif - - // Caller is responsible for filling in result's mRefCnt. - return mPurpleBuf.Put(n); -} - - -bool -nsCycleCollector::Forget2(nsPurpleBufferEntry *e) -{ - AbortIfOffMainThreadIfCheckFast(); - - // Re-entering ::Forget during collection used to be a fault, but - // we are canonicalizing nsISupports pointers using QI, so we will - // see some spurious refcount traffic here. - - if (mScanInProgress) - return false; - -#ifdef DEBUG_CC - mStats.mForgetNode++; - -#ifndef __MINGW32__ - if (mParams.mHookMalloc) - InitMemHook(); -#endif - - if (mParams.mLogPointers) { - if (!mPtrLog) - mPtrLog = fopen("pointer_log", "w"); - fprintf(mPtrLog, "F %p\n", static_cast(e->mObject)); - } -#endif - - mPurpleBuf.Remove(e); - return true; -} - -#ifdef DEBUG_CC -void -nsCycleCollector::Allocated(void *n, size_t sz) -{ -} - -void -nsCycleCollector::Freed(void *n) -{ - mStats.mFreeCalls++; - - if (!n) { - // Ignore null pointers coming through - return; - } - - if (mPurpleBuf.Exists(n)) { - mStats.mForgetNode++; - mStats.mFreedWhilePurple++; - Fault("freed while purple", n); - - if (mParams.mLogPointers) { - if (!mPtrLog) - mPtrLog = fopen("pointer_log", "w"); - fprintf(mPtrLog, "R %p\n", n); - } - } -} -#endif - -// The cycle collector uses the mark bitmap to discover what JS objects -// were reachable only from XPConnect roots that might participate in -// cycles. We ask the JS runtime whether we need to force a GC before -// this CC. It returns true on startup (before the mark bits have been set), -// and also when UnmarkGray has run out of stack. We also force GCs on shut -// down to collect cycles involving both DOM and JS. -void -nsCycleCollector::GCIfNeeded(bool aForceGC) -{ - NS_ASSERTION(NS_IsMainThread(), - "nsCycleCollector::GCIfNeeded() must be called on the main thread."); - - if (mParams.mDoNothing) - return; - - if (!mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]) - return; - - nsCycleCollectionJSRuntime* rt = - static_cast - (mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]); - if (!rt->NeedCollect() && !aForceGC) - return; - -#ifdef COLLECT_TIME_DEBUG - PRTime start = PR_Now(); -#endif - // rt->Collect() must be called from the main thread, - // because it invokes XPCJSRuntime::GCCallback(cx, JSGC_BEGIN) - // which returns false if not in the main thread. - rt->Collect(); -#ifdef COLLECT_TIME_DEBUG - printf("cc: GC() took %lldms\n", (PR_Now() - start) / PR_USEC_PER_MSEC); -#endif -} - -bool -nsCycleCollector::PrepareForCollection(nsTArray *aWhiteNodes) -{ -#if defined(DEBUG_CC) && !defined(__MINGW32__) - if (!mParams.mDoNothing && mParams.mHookMalloc) - InitMemHook(); -#endif - - // This can legitimately happen in a few cases. See bug 383651. - if (mCollectionInProgress) - return false; - - NS_TIME_FUNCTION; - -#ifdef COLLECT_TIME_DEBUG - printf("cc: nsCycleCollector::PrepareForCollection()\n"); -#endif - mCollectionStart = TimeStamp::Now(); - mVisitedRefCounted = 0; - mVisitedGCed = 0; - - mCollectionInProgress = true; - - nsCOMPtr obs = - mozilla::services::GetObserverService(); - if (obs) - obs->NotifyObservers(nsnull, "cycle-collector-begin", nsnull); - - mFollowupCollection = false; - mCollectedObjects = 0; - - mWhiteNodes = aWhiteNodes; - - return true; -} - -void -nsCycleCollector::CleanupAfterCollection() -{ - mWhiteNodes = nsnull; - mCollectionInProgress = false; - -#ifdef XP_OS2 - // Now that the cycle collector has freed some memory, we can try to - // force the C library to give back as much memory to the system as - // possible. - _heapmin(); -#endif - - PRUint32 interval((TimeStamp::Now() - mCollectionStart).ToMilliseconds()); -#ifdef COLLECT_TIME_DEBUG - printf("cc: CleanupAfterCollection(), total time %ums\n", interval); -#endif - Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR, interval); - Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_VISITED_REF_COUNTED, mVisitedRefCounted); - Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_VISITED_GCED, mVisitedGCed); - Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_COLLECTED, mWhiteNodeCount); - -#ifdef DEBUG_CC - ExplainLiveExpectedGarbage(); -#endif -} - -PRUint32 -nsCycleCollector::Collect(PRUint32 aTryCollections, - nsICycleCollectorListener *aListener) -{ - nsAutoTArray whiteNodes; - - if (!PrepareForCollection(&whiteNodes)) - return 0; - - PRUint32 totalCollections = 0; - while (aTryCollections > totalCollections) { - // Synchronous cycle collection. Always force a JS GC beforehand. - GCIfNeeded(true); - if (aListener && NS_FAILED(aListener->Begin())) - aListener = nsnull; - if (!(BeginCollection(aListener) && - FinishCollection(aListener))) - break; - - ++totalCollections; - } - - CleanupAfterCollection(); - - return mCollectedObjects; -} - -bool -nsCycleCollector::BeginCollection(nsICycleCollectorListener *aListener) -{ - // aListener should be Begin()'d before this - if (mParams.mDoNothing) - return false; - - GCGraphBuilder builder(mGraph, mRuntimes, aListener); - if (!builder.Initialized()) - return false; - -#ifdef COLLECT_TIME_DEBUG - PRTime now = PR_Now(); -#endif - for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { - if (mRuntimes[i]) - mRuntimes[i]->BeginCycleCollection(builder, false); - } - -#ifdef COLLECT_TIME_DEBUG - printf("cc: mRuntimes[*]->BeginCycleCollection() took %lldms\n", - (PR_Now() - now) / PR_USEC_PER_MSEC); - - now = PR_Now(); -#endif - -#ifdef DEBUG_CC - PRUint32 purpleStart = builder.Count(); -#endif - mScanInProgress = true; - SelectPurple(builder); -#ifdef DEBUG_CC - PRUint32 purpleEnd = builder.Count(); - - if (purpleStart != purpleEnd) { -#ifndef __MINGW32__ - if (mParams.mHookMalloc) - InitMemHook(); -#endif - if (mParams.mLogPointers && !mPtrLog) - mPtrLog = fopen("pointer_log", "w"); - - PRUint32 i = 0; - NodePool::Enumerator queue(mGraph.mNodes); - while (i++ < purpleStart) { - queue.GetNext(); - } - while (i++ < purpleEnd) { - mStats.mForgetNode++; - if (mParams.mLogPointers) - fprintf(mPtrLog, "F %p\n", queue.GetNext()->mPointer); - } - } -#endif - -#ifdef COLLECT_TIME_DEBUG - printf("cc: SelectPurple() took %lldms\n", - (PR_Now() - now) / PR_USEC_PER_MSEC); -#endif - - if (builder.Count() > 0) { - // The main Bacon & Rajan collection algorithm. - -#ifdef COLLECT_TIME_DEBUG - now = PR_Now(); -#endif - - MarkRoots(builder); - -#ifdef COLLECT_TIME_DEBUG - { - PRTime then = PR_Now(); - printf("cc: MarkRoots() took %lldms\n", - (then - now) / PR_USEC_PER_MSEC); - now = then; - } -#endif - - ScanRoots(); - -#ifdef COLLECT_TIME_DEBUG - printf("cc: ScanRoots() took %lldms\n", - (PR_Now() - now) / PR_USEC_PER_MSEC); -#endif - - mScanInProgress = false; - - if (aListener) { - aListener->BeginResults(); - - NodePool::Enumerator etor(mGraph.mNodes); - while (!etor.IsDone()) { - PtrInfo *pi = etor.GetNext(); - if (pi->mColor == black && - pi->mRefCount > 0 && pi->mRefCount < PR_UINT32_MAX && - pi->mInternalRefs != pi->mRefCount) { - aListener->DescribeRoot((PRUint64)pi->mPointer, - pi->mInternalRefs); - } - } - } - -#ifdef DEBUG_CC - if (mFollowupCollection && purpleStart != purpleEnd) { - PRUint32 i = 0; - NodePool::Enumerator queue(mGraph.mNodes); - while (i++ < purpleStart) { - queue.GetNext(); - } - while (i++ < purpleEnd) { - PtrInfo *pi = queue.GetNext(); - if (pi->mColor == white) { - printf("nsCycleCollector: a later shutdown collection collected the additional\n" - " suspect %p %s\n" - " (which could be fixed by improving traversal)\n", - pi->mPointer, pi->mName); - } - } - } -#endif - - for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { - if (mRuntimes[i]) - mRuntimes[i]->FinishTraverse(); - } - } - else { - mScanInProgress = false; - } - - return true; -} - -bool -nsCycleCollector::FinishCollection(nsICycleCollectorListener *aListener) -{ -#ifdef COLLECT_TIME_DEBUG - PRTime now = PR_Now(); -#endif - - bool collected = CollectWhite(aListener); - -#ifdef COLLECT_TIME_DEBUG - printf("cc: CollectWhite() took %lldms\n", - (PR_Now() - now) / PR_USEC_PER_MSEC); -#endif - -#ifdef DEBUG_CC - mStats.mCollection++; - if (mParams.mReportStats) - mStats.Dump(); -#endif - - for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { - if (mRuntimes[i]) - mRuntimes[i]->FinishCycleCollection(); - } - - mFollowupCollection = true; - -#ifdef DEBUG_CC - // We wait until after FinishCollection to check the white nodes because - // some objects may outlive CollectWhite but then be freed by - // FinishCycleCollection (like XPConnect's deferred release of native - // objects). - PRUint32 i, count = mWhiteNodes->Length(); - for (i = 0; i < count; ++i) { - PtrInfo *pinfo = mWhiteNodes->ElementAt(i); - if (pinfo->mLangID == nsIProgrammingLanguage::CPLUSPLUS && - mPurpleBuf.Exists(pinfo->mPointer)) { - printf("nsCycleCollector: %s object @%p is still alive after\n" - " calling RootAndUnlinkJSObjects, Unlink, and Unroot on" - " it! This probably\n" - " means the Unlink implementation was insufficient.\n", - pinfo->mName, pinfo->mPointer); - } - } -#endif - - mWhiteNodes->Clear(); - ClearGraph(); - - mParams.mDoNothing = false; - - return collected; -} - -PRUint32 -nsCycleCollector::SuspectedCount() -{ - return mPurpleBuf.Count(); -} - -void -nsCycleCollector::Shutdown() -{ - // Here we want to run a final collection and then permanently - // disable the collector because the program is shutting down. - - nsCOMPtr listener; - if (mParams.mLogGraphs) { - listener = new nsCycleCollectorLogger(); - } - Collect(SHUTDOWN_COLLECTIONS(mParams), listener); - -#ifdef DEBUG_CC - GCGraphBuilder builder(mGraph, mRuntimes, nsnull); - mScanInProgress = true; - SelectPurple(builder); - mScanInProgress = false; - if (builder.Count() != 0) { - printf("Might have been able to release more cycles if the cycle collector would " - "run once more at shutdown.\n"); - } - ClearGraph(); -#endif - mParams.mDoNothing = true; -} - -#ifdef DEBUG_CC - -static PLDHashOperator -AddExpectedGarbage(nsVoidPtrHashKey *p, void *arg) -{ - GCGraphBuilder *builder = static_cast(arg); - nsISupports *root = - static_cast(const_cast(p->GetKey())); - builder->NoteXPCOMRoot(root); - return PL_DHASH_NEXT; -} - -struct SetSCCVisitor -{ - SetSCCVisitor(PRUint32 aIndex) : mIndex(aIndex) {} - bool ShouldVisitNode(PtrInfo const *pi) { return pi->mSCCIndex == 0; } - void VisitNode(PtrInfo *pi) { pi->mSCCIndex = mIndex; } -private: - PRUint32 mIndex; -}; - -struct SetNonRootGreyVisitor -{ - bool ShouldVisitNode(PtrInfo const *pi) { return pi->mColor == white; } - void VisitNode(PtrInfo *pi) { pi->mColor = grey; } -}; - -static void -PrintPathToExpectedGarbage(PtrInfo *pi) -{ - printf(" An object expected to be garbage could be " - "reached from it by the path:\n"); - for (PtrInfo *path = pi, *prev = nsnull; prev != path; - prev = path, - path = path->mShortestPathToExpectedGarbage) { - if (prev) { - nsCString *edgeName = prev - ->mShortestPathToExpectedGarbageEdgeName; - printf(" via %s\n", - edgeName->IsEmpty() ? "" - : edgeName->get()); - } - printf(" %s %p\n", path->mName, path->mPointer); - } -} - -void -nsCycleCollector::ExplainLiveExpectedGarbage() -{ - if (mScanInProgress || mCollectionInProgress) - Fault("can't explain expected garbage during collection itself"); - - if (mParams.mDoNothing) { - printf("nsCycleCollector: not explaining expected garbage since\n" - " cycle collection disabled\n"); - return; - } - - mCollectionInProgress = true; - mScanInProgress = true; - - { - GCGraphBuilder builder(mGraph, mRuntimes, nsnull); - - // Instead of adding roots from the purple buffer, we add them - // from the list of nodes we were expected to collect. - // Put the expected garbage in *before* calling - // BeginCycleCollection so that we can separate the expected - // garbage from the NoteRoot calls in such a way that something - // that's in both is considered expected garbage. - mExpectedGarbage.EnumerateEntries(&AddExpectedGarbage, &builder); - - PRUint32 expectedGarbageCount = builder.Count(); - - for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { - if (mRuntimes[i]) - mRuntimes[i]->BeginCycleCollection(builder, true); - } - - // But just for extra information, add entries from the purple - // buffer too, since it may give us extra information about - // traversal deficiencies. - mPurpleBuf.NoteAll(builder); - - MarkRoots(builder); - ScanRoots(); - - mScanInProgress = false; - - for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { - if (mRuntimes[i]) { - mRuntimes[i]->FinishTraverse(); - } - } - - bool describeExtraRefcounts = false; - bool findCycleRoots = false; - { - NodePool::Enumerator queue(mGraph.mNodes); - PRUint32 i = 0; - while (!queue.IsDone()) { - PtrInfo *pi = queue.GetNext(); - if (pi->mColor == white) { - findCycleRoots = true; - } - - if (pi->mInternalRefs != pi->mRefCount && - (i < expectedGarbageCount || i >= mGraph.mRootCount)) { - // This check isn't particularly useful anymore - // given that we need to enter this part for i >= - // mGraph.mRootCount and there are plenty of - // NoteRoot roots. - describeExtraRefcounts = true; - } - ++i; - } - } - - if ((describeExtraRefcounts || findCycleRoots) && - CreateReversedEdges()) { - // Note that the external references may have been external - // to a different node in the cycle collection that just - // happened, if that different node was purple and then - // black. - - // Use mSCCIndex temporarily to track whether we've reached - // nodes in the breadth-first search. - const PRUint32 INDEX_UNREACHED = 0; - const PRUint32 INDEX_REACHED = 1; - NodePool::Enumerator etor_clear(mGraph.mNodes); - while (!etor_clear.IsDone()) { - PtrInfo *pi = etor_clear.GetNext(); - pi->mSCCIndex = INDEX_UNREACHED; - } - - nsDeque queue; // for breadth-first search - NodePool::Enumerator etor_roots(mGraph.mNodes); - for (PRUint32 i = 0; i < mGraph.mRootCount; ++i) { - PtrInfo *root_pi = etor_roots.GetNext(); - if (i < expectedGarbageCount) { - root_pi->mSCCIndex = INDEX_REACHED; - root_pi->mShortestPathToExpectedGarbage = root_pi; - queue.Push(root_pi); - } - } - - while (queue.GetSize() > 0) { - PtrInfo *pi = (PtrInfo*)queue.PopFront(); - for (ReversedEdge *e = pi->mReversedEdges; e; e = e->mNext) { - if (e->mTarget->mSCCIndex == INDEX_UNREACHED) { - e->mTarget->mSCCIndex = INDEX_REACHED; - PtrInfo *target = e->mTarget; - if (!target->mShortestPathToExpectedGarbage) { - target->mShortestPathToExpectedGarbage = pi; - target->mShortestPathToExpectedGarbageEdgeName = - e->mEdgeName; - } - queue.Push(target); - } - } - - if (pi->mRefCount == PR_UINT32_MAX || - (pi->mInternalRefs != pi->mRefCount && pi->mRefCount > 0)) { - if (pi->mRefCount == PR_UINT32_MAX) { - printf("nsCycleCollector: %s %p was not collected due " - "to \n" - " external references\n", - pi->mName, pi->mPointer); - } - else { - printf("nsCycleCollector: %s %p was not collected due " - "to %d\n" - " external references (%d total - %d known)\n", - pi->mName, pi->mPointer, - pi->mRefCount - pi->mInternalRefs, - pi->mRefCount, pi->mInternalRefs); - } - - PrintPathToExpectedGarbage(pi); - - if (pi->mRefCount == PR_UINT32_MAX) { - printf(" The known references to it were from:\n"); - } - else { - printf(" The %d known references to it were from:\n", - pi->mInternalRefs); - } - for (ReversedEdge *e = pi->mReversedEdges; - e; e = e->mNext) { - printf(" %s %p", - e->mTarget->mName, e->mTarget->mPointer); - if (!e->mEdgeName->IsEmpty()) { - printf(" via %s", e->mEdgeName->get()); - } - printf("\n"); - } - mRuntimes[pi->mLangID]->PrintAllReferencesTo(pi->mPointer); - } - } - - if (findCycleRoots) { - // NOTE: This code changes the white nodes that are not - // roots to gray. - - // Put the nodes in post-order traversal order from a - // depth-first search. - nsDeque DFSPostOrder; - - { - // Use mSCCIndex temporarily to track the DFS numbering: - const PRUint32 INDEX_UNREACHED = 0; - const PRUint32 INDEX_TRAVERSING = 1; - const PRUint32 INDEX_NUMBERED = 2; - - NodePool::Enumerator etor_clear(mGraph.mNodes); - while (!etor_clear.IsDone()) { - PtrInfo *pi = etor_clear.GetNext(); - pi->mSCCIndex = INDEX_UNREACHED; - } - - nsDeque stack; - - NodePool::Enumerator etor_roots(mGraph.mNodes); - for (PRUint32 i = 0; i < mGraph.mRootCount; ++i) { - PtrInfo *root_pi = etor_roots.GetNext(); - stack.Push(root_pi); - } - - while (stack.GetSize() > 0) { - PtrInfo *pi = (PtrInfo*)stack.Peek(); - if (pi->mSCCIndex == INDEX_UNREACHED) { - pi->mSCCIndex = INDEX_TRAVERSING; - for (EdgePool::Iterator child = pi->FirstChild(), - child_end = pi->LastChild(); - child != child_end; ++child) { - stack.Push(*child); - } - } else { - stack.Pop(); - // Somebody else might have numbered it already - // (since this is depth-first, not breadth-first). - // This happens if a node is pushed on the stack - // a second time while it is on the stack in - // UNREACHED state. - if (pi->mSCCIndex == INDEX_TRAVERSING) { - pi->mSCCIndex = INDEX_NUMBERED; - DFSPostOrder.Push(pi); - } - } - } - } - - // Put the nodes into strongly-connected components. - { - NodePool::Enumerator etor_clear(mGraph.mNodes); - while (!etor_clear.IsDone()) { - PtrInfo *pi = etor_clear.GetNext(); - pi->mSCCIndex = 0; - } - - PRUint32 currentSCC = 1; - - while (DFSPostOrder.GetSize() > 0) { - GraphWalker(SetSCCVisitor(currentSCC)).Walk((PtrInfo*)DFSPostOrder.PopFront()); - ++currentSCC; - } - } - - // Mark any white nodes reachable from other components as - // grey. - { - NodePool::Enumerator queue(mGraph.mNodes); - while (!queue.IsDone()) { - PtrInfo *pi = queue.GetNext(); - if (pi->mColor != white) - continue; - for (EdgePool::Iterator child = pi->FirstChild(), - child_end = pi->LastChild(); - child != child_end; ++child) { - if ((*child)->mSCCIndex != pi->mSCCIndex) { - GraphWalker(SetNonRootGreyVisitor()).Walk(*child); - } - } - } - } - - { - NodePool::Enumerator queue(mGraph.mNodes); - while (!queue.IsDone()) { - PtrInfo *pi = queue.GetNext(); - if (pi->mColor == white) { - if (pi->mLangID == - nsIProgrammingLanguage::CPLUSPLUS && - mPurpleBuf.Exists(pi->mPointer)) { - printf( -"nsCycleCollector: %s %p in component %d\n" -" which was reference counted during the root/unlink/unroot phase of the\n" -" last collection was not collected due to failure to unlink (see other\n" -" warnings) or deficiency in traverse that causes cycles referenced only\n" -" from other cycles to require multiple rounds of cycle collection in which\n" -" this object was likely the reachable object\n", - pi->mName, pi->mPointer, pi->mSCCIndex); - } else { - printf( -"nsCycleCollector: %s %p in component %d\n" -" was not collected due to missing call to suspect, failure to unlink (see\n" -" other warnings), or deficiency in traverse that causes cycles referenced\n" -" only from other cycles to require multiple rounds of cycle collection\n", - pi->mName, pi->mPointer, pi->mSCCIndex); - } - if (pi->mShortestPathToExpectedGarbage) - PrintPathToExpectedGarbage(pi); - } - } - } - } - - DestroyReversedEdges(); - } - } - - ClearGraph(); - - mCollectionInProgress = false; - - for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { - if (mRuntimes[i]) - mRuntimes[i]->FinishCycleCollection(); - } -} - -bool -nsCycleCollector::CreateReversedEdges() -{ - // Count the edges in the graph. - PRUint32 edgeCount = 0; - NodePool::Enumerator countQueue(mGraph.mNodes); - while (!countQueue.IsDone()) { - PtrInfo *pi = countQueue.GetNext(); - for (EdgePool::Iterator e = pi->FirstChild(), e_end = pi->LastChild(); - e != e_end; ++e, ++edgeCount) { - } - } - - // Allocate a pool to hold all of the edges. - mGraph.mReversedEdges = new ReversedEdge[edgeCount]; - if (mGraph.mReversedEdges == nsnull) { - NS_NOTREACHED("allocation failure creating reversed edges"); - return false; - } - - // Fill in the reversed edges by scanning all forward edges. - ReversedEdge *current = mGraph.mReversedEdges; - NodePool::Enumerator buildQueue(mGraph.mNodes); - while (!buildQueue.IsDone()) { - PtrInfo *pi = buildQueue.GetNext(); - PRInt32 i = 0; - for (EdgePool::Iterator e = pi->FirstChild(), e_end = pi->LastChild(); - e != e_end; ++e) { - current->mTarget = pi; - current->mEdgeName = &pi->mEdgeNames[i]; - current->mNext = (*e)->mReversedEdges; - (*e)->mReversedEdges = current; - ++current; - ++i; - } - } - NS_ASSERTION(current - mGraph.mReversedEdges == ptrdiff_t(edgeCount), - "misallocation"); - return true; -} - -void -nsCycleCollector::DestroyReversedEdges() -{ - NodePool::Enumerator queue(mGraph.mNodes); - while (!queue.IsDone()) { - PtrInfo *pi = queue.GetNext(); - pi->mReversedEdges = nsnull; - } - - delete mGraph.mReversedEdges; - mGraph.mReversedEdges = nsnull; -} - -void -nsCycleCollector::ShouldBeFreed(nsISupports *n) -{ - if (n) { - mExpectedGarbage.PutEntry(n); - } -} - -void -nsCycleCollector::WasFreed(nsISupports *n) -{ - if (n) { - mExpectedGarbage.RemoveEntry(n); - } -} -#endif - - -//////////////////////// -// Memory reporter -//////////////////////// - -static PRInt64 -ReportCycleCollectorMem() -{ - if (!sCollector) - return 0; - PRInt64 size = sizeof(nsCycleCollector) + - sCollector->mPurpleBuf.BlocksSize() + - sCollector->mGraph.BlocksSize(); - if (sCollector->mWhiteNodes) - size += sCollector->mWhiteNodes->Capacity() * sizeof(PtrInfo*); - return size; -} - -NS_MEMORY_REPORTER_IMPLEMENT(CycleCollector, - "explicit/cycle-collector", - KIND_HEAP, - UNITS_BYTES, - ReportCycleCollectorMem, - "Memory used by the cycle collector. This " - "includes the cycle collector structure, the " - "purple buffer, the graph, and the white nodes. " - "The latter two are expected to be empty when the " - "cycle collector is idle.") - - -//////////////////////////////////////////////////////////////////////// -// Module public API (exported in nsCycleCollector.h) -// Just functions that redirect into the singleton, once it's built. -//////////////////////////////////////////////////////////////////////// - -void -nsCycleCollector_registerRuntime(PRUint32 langID, - nsCycleCollectionLanguageRuntime *rt) -{ - static bool regMemReport = true; - if (sCollector) - sCollector->RegisterRuntime(langID, rt); - if (regMemReport) { - regMemReport = false; - NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(CycleCollector)); - } -} - -nsCycleCollectionLanguageRuntime * -nsCycleCollector_getRuntime(PRUint32 langID) -{ - if (sCollector) - sCollector->GetRuntime(langID); - return nsnull; -} - -void -nsCycleCollector_forgetRuntime(PRUint32 langID) -{ - if (sCollector) - sCollector->ForgetRuntime(langID); -} - - -bool -NS_CycleCollectorSuspect(nsISupports *n) -{ - if (sCollector) - return sCollector->Suspect(n); - return false; -} - -bool -NS_CycleCollectorForget(nsISupports *n) -{ - return sCollector ? sCollector->Forget(n) : true; -} - -nsPurpleBufferEntry* -NS_CycleCollectorSuspect2(nsISupports *n) -{ - if (sCollector) - return sCollector->Suspect2(n); - return nsnull; -} - -bool -NS_CycleCollectorForget2(nsPurpleBufferEntry *e) -{ - return sCollector ? sCollector->Forget2(e) : true; -} - -PRUint32 -nsCycleCollector_suspectedCount() -{ - return sCollector ? sCollector->SuspectedCount() : 0; -} - -#ifdef DEBUG -void -nsCycleCollector_DEBUG_shouldBeFreed(nsISupports *n) -{ -#ifdef DEBUG_CC - if (sCollector) - sCollector->ShouldBeFreed(n); -#endif -} - -void -nsCycleCollector_DEBUG_wasFreed(nsISupports *n) -{ -#ifdef DEBUG_CC - if (sCollector) - sCollector->WasFreed(n); -#endif -} -#endif - -class nsCycleCollectorRunner : public nsRunnable -{ - nsCycleCollector *mCollector; - nsICycleCollectorListener *mListener; - Mutex mLock; - CondVar mRequest; - CondVar mReply; - bool mRunning; - bool mShutdown; - bool mCollected; - - nsCycleCollectionJSRuntime *GetJSRuntime() - { - return static_cast - (mCollector->mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]); - } - -public: - NS_IMETHOD Run() - { -#ifdef XP_WIN - TlsSetValue(gTLSThreadIDIndex, - (void*) mozilla::threads::CycleCollector); -#elif defined(NS_TLS) - gTLSThreadID = mozilla::threads::CycleCollector; -#else - gCycleCollectorThread = PR_GetCurrentThread(); -#endif - - NS_ASSERTION(NS_IsCycleCollectorThread() && !NS_IsMainThread(), - "Wrong thread!"); - - MutexAutoLock autoLock(mLock); - - if (mShutdown) - return NS_OK; - - mRunning = true; - - while (1) { - mRequest.Wait(); - - if (!mRunning) { - mReply.Notify(); - return NS_OK; - } - - GetJSRuntime()->NotifyEnterCycleCollectionThread(); - mCollected = mCollector->BeginCollection(mListener); - GetJSRuntime()->NotifyLeaveCycleCollectionThread(); - - mReply.Notify(); - } - - return NS_OK; - } - - nsCycleCollectorRunner(nsCycleCollector *collector) - : mCollector(collector), - mListener(nsnull), - mLock("cycle collector lock"), - mRequest(mLock, "cycle collector request condvar"), - mReply(mLock, "cycle collector reply condvar"), - mRunning(false), - mShutdown(false), - mCollected(false) - { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - } - - PRUint32 Collect(nsICycleCollectorListener* aListener) - { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - mCollector->GCIfNeeded(false); - - MutexAutoLock autoLock(mLock); - - if (!mRunning) - return 0; - - nsAutoTArray whiteNodes; - if (!mCollector->PrepareForCollection(&whiteNodes)) - return 0; - - NS_ASSERTION(!mListener, "Should have cleared this already!"); - if (aListener && NS_FAILED(aListener->Begin())) - aListener = nsnull; - mListener = aListener; - - GetJSRuntime()->NotifyLeaveMainThread(); - mRequest.Notify(); - mReply.Wait(); - GetJSRuntime()->NotifyEnterMainThread(); - - mListener = nsnull; - - if (mCollected) { - mCollected = mCollector->FinishCollection(aListener); - - mCollector->CleanupAfterCollection(); - - return mCollected ? mCollector->mCollectedObjects : 0; - } - - return 0; - } - - void Shutdown() - { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - MutexAutoLock autoLock(mLock); - - mShutdown = true; - - if (!mRunning) - return; - - mRunning = false; - mRequest.Notify(); - mReply.Wait(); - } -}; - -// Holds a reference. -static nsCycleCollectorRunner* sCollectorRunner; - -// Holds a reference. -static nsIThread* sCollectorThread; - -nsresult -nsCycleCollector_startup() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!sCollector, "Forgot to call nsCycleCollector_shutdown?"); - - sCollector = new nsCycleCollector(); - - nsRefPtr runner = - new nsCycleCollectorRunner(sCollector); - - nsCOMPtr thread; - nsresult rv = NS_NewThread(getter_AddRefs(thread), runner); - NS_ENSURE_SUCCESS(rv, rv); - - runner.swap(sCollectorRunner); - thread.swap(sCollectorThread); - - return rv; -} - -PRUint32 -nsCycleCollector_collect(nsICycleCollectorListener *aListener) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - nsCOMPtr listener(aListener); - if (!aListener && sCollector && sCollector->mParams.mLogGraphs) { - listener = new nsCycleCollectorLogger(); - } - - if (sCollectorRunner) - return sCollectorRunner->Collect(listener); - return sCollector ? sCollector->Collect(1, listener) : 0; -} - -void -nsCycleCollector_shutdownThreads() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - if (sCollectorRunner) { - nsRefPtr runner; - runner.swap(sCollectorRunner); - runner->Shutdown(); - } - - if (sCollectorThread) { - nsCOMPtr thread; - thread.swap(sCollectorThread); - thread->Shutdown(); - } -} - -void -nsCycleCollector_shutdown() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!sCollectorRunner, "Should have finished before!"); - NS_ASSERTION(!sCollectorThread, "Should have finished before!"); - - if (sCollector) { - sCollector->Shutdown(); - delete sCollector; - sCollector = nsnull; - } -} diff --git a/tests/cpp/nsIOThreadPool.cpp b/tests/cpp/nsIOThreadPool.cpp deleted file mode 100644 index 36d2042..0000000 --- a/tests/cpp/nsIOThreadPool.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla. - * - * The Initial Developer of the Original Code is IBM Corporation. - * Portions created by IBM Corporation are Copyright (C) 2003 - * IBM Corporation. All Rights Reserved. - * - * Contributor(s): - * IBM Corp. - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsIEventTarget.h" -#include "nsIServiceManager.h" -#include "nsIObserverService.h" -#include "nsIObserver.h" -#include "nsAutoLock.h" -#include "nsCOMPtr.h" -#include "prclist.h" -#include "prlog.h" - -#if defined(PR_LOGGING) -// -// NSPR_LOG_MODULES=nsIOThreadPool:5 -// -static PRLogModuleInfo *gIOThreadPoolLog = nsnull; -#endif -#define LOG(args) PR_LOG(gIOThreadPoolLog, PR_LOG_DEBUG, args) - -// this number specifies the maximum number of threads. -#define MAX_THREADS 4 - -// this number specifies how long to wait before killing an idle thread. it's -// important to pick a large enough value here to minimize thread churn. -#define IDLE_TIMEOUT PR_SecondsToInterval(60) - -#define PLEVENT_FROM_LINK(_link) \ - ((PLEvent*) ((char*) (_link) - offsetof(PLEvent, link))) - -//----------------------------------------------------------------------------- -// pool of joinable threads used for general purpose i/o tasks -// -// the main entry point to this class is nsIEventTarget. events posted to -// the thread pool are dispatched on one of the threads. a variable number -// of threads are maintained. the threads die off if they remain idle for -// more than THREAD_IDLE_TIMEOUT. the thread pool shuts down when it receives -// the "xpcom-shutdown" event. -//----------------------------------------------------------------------------- - -class nsIOThreadPool : public nsIEventTarget - , public nsIObserver -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIEVENTTARGET - NS_DECL_NSIOBSERVER - - nsresult Init(); - void Shutdown(); - -private: - virtual ~nsIOThreadPool(); - - PR_STATIC_CALLBACK(void) ThreadFunc(void *); - - // mLock protects all (exceptions during Init and Shutdown) - PRLock *mLock; - PRCondVar *mIdleThreadCV; // notified to wake up an idle thread - PRCondVar *mExitThreadCV; // notified when a thread exits - PRUint32 mNumThreads; // number of active + idle threads - PRUint32 mNumIdleThreads; // number of idle threads - PRCList mEventQ; // queue of PLEvent structs - PRBool mShutdown; // set to true if shutting down -}; - -NS_IMPL_THREADSAFE_ISUPPORTS2(nsIOThreadPool, nsIEventTarget, nsIObserver) - -nsresult -nsIOThreadPool::Init() -{ -#if defined(PR_LOGGING) - if (!gIOThreadPoolLog) - gIOThreadPoolLog = PR_NewLogModule("nsIOThreadPool"); -#endif - - mNumThreads = 0; - mNumIdleThreads = 0; - mShutdown = PR_FALSE; - - mLock = PR_NewLock(); - if (!mLock) - return NS_ERROR_OUT_OF_MEMORY; - - mIdleThreadCV = PR_NewCondVar(mLock); - if (!mIdleThreadCV) - return NS_ERROR_OUT_OF_MEMORY; - - mExitThreadCV = PR_NewCondVar(mLock); - if (!mExitThreadCV) - return NS_ERROR_OUT_OF_MEMORY; - - PR_INIT_CLIST(&mEventQ); - - // we want to shutdown the i/o thread pool at xpcom-shutdown time... - nsCOMPtr os = do_GetService("@mozilla.org/observer-service;1"); - if (os) - os->AddObserver(this, "xpcom-shutdown", PR_FALSE); - return NS_OK; -} - -nsIOThreadPool::~nsIOThreadPool() -{ - LOG(("Destroying nsIOThreadPool @%p\n", this)); - -#ifdef DEBUG - NS_ASSERTION(PR_CLIST_IS_EMPTY(&mEventQ), "leaking events"); - NS_ASSERTION(mNumThreads == 0, "leaking thread(s)"); -#endif - - if (mIdleThreadCV) - PR_DestroyCondVar(mIdleThreadCV); - if (mExitThreadCV) - PR_DestroyCondVar(mExitThreadCV); - if (mLock) - PR_DestroyLock(mLock); -} - -void -nsIOThreadPool::Shutdown() -{ - LOG(("nsIOThreadPool::Shutdown\n")); - - // synchronize with background threads... - { - nsAutoLock lock(mLock); - mShutdown = PR_TRUE; - - PR_NotifyAllCondVar(mIdleThreadCV); - - while (mNumThreads != 0) - PR_WaitCondVar(mExitThreadCV, PR_INTERVAL_NO_TIMEOUT); - } -} - -NS_IMETHODIMP -nsIOThreadPool::PostEvent(PLEvent *event) -{ - LOG(("nsIOThreadPool::PostEvent [event=%p]\n", event)); - - nsAutoLock lock(mLock); - - // if we are shutting down, then prevent additional events from being - // added to the queue... - if (mShutdown) - return NS_ERROR_UNEXPECTED; - - nsresult rv = NS_OK; - - PR_APPEND_LINK(&event->link, &mEventQ); - - // now, look for an available idle thread... - if (mNumIdleThreads) - PR_NotifyCondVar(mIdleThreadCV); // wake up an idle thread - - // or, try to create a new thread unless we have reached our maximum... - else if (mNumThreads < MAX_THREADS) { - NS_ADDREF_THIS(); // the thread owns a reference to us - mNumThreads++; - PRThread *thread = PR_CreateThread(PR_USER_THREAD, - ThreadFunc, - this, - PR_PRIORITY_NORMAL, - PR_GLOBAL_THREAD, - PR_UNJOINABLE_THREAD, - 0); - if (!thread) { - NS_RELEASE_THIS(); - mNumThreads--; - rv = NS_ERROR_OUT_OF_MEMORY; - } - } - // else, we expect one of the active threads to process the event queue. - - return rv; -} - -NS_IMETHODIMP -nsIOThreadPool::IsOnCurrentThread(PRBool *result) -{ - // no one should be calling this method. if this assertion gets hit, - // then we need to think carefully about what this method should be - // returning. - NS_NOTREACHED("nsIOThreadPool::IsOnCurrentThread"); - - // fudging this a bit since we actually cover several threads... - *result = PR_FALSE; - return NS_OK; -} - -NS_IMETHODIMP -nsIOThreadPool::Observe(nsISupports *, const char *topic, const PRUnichar *) -{ - NS_ASSERTION(strcmp(topic, "xpcom-shutdown") == 0, "unexpected topic"); - Shutdown(); - return NS_OK; -} - -void -nsIOThreadPool::ThreadFunc(void *arg) -{ - nsIOThreadPool *pool = (nsIOThreadPool *) arg; - - LOG(("entering ThreadFunc\n")); - - { - nsAutoLock lock(pool->mLock); - - for (;;) { - PRIntervalTime start = PR_IntervalNow(), timeout = IDLE_TIMEOUT; - // - // wait for one or more of the following to occur: - // (1) the event queue has an event to process - // (2) the shutdown flag has been set - // (3) the thread has been idle for too long - // - // PR_WaitCondVar will return when any of these conditions is true. - // - while (PR_CLIST_IS_EMPTY(&pool->mEventQ) && !pool->mShutdown) { - pool->mNumIdleThreads++; - PR_WaitCondVar(pool->mIdleThreadCV, timeout); - pool->mNumIdleThreads--; - - PRIntervalTime delta = PR_IntervalNow() - start; - if (delta >= timeout) - break; - timeout -= delta; - start += delta; - } - - // if the queue is still empty, then kill this thread (either we - // are shutting down or the thread exceeded the idle timeout)... - if (PR_CLIST_IS_EMPTY(&pool->mEventQ)) - break; - - // handle one event at a time: we don't want this one thread to hog - // all the events while other threads may be able to help out ;-) - do { - PLEvent *event = PLEVENT_FROM_LINK(PR_LIST_HEAD(&pool->mEventQ)); - PR_REMOVE_AND_INIT_LINK(&event->link); - - LOG(("event:%p\n", event)); - - // release lock! - lock.unlock(); - PL_HandleEvent(event); - lock.lock(); - } - while (!PR_CLIST_IS_EMPTY(&pool->mEventQ)); - } - - // thread is going away... - pool->mNumThreads--; - PR_NotifyCondVar(pool->mExitThreadCV); - } - - // release our reference to the pool - NS_RELEASE(pool); - - LOG(("leaving ThreadFunc\n")); -} - -//----------------------------------------------------------------------------- - -NS_METHOD -net_NewIOThreadPool(nsISupports *outer, REFNSIID iid, void **result) -{ - nsIOThreadPool *pool = new nsIOThreadPool(); - if (!pool) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(pool); - nsresult rv = pool->Init(); - if (NS_SUCCEEDED(rv)) - rv = pool->QueryInterface(iid, result); - NS_RELEASE(pool); - return rv; -} diff --git a/tests/cpp/nsTextFrameThebes.cpp b/tests/cpp/nsTextFrameThebes.cpp deleted file mode 100644 index 014368a..0000000 --- a/tests/cpp/nsTextFrameThebes.cpp +++ /dev/null @@ -1,6873 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Robert O'Callahan - * Roger B. Sidje - * Pierre Phaneuf - * Prabhat Hegde - * Tomi Leppikangas - * Roland Mainz - * Daniel Glazman - * Neil Deakin - * Masayuki Nakano - * Mats Palmgren - * Uri Bernstein - * Stephen Blackheath - * Michael Ventnor - * Ehsan Akhgari - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* rendering object for textual content of elements */ - -#include "nsCOMPtr.h" -#include "nsHTMLParts.h" -#include "nsCRT.h" -#include "nsSplittableFrame.h" -#include "nsLineLayout.h" -#include "nsString.h" -#include "nsUnicharUtils.h" -#include "nsPresContext.h" -#include "nsIContent.h" -#include "nsStyleConsts.h" -#include "nsStyleContext.h" -#include "nsCoord.h" -#include "nsIFontMetrics.h" -#include "nsIRenderingContext.h" -#include "nsIPresShell.h" -#include "nsITimer.h" -#include "nsTArray.h" -#include "nsIDOMText.h" -#include "nsIDocument.h" -#include "nsIDeviceContext.h" -#include "nsCSSPseudoElements.h" -#include "nsCompatibility.h" -#include "nsCSSColorUtils.h" -#include "nsLayoutUtils.h" -#include "nsDisplayList.h" -#include "nsFrame.h" -#include "nsTextFrameUtils.h" -#include "nsTextRunTransformations.h" -#include "nsFrameManager.h" -#include "nsTextFrameTextRunCache.h" -#include "nsExpirationTracker.h" -#include "nsTextFrame.h" -#include "nsICaseConversion.h" -#include "nsIUGenCategory.h" -#include "nsUnicharUtilCIID.h" - -#include "nsTextFragment.h" -#include "nsGkAtoms.h" -#include "nsFrameSelection.h" -#include "nsISelection.h" -#include "nsIDOMRange.h" -#include "nsILookAndFeel.h" -#include "nsCSSRendering.h" -#include "nsContentUtils.h" -#include "nsLineBreaker.h" -#include "nsIWordBreaker.h" -#include "nsGenericDOMDataNode.h" - -#include "nsILineIterator.h" - -#include "nsIServiceManager.h" -#ifdef ACCESSIBILITY -#include "nsIAccessible.h" -#include "nsIAccessibilityService.h" -#endif -#include "nsAutoPtr.h" -#include "nsStyleSet.h" - -#include "nsBidiFrames.h" -#include "nsBidiPresUtils.h" -#include "nsBidiUtils.h" - -#include "nsIThebesFontMetrics.h" -#include "gfxFont.h" -#include "gfxContext.h" -#include "gfxTextRunWordCache.h" -#include "gfxImageSurface.h" - -#ifdef NS_DEBUG -#undef NOISY_BLINK -#undef NOISY_REFLOW -#undef NOISY_TRIM -#else -#undef NOISY_BLINK -#undef NOISY_REFLOW -#undef NOISY_TRIM -#endif - -// The following flags are set during reflow - -// This bit is set on the first frame in a continuation indicating -// that it was chopped short because of :first-letter style. -#define TEXT_FIRST_LETTER 0x00100000 -// This bit is set on frames that are logically adjacent to the start of the -// line (i.e. no prior frame on line with actual displayed in-flow content). -#define TEXT_START_OF_LINE 0x00200000 -// This bit is set on frames that are logically adjacent to the end of the -// line (i.e. no following on line with actual displayed in-flow content). -#define TEXT_END_OF_LINE 0x00400000 -// This bit is set on frames that end with a hyphenated break. -#define TEXT_HYPHEN_BREAK 0x00800000 -// This bit is set on frames that trimmed trailing whitespace characters when -// calculating their width during reflow. -#define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000 -// This bit is set on frames that have justification enabled. We record -// this in a state bit because we don't always have the containing block -// easily available to check text-align on. -#define TEXT_JUSTIFICATION_ENABLED 0x02000000 -// Set this bit if the textframe has overflow area for IME/spellcheck underline. -#define TEXT_SELECTION_UNDERLINE_OVERFLOWED 0x04000000 - -#define TEXT_REFLOW_FLAGS \ - (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \ - TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \ - TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED) - -// Cache bits for IsEmpty(). -// Set this bit if the textframe is known to be only collapsible whitespace. -#define TEXT_IS_ONLY_WHITESPACE 0x08000000 -// Set this bit if the textframe is known to be not only collapsible whitespace. -#define TEXT_ISNOT_ONLY_WHITESPACE 0x10000000 - -#define TEXT_WHITESPACE_FLAGS 0x18000000 - -// nsTextFrame.h has -// #define TEXT_BLINK_ON_OR_PRINTING 0x20000000 - -// Set when this text frame is mentioned in the userdata for a textrun -#define TEXT_IN_TEXTRUN_USER_DATA 0x40000000 - -// nsTextFrame.h has -// #define TEXT_HAS_NONCOLLAPSED_CHARACTERS 0x80000000 - -/* - * Some general notes - * - * Text frames delegate work to gfxTextRun objects. The gfxTextRun object - * transforms text to positioned glyphs. It can report the geometry of the - * glyphs and paint them. Text frames configure gfxTextRuns by providing text, - * spacing, language, and other information. - * - * A gfxTextRun can cover more than one DOM text node. This is necessary to - * get kerning, ligatures and shaping for text that spans multiple text nodes - * but is all the same font. The userdata for a gfxTextRun object is a - * TextRunUserData* or an nsIFrame*. - * - * We go to considerable effort to make sure things work even if in-flow - * siblings have different style contexts (i.e., first-letter and first-line). - * - * Our convention is that unsigned integer character offsets are offsets into - * the transformed string. Signed integer character offsets are offsets into - * the DOM string. - * - * XXX currently we don't handle hyphenated breaks between text frames where the - * hyphen occurs at the end of the first text frame, e.g. - * Kit­ty - */ - -/** - * We use an array of these objects to record which text frames - * are associated with the textrun. mStartFrame is the start of a list of - * text frames. Some sequence of its continuations are covered by the textrun. - * A content textnode can have at most one TextRunMappedFlow associated with it - * for a given textrun. - * - * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain - * the offset into the before-transformation text of the textrun. It can be - * positive (when a text node starts in the middle of a text run) or - * negative (when a text run starts in the middle of a text node). Of course - * it can also be zero. - */ -struct TextRunMappedFlow { - nsTextFrame* mStartFrame; - PRInt32 mDOMOffsetToBeforeTransformOffset; - // The text mapped starts at mStartFrame->GetContentOffset() and is this long - PRUint32 mContentLength; -}; - -/** - * This is our user data for the textrun, when textRun->GetFlags() does not - * have TEXT_SIMPLE_FLOW set. When TEXT_SIMPLE_FLOW is set, there is just one - * flow, the textrun's user data pointer is a pointer to mStartFrame - * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength - * is the length of the text node. - */ -struct TextRunUserData { - TextRunMappedFlow* mMappedFlows; - PRInt32 mMappedFlowCount; - - PRUint32 mLastFlowIndex; -}; - -/** - * This helper object computes colors used for painting, and also IME - * underline information. The data is computed lazily and cached as necessary. - * These live for just the duration of one paint operation. - */ -class nsTextPaintStyle { -public: - nsTextPaintStyle(nsTextFrame* aFrame); - - nscolor GetTextColor(); - /** - * Compute the colors for normally-selected text. Returns false if - * the normal selection is not being displayed. - */ - PRBool GetSelectionColors(nscolor* aForeColor, - nscolor* aBackColor); - void GetHighlightColors(nscolor* aForeColor, - nscolor* aBackColor); - void GetIMESelectionColors(PRInt32 aIndex, - nscolor* aForeColor, - nscolor* aBackColor); - // if this returns PR_FALSE, we don't need to draw underline. - PRBool GetSelectionUnderlineForPaint(PRInt32 aIndex, - nscolor* aLineColor, - float* aRelativeSize, - PRUint8* aStyle); - - // if this returns PR_FALSE, we don't need to draw underline. - static PRBool GetSelectionUnderline(nsPresContext* aPresContext, - PRInt32 aIndex, - nscolor* aLineColor, - float* aRelativeSize, - PRUint8* aStyle); - - nsPresContext* PresContext() { return mPresContext; } - - enum { - eIndexRawInput = 0, - eIndexSelRawText, - eIndexConvText, - eIndexSelConvText, - eIndexSpellChecker - }; - - static PRInt32 GetUnderlineStyleIndexForSelectionType(PRInt32 aSelectionType) - { - switch (aSelectionType) { - case nsISelectionController::SELECTION_IME_RAWINPUT: - return eIndexRawInput; - case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: - return eIndexSelRawText; - case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: - return eIndexConvText; - case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: - return eIndexSelConvText; - case nsISelectionController::SELECTION_SPELLCHECK: - return eIndexSpellChecker; - default: - NS_WARNING("non-IME selection type"); - return eIndexRawInput; - } - } - -protected: - nsTextFrame* mFrame; - nsPresContext* mPresContext; - PRPackedBool mInitCommonColors; - PRPackedBool mInitSelectionColors; - - // Selection data - - PRInt16 mSelectionStatus; // see nsIDocument.h SetDisplaySelection() - nscolor mSelectionTextColor; - nscolor mSelectionBGColor; - - // Common data - - PRInt32 mSufficientContrast; - nscolor mFrameBackgroundColor; - - // selection colors and underline info, the colors are resolved colors, - // i.e., the foreground color and background color are swapped if it's needed. - // And also line color will be resolved from them. - struct nsSelectionStyle { - PRBool mInit; - nscolor mTextColor; - nscolor mBGColor; - nscolor mUnderlineColor; - PRUint8 mUnderlineStyle; - float mUnderlineRelativeSize; - }; - nsSelectionStyle mSelectionStyle[5]; - - // Color initializations - void InitCommonColors(); - PRBool InitSelectionColors(); - - nsSelectionStyle* GetSelectionStyle(PRInt32 aIndex); - void InitSelectionStyle(PRInt32 aIndex); - - PRBool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor); - - nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor, - nscolor aBackColor); -}; - -static void -DestroyUserData(void* aUserData) -{ - TextRunUserData* userData = static_cast(aUserData); - if (userData) { - nsMemory::Free(userData); - } -} - -// Remove the textrun from the frame continuation chain starting at aFrame, -// which should be marked as a textrun owner. -static void -ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun) -{ - aFrame->RemoveStateBits(TEXT_IN_TEXTRUN_USER_DATA); - while (aFrame) { - NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, - "Bad frame"); - if (aFrame->GetTextRun() != aTextRun) - break; - aFrame->SetTextRun(nsnull); - aFrame = static_cast(aFrame->GetNextContinuation()); - } -} - -// Figure out which frames -static void -UnhookTextRunFromFrames(gfxTextRun* aTextRun) -{ - if (!aTextRun->GetUserData()) - return; - - // Kill all references to the textrun. It could be referenced by any of its - // owners, and all their in-flows. - if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { - nsIFrame* firstInFlow = static_cast(aTextRun->GetUserData()); - ClearAllTextRunReferences(static_cast(firstInFlow), aTextRun); - } else { - TextRunUserData* userData = - static_cast(aTextRun->GetUserData()); - PRInt32 i; - for (i = 0; i < userData->mMappedFlowCount; ++i) { - ClearAllTextRunReferences(userData->mMappedFlows[i].mStartFrame, aTextRun); - } - DestroyUserData(userData); - } - aTextRun->SetUserData(nsnull); -} - -class FrameTextRunCache; - -static FrameTextRunCache *gTextRuns = nsnull; - -/* - * Cache textruns and expire them after 3*10 seconds of no use. - */ -class FrameTextRunCache : public nsExpirationTracker { -public: - enum { TIMEOUT_SECONDS = 10 }; - FrameTextRunCache() - : nsExpirationTracker(TIMEOUT_SECONDS*1000) {} - ~FrameTextRunCache() { - AgeAllGenerations(); - } - - void RemoveFromCache(gfxTextRun* aTextRun) { - if (aTextRun->GetExpirationState()->IsTracked()) { - RemoveObject(aTextRun); - } - if (aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE) { - gfxTextRunWordCache::RemoveTextRun(aTextRun); - } - } - - // This gets called when the timeout has expired on a gfxTextRun - virtual void NotifyExpired(gfxTextRun* aTextRun) { - UnhookTextRunFromFrames(aTextRun); - RemoveFromCache(aTextRun); - delete aTextRun; - } -}; - -static gfxTextRun * -MakeTextRun(const PRUnichar *aText, PRUint32 aLength, - gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams, - PRUint32 aFlags) -{ - nsAutoPtr textRun; - if (aLength == 0) { - textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags); - } else if (aLength == 1 && aText[0] == ' ') { - textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags); - } else { - textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup, - aParams, aFlags); - } - if (!textRun) - return nsnull; - nsresult rv = gTextRuns->AddObject(textRun); - if (NS_FAILED(rv)) { - gTextRuns->RemoveFromCache(textRun); - return nsnull; - } - return textRun.forget(); -} - -static gfxTextRun * -MakeTextRun(const PRUint8 *aText, PRUint32 aLength, - gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams, - PRUint32 aFlags) -{ - nsAutoPtr textRun; - if (aLength == 0) { - textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags); - } else if (aLength == 1 && aText[0] == ' ') { - textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags); - } else { - textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup, - aParams, aFlags); - } - if (!textRun) - return nsnull; - nsresult rv = gTextRuns->AddObject(textRun); - if (NS_FAILED(rv)) { - gTextRuns->RemoveFromCache(textRun); - return nsnull; - } - return textRun.forget(); -} - -nsresult -nsTextFrameTextRunCache::Init() { - gTextRuns = new FrameTextRunCache(); - return gTextRuns ? NS_OK : NS_ERROR_OUT_OF_MEMORY; -} - -void -nsTextFrameTextRunCache::Shutdown() { - delete gTextRuns; - gTextRuns = nsnull; -} - -PRInt32 nsTextFrame::GetContentEnd() const { - nsTextFrame* next = static_cast(GetNextContinuation()); - return next ? next->GetContentOffset() : GetFragment()->GetLength(); -} - -PRInt32 nsTextFrame::GetInFlowContentLength() { -#ifdef IBMBIDI - nsTextFrame* nextBidi = nsnull; - PRInt32 start = -1, end; - - if (mState & NS_FRAME_IS_BIDI) { - nextBidi = static_cast(GetLastInFlow()->GetNextContinuation()); - if (nextBidi) { - nextBidi->GetOffsets(start, end); - return start - mContentOffset; - } - } -#endif //IBMBIDI - return GetFragment()->GetLength() - mContentOffset; -} - -// Smarter versions of XP_IS_SPACE. -// Unicode is really annoying; sometimes a space character isn't whitespace --- -// when it combines with another character -// So we have several versions of IsSpace for use in different contexts. - -static PRBool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, PRUint32 aPos) -{ - NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset"); - if (!aFrag->Is2b()) - return PR_FALSE; - return nsTextFrameUtils::IsSpaceCombiningSequenceTail( - aFrag->Get2b() + aPos, aFrag->GetLength() - aPos); -} - -// Check whether aPos is a space for CSS 'word-spacing' purposes -static PRBool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, - PRUint32 aPos, const nsStyleText* aStyleText) -{ - NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); - - PRUnichar ch = aFrag->CharAt(aPos); - switch (ch) { - case ' ': - case CH_NBSP: - return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); - case '\t': return !aStyleText->WhiteSpaceIsSignificant(); - case '\n': return !aStyleText->NewlineIsSignificant(); - default: return PR_FALSE; - } -} - -// Check whether the string aChars/aLength starts with space that's -// trimmable according to CSS 'white-space:normal/nowrap'. -static PRBool IsTrimmableSpace(const PRUnichar* aChars, PRUint32 aLength) -{ - NS_ASSERTION(aLength > 0, "No text for IsSpace!"); - - PRUnichar ch = *aChars; - if (ch == ' ') - return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1); - return ch == '\t' || ch == '\f' || ch == '\n'; -} - -// Check whether the character aCh is trimmable according to CSS -// 'white-space:normal/nowrap' -static PRBool IsTrimmableSpace(char aCh) -{ - return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n'; -} - -static PRBool IsTrimmableSpace(const nsTextFragment* aFrag, PRUint32 aPos, - const nsStyleText* aStyleText) -{ - NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); - - switch (aFrag->CharAt(aPos)) { - case ' ': return !aStyleText->WhiteSpaceIsSignificant() && - !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); - case '\n': return !aStyleText->NewlineIsSignificant(); - case '\t': - case '\f': return !aStyleText->WhiteSpaceIsSignificant(); - default: return PR_FALSE; - } -} - -static PRBool IsSelectionSpace(const nsTextFragment* aFrag, PRUint32 aPos) -{ - NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); - PRUnichar ch = aFrag->CharAt(aPos); - if (ch == ' ' || ch == CH_NBSP) - return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); - return ch == '\t' || ch == '\n' || ch == '\f'; -} - -// Count the amount of trimmable whitespace (as per CSS -// 'white-space:normal/nowrap') in a text fragment. The first -// character is at offset aStartOffset; the maximum number of characters -// to check is aLength. aDirection is -1 or 1 depending on whether we should -// progress backwards or forwards. -static PRUint32 -GetTrimmableWhitespaceCount(const nsTextFragment* aFrag, - PRInt32 aStartOffset, PRInt32 aLength, - PRInt32 aDirection) -{ - PRInt32 count = 0; - if (aFrag->Is2b()) { - const PRUnichar* str = aFrag->Get2b() + aStartOffset; - PRInt32 fragLen = aFrag->GetLength() - aStartOffset; - for (; count < aLength; ++count) { - if (!IsTrimmableSpace(str, fragLen)) - break; - str += aDirection; - fragLen -= aDirection; - } - } else { - const char* str = aFrag->Get1b() + aStartOffset; - for (; count < aLength; ++count) { - if (!IsTrimmableSpace(*str)) - break; - str += aDirection; - } - } - return count; -} - -static PRBool -IsAllWhitespace(const nsTextFragment* aFrag, PRBool aAllowNewline) -{ - if (aFrag->Is2b()) - return PR_FALSE; - PRInt32 len = aFrag->GetLength(); - const char* str = aFrag->Get1b(); - for (PRInt32 i = 0; i < len; ++i) { - char ch = str[i]; - if (ch == ' ' || ch == '\t' || (ch == '\n' && aAllowNewline)) - continue; - return PR_FALSE; - } - return PR_TRUE; -} - -/** - * This class accumulates state as we scan a paragraph of text. It detects - * textrun boundaries (changes from text to non-text, hard - * line breaks, and font changes) and builds a gfxTextRun at each boundary. - * It also detects linebreaker run boundaries (changes from text to non-text, - * and hard line breaks) and at each boundary runs the linebreaker to compute - * potential line breaks. It also records actual line breaks to store them in - * the textruns. - */ -class BuildTextRunsScanner { -public: - BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext, - nsIFrame* aLineContainer) : - mCurrentFramesAllSameTextRun(nsnull), - mContext(aContext), - mLineContainer(aLineContainer), - mBidiEnabled(aPresContext->BidiEnabled()), - mSkipIncompleteTextRuns(PR_FALSE), - mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE), - mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) { - ResetRunInfo(); - } - ~BuildTextRunsScanner() { - NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared"); - NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared"); - NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared"); - NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared"); - } - - void SetAtStartOfLine() { - mStartOfLine = PR_TRUE; - mCanStopOnThisLine = PR_FALSE; - } - void SetSkipIncompleteTextRuns(PRBool aSkip) { - mSkipIncompleteTextRuns = aSkip; - } - void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) { - mCommonAncestorWithLastFrame = aFrame; - } - PRBool CanStopOnThisLine() { - return mCanStopOnThisLine; - } - nsIFrame* GetCommonAncestorWithLastFrame() { - return mCommonAncestorWithLastFrame; - } - void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) { - if (mCommonAncestorWithLastFrame && - mCommonAncestorWithLastFrame->GetParent() == aFrame) { - mCommonAncestorWithLastFrame = aFrame; - } - } - void ScanFrame(nsIFrame* aFrame); - PRBool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun); - void FlushFrames(PRBool aFlushLineBreaks, PRBool aSuppressTrailingBreak); - void FlushLineBreaks(gfxTextRun* aTrailingTextRun); - void ResetRunInfo() { - mLastFrame = nsnull; - mMappedFlows.Clear(); - mLineBreakBeforeFrames.Clear(); - mMaxTextLength = 0; - mDoubleByteText = PR_FALSE; - } - void AccumulateRunInfo(nsTextFrame* aFrame); - /** - * @return null to indicate either textrun construction failed or - * we constructed just a partial textrun to set up linebreaker and other - * state for following textruns. - */ - gfxTextRun* BuildTextRunForFrames(void* aTextBuffer); - void AssignTextRun(gfxTextRun* aTextRun); - nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex); - void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun, - PRBool aSuppressSink); - struct FindBoundaryState { - nsIFrame* mStopAtFrame; - nsTextFrame* mFirstTextFrame; - nsTextFrame* mLastTextFrame; - PRPackedBool mSeenTextRunBoundaryOnLaterLine; - PRPackedBool mSeenTextRunBoundaryOnThisLine; - PRPackedBool mSeenSpaceForLineBreakingOnThisLine; - }; - enum FindBoundaryResult { - FB_CONTINUE, - FB_STOPPED_AT_STOP_FRAME, - FB_FOUND_VALID_TEXTRUN_BOUNDARY - }; - FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState); - - PRBool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2); - - // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame - // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then - // continuations starting from mStartFrame are a sequence of in-flow frames). - struct MappedFlow { - nsTextFrame* mStartFrame; - nsTextFrame* mEndFrame; - // When we consider breaking between elements, the nearest common - // ancestor of the elements containing the characters is the one whose - // CSS 'white-space' property governs. So this records the nearest common - // ancestor of mStartFrame and the previous text frame, or null if there - // was no previous text frame on this line. - nsIFrame* mAncestorControllingInitialBreak; - - PRInt32 GetContentEnd() { - return mEndFrame ? mEndFrame->GetContentOffset() - : mStartFrame->GetFragment()->GetLength(); - } - }; - - class BreakSink : public nsILineBreakSink { - public: - BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, PRUint32 aOffsetIntoTextRun, - PRBool aExistingTextRun) : - mTextRun(aTextRun), mContext(aContext), - mOffsetIntoTextRun(aOffsetIntoTextRun), - mChangedBreaks(PR_FALSE), mExistingTextRun(aExistingTextRun) {} - - virtual void SetBreaks(PRUint32 aOffset, PRUint32 aLength, - PRPackedBool* aBreakBefore) { - if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength, - aBreakBefore, mContext)) { - mChangedBreaks = PR_TRUE; - // Be conservative and assume that some breaks have been set - mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS); - } - } - - virtual void SetCapitalization(PRUint32 aOffset, PRUint32 aLength, - PRPackedBool* aCapitalize) { - NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED, - "Text run should be transformed!"); - nsTransformedTextRun* transformedTextRun = - static_cast(mTextRun); - transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength, - aCapitalize, mContext); - } - - void Finish() { - NS_ASSERTION(!(mTextRun->GetFlags() & - (gfxTextRunWordCache::TEXT_UNUSED_FLAGS | - nsTextFrameUtils::TEXT_UNUSED_FLAG)), - "Flag set that should never be set! (memory safety error?)"); - if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) { - nsTransformedTextRun* transformedTextRun = - static_cast(mTextRun); - transformedTextRun->FinishSettingProperties(mContext); - } - } - - gfxTextRun* mTextRun; - gfxContext* mContext; - PRUint32 mOffsetIntoTextRun; - PRPackedBool mChangedBreaks; - PRPackedBool mExistingTextRun; - }; - -private: - nsAutoTArray mMappedFlows; - nsAutoTArray mLineBreakBeforeFrames; - nsAutoTArray,10> mBreakSinks; - nsAutoTArray mTextRunsToDelete; - nsLineBreaker mLineBreaker; - gfxTextRun* mCurrentFramesAllSameTextRun; - gfxContext* mContext; - nsIFrame* mLineContainer; - nsTextFrame* mLastFrame; - // The common ancestor of the current frame and the previous leaf frame - // on the line, or null if there was no previous leaf frame. - nsIFrame* mCommonAncestorWithLastFrame; - // mMaxTextLength is an upper bound on the size of the text in all mapped frames - PRUint32 mMaxTextLength; - PRPackedBool mDoubleByteText; - PRPackedBool mBidiEnabled; - PRPackedBool mStartOfLine; - PRPackedBool mSkipIncompleteTextRuns; - PRPackedBool mCanStopOnThisLine; - PRUint8 mNextRunContextInfo; - PRUint8 mCurrentRunContextInfo; -}; - -static nsIFrame* -FindLineContainer(nsIFrame* aFrame) -{ - while (aFrame && aFrame->CanContinueTextRun()) { - aFrame = aFrame->GetParent(); - } - return aFrame; -} - -static PRBool -TextContainsLineBreakerWhiteSpace(const void* aText, PRUint32 aLength, - PRBool aIsDoubleByte) -{ - PRUint32 i; - if (aIsDoubleByte) { - const PRUnichar* chars = static_cast(aText); - for (i = 0; i < aLength; ++i) { - if (nsLineBreaker::IsSpace(chars[i])) - return PR_TRUE; - } - return PR_FALSE; - } else { - const PRUint8* chars = static_cast(aText); - for (i = 0; i < aLength; ++i) { - if (nsLineBreaker::IsSpace(chars[i])) - return PR_TRUE; - } - return PR_FALSE; - } -} - -struct FrameTextTraversal { - // These fields identify which frames should be recursively scanned - // The first normal frame to scan (or null, if no such frame should be scanned) - nsIFrame* mFrameToScan; - // The first overflow frame to scan (or null, if no such frame should be scanned) - nsIFrame* mOverflowFrameToScan; - // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto - PRPackedBool mScanSiblings; - - // These identify the boundaries of the context required for - // line breaking or textrun construction - PRPackedBool mLineBreakerCanCrossFrameBoundary; - PRPackedBool mTextRunCanCrossFrameBoundary; - - nsIFrame* NextFrameToScan() { - nsIFrame* f; - if (mFrameToScan) { - f = mFrameToScan; - mFrameToScan = mScanSiblings ? f->GetNextSibling() : nsnull; - } else if (mOverflowFrameToScan) { - f = mOverflowFrameToScan; - mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nsnull; - } else { - f = nsnull; - } - return f; - } -}; - -static FrameTextTraversal -CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType) -{ - NS_ASSERTION(aType == aFrame->GetType(), "Wrong type"); - - FrameTextTraversal result; - - PRBool continuesTextRun = aFrame->CanContinueTextRun(); - if (aType == nsGkAtoms::placeholderFrame) { - // placeholders are "invisible", so a text run should be able to span - // across one. But don't descend into the out-of-flow. - result.mLineBreakerCanCrossFrameBoundary = PR_TRUE; - result.mOverflowFrameToScan = nsnull; - if (continuesTextRun) { - // ... Except for first-letter floats, which are really in-flow - // from the point of view of capitalization etc, so we'd better - // descend into them. But we actually need to break the textrun for - // first-letter floats since things look bad if, say, we try to make a - // ligature across the float boundary. - result.mFrameToScan = - (static_cast(aFrame))->GetOutOfFlowFrame(); - result.mScanSiblings = PR_FALSE; - result.mTextRunCanCrossFrameBoundary = PR_FALSE; - } else { - result.mFrameToScan = nsnull; - result.mTextRunCanCrossFrameBoundary = PR_TRUE; - } - } else { - if (continuesTextRun) { - result.mFrameToScan = aFrame->GetFirstChild(nsnull); - result.mOverflowFrameToScan = aFrame->GetFirstChild(nsGkAtoms::overflowList); - NS_WARN_IF_FALSE(!result.mOverflowFrameToScan, - "Scanning overflow inline frames is something we should avoid"); - result.mScanSiblings = PR_TRUE; - result.mTextRunCanCrossFrameBoundary = PR_TRUE; - result.mLineBreakerCanCrossFrameBoundary = PR_TRUE; - } else { - result.mFrameToScan = nsnull; - result.mOverflowFrameToScan = nsnull; - result.mTextRunCanCrossFrameBoundary = PR_FALSE; - result.mLineBreakerCanCrossFrameBoundary = PR_FALSE; - } - } - return result; -} - -BuildTextRunsScanner::FindBoundaryResult -BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState) -{ - nsIAtom* frameType = aFrame->GetType(); - nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame - ? static_cast(aFrame) : nsnull; - if (textFrame) { - if (aState->mLastTextFrame && - textFrame != aState->mLastTextFrame->GetNextInFlow() && - !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) { - aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE; - if (aState->mSeenSpaceForLineBreakingOnThisLine) - return FB_FOUND_VALID_TEXTRUN_BOUNDARY; - } - if (!aState->mFirstTextFrame) { - aState->mFirstTextFrame = textFrame; - } - aState->mLastTextFrame = textFrame; - } - - if (aFrame == aState->mStopAtFrame) - return FB_STOPPED_AT_STOP_FRAME; - - if (textFrame) { - if (!aState->mSeenSpaceForLineBreakingOnThisLine) { - const nsTextFragment* frag = textFrame->GetFragment(); - PRUint32 start = textFrame->GetContentOffset(); - const void* text = frag->Is2b() - ? static_cast(frag->Get2b() + start) - : static_cast(frag->Get1b() + start); - if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(), - frag->Is2b())) { - aState->mSeenSpaceForLineBreakingOnThisLine = PR_TRUE; - if (aState->mSeenTextRunBoundaryOnLaterLine) - return FB_FOUND_VALID_TEXTRUN_BOUNDARY; - } - } - return FB_CONTINUE; - } - - FrameTextTraversal traversal = - CanTextCrossFrameBoundary(aFrame, frameType); - if (!traversal.mTextRunCanCrossFrameBoundary) { - aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE; - if (aState->mSeenSpaceForLineBreakingOnThisLine) - return FB_FOUND_VALID_TEXTRUN_BOUNDARY; - } - - for (nsIFrame* f = traversal.NextFrameToScan(); f; - f = traversal.NextFrameToScan()) { - FindBoundaryResult result = FindBoundaries(f, aState); - if (result != FB_CONTINUE) - return result; - } - - if (!traversal.mTextRunCanCrossFrameBoundary) { - aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE; - if (aState->mSeenSpaceForLineBreakingOnThisLine) - return FB_FOUND_VALID_TEXTRUN_BOUNDARY; - } - - return FB_CONTINUE; -} - -// build text runs for the 200 lines following aForFrame, and stop after that -// when we get a chance. -#define NUM_LINES_TO_BUILD_TEXT_RUNS 200 - -/** - * General routine for building text runs. This is hairy because of the need - * to build text runs that span content nodes. - * - * @param aForFrameLine the line containing aForFrame; if null, we'll figure - * out the line (slowly) - * @param aLineContainer the line container containing aForFrame; if null, - * we'll walk the ancestors to find it. It's required to be non-null when - * aForFrameLine is non-null. - */ -static void -BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame, - nsIFrame* aLineContainer, - const nsLineList::iterator* aForFrameLine) -{ - NS_ASSERTION(aForFrame || aLineContainer, - "One of aForFrame or aLineContainer must be set!"); - NS_ASSERTION(!aForFrameLine || aLineContainer, - "line but no line container"); - - if (!aLineContainer) { - aLineContainer = FindLineContainer(aForFrame); - } else { - NS_ASSERTION(!aForFrame || - (aLineContainer == FindLineContainer(aForFrame) || - (aLineContainer->GetType() == nsGkAtoms::letterFrame && - aLineContainer->GetStyleDisplay()->IsFloating())), - "Wrong line container hint"); - } - - nsPresContext* presContext = aLineContainer->PresContext(); - BuildTextRunsScanner scanner(presContext, aContext, aLineContainer); - - nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer); - - if (!block) { - NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(), - "Breakable non-block line containers not supported"); - // Just loop through all the children of the linecontainer ... it's really - // just one line - scanner.SetAtStartOfLine(); - scanner.SetCommonAncestorWithLastFrame(nsnull); - nsIFrame* child = aLineContainer->GetFirstChild(nsnull); - while (child) { - scanner.ScanFrame(child); - child = child->GetNextSibling(); - } - // Set mStartOfLine so FlushFrames knows its textrun ends a line - scanner.SetAtStartOfLine(); - scanner.FlushFrames(PR_TRUE, PR_FALSE); - return; - } - - // Find the line containing aForFrame - - PRBool isValid = PR_TRUE; - nsBlockInFlowLineIterator backIterator(block, &isValid); - if (aForFrameLine) { - backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine, PR_FALSE); - } else { - backIterator = nsBlockInFlowLineIterator(block, aForFrame, &isValid); - NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us"); - NS_ASSERTION(backIterator.GetContainer() == block, - "Someone lied to us about the block"); - } - nsBlockFrame::line_iterator startLine = backIterator.GetLine(); - - // Find a line where we can start building text runs. We choose the last line - // where: - // -- there is a textrun boundary between the start of the line and the - // start of aForFrame - // -- there is a space between the start of the line and the textrun boundary - // (this is so we can be sure the line breaks will be set properly - // on the textruns we construct). - // The possibly-partial text runs up to and including the first space - // are not reconstructed. We construct partial text runs for that text --- - // for the sake of simplifying the code and feeding the linebreaker --- - // but we discard them instead of assigning them to frames. - // This is a little awkward because we traverse lines in the reverse direction - // but we traverse the frames in each line in the forward direction. - nsBlockInFlowLineIterator forwardIterator = backIterator; - nsTextFrame* stopAtFrame = aForFrame; - nsTextFrame* nextLineFirstTextFrame = nsnull; - PRBool seenTextRunBoundaryOnLaterLine = PR_FALSE; - PRBool mayBeginInTextRun = PR_TRUE; - while (PR_TRUE) { - forwardIterator = backIterator; - nsBlockFrame::line_iterator line = backIterator.GetLine(); - if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { - mayBeginInTextRun = PR_FALSE; - break; - } - - BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nsnull, nsnull, - seenTextRunBoundaryOnLaterLine, PR_FALSE, PR_FALSE }; - nsIFrame* child = line->mFirstChild; - PRBool foundBoundary = PR_FALSE; - PRInt32 i; - for (i = line->GetChildCount() - 1; i >= 0; --i) { - BuildTextRunsScanner::FindBoundaryResult result = - scanner.FindBoundaries(child, &state); - if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) { - foundBoundary = PR_TRUE; - break; - } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) { - break; - } - child = child->GetNextSibling(); - } - if (foundBoundary) - break; - if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame && - !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) { - // Found a usable textrun boundary at the end of the line - if (state.mSeenSpaceForLineBreakingOnThisLine) - break; - seenTextRunBoundaryOnLaterLine = PR_TRUE; - } else if (state.mSeenTextRunBoundaryOnThisLine) { - seenTextRunBoundaryOnLaterLine = PR_TRUE; - } - stopAtFrame = nsnull; - if (state.mFirstTextFrame) { - nextLineFirstTextFrame = state.mFirstTextFrame; - } - } - scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); - - // Now iterate over all text frames starting from the current line. First-in-flow - // text frames will be accumulated into textRunFrames as we go. When a - // text run boundary is required we flush textRunFrames ((re)building their - // gfxTextRuns as necessary). - PRBool seenStartLine = PR_FALSE; - PRUint32 linesAfterStartLine = 0; - do { - nsBlockFrame::line_iterator line = forwardIterator.GetLine(); - if (line->IsBlock()) - break; - line->SetInvalidateTextRuns(PR_FALSE); - scanner.SetAtStartOfLine(); - scanner.SetCommonAncestorWithLastFrame(nsnull); - nsIFrame* child = line->mFirstChild; - PRInt32 i; - for (i = line->GetChildCount() - 1; i >= 0; --i) { - scanner.ScanFrame(child); - child = child->GetNextSibling(); - } - if (line.get() == startLine.get()) { - seenStartLine = PR_TRUE; - } - if (seenStartLine) { - ++linesAfterStartLine; - if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) { - // Don't flush frames; we may be in the middle of a textrun - // that we can't end here. That's OK, we just won't build it. - // Note that we must already have finished the textrun for aForFrame, - // because we've seen the end of a textrun in a line after the line - // containing aForFrame. - scanner.FlushLineBreaks(nsnull); - // This flushes out mMappedFlows and mLineBreakBeforeFrames, which - // silences assertions in the scanner destructor. - scanner.ResetRunInfo(); - return; - } - } - } while (forwardIterator.Next()); - - // Set mStartOfLine so FlushFrames knows its textrun ends a line - scanner.SetAtStartOfLine(); - scanner.FlushFrames(PR_TRUE, PR_FALSE); -} - -static PRUnichar* -ExpandBuffer(PRUnichar* aDest, PRUint8* aSrc, PRUint32 aCount) -{ - while (aCount) { - *aDest = *aSrc; - ++aDest; - ++aSrc; - --aCount; - } - return aDest; -} - -PRBool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun) -{ - if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) - return mMappedFlows.Length() == 1 && - mMappedFlows[0].mStartFrame == static_cast(aTextRun->GetUserData()) && - mMappedFlows[0].mEndFrame == nsnull; - - TextRunUserData* userData = static_cast(aTextRun->GetUserData()); - if (userData->mMappedFlowCount != PRInt32(mMappedFlows.Length())) - return PR_FALSE; - PRUint32 i; - for (i = 0; i < mMappedFlows.Length(); ++i) { - if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame || - PRInt32(userData->mMappedFlows[i].mContentLength) != - mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset()) - return PR_FALSE; - } - return PR_TRUE; -} - -/** - * This gets called when we need to make a text run for the current list of - * frames. - */ -void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks, PRBool aSuppressTrailingBreak) -{ - gfxTextRun* textRun = nsnull; - if (!mMappedFlows.IsEmpty()) { - if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && - ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) == - ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) && - ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR) != 0) == - ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) && - IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { - // Optimization: We do not need to (re)build the textrun. - textRun = mCurrentFramesAllSameTextRun; - - // Feed this run's text into the linebreaker to provide context. This also - // updates mNextRunContextInfo appropriately. - SetupBreakSinksForTextRun(textRun, PR_TRUE, PR_FALSE); - mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE; - if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) { - mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE; - } - if (textRun->GetFlags() & gfxTextRunWordCache::TEXT_TRAILING_ARABICCHAR) { - mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR; - } - } else { - nsAutoTArray buffer; - if (!buffer.AppendElements(mMaxTextLength*(mDoubleByteText ? 2 : 1))) - return; - textRun = BuildTextRunForFrames(buffer.Elements()); - } - } - - if (aFlushLineBreaks) { - FlushLineBreaks(aSuppressTrailingBreak ? nsnull : textRun); - } - - mCanStopOnThisLine = PR_TRUE; - ResetRunInfo(); -} - -void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) -{ - PRBool trailingLineBreak; - nsresult rv = mLineBreaker.Reset(&trailingLineBreak); - // textRun may be null for various reasons, including because we constructed - // a partial textrun just to get the linebreaker and other state set up - // to build the next textrun. - if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) { - aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK); - } - - PRUint32 i; - for (i = 0; i < mBreakSinks.Length(); ++i) { - if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) { - // TODO cause frames associated with the textrun to be reflowed, if they - // aren't being reflowed already! - } - mBreakSinks[i]->Finish(); - } - mBreakSinks.Clear(); - - for (i = 0; i < mTextRunsToDelete.Length(); ++i) { - gfxTextRun* deleteTextRun = mTextRunsToDelete[i]; - gTextRuns->RemoveFromCache(deleteTextRun); - delete deleteTextRun; - } - mTextRunsToDelete.Clear(); -} - -void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) -{ - NS_ASSERTION(mMaxTextLength <= mMaxTextLength + aFrame->GetContentLength(), "integer overflow"); - mMaxTextLength += aFrame->GetContentLength(); - mDoubleByteText |= aFrame->GetFragment()->Is2b(); - mLastFrame = aFrame; - mCommonAncestorWithLastFrame = aFrame->GetParent(); - - MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; - NS_ASSERTION(mappedFlow->mStartFrame == aFrame || - mappedFlow->GetContentEnd() == aFrame->GetContentOffset(), - "Overlapping or discontiguous frames => BAD"); - mappedFlow->mEndFrame = static_cast(aFrame->GetNextContinuation()); - if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun()) { - mCurrentFramesAllSameTextRun = nsnull; - } - - if (mStartOfLine) { - mLineBreakBeforeFrames.AppendElement(aFrame); - mStartOfLine = PR_FALSE; - } -} - -static nscoord StyleToCoord(const nsStyleCoord& aCoord) -{ - if (eStyleUnit_Coord == aCoord.GetUnit()) { - return aCoord.GetCoordValue(); - } else { - return 0; - } -} - -static PRBool -HasTerminalNewline(const nsTextFrame* aFrame) -{ - if (aFrame->GetContentLength() == 0) - return PR_FALSE; - const nsTextFragment* frag = aFrame->GetFragment(); - return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n'; -} - -PRBool -BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2) -{ - if (mBidiEnabled && - NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2)) - return PR_FALSE; - - nsStyleContext* sc1 = aFrame1->GetStyleContext(); - const nsStyleText* textStyle1 = sc1->GetStyleText(); - // If the first frame ends in a preformatted newline, then we end the textrun - // here. This avoids creating giant textruns for an entire plain text file. - // Note that we create a single text frame for a preformatted text node, - // even if it has newlines in it, so typically we won't see trailing newlines - // until after reflow has broken up the frame into one (or more) frames per - // line. That's OK though. - if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1)) - return PR_FALSE; - - if (aFrame1->GetContent() == aFrame2->GetContent() && - aFrame1->GetNextInFlow() != aFrame2) { - // aFrame2 must be a non-fluid continuation of aFrame1. This can happen - // sometimes when the unicode-bidi property is used; the bidi resolver - // breaks text into different frames even though the text has the same - // direction. We can't allow these two frames to share the same textrun - // because that would violate our invariant that two flows in the same - // textrun have different content elements. - return PR_FALSE; - } - - nsStyleContext* sc2 = aFrame2->GetStyleContext(); - if (sc1 == sc2) - return PR_TRUE; - const nsStyleFont* fontStyle1 = sc1->GetStyleFont(); - const nsStyleFont* fontStyle2 = sc2->GetStyleFont(); - const nsStyleText* textStyle2 = sc2->GetStyleText(); - return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) && - sc1->GetStyleVisibility()->mLangGroup == sc2->GetStyleVisibility()->mLangGroup && - nsLayoutUtils::GetTextRunFlagsForStyle(sc1, textStyle1, fontStyle1) == - nsLayoutUtils::GetTextRunFlagsForStyle(sc2, textStyle2, fontStyle2); -} - -void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) -{ - // First check if we can extend the current mapped frame block. This is common. - if (mMappedFlows.Length() > 0) { - MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; - if (mappedFlow->mEndFrame == aFrame && - (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) { - NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, - "Flow-sibling of a text frame is not a text frame?"); - - // Don't do this optimization if mLastFrame has a terminal newline... - // it's quite likely preformatted and we might want to end the textrun here. - // This is almost always true: - if (mLastFrame->GetStyleContext() == aFrame->GetStyleContext() && - !HasTerminalNewline(mLastFrame)) { - AccumulateRunInfo(static_cast(aFrame)); - return; - } - } - } - - nsIAtom* frameType = aFrame->GetType(); - // Now see if we can add a new set of frames to the current textrun - if (frameType == nsGkAtoms::textFrame) { - nsTextFrame* frame = static_cast(aFrame); - - if (mLastFrame) { - if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) { - FlushFrames(PR_FALSE, PR_FALSE); - } else { - if (mLastFrame->GetContent() == frame->GetContent()) { - AccumulateRunInfo(frame); - return; - } - } - } - - MappedFlow* mappedFlow = mMappedFlows.AppendElement(); - if (!mappedFlow) - return; - - mappedFlow->mStartFrame = frame; - mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame; - - AccumulateRunInfo(frame); - if (mMappedFlows.Length() == 1) { - mCurrentFramesAllSameTextRun = frame->GetTextRun(); - mCurrentRunContextInfo = mNextRunContextInfo; - } - return; - } - - FrameTextTraversal traversal = - CanTextCrossFrameBoundary(aFrame, frameType); - PRBool isBR = frameType == nsGkAtoms::brFrame; - if (!traversal.mLineBreakerCanCrossFrameBoundary) { - // BR frames are special. We do not need or want to record a break opportunity - // before a BR frame. - FlushFrames(PR_TRUE, isBR); - mCommonAncestorWithLastFrame = aFrame; - mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; - mStartOfLine = PR_FALSE; - } else if (!traversal.mTextRunCanCrossFrameBoundary) { - FlushFrames(PR_FALSE, PR_FALSE); - } - - for (nsIFrame* f = traversal.NextFrameToScan(); f; - f = traversal.NextFrameToScan()) { - ScanFrame(f); - } - - if (!traversal.mLineBreakerCanCrossFrameBoundary) { - // Really if we're a BR frame this is unnecessary since descendInto will be - // false. In fact this whole "if" statement should move into the descendInto. - FlushFrames(PR_TRUE, isBR); - mCommonAncestorWithLastFrame = aFrame; - mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; - } else if (!traversal.mTextRunCanCrossFrameBoundary) { - FlushFrames(PR_FALSE, PR_FALSE); - } - - LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent()); -} - -nsTextFrame* -BuildTextRunsScanner::GetNextBreakBeforeFrame(PRUint32* aIndex) -{ - PRUint32 index = *aIndex; - if (index >= mLineBreakBeforeFrames.Length()) - return nsnull; - *aIndex = index + 1; - return static_cast(mLineBreakBeforeFrames.ElementAt(index)); -} - -static PRUint32 -GetSpacingFlags(nscoord spacing) -{ - return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0; -} - -static gfxFontGroup* -GetFontGroupForFrame(nsIFrame* aFrame, - nsIFontMetrics** aOutFontMetrics = nsnull) -{ - if (aOutFontMetrics) - *aOutFontMetrics = nsnull; - - nsCOMPtr metrics; - nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics)); - - if (!metrics) - return nsnull; - - nsIFontMetrics* metricsRaw = metrics; - if (aOutFontMetrics) { - *aOutFontMetrics = metricsRaw; - NS_ADDREF(*aOutFontMetrics); - } - nsIThebesFontMetrics* fm = static_cast(metricsRaw); - // XXX this is a bit bogus, we're releasing 'metrics' so the returned font-group - // might actually be torn down, although because of the way the device context - // caches font metrics, this seems to not actually happen. But we should fix - // this. - return fm->GetThebesFontGroup(); -} - -static already_AddRefed -GetReferenceRenderingContext(nsTextFrame* aTextFrame, nsIRenderingContext* aRC) -{ - nsCOMPtr tmp = aRC; - if (!tmp) { - nsresult rv = aTextFrame->PresContext()->PresShell()-> - CreateRenderingContext(aTextFrame, getter_AddRefs(tmp)); - if (NS_FAILED(rv)) - return nsnull; - } - - gfxContext* ctx = tmp->ThebesContext(); - NS_ADDREF(ctx); - return ctx; -} - -/** - * The returned textrun must be released via gfxTextRunCache::ReleaseTextRun - * or gfxTextRunCache::AutoTextRun. - */ -static gfxTextRun* -GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame) -{ - nsRefPtr ctx = aContext; - if (!ctx) { - ctx = GetReferenceRenderingContext(aTextFrame, nsnull); - } - if (!ctx) - return nsnull; - - gfxFontGroup* fontGroup = aTextRun->GetFontGroup(); - PRUint32 flags = gfxFontGroup::TEXT_IS_PERSISTENT; - - // only use U+2010 if it is supported by the first font in the group; - // it's better to use ASCII '-' from the primary font than to fall back to U+2010 - // from some other, possibly poorly-matching face - static const PRUnichar unicodeHyphen = 0x2010; - gfxFont *font = fontGroup->GetFontAt(0); - if (font && font->HasCharacter(unicodeHyphen)) { - return gfxTextRunCache::MakeTextRun(&unicodeHyphen, 1, fontGroup, ctx, - aTextRun->GetAppUnitsPerDevUnit(), flags); - } - - static const PRUint8 dash = '-'; - return gfxTextRunCache::MakeTextRun(&dash, 1, fontGroup, ctx, - aTextRun->GetAppUnitsPerDevUnit(), - flags); -} - -static gfxFont::Metrics -GetFirstFontMetrics(gfxFontGroup* aFontGroup) -{ - if (!aFontGroup) - return gfxFont::Metrics(); - gfxFont* font = aFontGroup->GetFontAt(0); - if (!font) - return gfxFont::Metrics(); - return font->GetMetrics(); -} - -PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0); -PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1); -PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2); -PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3); -PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4); - -static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] = -{ - nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal - nsTextFrameUtils::COMPRESS_NONE, // pre - nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap - nsTextFrameUtils::COMPRESS_NONE, // pre-wrap - nsTextFrameUtils::COMPRESS_WHITESPACE // pre-line -}; - -gfxTextRun* -BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer) -{ - gfxSkipCharsBuilder builder; - - const void* textPtr = aTextBuffer; - PRBool anySmallcapsStyle = PR_FALSE; - PRBool anyTextTransformStyle = PR_FALSE; - PRInt32 endOfLastContent = 0; - PRUint32 textFlags = nsTextFrameUtils::TEXT_NO_BREAKS; - - if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { - textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE; - } - if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { - textFlags |= gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR; - } - - nsAutoTArray textBreakPoints; - TextRunUserData dummyData; - TextRunMappedFlow dummyMappedFlow; - - TextRunUserData* userData; - TextRunUserData* userDataToDestroy; - // If the situation is particularly simple (and common) we don't need to - // allocate userData. - if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && - mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { - userData = &dummyData; - userDataToDestroy = nsnull; - dummyData.mMappedFlows = &dummyMappedFlow; - } else { - userData = static_cast - (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow))); - userDataToDestroy = userData; - userData->mMappedFlows = reinterpret_cast(userData + 1); - } - userData->mMappedFlowCount = mMappedFlows.Length(); - userData->mLastFlowIndex = 0; - - PRUint32 currentTransformedTextOffset = 0; - - PRUint32 nextBreakIndex = 0; - nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); - PRBool enabledJustification = mLineContainer && - mLineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY; - - PRUint32 i; - const nsStyleText* textStyle = nsnull; - const nsStyleFont* fontStyle = nsnull; - nsStyleContext* lastStyleContext = nsnull; - for (i = 0; i < mMappedFlows.Length(); ++i) { - MappedFlow* mappedFlow = &mMappedFlows[i]; - nsTextFrame* f = mappedFlow->mStartFrame; - - lastStyleContext = f->GetStyleContext(); - // Detect use of text-transform or font-variant anywhere in the run - textStyle = f->GetStyleText(); - if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) { - anyTextTransformStyle = PR_TRUE; - } - textFlags |= GetSpacingFlags(StyleToCoord(textStyle->mLetterSpacing)); - textFlags |= GetSpacingFlags(textStyle->mWordSpacing); - nsTextFrameUtils::CompressionMode compression = - CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace]; - if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) { - textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; - } - fontStyle = f->GetStyleFont(); - if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) { - anySmallcapsStyle = PR_TRUE; - } - - // Figure out what content is included in this flow. - nsIContent* content = f->GetContent(); - const nsTextFragment* frag = f->GetFragment(); - PRInt32 contentStart = mappedFlow->mStartFrame->GetContentOffset(); - PRInt32 contentEnd = mappedFlow->GetContentEnd(); - PRInt32 contentLength = contentEnd - contentStart; - - TextRunMappedFlow* newFlow = &userData->mMappedFlows[i]; - newFlow->mStartFrame = mappedFlow->mStartFrame; - newFlow->mDOMOffsetToBeforeTransformOffset = builder.GetCharCount() - - mappedFlow->mStartFrame->GetContentOffset(); - newFlow->mContentLength = contentLength; - - while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) { - textBreakPoints.AppendElement( - nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset); - nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); - } - - PRUint32 analysisFlags; - if (frag->Is2b()) { - NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); - PRUnichar* bufStart = static_cast(aTextBuffer); - PRUnichar* bufEnd = nsTextFrameUtils::TransformText( - frag->Get2b() + contentStart, contentLength, bufStart, - compression, &mNextRunContextInfo, &builder, &analysisFlags); - aTextBuffer = bufEnd; - } else { - if (mDoubleByteText) { - // Need to expand the text. First transform it into a temporary buffer, - // then expand. - nsAutoTArray tempBuf; - if (!tempBuf.AppendElements(contentLength)) { - DestroyUserData(userDataToDestroy); - return nsnull; - } - PRUint8* bufStart = tempBuf.Elements(); - PRUint8* end = nsTextFrameUtils::TransformText( - reinterpret_cast(frag->Get1b()) + contentStart, contentLength, - bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags); - aTextBuffer = ExpandBuffer(static_cast(aTextBuffer), - tempBuf.Elements(), end - tempBuf.Elements()); - } else { - PRUint8* bufStart = static_cast(aTextBuffer); - PRUint8* end = nsTextFrameUtils::TransformText( - reinterpret_cast(frag->Get1b()) + contentStart, contentLength, - bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags); - aTextBuffer = end; - } - } - textFlags |= analysisFlags; - - currentTransformedTextOffset = - (static_cast(aTextBuffer) - static_cast(textPtr)) >> mDoubleByteText; - - endOfLastContent = contentEnd; - } - - // Check for out-of-memory in gfxSkipCharsBuilder - if (!builder.IsOK()) { - DestroyUserData(userDataToDestroy); - return nsnull; - } - - void* finalUserData; - if (userData == &dummyData) { - textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW; - userData = nsnull; - finalUserData = mMappedFlows[0].mStartFrame; - } else { - finalUserData = userData; - } - - PRUint32 transformedLength = currentTransformedTextOffset; - - // Now build the textrun - nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame; - gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame); - if (!fontGroup) { - DestroyUserData(userDataToDestroy); - return nsnull; - } - - if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) { - textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; - } - if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) { - textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS; - } - if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) { - textFlags |= gfxTextRunFactory::TEXT_IS_RTL; - } - if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { - textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE; - } - if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { - textFlags |= gfxTextRunWordCache::TEXT_TRAILING_ARABICCHAR; - } - // ContinueTextRunAcrossFrames guarantees that it doesn't matter which - // frame's style is used, so use the last frame's - textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext, - textStyle, fontStyle); - // XXX this is a bit of a hack. For performance reasons, if we're favouring - // performance over quality, don't try to get accurate glyph extents. - if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) { - textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX; - } - - gfxSkipChars skipChars; - skipChars.TakeFrom(&builder); - // Convert linebreak coordinates to transformed string offsets - NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(), - "Didn't find all the frames to break-before..."); - gfxSkipCharsIterator iter(skipChars); - nsAutoTArray textBreakPointsAfterTransform; - for (i = 0; i < textBreakPoints.Length(); ++i) { - nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, - iter.ConvertOriginalToSkipped(textBreakPoints[i])); - } - if (mStartOfLine) { - nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, - transformedLength); - } - - // Setup factory chain - nsAutoPtr transformingFactory; - if (anySmallcapsStyle) { - transformingFactory = new nsFontVariantTextRunFactory(); - } - if (anyTextTransformStyle) { - transformingFactory = - new nsCaseTransformTextRunFactory(transformingFactory.forget()); - } - nsTArray styles; - if (transformingFactory) { - iter.SetOriginalOffset(0); - for (i = 0; i < mMappedFlows.Length(); ++i) { - MappedFlow* mappedFlow = &mMappedFlows[i]; - nsTextFrame* f; - for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; - f = static_cast(f->GetNextContinuation())) { - PRUint32 offset = iter.GetSkippedOffset(); - iter.AdvanceOriginal(f->GetContentLength()); - PRUint32 end = iter.GetSkippedOffset(); - nsStyleContext* sc = f->GetStyleContext(); - PRUint32 j; - for (j = offset; j < end; ++j) { - styles.AppendElement(sc); - } - } - } - textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED; - NS_ASSERTION(iter.GetSkippedOffset() == transformedLength, - "We didn't cover all the characters in the text run!"); - } - - gfxTextRun* textRun; - gfxTextRunFactory::Parameters params = - { mContext, finalUserData, &skipChars, - textBreakPointsAfterTransform.Elements(), textBreakPointsAfterTransform.Length(), - firstFrame->PresContext()->AppUnitsPerDevPixel() }; - - if (mDoubleByteText) { - const PRUnichar* text = static_cast(textPtr); - if (transformingFactory) { - textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, - fontGroup, textFlags, styles.Elements()); - if (textRun) { - // ownership of the factory has passed to the textrun - transformingFactory.forget(); - } - } else { - textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); - } - } else { - const PRUint8* text = static_cast(textPtr); - textFlags |= gfxFontGroup::TEXT_IS_8BIT; - if (transformingFactory) { - textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, - fontGroup, textFlags, styles.Elements()); - if (textRun) { - // ownership of the factory has passed to the textrun - transformingFactory.forget(); - } - } else { - textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); - } - } - if (!textRun) { - DestroyUserData(userDataToDestroy); - return nsnull; - } - - // We have to set these up after we've created the textrun, because - // the breaks may be stored in the textrun during this very call. - // This is a bit annoying because it requires another loop over the frames - // making up the textrun, but I don't see a way to avoid this. - SetupBreakSinksForTextRun(textRun, PR_FALSE, mSkipIncompleteTextRuns); - - if (mSkipIncompleteTextRuns) { - mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr, - transformedLength, mDoubleByteText); - // Arrange for this textrun to be deleted the next time the linebreaker - // is flushed out - mTextRunsToDelete.AppendElement(textRun); - // Since we're doing to destroy the user data now, avoid a dangling - // pointer. Strictly speaking we don't need to do this since it should - // not be used (since this textrun will not be used and will be - // itself deleted soon), but it's always better to not have dangling - // pointers around. - textRun->SetUserData(nsnull); - DestroyUserData(userDataToDestroy); - return nsnull; - } - - // Actually wipe out the textruns associated with the mapped frames and associate - // those frames with this text run. - AssignTextRun(textRun); - return textRun; -} - -static PRBool -HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText, - PRInt32 aContentEndOffset, - const gfxSkipCharsIterator& aIterator) -{ - if (!aIterator.IsOriginalCharSkipped()) - return PR_FALSE; - - gfxSkipCharsIterator iter = aIterator; - PRInt32 frameContentOffset = aFrame->GetContentOffset(); - const nsTextFragment* frag = aFrame->GetFragment(); - while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) { - if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) - return PR_TRUE; - ++frameContentOffset; - iter.AdvanceOriginal(1); - } - return PR_FALSE; -} - -void -BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, - PRBool aIsExistingTextRun, - PRBool aSuppressSink) -{ - // textruns have uniform language - nsIAtom* lang = mMappedFlows[0].mStartFrame->GetStyleVisibility()->mLangGroup; - // We keep this pointed at the skip-chars data for the current mappedFlow. - // This lets us cheaply check whether the flow has compressed initial - // whitespace... - gfxSkipCharsIterator iter(aTextRun->GetSkipChars()); - - PRUint32 i; - for (i = 0; i < mMappedFlows.Length(); ++i) { - MappedFlow* mappedFlow = &mMappedFlows[i]; - PRUint32 offset = iter.GetSkippedOffset(); - gfxSkipCharsIterator iterNext = iter; - iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() - - mappedFlow->mStartFrame->GetContentOffset()); - - nsAutoPtr* breakSink = mBreakSinks.AppendElement( - new BreakSink(aTextRun, mContext, offset, aIsExistingTextRun)); - if (!breakSink || !*breakSink) - return; - - PRUint32 length = iterNext.GetSkippedOffset() - offset; - PRUint32 flags = 0; - nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak; - if (!initialBreakController) { - initialBreakController = mLineContainer; - } - if (!initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) { - flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL; - } - nsTextFrame* startFrame = mappedFlow->mStartFrame; - const nsStyleText* textStyle = startFrame->GetStyleText(); - if (!textStyle->WhiteSpaceCanWrap()) { - flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE; - } - if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) { - flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS; - } - if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) { - flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION; - } - - if (HasCompressedLeadingWhitespace(startFrame, textStyle, - mappedFlow->GetContentEnd(), iter)) { - mLineBreaker.AppendInvisibleWhitespace(flags); - } - - if (length > 0) { - BreakSink* sink = aSuppressSink ? nsnull : (*breakSink).get(); - if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) { - mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset, - length, flags, sink); - } else { - mLineBreaker.AppendText(lang, aTextRun->GetTextUnicode() + offset, - length, flags, sink); - } - } - - iter = iterNext; - } -} - -void -BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun) -{ - PRUint32 i; - for (i = 0; i < mMappedFlows.Length(); ++i) { - MappedFlow* mappedFlow = &mMappedFlows[i]; - nsTextFrame* startFrame = mappedFlow->mStartFrame; - nsTextFrame* endFrame = mappedFlow->mEndFrame; - nsTextFrame* f; - for (f = startFrame; f != endFrame; - f = static_cast(f->GetNextContinuation())) { -#ifdef DEBUG_roc - if (f->GetTextRun()) { - gfxTextRun* textRun = f->GetTextRun(); - if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { - if (mMappedFlows[0].mStartFrame != static_cast(textRun->GetUserData())) { - NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!"); - } - } else { - TextRunUserData* userData = - static_cast(textRun->GetUserData()); - - if (PRUint32(userData->mMappedFlowCount) >= mMappedFlows.Length() || - userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame != - mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) { - NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!"); - } - } - } -#endif - f->ClearTextRun(); - f->SetTextRun(aTextRun); - } - // Set this bit now; we can't set it any earlier because - // f->ClearTextRun() might clear it out. - startFrame->AddStateBits(TEXT_IN_TEXTRUN_USER_DATA); - } -} - -gfxSkipCharsIterator -nsTextFrame::EnsureTextRun(gfxContext* aReferenceContext, nsIFrame* aLineContainer, - const nsLineList::iterator* aLine, - PRUint32* aFlowEndInTextRun) -{ - if (mTextRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) { - if (mTextRun->GetExpirationState()->IsTracked()) { - gTextRuns->MarkUsed(mTextRun); - } - } else { - nsRefPtr ctx = aReferenceContext; - if (!ctx) { - ctx = GetReferenceRenderingContext(this, nsnull); - } - if (ctx) { - BuildTextRuns(ctx, this, aLineContainer, aLine); - } - if (!mTextRun) { - // A text run was not constructed for this frame. This is bad. The caller - // will check mTextRun. - static const gfxSkipChars emptySkipChars; - return gfxSkipCharsIterator(emptySkipChars, 0); - } - } - - if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { - if (aFlowEndInTextRun) { - *aFlowEndInTextRun = mTextRun->GetLength(); - } - return gfxSkipCharsIterator(mTextRun->GetSkipChars(), 0, mContentOffset); - } - - TextRunUserData* userData = static_cast(mTextRun->GetUserData()); - // Find the flow that contains us - PRInt32 direction; - PRInt32 startAt = userData->mLastFlowIndex; - // Search first forward and then backward from the current position - for (direction = 1; direction >= -1; direction -= 2) { - PRInt32 i; - for (i = startAt; 0 <= i && i < userData->mMappedFlowCount; i += direction) { - TextRunMappedFlow* flow = &userData->mMappedFlows[i]; - if (flow->mStartFrame->GetContent() == mContent) { - // Since textruns can only contain one flow for a given content element, - // this must be our flow. - userData->mLastFlowIndex = i; - gfxSkipCharsIterator iter(mTextRun->GetSkipChars(), - flow->mDOMOffsetToBeforeTransformOffset, mContentOffset); - if (aFlowEndInTextRun) { - if (i + 1 < userData->mMappedFlowCount) { - gfxSkipCharsIterator end(mTextRun->GetSkipChars()); - *aFlowEndInTextRun = end.ConvertOriginalToSkipped( - flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset); - } else { - *aFlowEndInTextRun = mTextRun->GetLength(); - } - } - return iter; - } - ++flow; - } - startAt = userData->mLastFlowIndex - 1; - } - NS_ERROR("Can't find flow containing this frame???"); - static const gfxSkipChars emptySkipChars; - return gfxSkipCharsIterator(emptySkipChars, 0); -} - -static PRUint32 -GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText, - PRUint32 aStart, PRUint32 aEnd, - gfxSkipCharsIterator* aIterator) -{ - aIterator->SetSkippedOffset(aEnd); - while (aIterator->GetSkippedOffset() > aStart) { - aIterator->AdvanceSkipped(-1); - if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText)) - return aIterator->GetSkippedOffset() + 1; - } - return aStart; -} - -nsTextFrame::TrimmedOffsets -nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag, - PRBool aTrimAfter) -{ - NS_ASSERTION(mTextRun, "Need textrun here"); - // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS - // to be set correctly. - NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW), - "Can only call this on frames that have been reflowed"); - NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW), - "Can only call this on frames that are not being reflowed"); - - TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() }; - const nsStyleText* textStyle = GetStyleText(); - // Note that pre-line newlines should still allow us to trim spaces - // for display - if (textStyle->WhiteSpaceIsSignificant()) - return offsets; - - if (GetStateBits() & TEXT_START_OF_LINE) { - PRInt32 whitespaceCount = - GetTrimmableWhitespaceCount(aFrag, - offsets.mStart, offsets.mLength, 1); - offsets.mStart += whitespaceCount; - offsets.mLength -= whitespaceCount; - } - - if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) { - // This treats a trailing 'pre-line' newline as trimmable. That's fine, - // it's actually what we want since we want whitespace before it to - // be trimmed. - PRInt32 whitespaceCount = - GetTrimmableWhitespaceCount(aFrag, - offsets.GetEnd() - 1, offsets.mLength, -1); - offsets.mLength -= whitespaceCount; - } - return offsets; -} - -/* - * Currently only Unicode characters below 0x10000 have their spacing modified - * by justification. If characters above 0x10000 turn out to need - * justification spacing, that will require extra work. Currently, - * this function must not include 0xd800 to 0xdbff because these characters - * are surrogates. - */ -static PRBool IsJustifiableCharacter(const nsTextFragment* aFrag, PRInt32 aPos, - PRBool aLangIsCJ) -{ - PRUnichar ch = aFrag->CharAt(aPos); - if (ch == '\n' || ch == '\t') - return PR_TRUE; - if (ch == ' ') { - // Don't justify spaces that are combined with diacriticals - if (!aFrag->Is2b()) - return PR_TRUE; - return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( - aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); - } - if (ch < 0x2150u) - return PR_FALSE; - if (aLangIsCJ && ( - (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators - (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics - (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats - (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, - // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators, - // Miscellaneous Symbols and Arrows - (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement, - // Ideographic Description Characters, CJK Symbols and Punctuation, - // Hiragana, Katakana, Bopomofo - (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, - // Enclosed CJK Letters and Months, CJK Compatibility, - // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, - // CJK Unified Ideographs, Yi Syllables, Yi Radicals - (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs - (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part) - )) - return PR_TRUE; - return PR_FALSE; -} - -static void ClearMetrics(nsHTMLReflowMetrics& aMetrics) -{ - aMetrics.width = 0; - aMetrics.height = 0; - aMetrics.ascent = 0; -} - -static PRInt32 FindChar(const nsTextFragment* frag, - PRInt32 aOffset, PRInt32 aLength, PRUnichar ch) -{ - PRInt32 i = 0; - if (frag->Is2b()) { - const PRUnichar* str = frag->Get2b() + aOffset; - for (; i < aLength; ++i) { - if (*str == ch) - return i + aOffset; - ++str; - } - } else { - if (PRUint16(ch) <= 0xFF) { - const char* str = frag->Get1b() + aOffset; - const void* p = memchr(str, ch, aLength); - if (p) - return (static_cast(p) - str) + aOffset; - } - } - return -1; -} - -static PRBool IsChineseJapaneseLangGroup(nsIFrame* aFrame) -{ - nsIAtom* langGroup = aFrame->GetStyleVisibility()->mLangGroup; - return langGroup == nsGkAtoms::Japanese - || langGroup == nsGkAtoms::Chinese - || langGroup == nsGkAtoms::Taiwanese - || langGroup == nsGkAtoms::HongKongChinese; -} - -#ifdef DEBUG -static PRBool IsInBounds(const gfxSkipCharsIterator& aStart, PRInt32 aContentLength, - PRUint32 aOffset, PRUint32 aLength) { - if (aStart.GetSkippedOffset() > aOffset) - return PR_FALSE; - if (aContentLength == PR_INT32_MAX) - return PR_TRUE; - gfxSkipCharsIterator iter(aStart); - iter.AdvanceOriginal(aContentLength); - return iter.GetSkippedOffset() >= aOffset + aLength; -} -#endif - -class NS_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider { -public: - /** - * Use this constructor for reflow, when we don't know what text is - * really mapped by the frame and we have a lot of other data around. - * - * @param aLength can be PR_INT32_MAX to indicate we cover all the text - * associated with aFrame up to where its flow chain ends in the given - * textrun. If PR_INT32_MAX is passed, justification and hyphen-related methods - * cannot be called, nor can GetOriginalLength(). - */ - PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle, - const nsTextFragment* aFrag, nsTextFrame* aFrame, - const gfxSkipCharsIterator& aStart, PRInt32 aLength, - nsIFrame* aLineContainer, - nscoord aOffsetFromBlockOriginForTabs) - : mTextRun(aTextRun), mFontGroup(nsnull), - mTextStyle(aTextStyle), mFrag(aFrag), - mLineContainer(aLineContainer), - mFrame(aFrame), mStart(aStart), mTempIterator(aStart), - mTabWidths(nsnull), mLength(aLength), - mWordSpacing(mTextStyle->mWordSpacing), - mLetterSpacing(StyleToCoord(mTextStyle->mLetterSpacing)), - mJustificationSpacing(0), - mHyphenWidth(-1), - mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), - mReflowing(PR_TRUE) - { - NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?"); - } - - /** - * Use this constructor after the frame has been reflowed and we don't - * have other data around. Gets everything from the frame. EnsureTextRun - * *must* be called before this!!! - */ - PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart) - : mTextRun(aFrame->GetTextRun()), mFontGroup(nsnull), - mTextStyle(aFrame->GetStyleText()), - mFrag(aFrame->GetFragment()), - mLineContainer(nsnull), - mFrame(aFrame), mStart(aStart), mTempIterator(aStart), - mTabWidths(nsnull), - mLength(aFrame->GetContentLength()), - mWordSpacing(mTextStyle->mWordSpacing), - mLetterSpacing(StyleToCoord(mTextStyle->mLetterSpacing)), - mJustificationSpacing(0), - mHyphenWidth(-1), - mOffsetFromBlockOriginForTabs(0), - mReflowing(PR_FALSE) - { - NS_ASSERTION(mTextRun, "Textrun not initialized!"); - } - - // Call this after construction if you're not going to reflow the text - void InitializeForDisplay(PRBool aTrimAfter); - - virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing); - virtual gfxFloat GetHyphenWidth(); - virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, - PRPackedBool* aBreakBefore); - - void GetSpacingInternal(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing, - PRBool aIgnoreTabs); - - /** - * Count the number of justifiable characters in the given DOM range - */ - PRUint32 ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength); - /** - * Find the start and end of the justifiable characters. Does not depend on the - * position of aStart or aEnd, although it's most efficient if they are near the - * start and end of the text frame. - */ - void FindJustificationRange(gfxSkipCharsIterator* aStart, - gfxSkipCharsIterator* aEnd); - - const nsStyleText* GetStyleText() { return mTextStyle; } - nsTextFrame* GetFrame() { return mFrame; } - // This may not be equal to the frame offset/length in because we may have - // adjusted for whitespace trimming according to the state bits set in the frame - // (for the static provider) - const gfxSkipCharsIterator& GetStart() { return mStart; } - // May return PR_INT32_MAX if that was given to the constructor - PRUint32 GetOriginalLength() { - NS_ASSERTION(mLength != PR_INT32_MAX, "Length not known"); - return mLength; - } - const nsTextFragment* GetFragment() { return mFrag; } - - gfxFontGroup* GetFontGroup() { - if (!mFontGroup) - InitFontGroupAndFontMetrics(); - return mFontGroup; - } - - nsIFontMetrics* GetFontMetrics() { - if (!mFontMetrics) - InitFontGroupAndFontMetrics(); - return mFontMetrics; - } - - gfxFloat* GetTabWidths(PRUint32 aTransformedStart, PRUint32 aTransformedLength); - - const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; } - -protected: - void SetupJustificationSpacing(); - - void InitFontGroupAndFontMetrics() { - mFontGroup = GetFontGroupForFrame(mFrame, getter_AddRefs(mFontMetrics)); - } - - gfxTextRun* mTextRun; - gfxFontGroup* mFontGroup; - nsCOMPtr mFontMetrics; - const nsStyleText* mTextStyle; - const nsTextFragment* mFrag; - nsIFrame* mLineContainer; - nsTextFrame* mFrame; - gfxSkipCharsIterator mStart; // Offset in original and transformed string - gfxSkipCharsIterator mTempIterator; - - // Widths for each transformed string character, 0 for non-tab characters. - // Either null, or pointing to the frame's tabWidthProperty. - nsTArray* mTabWidths; - - PRInt32 mLength; // DOM string length, may be PR_INT32_MAX - gfxFloat mWordSpacing; // space for each whitespace char - gfxFloat mLetterSpacing; // space for each letter - gfxFloat mJustificationSpacing; - gfxFloat mHyphenWidth; - gfxFloat mOffsetFromBlockOriginForTabs; - PRPackedBool mReflowing; -}; - -PRUint32 -PropertyProvider::ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength) -{ - // Scan non-skipped characters and count justifiable chars. - nsSkipCharsRunIterator - run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); - run.SetOriginalOffset(aOffset); - PRUint32 justifiableChars = 0; - PRBool isCJK = IsChineseJapaneseLangGroup(mFrame); - while (run.NextRun()) { - PRInt32 i; - for (i = 0; i < run.GetRunLength(); ++i) { - justifiableChars += - IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK); - } - } - return justifiableChars; -} - -/** - * Finds the offset of the first character of the cluster containing aPos - */ -static void FindClusterStart(gfxTextRun* aTextRun, PRInt32 aOriginalStart, - gfxSkipCharsIterator* aPos) -{ - while (aPos->GetOriginalOffset() > aOriginalStart) { - if (aPos->IsOriginalCharSkipped() || - aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { - break; - } - aPos->AdvanceOriginal(-1); - } -} - -/** - * Finds the offset of the last character of the cluster containing aPos - */ -static void FindClusterEnd(gfxTextRun* aTextRun, PRInt32 aOriginalEnd, - gfxSkipCharsIterator* aPos) -{ - NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd, - "character outside string"); - aPos->AdvanceOriginal(1); - while (aPos->GetOriginalOffset() < aOriginalEnd) { - if (aPos->IsOriginalCharSkipped() || - aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { - break; - } - aPos->AdvanceOriginal(1); - } - aPos->AdvanceOriginal(-1); -} - -// aStart, aLength in transformed string offsets -void -PropertyProvider::GetSpacing(PRUint32 aStart, PRUint32 aLength, - Spacing* aSpacing) -{ - GetSpacingInternal(aStart, aLength, aSpacing, - (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0); -} - -static PRBool -CanAddSpacingAfter(gfxTextRun* aTextRun, PRUint32 aOffset) -{ - if (aOffset + 1 >= aTextRun->GetLength()) - return PR_TRUE; - return aTextRun->IsClusterStart(aOffset + 1) && - aTextRun->IsLigatureGroupStart(aOffset + 1); -} - -void -PropertyProvider::GetSpacingInternal(PRUint32 aStart, PRUint32 aLength, - Spacing* aSpacing, PRBool aIgnoreTabs) -{ - NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); - - PRUint32 index; - for (index = 0; index < aLength; ++index) { - aSpacing[index].mBefore = 0.0; - aSpacing[index].mAfter = 0.0; - } - - // Find our offset into the original+transformed string - gfxSkipCharsIterator start(mStart); - start.SetSkippedOffset(aStart); - - // First, compute the word and letter spacing - if (mWordSpacing || mLetterSpacing) { - // Iterate over non-skipped characters - nsSkipCharsRunIterator - run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); - while (run.NextRun()) { - PRUint32 runOffsetInSubstring = run.GetSkippedOffset() - aStart; - PRInt32 i; - gfxSkipCharsIterator iter = run.GetPos(); - for (i = 0; i < run.GetRunLength(); ++i) { - if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) { - // End of a cluster, not in a ligature: put letter-spacing after it - aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing; - } - if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), - mTextStyle)) { - // It kinda sucks, but space characters can be part of clusters, - // and even still be whitespace (I think!) - iter.SetSkippedOffset(run.GetSkippedOffset() + i); - FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), - &iter); - aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing; - } - } - } - } - - // Now add tab spacing, if there is any - if (!aIgnoreTabs) { - gfxFloat* tabs = GetTabWidths(aStart, aLength); - if (tabs) { - for (index = 0; index < aLength; ++index) { - aSpacing[index].mAfter += tabs[index]; - } - } - } - - // Now add in justification spacing - if (mJustificationSpacing) { - gfxFloat halfJustificationSpace = mJustificationSpacing/2; - // Scan non-skipped characters and adjust justifiable chars, adding - // justification space on either side of the cluster - PRBool isCJK = IsChineseJapaneseLangGroup(mFrame); - gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart); - FindJustificationRange(&justificationStart, &justificationEnd); - - nsSkipCharsRunIterator - run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); - while (run.NextRun()) { - PRInt32 i; - gfxSkipCharsIterator iter = run.GetPos(); - PRInt32 runOriginalOffset = run.GetOriginalOffset(); - for (i = 0; i < run.GetRunLength(); ++i) { - PRInt32 iterOriginalOffset = runOriginalOffset + i; - if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) { - iter.SetOriginalOffset(iterOriginalOffset); - FindClusterStart(mTextRun, runOriginalOffset, &iter); - PRUint32 clusterFirstChar = iter.GetSkippedOffset(); - FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter); - PRUint32 clusterLastChar = iter.GetSkippedOffset(); - // Only apply justification to characters before justificationEnd - if (clusterFirstChar >= justificationStart.GetSkippedOffset() && - clusterLastChar < justificationEnd.GetSkippedOffset()) { - aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace; - aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace; - } - } - } - } - } -} - -static void TabWidthDestructor(void* aObject, nsIAtom* aProp, void* aValue, - void* aData) -{ - delete static_cast*>(aValue); -} - -static gfxFloat -ComputeTabWidthAppUnits(nsIFrame* aLineContainer, gfxTextRun* aTextRun) -{ - // Round the space width when converting to appunits the same way - // textruns do - gfxFloat spaceWidthAppUnits = - NS_roundf(GetFirstFontMetrics( - GetFontGroupForFrame(aLineContainer)).spaceWidth * - aTextRun->GetAppUnitsPerDevUnit()); - return 8*spaceWidthAppUnits; -} - -// aX and the result are in whole appunits. -static gfxFloat -AdvanceToNextTab(gfxFloat aX, nsIFrame* aLineContainer, - gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth) -{ - if (*aCachedTabWidth < 0) { - *aCachedTabWidth = ComputeTabWidthAppUnits(aLineContainer, aTextRun); - } - - // Advance aX to the next multiple of *aCachedTabWidth. We must advance - // by at least 1 appunit. - // XXX should we make this 1 CSS pixel? - return NS_ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth); -} - -gfxFloat* -PropertyProvider::GetTabWidths(PRUint32 aStart, PRUint32 aLength) -{ - if (!mTabWidths) { - if (!mReflowing) { - mTabWidths = static_cast*> - (mFrame->GetProperty(nsGkAtoms::tabWidthProperty)); - if (!mTabWidths) { - NS_WARNING("We need precomputed tab widths, but they're not here..."); - return nsnull; - } - } else { - if (!mLineContainer) { - // Intrinsic width computation does its own tab processing. We - // just don't do anything here. - return nsnull; - } - - nsAutoPtr > tabs(new nsTArray()); - if (!tabs) - return nsnull; - nsresult rv = mFrame->SetProperty(nsGkAtoms::tabWidthProperty, tabs, - TabWidthDestructor, nsnull); - if (NS_FAILED(rv)) - return nsnull; - mTabWidths = tabs.forget(); - } - } - - PRUint32 startOffset = mStart.GetSkippedOffset(); - PRUint32 tabsEnd = startOffset + mTabWidths->Length(); - if (tabsEnd < aStart + aLength) { - if (!mReflowing) { - NS_WARNING("We need precomputed tab widths, but we don't have enough..."); - return nsnull; - } - - if (!mTabWidths->AppendElements(aStart + aLength - tabsEnd)) - return nsnull; - - gfxFloat tabWidth = -1; - for (PRUint32 i = tabsEnd; i < aStart + aLength; ++i) { - Spacing spacing; - GetSpacingInternal(i, 1, &spacing, PR_TRUE); - mOffsetFromBlockOriginForTabs += spacing.mBefore; - - if (mTextRun->GetChar(i) != '\t') { - (*mTabWidths)[i - startOffset] = 0; - if (mTextRun->IsClusterStart(i)) { - PRUint32 clusterEnd = i + 1; - while (clusterEnd < mTextRun->GetLength() && - !mTextRun->IsClusterStart(clusterEnd)) { - ++clusterEnd; - } - mOffsetFromBlockOriginForTabs += - mTextRun->GetAdvanceWidth(i, clusterEnd - i, nsnull); - } - } else { - double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs, - mLineContainer, mTextRun, &tabWidth); - (*mTabWidths)[i - startOffset] = nextTab - mOffsetFromBlockOriginForTabs; - mOffsetFromBlockOriginForTabs = nextTab; - } - - mOffsetFromBlockOriginForTabs += spacing.mAfter; - } - } - - return mTabWidths->Elements() + aStart - startOffset; -} - -gfxFloat -PropertyProvider::GetHyphenWidth() -{ - if (mHyphenWidth < 0) { - gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, mFrame)); - mHyphenWidth = mLetterSpacing; - if (hyphenTextRun.get()) { - mHyphenWidth += hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull); - } - } - return mHyphenWidth; -} - -void -PropertyProvider::GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, - PRPackedBool* aBreakBefore) -{ - NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); - NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length"); - - if (!mTextStyle->WhiteSpaceCanWrap()) { - memset(aBreakBefore, PR_FALSE, aLength); - return; - } - - // Iterate through the original-string character runs - nsSkipCharsRunIterator - run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); - run.SetSkippedOffset(aStart); - // We need to visit skipped characters so that we can detect SHY - run.SetVisitSkipped(); - - PRInt32 prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1; - PRBool allowHyphenBreakBeforeNextChar = - prevTrailingCharOffset >= mStart.GetOriginalOffset() && - prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength && - mFrag->CharAt(prevTrailingCharOffset) == CH_SHY; - - while (run.NextRun()) { - NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs"); - if (run.IsSkipped()) { - // Check if there's a soft hyphen which would let us hyphenate before - // the next non-skipped character. Don't look at soft hyphens followed - // by other skipped characters, we won't use them. - allowHyphenBreakBeforeNextChar = - mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY; - } else { - PRInt32 runOffsetInSubstring = run.GetSkippedOffset() - aStart; - memset(aBreakBefore + runOffsetInSubstring, 0, run.GetRunLength()); - // Don't allow hyphen breaks at the start of the line - aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar && - (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) || - run.GetSkippedOffset() > mStart.GetSkippedOffset()); - allowHyphenBreakBeforeNextChar = PR_FALSE; - } - } -} - -void -PropertyProvider::InitializeForDisplay(PRBool aTrimAfter) -{ - nsTextFrame::TrimmedOffsets trimmed = - mFrame->GetTrimmedOffsets(mFrag, aTrimAfter); - mStart.SetOriginalOffset(trimmed.mStart); - mLength = trimmed.mLength; - SetupJustificationSpacing(); -} - -static PRUint32 GetSkippedDistance(const gfxSkipCharsIterator& aStart, - const gfxSkipCharsIterator& aEnd) -{ - return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset(); -} - -void -PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart, - gfxSkipCharsIterator* aEnd) -{ - NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length"); - NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null"); - - aStart->SetOriginalOffset(mStart.GetOriginalOffset()); - aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength); - - // Ignore first cluster at start of line for justification purposes - if (mFrame->GetStateBits() & TEXT_START_OF_LINE) { - while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) { - aStart->AdvanceOriginal(1); - if (!aStart->IsOriginalCharSkipped() && - mTextRun->IsClusterStart(aStart->GetSkippedOffset())) - break; - } - } - - // Ignore trailing cluster at end of line for justification purposes - if (mFrame->GetStateBits() & TEXT_END_OF_LINE) { - while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) { - aEnd->AdvanceOriginal(-1); - if (!aEnd->IsOriginalCharSkipped() && - mTextRun->IsClusterStart(aEnd->GetSkippedOffset())) - break; - } - } -} - -void -PropertyProvider::SetupJustificationSpacing() -{ - NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length"); - - if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) - return; - - gfxSkipCharsIterator start(mStart), end(mStart); - end.AdvanceOriginal(mLength); - gfxSkipCharsIterator realEnd(end); - FindJustificationRange(&start, &end); - - PRInt32 justifiableCharacters = - ComputeJustifiableCharacters(start.GetOriginalOffset(), - end.GetOriginalOffset() - start.GetOriginalOffset()); - if (justifiableCharacters == 0) { - // Nothing to do, nothing is justifiable and we shouldn't have any - // justification space assigned - return; - } - - gfxFloat naturalWidth = - mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(), - GetSkippedDistance(mStart, realEnd), this); - if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) { - gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, mFrame)); - if (hyphenTextRun.get()) { - naturalWidth += - hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull); - } - } - gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth; - if (totalJustificationSpace <= 0) { - // No space available - return; - } - - mJustificationSpacing = totalJustificationSpace/justifiableCharacters; -} - -//---------------------------------------------------------------------- - -// Helper class for managing blinking text - -class nsBlinkTimer : public nsITimerCallback -{ -public: - nsBlinkTimer(); - virtual ~nsBlinkTimer(); - - NS_DECL_ISUPPORTS - - void AddFrame(nsPresContext* aPresContext, nsIFrame* aFrame); - - PRBool RemoveFrame(nsIFrame* aFrame); - - PRInt32 FrameCount(); - - void Start(); - - void Stop(); - - NS_DECL_NSITIMERCALLBACK - - static nsresult AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame); - static nsresult RemoveBlinkFrame(nsIFrame* aFrame); - - static PRBool GetBlinkIsOff() { return sState == 3; } - -protected: - - struct FrameData { - nsPresContext* mPresContext; // pres context associated with the frame - nsIFrame* mFrame; - - - FrameData(nsPresContext* aPresContext, - nsIFrame* aFrame) - : mPresContext(aPresContext), mFrame(aFrame) {} - }; - - class FrameDataComparator { - public: - PRBool Equals(const FrameData& aTimer, nsIFrame* const& aFrame) const { - return aTimer.mFrame == aFrame; - } - }; - - nsCOMPtr mTimer; - nsTArray mFrames; - nsPresContext* mPresContext; - -protected: - - static nsBlinkTimer* sTextBlinker; - static PRUint32 sState; // 0-2 == on; 3 == off - -}; - -nsBlinkTimer* nsBlinkTimer::sTextBlinker = nsnull; -PRUint32 nsBlinkTimer::sState = 0; - -#ifdef NOISY_BLINK -static PRTime gLastTick; -#endif - -nsBlinkTimer::nsBlinkTimer() -{ -} - -nsBlinkTimer::~nsBlinkTimer() -{ - Stop(); - sTextBlinker = nsnull; -} - -void nsBlinkTimer::Start() -{ - nsresult rv; - mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - if (NS_OK == rv) { - mTimer->InitWithCallback(this, 250, nsITimer::TYPE_REPEATING_PRECISE); - } -} - -void nsBlinkTimer::Stop() -{ - if (nsnull != mTimer) { - mTimer->Cancel(); - mTimer = nsnull; - } -} - -NS_IMPL_ISUPPORTS1(nsBlinkTimer, nsITimerCallback) - -void nsBlinkTimer::AddFrame(nsPresContext* aPresContext, nsIFrame* aFrame) { - mFrames.AppendElement(FrameData(aPresContext, aFrame)); - if (1 == mFrames.Length()) { - Start(); - } -} - -PRBool nsBlinkTimer::RemoveFrame(nsIFrame* aFrame) { - mFrames.RemoveElement(aFrame, FrameDataComparator()); - - if (mFrames.IsEmpty()) { - Stop(); - } - return PR_TRUE; -} - -PRInt32 nsBlinkTimer::FrameCount() { - return PRInt32(mFrames.Length()); -} - -NS_IMETHODIMP nsBlinkTimer::Notify(nsITimer *timer) -{ - // Toggle blink state bit so that text code knows whether or not to - // render. All text code shares the same flag so that they all blink - // in unison. - sState = (sState + 1) % 4; - if (sState == 1 || sState == 2) - // States 0, 1, and 2 are all the same. - return NS_OK; - -#ifdef NOISY_BLINK - PRTime now = PR_Now(); - char buf[50]; - PRTime delta; - LL_SUB(delta, now, gLastTick); - gLastTick = now; - PR_snprintf(buf, sizeof(buf), "%lldusec", delta); - printf("%s\n", buf); -#endif - - PRUint32 i, n = mFrames.Length(); - for (i = 0; i < n; i++) { - FrameData& frameData = mFrames.ElementAt(i); - - // Determine damaged area and tell view manager to redraw it - // blink doesn't blink outline ... I hope - nsRect bounds(nsPoint(0, 0), frameData.mFrame->GetSize()); - frameData.mFrame->Invalidate(bounds); - } - return NS_OK; -} - - -// static -nsresult nsBlinkTimer::AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame) -{ - if (!sTextBlinker) - { - sTextBlinker = new nsBlinkTimer; - if (!sTextBlinker) return NS_ERROR_OUT_OF_MEMORY; - } - - NS_ADDREF(sTextBlinker); - - sTextBlinker->AddFrame(aPresContext, aFrame); - return NS_OK; -} - - -// static -nsresult nsBlinkTimer::RemoveBlinkFrame(nsIFrame* aFrame) -{ - NS_ASSERTION(sTextBlinker, "Should have blink timer here"); - - nsBlinkTimer* blinkTimer = sTextBlinker; // copy so we can call NS_RELEASE on it - if (!blinkTimer) return NS_OK; - - blinkTimer->RemoveFrame(aFrame); - NS_RELEASE(blinkTimer); - - return NS_OK; -} - -//---------------------------------------------------------------------- - -static nscolor -EnsureDifferentColors(nscolor colorA, nscolor colorB) -{ - if (colorA == colorB) { - nscolor res; - res = NS_RGB(NS_GET_R(colorA) ^ 0xff, - NS_GET_G(colorA) ^ 0xff, - NS_GET_B(colorA) ^ 0xff); - return res; - } - return colorA; -} - -//----------------------------------------------------------------------------- - -static nscolor -DarkenColor(nscolor aColor) -{ - PRUint16 hue, sat, value; - PRUint8 alpha; - - // convert the RBG to HSV so we can get the lightness (which is the v) - NS_RGB2HSV(aColor, hue, sat, value, alpha); - - // The goal here is to send white to black while letting colored - // stuff stay colored... So we adopt the following approach. - // Something with sat = 0 should end up with value = 0. Something - // with a high sat can end up with a high value and it's ok.... At - // the same time, we don't want to make things lighter. Do - // something simple, since it seems to work. - if (value > sat) { - value = sat; - // convert this color back into the RGB color space. - NS_HSV2RGB(aColor, hue, sat, value, alpha); - } - return aColor; -} - -// Check whether we should darken text colors. We need to do this if -// background images and colors are being suppressed, because that means -// light text will not be visible against the (presumed light-colored) background. -static PRBool -ShouldDarkenColors(nsPresContext* aPresContext) -{ - return !aPresContext->GetBackgroundColorDraw() && - !aPresContext->GetBackgroundImageDraw(); -} - -nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame) - : mFrame(aFrame), - mPresContext(aFrame->PresContext()), - mInitCommonColors(PR_FALSE), - mInitSelectionColors(PR_FALSE) -{ - for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mSelectionStyle); i++) - mSelectionStyle[i].mInit = PR_FALSE; -} - -PRBool -nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor) -{ - InitCommonColors(); - - // If the combination of selection background color and frame background color - // is sufficient contrast, don't exchange the selection colors. - PRInt32 backLuminosityDifference = - NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor); - if (backLuminosityDifference >= mSufficientContrast) - return PR_FALSE; - - // Otherwise, we should use the higher-contrast color for the selection - // background color. - PRInt32 foreLuminosityDifference = - NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor); - if (backLuminosityDifference < foreLuminosityDifference) { - nscolor tmpColor = *aForeColor; - *aForeColor = *aBackColor; - *aBackColor = tmpColor; - return PR_TRUE; - } - return PR_FALSE; -} - -nscolor -nsTextPaintStyle::GetTextColor() -{ - nscolor color = mFrame->GetStyleColor()->mColor; - if (ShouldDarkenColors(mPresContext)) { - color = DarkenColor(color); - } - return color; -} - -PRBool -nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor, - nscolor* aBackColor) -{ - NS_ASSERTION(aForeColor, "aForeColor is null"); - NS_ASSERTION(aBackColor, "aBackColor is null"); - - if (!InitSelectionColors()) - return PR_FALSE; - - *aForeColor = mSelectionTextColor; - *aBackColor = mSelectionBGColor; - return PR_TRUE; -} - -void -nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor, - nscolor* aBackColor) -{ - NS_ASSERTION(aForeColor, "aForeColor is null"); - NS_ASSERTION(aBackColor, "aBackColor is null"); - - nsILookAndFeel* look = mPresContext->LookAndFeel(); - nscolor foreColor, backColor; - look->GetColor(nsILookAndFeel::eColor_TextHighlightBackground, - backColor); - look->GetColor(nsILookAndFeel::eColor_TextHighlightForeground, - foreColor); - EnsureSufficientContrast(&foreColor, &backColor); - *aForeColor = foreColor; - *aBackColor = backColor; -} - -void -nsTextPaintStyle::GetIMESelectionColors(PRInt32 aIndex, - nscolor* aForeColor, - nscolor* aBackColor) -{ - NS_ASSERTION(aForeColor, "aForeColor is null"); - NS_ASSERTION(aBackColor, "aBackColor is null"); - NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); - - nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); - *aForeColor = selectionStyle->mTextColor; - *aBackColor = selectionStyle->mBGColor; -} - -PRBool -nsTextPaintStyle::GetSelectionUnderlineForPaint(PRInt32 aIndex, - nscolor* aLineColor, - float* aRelativeSize, - PRUint8* aStyle) -{ - NS_ASSERTION(aLineColor, "aLineColor is null"); - NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); - NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); - - nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); - if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE || - selectionStyle->mUnderlineColor == NS_TRANSPARENT || - selectionStyle->mUnderlineRelativeSize <= 0.0f) - return PR_FALSE; - - *aLineColor = selectionStyle->mUnderlineColor; - *aRelativeSize = selectionStyle->mUnderlineRelativeSize; - *aStyle = selectionStyle->mUnderlineStyle; - return PR_TRUE; -} - -void -nsTextPaintStyle::InitCommonColors() -{ - if (mInitCommonColors) - return; - - nsStyleContext* sc = mFrame->GetStyleContext(); - - nsStyleContext* bgContext = - nsCSSRendering::FindNonTransparentBackground(sc); - NS_ASSERTION(bgContext, "Cannot find NonTransparentBackground."); - const nsStyleBackground* bg = bgContext->GetStyleBackground(); - - nscolor defaultBgColor = mPresContext->DefaultBackgroundColor(); - mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, - bg->mBackgroundColor); - - if (bgContext->GetStyleDisplay()->mAppearance) { - // Assume a native widget has sufficient contrast always - mSufficientContrast = 0; - mInitCommonColors = PR_TRUE; - return; - } - - NS_ASSERTION(NS_GET_A(defaultBgColor) == 255, - "default background color is not opaque"); - - nsILookAndFeel* look = mPresContext->LookAndFeel(); - nscolor defaultWindowBackgroundColor, selectionTextColor, selectionBGColor; - look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, - selectionBGColor); - look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, - selectionTextColor); - look->GetColor(nsILookAndFeel::eColor_WindowBackground, - defaultWindowBackgroundColor); - - mSufficientContrast = - PR_MIN(PR_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE, - NS_LUMINOSITY_DIFFERENCE(selectionTextColor, - selectionBGColor)), - NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, - selectionBGColor)); - - mInitCommonColors = PR_TRUE; -} - -static nsIContent* -FindElementAncestor(nsINode* aNode) -{ - while (aNode && !aNode->IsNodeOfType(nsINode::eELEMENT)) { - aNode = aNode->GetParent(); - } - return static_cast(aNode); -} - -PRBool -nsTextPaintStyle::InitSelectionColors() -{ - if (mInitSelectionColors) - return PR_TRUE; - - PRInt16 selectionFlags; - PRInt16 selectionStatus = mFrame->GetSelectionStatus(&selectionFlags); - if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) || - selectionStatus < nsISelectionController::SELECTION_ON) { - // Not displaying the normal selection. - // We're not caching this fact, so every call to GetSelectionColors - // will come through here. We could avoid this, but it's not really worth it. - return PR_FALSE; - } - - mInitSelectionColors = PR_TRUE; - - nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame); - nsIContent* selectionContent = FindElementAncestor(nonGeneratedAncestor->GetContent()); - - if (selectionContent && - selectionStatus == nsISelectionController::SELECTION_ON) { - nsRefPtr sc = nsnull; - sc = mPresContext->StyleSet()-> - ProbePseudoStyleFor(selectionContent, nsCSSPseudoElements::mozSelection, - mFrame->GetStyleContext()); - // Use -moz-selection pseudo class. - if (sc) { - const nsStyleBackground* bg = sc->GetStyleBackground(); - mSelectionBGColor = bg->mBackgroundColor; - mSelectionTextColor = sc->GetStyleColor()->mColor; - return PR_TRUE; - } - } - - nsILookAndFeel* look = mPresContext->LookAndFeel(); - - nscolor selectionBGColor; - look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, - selectionBGColor); - - if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) { - look->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundAttention, - mSelectionBGColor); - mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, - selectionBGColor); - } else if (selectionStatus != nsISelectionController::SELECTION_ON) { - look->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundDisabled, - mSelectionBGColor); - mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, - selectionBGColor); - } else { - mSelectionBGColor = selectionBGColor; - } - - look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, - mSelectionTextColor); - - // On MacOS X, we don't exchange text color and BG color. - if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { - mSelectionTextColor = EnsureDifferentColors(mFrame->GetStyleColor()->mColor, - mSelectionBGColor); - } else { - EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor); - } - return PR_TRUE; -} - -nsTextPaintStyle::nsSelectionStyle* -nsTextPaintStyle::GetSelectionStyle(PRInt32 aIndex) -{ - InitSelectionStyle(aIndex); - return &mSelectionStyle[aIndex]; -} - -struct StyleIDs { - nsILookAndFeel::nsColorID mForeground, mBackground, mLine; - nsILookAndFeel::nsMetricID mLineStyle; - nsILookAndFeel::nsMetricFloatID mLineRelativeSize; -}; -static StyleIDs SelectionStyleIDs[] = { - { nsILookAndFeel::eColor_IMERawInputForeground, - nsILookAndFeel::eColor_IMERawInputBackground, - nsILookAndFeel::eColor_IMERawInputUnderline, - nsILookAndFeel::eMetric_IMERawInputUnderlineStyle, - nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, - { nsILookAndFeel::eColor_IMESelectedRawTextForeground, - nsILookAndFeel::eColor_IMESelectedRawTextBackground, - nsILookAndFeel::eColor_IMESelectedRawTextUnderline, - nsILookAndFeel::eMetric_IMESelectedRawTextUnderlineStyle, - nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, - { nsILookAndFeel::eColor_IMEConvertedTextForeground, - nsILookAndFeel::eColor_IMEConvertedTextBackground, - nsILookAndFeel::eColor_IMEConvertedTextUnderline, - nsILookAndFeel::eMetric_IMEConvertedTextUnderlineStyle, - nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, - { nsILookAndFeel::eColor_IMESelectedConvertedTextForeground, - nsILookAndFeel::eColor_IMESelectedConvertedTextBackground, - nsILookAndFeel::eColor_IMESelectedConvertedTextUnderline, - nsILookAndFeel::eMetric_IMESelectedConvertedTextUnderline, - nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, - { nsILookAndFeel::eColor_LAST_COLOR, - nsILookAndFeel::eColor_LAST_COLOR, - nsILookAndFeel::eColor_SpellCheckerUnderline, - nsILookAndFeel::eMetric_SpellCheckerUnderlineStyle, - nsILookAndFeel::eMetricFloat_SpellCheckerUnderlineRelativeSize } -}; - -static PRUint8 sUnderlineStyles[] = { - nsCSSRendering::DECORATION_STYLE_NONE, // NS_UNDERLINE_STYLE_NONE 0 - nsCSSRendering::DECORATION_STYLE_DOTTED, // NS_UNDERLINE_STYLE_DOTTED 1 - nsCSSRendering::DECORATION_STYLE_DASHED, // NS_UNDERLINE_STYLE_DASHED 2 - nsCSSRendering::DECORATION_STYLE_SOLID, // NS_UNDERLINE_STYLE_SOLID 3 - nsCSSRendering::DECORATION_STYLE_DOUBLE, // NS_UNDERLINE_STYLE_DOUBLE 4 - nsCSSRendering::DECORATION_STYLE_WAVY // NS_UNDERLINE_STYLE_WAVY 5 -}; - -void -nsTextPaintStyle::InitSelectionStyle(PRInt32 aIndex) -{ - NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid"); - nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex]; - if (selectionStyle->mInit) - return; - - StyleIDs* styleIDs = &SelectionStyleIDs[aIndex]; - - nsILookAndFeel* look = mPresContext->LookAndFeel(); - nscolor foreColor, backColor; - if (styleIDs->mForeground == nsILookAndFeel::eColor_LAST_COLOR) { - foreColor = NS_SAME_AS_FOREGROUND_COLOR; - } else { - look->GetColor(styleIDs->mForeground, foreColor); - } - if (styleIDs->mBackground == nsILookAndFeel::eColor_LAST_COLOR) { - backColor = NS_TRANSPARENT; - } else { - look->GetColor(styleIDs->mBackground, backColor); - } - - // Convert special color to actual color - NS_ASSERTION(foreColor != NS_TRANSPARENT, - "foreColor cannot be NS_TRANSPARENT"); - NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR, - "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR"); - NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR, - "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR"); - - foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor); - - if (NS_GET_A(backColor) > 0) - EnsureSufficientContrast(&foreColor, &backColor); - - nscolor lineColor; - float relativeSize; - PRUint8 lineStyle; - GetSelectionUnderline(mPresContext, aIndex, - &lineColor, &relativeSize, &lineStyle); - lineColor = GetResolvedForeColor(lineColor, foreColor, backColor); - - selectionStyle->mTextColor = foreColor; - selectionStyle->mBGColor = backColor; - selectionStyle->mUnderlineColor = lineColor; - selectionStyle->mUnderlineStyle = lineStyle; - selectionStyle->mUnderlineRelativeSize = relativeSize; - selectionStyle->mInit = PR_TRUE; -} - -/* static */ PRBool -nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext, - PRInt32 aIndex, - nscolor* aLineColor, - float* aRelativeSize, - PRUint8* aStyle) -{ - NS_ASSERTION(aPresContext, "aPresContext is null"); - NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); - NS_ASSERTION(aStyle, "aStyle is null"); - NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); - - nsILookAndFeel* look = aPresContext->LookAndFeel(); - - StyleIDs& styleID = SelectionStyleIDs[aIndex]; - nscolor color; - float size; - PRInt32 style; - - look->GetColor(styleID.mLine, color); - look->GetMetric(styleID.mLineStyle, style); - if (!NS_IS_VALID_UNDERLINE_STYLE(style)) { - NS_ERROR("Invalid underline style value is specified"); - style = NS_UNDERLINE_STYLE_SOLID; - } - look->GetMetric(styleID.mLineRelativeSize, size); - - NS_ASSERTION(size, "selection underline relative size must be larger than 0"); - - if (aLineColor) { - *aLineColor = color; - } - *aRelativeSize = size; - *aStyle = sUnderlineStyles[style]; - - return sUnderlineStyles[style] != nsCSSRendering::DECORATION_STYLE_NONE && - color != NS_TRANSPARENT && - size > 0.0f; -} - -inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) -{ - nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), - NS_GET_G(aForeColor), - NS_GET_B(aForeColor), - (PRUint8)(255 * 0.4f)); - // Don't use true alpha color for readability. - return NS_ComposeColors(aBackColor, foreColor); -} - -nscolor -nsTextPaintStyle::GetResolvedForeColor(nscolor aColor, - nscolor aDefaultForeColor, - nscolor aBackColor) -{ - if (aColor == NS_SAME_AS_FOREGROUND_COLOR) - return aDefaultForeColor; - - if (aColor != NS_40PERCENT_FOREGROUND_COLOR) - return aColor; - - // Get actual background color - nscolor actualBGColor = aBackColor; - if (actualBGColor == NS_TRANSPARENT) { - InitCommonColors(); - actualBGColor = mFrameBackgroundColor; - } - return Get40PercentColor(aDefaultForeColor, actualBGColor); -} - -//----------------------------------------------------------------------------- - -#ifdef ACCESSIBILITY -NS_IMETHODIMP nsTextFrame::GetAccessible(nsIAccessible** aAccessible) -{ - if (IsEmpty()) { - nsAutoString renderedWhitespace; - GetRenderedText(&renderedWhitespace, nsnull, nsnull, 0, 1); - if (renderedWhitespace.IsEmpty()) { - return NS_ERROR_FAILURE; - } - } - - nsCOMPtr accService = do_GetService("@mozilla.org/accessibilityService;1"); - - if (accService) { - return accService->CreateHTMLTextAccessible(static_cast(this), aAccessible); - } - return NS_ERROR_FAILURE; -} -#endif - - -//----------------------------------------------------------------------------- -NS_IMETHODIMP -nsTextFrame::Init(nsIContent* aContent, - nsIFrame* aParent, - nsIFrame* aPrevInFlow) -{ - NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!"); - NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT), - "Bogus content!"); - - if (!PresContext()->IsDynamic()) { - AddStateBits(TEXT_BLINK_ON_OR_PRINTING); - } - - // Since our content has a frame now, this flag is no longer needed. - aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE); - // We're not a continuing frame. - // mContentOffset = 0; not necessary since we get zeroed out at init - return nsFrame::Init(aContent, aParent, aPrevInFlow); -} - -void -nsTextFrame::Destroy() -{ - // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or - // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame - // type might be changing. Not clear whether it's worth it. - ClearTextRun(); - if (mNextContinuation) { - mNextContinuation->SetPrevInFlow(nsnull); - } - // Let the base class destroy the frame - nsFrame::Destroy(); -} - -class nsContinuingTextFrame : public nsTextFrame { -public: - NS_DECL_FRAMEARENA_HELPERS - - friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); - - NS_IMETHOD Init(nsIContent* aContent, - nsIFrame* aParent, - nsIFrame* aPrevInFlow); - - virtual void Destroy(); - - virtual nsIFrame* GetPrevContinuation() const { - return mPrevContinuation; - } - NS_IMETHOD SetPrevContinuation(nsIFrame* aPrevContinuation) { - NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(), - "setting a prev continuation with incorrect type!"); - NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this), - "creating a loop in continuation chain!"); - mPrevContinuation = aPrevContinuation; - RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); - return NS_OK; - } - virtual nsIFrame* GetPrevInFlowVirtual() const { return GetPrevInFlow(); } - nsIFrame* GetPrevInFlow() const { - return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nsnull; - } - NS_IMETHOD SetPrevInFlow(nsIFrame* aPrevInFlow) { - NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(), - "setting a prev in flow with incorrect type!"); - NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this), - "creating a loop in continuation chain!"); - mPrevContinuation = aPrevInFlow; - AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); - return NS_OK; - } - virtual nsIFrame* GetFirstInFlow() const; - virtual nsIFrame* GetFirstContinuation() const; - - virtual void AddInlineMinWidth(nsIRenderingContext *aRenderingContext, - InlineMinWidthData *aData); - virtual void AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, - InlinePrefWidthData *aData); - - virtual nsresult GetRenderedText(nsAString* aString = nsnull, - gfxSkipChars* aSkipChars = nsnull, - gfxSkipCharsIterator* aSkipIter = nsnull, - PRUint32 aSkippedStartOffset = 0, - PRUint32 aSkippedMaxLength = PR_UINT32_MAX) - { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only - -protected: - nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} - nsIFrame* mPrevContinuation; -}; - -NS_IMETHODIMP -nsContinuingTextFrame::Init(nsIContent* aContent, - nsIFrame* aParent, - nsIFrame* aPrevInFlow) -{ - NS_ASSERTION(aPrevInFlow, "Must be a continuation!"); - - if (!PresContext()->IsDynamic()) { - AddStateBits(TEXT_BLINK_ON_OR_PRINTING); - } - - // NOTE: bypassing nsTextFrame::Init!!! - nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow); - -#ifdef IBMBIDI - nsTextFrame* nextContinuation = - static_cast(aPrevInFlow->GetNextContinuation()); -#endif // IBMBIDI - // Hook the frame into the flow - SetPrevInFlow(aPrevInFlow); - aPrevInFlow->SetNextInFlow(this); - nsTextFrame* prev = static_cast(aPrevInFlow); - mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint(); - NS_ASSERTION(mContentOffset < PRInt32(GetFragment()->GetLength()), - "Creating ContinuingTextFrame, but there is no more content"); - if (prev->GetStyleContext() != GetStyleContext()) { - // We're taking part of prev's text, and its style may be different - // so clear its textrun which may no longer be valid (and don't set ours) - prev->ClearTextRun(); - } else { - mTextRun = prev->GetTextRun(); - } -#ifdef IBMBIDI - if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) { - nsPropertyTable *propTable = PresContext()->PropertyTable(); - propTable->SetProperty(this, nsGkAtoms::embeddingLevel, - propTable->GetProperty(aPrevInFlow, nsGkAtoms::embeddingLevel), - nsnull, nsnull); - propTable->SetProperty(this, nsGkAtoms::baseLevel, - propTable->GetProperty(aPrevInFlow, nsGkAtoms::baseLevel), - nsnull, nsnull); - propTable->SetProperty(this, nsGkAtoms::charType, - propTable->GetProperty(aPrevInFlow, nsGkAtoms::charType), - nsnull, nsnull); - if (nextContinuation) { - SetNextContinuation(nextContinuation); - nextContinuation->SetPrevContinuation(this); - // Adjust next-continuations' content offset as needed. - while (nextContinuation && - nextContinuation->GetContentOffset() < mContentOffset) { - NS_ASSERTION( - propTable->GetProperty(this, nsGkAtoms::embeddingLevel) == - propTable->GetProperty(nextContinuation, nsGkAtoms::embeddingLevel) && - propTable->GetProperty(this, nsGkAtoms::baseLevel) == - propTable->GetProperty(nextContinuation, nsGkAtoms::baseLevel) && - propTable->GetProperty(this, nsGkAtoms::charType) == - propTable->GetProperty(nextContinuation, nsGkAtoms::charType), - "stealing text from different type of BIDI continuation"); - nextContinuation->mContentOffset = mContentOffset; - nextContinuation = static_cast(nextContinuation->GetNextContinuation()); - } - } - mState |= NS_FRAME_IS_BIDI; - } // prev frame is bidi -#endif // IBMBIDI - - return rv; -} - -void -nsContinuingTextFrame::Destroy() -{ - // The text associated with this frame will become associated with our - // prev-continuation. If that means the text has changed style, then - // we need to wipe out the text run for the text. - // Note that mPrevContinuation can be null if we're destroying the whole - // frame chain from the start to the end. - // If this frame is mentioned in the userData for a textrun (say - // because there's a direction change at the start of this frame), then - // we have to clear the textrun because we're going away and the - // textrun had better not keep a dangling reference to us. - if ((GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA) || - !mPrevContinuation || - mPrevContinuation->GetStyleContext() != GetStyleContext()) { - ClearTextRun(); - // Clear the previous continuation's text run also, so that it can rebuild - // the text run to include our text. - if (mPrevContinuation) { - (static_cast(mPrevContinuation))->ClearTextRun(); - } - } - nsSplittableFrame::RemoveFromFlow(this); - // Let the base class destroy the frame - nsFrame::Destroy(); -} - -nsIFrame* -nsContinuingTextFrame::GetFirstInFlow() const -{ - // Can't cast to |nsContinuingTextFrame*| because the first one isn't. - nsIFrame *firstInFlow, - *previous = const_cast - (static_cast(this)); - do { - firstInFlow = previous; - previous = firstInFlow->GetPrevInFlow(); - } while (previous); - return firstInFlow; -} - -nsIFrame* -nsContinuingTextFrame::GetFirstContinuation() const -{ - // Can't cast to |nsContinuingTextFrame*| because the first one isn't. - nsIFrame *firstContinuation, - *previous = const_cast - (static_cast(mPrevContinuation)); - - NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?"); - - do { - firstContinuation = previous; - previous = firstContinuation->GetPrevContinuation(); - } while (previous); - return firstContinuation; -} - -// XXX Do we want to do all the work for the first-in-flow or do the -// work for each part? (Be careful of first-letter / first-line, though, -// especially first-line!) Doing all the work on the first-in-flow has -// the advantage of avoiding the potential for incremental reflow bugs, -// but depends on our maintining the frame tree in reasonable ways even -// for edge cases (block-within-inline splits, nextBidi, etc.) - -// XXX We really need to make :first-letter happen during frame -// construction. - -// Needed for text frames in XUL. -/* virtual */ nscoord -nsTextFrame::GetMinWidth(nsIRenderingContext *aRenderingContext) -{ - return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext); -} - -// Needed for text frames in XUL. -/* virtual */ nscoord -nsTextFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext) -{ - return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext); -} - -/* virtual */ void -nsContinuingTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext, - InlineMinWidthData *aData) -{ - // Do nothing, since the first-in-flow accounts for everything. - return; -} - -/* virtual */ void -nsContinuingTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, - InlinePrefWidthData *aData) -{ - // Do nothing, since the first-in-flow accounts for everything. - return; -} - -static void -DestroySelectionDetails(SelectionDetails* aDetails) -{ - while (aDetails) { - SelectionDetails* next = aDetails->mNext; - delete aDetails; - aDetails = next; - } -} - -//---------------------------------------------------------------------- - -#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) -static void -VerifyNotDirty(nsFrameState state) -{ - PRBool isZero = state & NS_FRAME_FIRST_REFLOW; - PRBool isDirty = state & NS_FRAME_IS_DIRTY; - if (!isZero && isDirty) - NS_WARNING("internal offsets may be out-of-sync"); -} -#define DEBUG_VERIFY_NOT_DIRTY(state) \ -VerifyNotDirty(state) -#else -#define DEBUG_VERIFY_NOT_DIRTY(state) -#endif - -nsIFrame* -NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) -{ - return new (aPresShell) nsTextFrame(aContext); -} - -NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame) - -nsIFrame* -NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) -{ - return new (aPresShell) nsContinuingTextFrame(aContext); -} - -NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame) - -nsTextFrame::~nsTextFrame() -{ - if (0 != (mState & TEXT_BLINK_ON_OR_PRINTING) && PresContext()->IsDynamic()) - { - nsBlinkTimer::RemoveBlinkFrame(this); - } -} - -NS_IMETHODIMP -nsTextFrame::GetCursor(const nsPoint& aPoint, - nsIFrame::Cursor& aCursor) -{ - FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor); - if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { - aCursor.mCursor = NS_STYLE_CURSOR_TEXT; - - // If tabindex >= 0, use default cursor to indicate it's not selectable - nsIFrame *ancestorFrame = this; - while ((ancestorFrame = ancestorFrame->GetParent()) != nsnull) { - nsIContent *ancestorContent = ancestorFrame->GetContent(); - if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { - nsAutoString tabIndexStr; - ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); - if (!tabIndexStr.IsEmpty()) { - PRInt32 rv, tabIndexVal = tabIndexStr.ToInteger(&rv); - if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) { - aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; - break; - } - } - } - } - } - - return NS_OK; -} - -nsIFrame* -nsTextFrame::GetLastInFlow() const -{ - nsTextFrame* lastInFlow = const_cast(this); - while (lastInFlow->GetNextInFlow()) { - lastInFlow = static_cast(lastInFlow->GetNextInFlow()); - } - NS_POSTCONDITION(lastInFlow, "illegal state in flow chain."); - return lastInFlow; -} -nsIFrame* -nsTextFrame::GetLastContinuation() const -{ - nsTextFrame* lastInFlow = const_cast(this); - while (lastInFlow->mNextContinuation) { - lastInFlow = static_cast(lastInFlow->mNextContinuation); - } - NS_POSTCONDITION(lastInFlow, "illegal state in continuation chain."); - return lastInFlow; -} - -void -nsTextFrame::ClearTextRun() -{ - // save textrun because ClearAllTextRunReferences will clear ours - gfxTextRun* textRun = mTextRun; - - if (!textRun) - return; - - UnhookTextRunFromFrames(textRun); - // see comments in BuildTextRunForFrames... -// if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) { -// NS_ERROR("Shouldn't reach here for now..."); -// // the textrun's text may be referencing a DOM node that has changed, -// // so we'd better kill this textrun now. -// if (textRun->GetExpirationState()->IsTracked()) { -// gTextRuns->RemoveFromCache(textRun); -// } -// delete textRun; -// return; -// } - - if (!(textRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) { - // Remove it now because it's not doing anything useful - gTextRuns->RemoveFromCache(textRun); - delete textRun; - } -} - -static void -ClearTextRunsInFlowChain(nsTextFrame* aFrame) -{ - nsTextFrame* f; - for (f = aFrame; f; f = static_cast(f->GetNextContinuation())) { - f->ClearTextRun(); - } -} - -NS_IMETHODIMP -nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) -{ - ClearTextRunsInFlowChain(this); - - nsTextFrame* targetTextFrame; - PRInt32 nodeLength = mContent->GetText()->GetLength(); - - if (aInfo->mAppend) { - targetTextFrame = static_cast(GetLastContinuation()); - targetTextFrame->mState &= ~TEXT_WHITESPACE_FLAGS; - } else { - // Mark all the continuation frames as dirty, and fix up content offsets to - // be valid. - // Don't set NS_FRAME_IS_DIRTY on |this|, since we call FrameNeedsReflow - // below. - nsTextFrame* textFrame = this; - PRInt32 newLength = nodeLength; - do { - textFrame->mState &= ~TEXT_WHITESPACE_FLAGS; - // If the text node has shrunk, clip the frame contentlength as necessary - if (textFrame->mContentOffset > newLength) { - textFrame->mContentOffset = newLength; - } - textFrame = static_cast(textFrame->GetNextContinuation()); - if (!textFrame) { - break; - } - textFrame->mState |= NS_FRAME_IS_DIRTY; - } while (1); - targetTextFrame = this; - } - - // Ask the parent frame to reflow me. - PresContext()->GetPresShell()->FrameNeedsReflow(targetTextFrame, - nsIPresShell::eStyleChange, - NS_FRAME_IS_DIRTY); - - return NS_OK; -} - -/* virtual */ void -nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) -{ - nsFrame::DidSetStyleContext(aOldStyleContext); - ClearTextRun(); -} - -class nsDisplayText : public nsDisplayItem { -public: - nsDisplayText(nsTextFrame* aFrame) : nsDisplayItem(aFrame) { - MOZ_COUNT_CTOR(nsDisplayText); - } -#ifdef NS_BUILD_REFCNT_LOGGING - virtual ~nsDisplayText() { - MOZ_COUNT_DTOR(nsDisplayText); - } -#endif - - virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) { - return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame); - } - virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, - HitTestState* aState, nsTArray *aOutFrames) { - if (nsRect(aBuilder->ToReferenceFrame(mFrame), mFrame->GetSize()).Intersects(aRect)) { - aOutFrames->AppendElement(mFrame); - } - } - virtual void Paint(nsDisplayListBuilder* aBuilder, - nsIRenderingContext* aCtx); - NS_DISPLAY_DECL_NAME("Text") -}; - -void -nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, - nsIRenderingContext* aCtx) { - // Add 1 pixel of dirty area around mVisibleRect to allow us to paint - // antialiased pixels beyond the measured text extents. - // This is temporary until we do this in the actual calculation of text extents. - nsRect extraVisible = mVisibleRect; - nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); - extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel); - static_cast(mFrame)-> - PaintText(aCtx, aBuilder->ToReferenceFrame(mFrame), extraVisible); -} - -NS_IMETHODIMP -nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, - const nsRect& aDirtyRect, - const nsDisplayListSet& aLists) -{ - if (!IsVisibleForPainting(aBuilder)) - return NS_OK; - - DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame"); - - if ((0 != (mState & TEXT_BLINK_ON_OR_PRINTING)) && nsBlinkTimer::GetBlinkIsOff() && - PresContext()->IsDynamic() && !aBuilder->IsForEventDelivery()) - return NS_OK; - - return aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayText(this)); -} - -static nsIFrame* -GetGeneratedContentOwner(nsIFrame* aFrame, PRBool* aIsBefore) -{ - *aIsBefore = PR_FALSE; - while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { - if (aFrame->GetStyleContext()->GetPseudoType() == nsCSSPseudoElements::before) { - *aIsBefore = PR_TRUE; - } - aFrame = aFrame->GetParent(); - } - return aFrame; -} - -SelectionDetails* -nsTextFrame::GetSelectionDetails() -{ - const nsFrameSelection* frameSelection = GetConstFrameSelection(); - if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { - SelectionDetails* details = - frameSelection->LookUpSelection(mContent, GetContentOffset(), - GetContentLength(), PR_FALSE); - SelectionDetails* sd; - for (sd = details; sd; sd = sd->mNext) { - sd->mStart += mContentOffset; - sd->mEnd += mContentOffset; - } - return details; - } - - // Check if the beginning or end of the element is selected, depending on - // whether we're :before content or :after content. - PRBool isBefore; - nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore); - if (!owner || !owner->GetContent()) - return nsnull; - - SelectionDetails* details = - frameSelection->LookUpSelection(owner->GetContent(), - isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, PR_FALSE); - SelectionDetails* sd; - for (sd = details; sd; sd = sd->mNext) { - // The entire text is selected! - sd->mStart = GetContentOffset(); - sd->mEnd = GetContentEnd(); - } - return details; -} - -static void -FillClippedRect(gfxContext* aCtx, nsPresContext* aPresContext, - nscolor aColor, const gfxRect& aDirtyRect, const gfxRect& aRect) -{ - gfxRect r = aRect.Intersect(aDirtyRect); - // For now, we need to put this in pixel coordinates - PRInt32 app = aPresContext->AppUnitsPerDevPixel(); - aCtx->NewPath(); - // pixel-snap - aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app, - r.Width() / app, r.Height() / app), PR_TRUE); - aCtx->SetColor(gfxRGBA(aColor)); - aCtx->Fill(); -} - -nsTextFrame::TextDecorations -nsTextFrame::GetTextDecorations(nsPresContext* aPresContext) -{ - TextDecorations decorations; - - // Quirks mode text decoration are rendered by children; see bug 1777 - // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint - // does the painting of text decorations. - if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode()) - return decorations; - - PRBool useOverride = PR_FALSE; - nscolor overrideColor; - - // A mask of all possible decorations. - PRUint8 decorMask = NS_STYLE_TEXT_DECORATION_UNDERLINE | - NS_STYLE_TEXT_DECORATION_OVERLINE | - NS_STYLE_TEXT_DECORATION_LINE_THROUGH; - - for (nsStyleContext* context = GetStyleContext(); - decorMask && context && context->HasTextDecorations(); - context = context->GetParent()) { - const nsStyleTextReset* styleText = context->GetStyleTextReset(); - if (!useOverride && - (NS_STYLE_TEXT_DECORATION_OVERRIDE_ALL & styleText->mTextDecoration)) { - // This handles the La - // la la case. The link underline should be green. - useOverride = PR_TRUE; - overrideColor = context->GetStyleColor()->mColor; - } - - PRUint8 useDecorations = decorMask & styleText->mTextDecoration; - if (useDecorations) {// a decoration defined here - nscolor color = context->GetStyleColor()->mColor; - - if (NS_STYLE_TEXT_DECORATION_UNDERLINE & useDecorations) { - decorations.mUnderColor = useOverride ? overrideColor : color; - decorMask &= ~NS_STYLE_TEXT_DECORATION_UNDERLINE; - decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_UNDERLINE; - } - if (NS_STYLE_TEXT_DECORATION_OVERLINE & useDecorations) { - decorations.mOverColor = useOverride ? overrideColor : color; - decorMask &= ~NS_STYLE_TEXT_DECORATION_OVERLINE; - decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_OVERLINE; - } - if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH & useDecorations) { - decorations.mStrikeColor = useOverride ? overrideColor : color; - decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH; - decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_LINE_THROUGH; - } - } - } - - return decorations; -} - -void -nsTextFrame::UnionTextDecorationOverflow(nsPresContext* aPresContext, - PropertyProvider& aProvider, - nsRect* aOverflowRect) -{ - // Text-shadow overflows - nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(*aOverflowRect, this); - aOverflowRect->UnionRect(*aOverflowRect, shadowRect); - - if (IsFloatingFirstLetterChild()) { - // The underline/overline drawable area must be contained in the overflow - // rect when this is in floating first letter frame at *both* modes. - nscoord fontAscent, fontHeight; - nsIFontMetrics* fm = aProvider.GetFontMetrics(); - fm->GetMaxAscent(fontAscent); - fm->GetMaxHeight(fontHeight); - nsRect fontRect(0, mAscent - fontAscent, GetSize().width, fontHeight); - aOverflowRect->UnionRect(*aOverflowRect, fontRect); - } - - // When this frame is not selected, the text-decoration area must be in - // frame bounds. - nsRect decorationRect; - if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT) || - !CombineSelectionUnderlineRect(aPresContext, *aOverflowRect)) - return; - AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); -} - -void -nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect, - const gfxPoint& aFramePt, - const gfxPoint& aTextBaselinePt, - nsTextPaintStyle& aTextPaintStyle, - PropertyProvider& aProvider, - const nscolor* aOverrideColor) -{ - TextDecorations decorations = - GetTextDecorations(aTextPaintStyle.PresContext()); - if (!decorations.HasDecorationlines()) - return; - - gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0); - if (!firstFont) - return; // OOM - const gfxFont::Metrics& fontMetrics = firstFont->GetMetrics(); - gfxFloat app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); - - // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? - gfxPoint pt(aFramePt.x / app, (aTextBaselinePt.y - mAscent) / app); - gfxSize size(GetRect().width / app, 0); - gfxFloat ascent = gfxFloat(mAscent) / app; - - nscolor lineColor; - if (decorations.HasOverline()) { - lineColor = aOverrideColor ? *aOverrideColor : decorations.mOverColor; - size.height = fontMetrics.underlineSize; - nsCSSRendering::PaintDecorationLine( - aCtx, lineColor, pt, size, ascent, fontMetrics.maxAscent, - NS_STYLE_TEXT_DECORATION_OVERLINE, - nsCSSRendering::DECORATION_STYLE_SOLID); - } - if (decorations.HasUnderline()) { - lineColor = aOverrideColor ? *aOverrideColor : decorations.mUnderColor; - size.height = fontMetrics.underlineSize; - gfxFloat offset = aProvider.GetFontGroup()->GetUnderlineOffset(); - nsCSSRendering::PaintDecorationLine( - aCtx, lineColor, pt, size, ascent, offset, - NS_STYLE_TEXT_DECORATION_UNDERLINE, - nsCSSRendering::DECORATION_STYLE_SOLID); - } - if (decorations.HasStrikeout()) { - lineColor = aOverrideColor ? *aOverrideColor : decorations.mStrikeColor; - size.height = fontMetrics.strikeoutSize; - gfxFloat offset = fontMetrics.strikeoutOffset; - nsCSSRendering::PaintDecorationLine( - aCtx, lineColor, pt, size, ascent, offset, - NS_STYLE_TEXT_DECORATION_LINE_THROUGH, - nsCSSRendering::DECORATION_STYLE_SOLID); - } -} - -static gfxFloat -ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext, - nsTextFrame* aFrame, - const gfxFont::Metrics& aFontMetrics) -{ - gfxFloat app = aPresContext->AppUnitsPerDevPixel(); - nscoord lineHeightApp = - nsHTMLReflowState::CalcLineHeight(aFrame->GetStyleContext(), NS_AUTOHEIGHT); - gfxFloat lineHeight = gfxFloat(lineHeightApp) / app; - if (lineHeight <= aFontMetrics.maxHeight) { - return aFontMetrics.maxDescent; - } - return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; -} - - -// Make sure this stays in sync with DrawSelectionDecorations below -static const SelectionType SelectionTypesWithDecorations = - nsISelectionController::SELECTION_SPELLCHECK | - nsISelectionController::SELECTION_IME_RAWINPUT | - nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT | - nsISelectionController::SELECTION_IME_CONVERTEDTEXT | - nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; - -static PRUint8 -GetTextDecorationStyle(const nsTextRangeStyle &aRangeStyle) -{ - NS_PRECONDITION(aRangeStyle.IsLineStyleDefined(), - "aRangeStyle.mLineStyle have to be defined"); - switch (aRangeStyle.mLineStyle) { - case nsTextRangeStyle::LINESTYLE_NONE: - return nsCSSRendering::DECORATION_STYLE_NONE; - case nsTextRangeStyle::LINESTYLE_SOLID: - return nsCSSRendering::DECORATION_STYLE_SOLID; - case nsTextRangeStyle::LINESTYLE_DOTTED: - return nsCSSRendering::DECORATION_STYLE_DOTTED; - case nsTextRangeStyle::LINESTYLE_DASHED: - return nsCSSRendering::DECORATION_STYLE_DASHED; - case nsTextRangeStyle::LINESTYLE_DOUBLE: - return nsCSSRendering::DECORATION_STYLE_DOUBLE; - case nsTextRangeStyle::LINESTYLE_WAVY: - return nsCSSRendering::DECORATION_STYLE_WAVY; - default: - NS_WARNING("Requested underline style is not valid"); - return nsCSSRendering::DECORATION_STYLE_SOLID; - } -} - -static gfxFloat -ComputeSelectionUnderlineHeight(nsPresContext* aPresContext, - const gfxFont::Metrics& aFontMetrics, - SelectionType aSelectionType) -{ - switch (aSelectionType) { - case nsISelectionController::SELECTION_IME_RAWINPUT: - case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: - case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: - case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: - return aFontMetrics.underlineSize; - case nsISelectionController::SELECTION_SPELLCHECK: { - // The thickness of the spellchecker underline shouldn't honor the font - // metrics. It should be constant pixels value which is decided from the - // default font size. Note that if the actual font size is smaller than - // the default font size, we should use the actual font size because the - // computed value from the default font size can be too thick for the - // current font size. - PRInt32 defaultFontSize = - aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size); - gfxFloat fontSize = PR_MIN(gfxFloat(defaultFontSize), - aFontMetrics.emHeight); - fontSize = PR_MAX(fontSize, 1.0); - return NS_ceil(fontSize / 20); - } - default: - NS_WARNING("Requested underline style is not valid"); - return aFontMetrics.underlineSize; - } -} - -/** - * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about - * drawing text decoration for selections. - */ -static void DrawSelectionDecorations(gfxContext* aContext, SelectionType aType, - nsTextFrame* aFrame, - nsTextPaintStyle& aTextPaintStyle, - const nsTextRangeStyle &aRangeStyle, - const gfxPoint& aPt, gfxFloat aWidth, - gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics) -{ - gfxPoint pt(aPt); - gfxSize size(aWidth, - ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(), - aFontMetrics, aType)); - gfxFloat descentLimit = - ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(), - aFrame, aFontMetrics); - - float relativeSize; - PRUint8 style; - nscolor color; - PRInt32 index = - nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType); - PRBool weDefineSelectionUnderline = - aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color, - &relativeSize, &style); - - switch (aType) { - case nsISelectionController::SELECTION_IME_RAWINPUT: - case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: - case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: - case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: { - // IME decoration lines should not be drawn on the both ends, i.e., we - // need to cut both edges of the decoration lines. Because same style - // IME selections can adjoin, but the users need to be able to know - // where are the boundaries of the selections. - // - // X: underline - // - // IME selection #1 IME selection #2 IME selection #3 - // | | | - // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX - // +---------------------+----------------------+-------------------- - // ^ ^ ^ ^ ^ - // gap gap gap - pt.x += 1.0; - size.width -= 2.0; - if (aRangeStyle.IsDefined()) { - // If IME defines the style, that should override our definition. - if (aRangeStyle.IsLineStyleDefined()) { - if (aRangeStyle.mLineStyle == nsTextRangeStyle::LINESTYLE_NONE) { - return; - } - style = GetTextDecorationStyle(aRangeStyle); - relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; - } else if (!weDefineSelectionUnderline) { - // There is no underline style definition. - return; - } - if (aRangeStyle.IsUnderlineColorDefined()) { - color = aRangeStyle.mUnderlineColor; - } else if (aRangeStyle.IsForegroundColorDefined()) { - color = aRangeStyle.mForegroundColor; - } else { - NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(), - "Only the background color is defined"); - color = aTextPaintStyle.GetTextColor(); - } - } else if (!weDefineSelectionUnderline) { - // IME doesn't specify the selection style and we don't define selection - // underline. - return; - } - break; - } - case nsISelectionController::SELECTION_SPELLCHECK: - if (!weDefineSelectionUnderline) - return; - break; - default: - NS_WARNING("Requested selection decorations when there aren't any"); - return; - } - size.height *= relativeSize; - nsCSSRendering::PaintDecorationLine( - aContext, color, pt, size, aAscent, aFontMetrics.underlineOffset, - NS_STYLE_TEXT_DECORATION_UNDERLINE, style, descentLimit); -} - -/** - * This function encapsulates all knowledge of how selections affect foreground - * and background colors. - * @return true if the selection affects colors, false otherwise - * @param aForeground the foreground color to use - * @param aBackground the background color to use, or RGBA(0,0,0,0) if no - * background should be painted - */ -static PRBool GetSelectionTextColors(SelectionType aType, - nsTextPaintStyle& aTextPaintStyle, - const nsTextRangeStyle &aRangeStyle, - nscolor* aForeground, nscolor* aBackground) -{ - switch (aType) { - case nsISelectionController::SELECTION_NORMAL: - return aTextPaintStyle.GetSelectionColors(aForeground, aBackground); - case nsISelectionController::SELECTION_FIND: - aTextPaintStyle.GetHighlightColors(aForeground, aBackground); - return PR_TRUE; - case nsISelectionController::SELECTION_IME_RAWINPUT: - case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: - case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: - case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: - if (aRangeStyle.IsDefined()) { - *aForeground = aTextPaintStyle.GetTextColor(); - *aBackground = NS_RGBA(0,0,0,0); - if (!aRangeStyle.IsForegroundColorDefined() && - !aRangeStyle.IsBackgroundColorDefined()) { - return PR_FALSE; - } - if (aRangeStyle.IsForegroundColorDefined()) { - *aForeground = aRangeStyle.mForegroundColor; - } - if (aRangeStyle.IsBackgroundColorDefined()) { - *aBackground = aRangeStyle.mBackgroundColor; - } - return PR_TRUE; - } - aTextPaintStyle.GetIMESelectionColors( - nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType), - aForeground, aBackground); - return PR_TRUE; - default: - *aForeground = aTextPaintStyle.GetTextColor(); - *aBackground = NS_RGBA(0,0,0,0); - return PR_FALSE; - } -} - -/** - * This class lets us iterate over chunks of text in a uniform selection state, - * observing cluster boundaries, in content order, maintaining the current - * x-offset as we go, and telling whether the text chunk has a hyphen after - * it or not. The caller is responsible for actually computing the advance - * width of each chunk. - */ -class SelectionIterator { -public: - /** - * aStart and aLength are in the original string. aSelectionDetails is - * according to the original string. - */ - SelectionIterator(SelectionDetails** aSelectionDetails, - PRInt32 aStart, PRInt32 aLength, - PropertyProvider& aProvider, gfxTextRun* aTextRun); - - /** - * Returns the next segment of uniformly selected (or not) text. - * @param aXOffset the offset from the origin of the frame to the start - * of the text (the left baseline origin for LTR, the right baseline origin - * for RTL) - * @param aOffset the transformed string offset of the text for this segment - * @param aLength the transformed string length of the text for this segment - * @param aHyphenWidth if a hyphen is to be rendered after the text, the - * width of the hyphen, otherwise zero - * @param aType the selection type for this segment - * @param aStyle the selection style for this segment - * @return false if there are no more segments - */ - PRBool GetNextSegment(gfxFloat* aXOffset, PRUint32* aOffset, PRUint32* aLength, - gfxFloat* aHyphenWidth, SelectionType* aType, - nsTextRangeStyle* aStyle); - void UpdateWithAdvance(gfxFloat aAdvance) { - mXOffset += aAdvance*mTextRun->GetDirection(); - } - -private: - SelectionDetails** mSelectionDetails; - PropertyProvider& mProvider; - gfxTextRun* mTextRun; - gfxSkipCharsIterator mIterator; - PRInt32 mOriginalStart; - PRInt32 mOriginalEnd; - gfxFloat mXOffset; -}; - -SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails, - PRInt32 aStart, PRInt32 aLength, PropertyProvider& aProvider, - gfxTextRun* aTextRun) - : mSelectionDetails(aSelectionDetails), mProvider(aProvider), - mTextRun(aTextRun), mIterator(aProvider.GetStart()), - mOriginalStart(aStart), mOriginalEnd(aStart + aLength), - mXOffset(mTextRun->IsRightToLeft() ? aProvider.GetFrame()->GetSize().width : 0) -{ - mIterator.SetOriginalOffset(aStart); -} - -PRBool SelectionIterator::GetNextSegment(gfxFloat* aXOffset, - PRUint32* aOffset, PRUint32* aLength, gfxFloat* aHyphenWidth, - SelectionType* aType, nsTextRangeStyle* aStyle) -{ - if (mIterator.GetOriginalOffset() >= mOriginalEnd) - return PR_FALSE; - - // save offset into transformed string now - PRUint32 runOffset = mIterator.GetSkippedOffset(); - - PRInt32 index = mIterator.GetOriginalOffset() - mOriginalStart; - SelectionDetails* sdptr = mSelectionDetails[index]; - SelectionType type = - sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE; - nsTextRangeStyle style; - if (sdptr) { - style = sdptr->mTextRangeStyle; - } - for (++index; mOriginalStart + index < mOriginalEnd; ++index) { - if (sdptr != mSelectionDetails[index]) - break; - } - mIterator.SetOriginalOffset(index + mOriginalStart); - - // Advance to the next cluster boundary - while (mIterator.GetOriginalOffset() < mOriginalEnd && - !mIterator.IsOriginalCharSkipped() && - !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) { - mIterator.AdvanceOriginal(1); - } - - PRBool haveHyphenBreak = - (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0; - *aOffset = runOffset; - *aLength = mIterator.GetSkippedOffset() - runOffset; - *aXOffset = mXOffset; - *aHyphenWidth = 0; - if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) { - *aHyphenWidth = mProvider.GetHyphenWidth(); - } - *aType = type; - *aStyle = style; - return PR_TRUE; -} - -static void -AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun, - gfxTextRun::Metrics* aMetrics, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext* aContext) -{ - // Fix up metrics to include hyphen - gfxTextRunCache::AutoTextRun hyphenTextRun( - GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame)); - if (!hyphenTextRun.get()) - return; - - gfxTextRun::Metrics hyphenMetrics = - hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(), - aBoundingBoxType, aContext, nsnull); - aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft()); -} - -void -nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength, - nsCSSShadowItem* aShadowDetails, - PropertyProvider* aProvider, const nsRect& aDirtyRect, - const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, - gfxContext* aCtx, const nscolor& aForegroundColor) -{ - gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset); - nscoord blurRadius = NS_MAX(aShadowDetails->mRadius, 0); - - gfxTextRun::Metrics shadowMetrics = - mTextRun->MeasureText(aOffset, aLength, gfxFont::LOOSE_INK_EXTENTS, - nsnull, aProvider); - if (GetStateBits() & TEXT_HYPHEN_BREAK) { - AddHyphenToMetrics(this, mTextRun, &shadowMetrics, gfxFont::LOOSE_INK_EXTENTS, aCtx); - } - - // This rect is the box which is equivalent to where the shadow will be painted. - // The origin of mBoundingBox is the text baseline left, so we must translate it by - // that much in order to make the origin the top-left corner of the text bounding box. - gfxRect shadowGfxRect = shadowMetrics.mBoundingBox + - gfxPoint(aFramePt.x, aTextBaselinePt.y) + shadowOffset; - nsRect shadowRect(shadowGfxRect.X(), shadowGfxRect.Y(), - shadowGfxRect.Width(), shadowGfxRect.Height());; - - nsContextBoxBlur contextBoxBlur; - gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, blurRadius, - PresContext()->AppUnitsPerDevPixel(), - aCtx, aDirtyRect); - if (!shadowContext) - return; - - nscolor shadowColor; - if (aShadowDetails->mHasColor) - shadowColor = aShadowDetails->mColor; - else - shadowColor = aForegroundColor; - - aCtx->Save(); - aCtx->NewPath(); - aCtx->SetColor(gfxRGBA(shadowColor)); - - // Draw the text onto our alpha-only surface to capture the alpha values. - // Remember that the box blur context has a device offset on it, so we don't need to - // translate any coordinates to fit on the surface. - gfxRect dirtyGfxRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); - gfxFloat advanceWidth; - DrawText(shadowContext, - aTextBaselinePt + shadowOffset, - aOffset, aLength, &dirtyGfxRect, aProvider, advanceWidth, - (GetStateBits() & TEXT_HYPHEN_BREAK) != 0); - - // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting - // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change - // any code behaviour here. - nsTextPaintStyle textPaintStyle(this); - PaintTextDecorations(shadowContext, dirtyGfxRect, aFramePt + shadowOffset, - aTextBaselinePt + shadowOffset, - textPaintStyle, *aProvider, &shadowColor); - - contextBoxBlur.DoPaint(); - aCtx->Restore(); -} - -// Paints selection backgrounds and text in the correct colors. Also computes -// aAllTypes, the union of all selection types that are applying to this text. -void -nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx, - const gfxPoint& aFramePt, - const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, - PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle, - SelectionDetails* aDetails, SelectionType* aAllTypes) -{ - PRInt32 contentOffset = aProvider.GetStart().GetOriginalOffset(); - PRInt32 contentLength = aProvider.GetOriginalLength(); - - // Figure out which selections control the colors to use for each character. - nsAutoTArray prevailingSelectionsBuffer; - if (!prevailingSelectionsBuffer.AppendElements(contentLength)) - return; - SelectionDetails** prevailingSelections = prevailingSelectionsBuffer.Elements(); - - PRInt32 i; - SelectionType allTypes = 0; - for (i = 0; i < contentLength; ++i) { - prevailingSelections[i] = nsnull; - } - - SelectionDetails *sdptr = aDetails; - PRBool anyBackgrounds = PR_FALSE; - while (sdptr) { - PRInt32 start = PR_MAX(0, sdptr->mStart - contentOffset); - PRInt32 end = PR_MIN(contentLength, sdptr->mEnd - contentOffset); - SelectionType type = sdptr->mType; - if (start < end) { - allTypes |= type; - // Ignore selections that don't set colors - nscolor foreground, background; - if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle, - &foreground, &background)) { - if (NS_GET_A(background) > 0) { - anyBackgrounds = PR_TRUE; - } - for (i = start; i < end; ++i) { - // Favour normal selection over IME selections - if (!prevailingSelections[i] || - type < prevailingSelections[i]->mType) { - prevailingSelections[i] = sdptr; - } - } - } - } - sdptr = sdptr->mNext; - } - *aAllTypes = allTypes; - - gfxFloat xOffset, hyphenWidth; - PRUint32 offset, length; // in transformed string - SelectionType type; - nsTextRangeStyle rangeStyle; - // Draw background colors - if (anyBackgrounds) { - SelectionIterator iterator(prevailingSelections, contentOffset, contentLength, - aProvider, mTextRun); - while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, - &type, &rangeStyle)) { - nscolor foreground, background; - GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, - &foreground, &background); - // Draw background color - gfxFloat advance = hyphenWidth + - mTextRun->GetAdvanceWidth(offset, length, &aProvider); - if (NS_GET_A(background) > 0) { - gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0); - FillClippedRect(aCtx, aTextPaintStyle.PresContext(), - background, aDirtyRect, - gfxRect(aFramePt.x + x, aFramePt.y, advance, GetSize().height)); - } - iterator.UpdateWithAdvance(advance); - } - } - - // Draw text - SelectionIterator iterator(prevailingSelections, contentOffset, contentLength, - aProvider, mTextRun); - while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, - &type, &rangeStyle)) { - nscolor foreground, background; - GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, - &foreground, &background); - // Draw text segment - aCtx->SetColor(gfxRGBA(foreground)); - gfxFloat advance; - - DrawText(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y), - offset, length, &aDirtyRect, &aProvider, - advance, hyphenWidth > 0); - if (hyphenWidth) { - advance += hyphenWidth; - } - iterator.UpdateWithAdvance(advance); - } -} - -void -nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, - const gfxPoint& aFramePt, - const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, - PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle, - SelectionDetails* aDetails, SelectionType aSelectionType) -{ - PRInt32 contentOffset = aProvider.GetStart().GetOriginalOffset(); - PRInt32 contentLength = aProvider.GetOriginalLength(); - - // Figure out which characters will be decorated for this selection. - nsAutoTArray selectedCharsBuffer; - if (!selectedCharsBuffer.AppendElements(contentLength)) - return; - SelectionDetails** selectedChars = selectedCharsBuffer.Elements(); - PRInt32 i; - for (i = 0; i < contentLength; ++i) { - selectedChars[i] = nsnull; - } - - SelectionDetails *sdptr = aDetails; - while (sdptr) { - if (sdptr->mType == aSelectionType) { - PRInt32 start = PR_MAX(0, sdptr->mStart - contentOffset); - PRInt32 end = PR_MIN(contentLength, sdptr->mEnd - contentOffset); - for (i = start; i < end; ++i) { - selectedChars[i] = sdptr; - } - } - sdptr = sdptr->mNext; - } - - gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0); - if (!firstFont) - return; // OOM - gfxFont::Metrics decorationMetrics(firstFont->GetMetrics()); - decorationMetrics.underlineOffset = - aProvider.GetFontGroup()->GetUnderlineOffset(); - - SelectionIterator iterator(selectedChars, contentOffset, contentLength, - aProvider, mTextRun); - gfxFloat xOffset, hyphenWidth; - PRUint32 offset, length; - PRInt32 app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); - // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint? - gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app); - SelectionType type; - nsTextRangeStyle selectedStyle; - while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, - &type, &selectedStyle)) { - gfxFloat advance = hyphenWidth + - mTextRun->GetAdvanceWidth(offset, length, &aProvider); - if (type == aSelectionType) { - pt.x = (aFramePt.x + xOffset - - (mTextRun->IsRightToLeft() ? advance : 0)) / app; - gfxFloat width = PR_ABS(advance) / app; - DrawSelectionDecorations(aCtx, aSelectionType, this, aTextPaintStyle, - selectedStyle, - pt, width, mAscent / app, decorationMetrics); - } - iterator.UpdateWithAdvance(advance); - } -} - -PRBool -nsTextFrame::PaintTextWithSelection(gfxContext* aCtx, - const gfxPoint& aFramePt, - const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, - PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle) -{ - SelectionDetails* details = GetSelectionDetails(); - if (!details) - return PR_FALSE; - - SelectionType allTypes; - PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, - aProvider, aTextPaintStyle, details, &allTypes); - PaintTextDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, - aTextPaintStyle, aProvider); - PRInt32 i; - // Iterate through just the selection types that paint decorations and - // paint decorations for any that actually occur in this frame. Paint - // higher-numbered selection types below lower-numered ones on the - // general principal that lower-numbered selections are higher priority. - allTypes &= SelectionTypesWithDecorations; - for (i = nsISelectionController::NUM_SELECTIONTYPES - 1; i >= 1; --i) { - SelectionType type = 1 << (i - 1); - if (allTypes & type) { - // There is some selection of this type. Try to paint its decorations - // (there might not be any for this type but that's OK, - // PaintTextSelectionDecorations will exit early). - PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, - aProvider, aTextPaintStyle, details, type); - } - } - - DestroySelectionDetails(details); - return PR_TRUE; -} - -static PRUint32 -ComputeTransformedLength(PropertyProvider& aProvider) -{ - gfxSkipCharsIterator iter(aProvider.GetStart()); - PRUint32 start = iter.GetSkippedOffset(); - iter.AdvanceOriginal(aProvider.GetOriginalLength()); - return iter.GetSkippedOffset() - start; -} - -gfxFloat -nsTextFrame::GetSnappedBaselineY(gfxContext* aContext, gfxFloat aY) -{ - gfxFloat appUnitsPerDevUnit = mTextRun->GetAppUnitsPerDevUnit(); - gfxFloat baseline = aY + mAscent; - gfxRect putativeRect(0, baseline/appUnitsPerDevUnit, 1, 1); - if (!aContext->UserToDevicePixelSnapped(putativeRect)) - return baseline; - return aContext->DeviceToUser(putativeRect.pos).y*appUnitsPerDevUnit; -} - -void -nsTextFrame::PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt, - const nsRect& aDirtyRect) -{ - // Don't pass in aRenderingContext here, because we need a *reference* - // context and aRenderingContext might have some transform in it - // XXX get the block and line passed to us somehow! This is slow! - gfxSkipCharsIterator iter = EnsureTextRun(); - if (!mTextRun) - return; - - nsTextPaintStyle textPaintStyle(this); - PropertyProvider provider(this, iter); - // Trim trailing whitespace - provider.InitializeForDisplay(PR_TRUE); - - gfxContext* ctx = aRenderingContext->ThebesContext(); - - gfxPoint framePt(aPt.x, aPt.y); - gfxPoint textBaselinePt( - mTextRun->IsRightToLeft() ? gfxFloat(aPt.x + GetSize().width) : framePt.x, - GetSnappedBaselineY(ctx, aPt.y)); - - gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, - aDirtyRect.width, aDirtyRect.height); - - gfxFloat advanceWidth; - gfxRGBA foregroundColor = gfxRGBA(textPaintStyle.GetTextColor()); - - // Paint the text shadow before doing any foreground stuff - const nsStyleText* textStyle = GetStyleText(); - if (textStyle->mTextShadow) { - // Text shadow happens with the last value being painted at the back, - // ie. it is painted first. - for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) { - PaintOneShadow(provider.GetStart().GetSkippedOffset(), - ComputeTransformedLength(provider), - textStyle->mTextShadow->ShadowAt(i - 1), &provider, - aDirtyRect, framePt, textBaselinePt, ctx, - textPaintStyle.GetTextColor()); - } - } - - // Fork off to the (slower) paint-with-selection path if necessary. - if (nsLayoutUtils::GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { - if (PaintTextWithSelection(ctx, framePt, textBaselinePt, - dirtyRect, provider, textPaintStyle)) - return; - } - - ctx->SetColor(foregroundColor); - - DrawText(ctx, textBaselinePt, provider.GetStart().GetSkippedOffset(), - ComputeTransformedLength(provider), &dirtyRect, - &provider, advanceWidth, - (GetStateBits() & TEXT_HYPHEN_BREAK) != 0); - PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt, - textPaintStyle, provider); -} - -void -nsTextFrame::DrawText(gfxContext* aCtx, const gfxPoint& aTextBaselinePt, - PRUint32 aOffset, PRUint32 aLength, - const gfxRect* aDirtyRect, PropertyProvider* aProvider, - gfxFloat& aAdvanceWidth, PRBool aDrawSoftHyphen) -{ - // Paint the text and soft-hyphen (if any) onto the given graphics context - mTextRun->Draw(aCtx, aTextBaselinePt, aOffset, aLength, - aDirtyRect, aProvider, &aAdvanceWidth); - - if (aDrawSoftHyphen) { - // Don't use ctx as the context, because we need a reference context here, - // ctx may be transformed. - gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this)); - if (hyphenTextRun.get()) { - // For right-to-left text runs, the soft-hyphen is positioned at the left - // of the text, minus its own width - gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth - - (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull) : 0); - hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y), - 0, hyphenTextRun->GetLength(), aDirtyRect, nsnull, nsnull); - } - } -} - -PRInt16 -nsTextFrame::GetSelectionStatus(PRInt16* aSelectionFlags) -{ - // get the selection controller - nsCOMPtr selectionController; - nsresult rv = GetSelectionController(PresContext(), - getter_AddRefs(selectionController)); - if (NS_FAILED(rv) || !selectionController) - return nsISelectionController::SELECTION_OFF; - - selectionController->GetSelectionFlags(aSelectionFlags); - - PRInt16 selectionValue; - selectionController->GetDisplaySelection(&selectionValue); - - return selectionValue; -} - -PRBool -nsTextFrame::IsVisibleInSelection(nsISelection* aSelection) -{ - // Check the quick way first - PRBool isSelected = (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; - if (!isSelected) - return PR_FALSE; - - SelectionDetails* details = GetSelectionDetails(); - PRBool found = PR_FALSE; - - // where are the selection points "really" - SelectionDetails *sdptr = details; - while (sdptr) { - if (sdptr->mEnd > GetContentOffset() && - sdptr->mStart < GetContentEnd() && - sdptr->mType == nsISelectionController::SELECTION_NORMAL) { - found = PR_TRUE; - break; - } - sdptr = sdptr->mNext; - } - DestroySelectionDetails(details); - - return found; -} - -/** - * Compute the longest prefix of text whose width is <= aWidth. Return - * the length of the prefix. Also returns the width of the prefix in aFitWidth. - */ -static PRUint32 -CountCharsFit(gfxTextRun* aTextRun, PRUint32 aStart, PRUint32 aLength, - gfxFloat aWidth, PropertyProvider* aProvider, - gfxFloat* aFitWidth) -{ - PRUint32 last = 0; - gfxFloat width = 0; - PRUint32 i; - for (i = 1; i <= aLength; ++i) { - if (i == aLength || aTextRun->IsClusterStart(aStart + i)) { - gfxFloat nextWidth = width + - aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider); - if (nextWidth > aWidth) - break; - last = i; - width = nextWidth; - } - } - *aFitWidth = width; - return last; -} - -nsIFrame::ContentOffsets -nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint) -{ - return GetCharacterOffsetAtFramePointInternal(aPoint, PR_TRUE); -} - -nsIFrame::ContentOffsets -nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint) -{ - return GetCharacterOffsetAtFramePointInternal(aPoint, PR_FALSE); -} - -nsIFrame::ContentOffsets -nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint, - PRBool aForInsertionPoint) -{ - ContentOffsets offsets; - - gfxSkipCharsIterator iter = EnsureTextRun(); - if (!mTextRun) - return offsets; - - PropertyProvider provider(this, iter); - // Trim leading but not trailing whitespace if possible - provider.InitializeForDisplay(PR_FALSE); - gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x; - gfxFloat fitWidth; - PRUint32 skippedLength = ComputeTransformedLength(provider); - - PRUint32 charsFit = CountCharsFit(mTextRun, - provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth); - - PRInt32 selectedOffset; - if (charsFit < skippedLength) { - // charsFit characters fitted, but no more could fit. See if we're - // more than halfway through the cluster.. If we are, choose the next - // cluster. - gfxSkipCharsIterator extraCluster(provider.GetStart()); - extraCluster.AdvanceSkipped(charsFit); - gfxSkipCharsIterator extraClusterLastChar(extraCluster); - FindClusterEnd(mTextRun, - provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(), - &extraClusterLastChar); - gfxFloat charWidth = - mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(), - GetSkippedDistance(extraCluster, extraClusterLastChar) + 1, - &provider); - selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2 - ? extraCluster.GetOriginalOffset() - : extraClusterLastChar.GetOriginalOffset() + 1; - } else { - // All characters fitted, we're at (or beyond) the end of the text. - // XXX This could be some pathological situation where negative spacing - // caused characters to move backwards. We can't really handle that - // in the current frame system because frames can't have negative - // intrinsic widths. - selectedOffset = - provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(); - } - - offsets.content = GetContent(); - offsets.offset = offsets.secondaryOffset = selectedOffset; - offsets.associateWithNext = mContentOffset == offsets.offset; - return offsets; -} - -PRBool -nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, - nsRect& aRect) -{ - if (aRect.IsEmpty()) - return PR_FALSE; - - nsRect givenRect = aRect; - - nsCOMPtr fm; - nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); - nsIThebesFontMetrics* tfm = static_cast(fm.get()); - gfxFontGroup* fontGroup = tfm->GetThebesFontGroup(); - gfxFont* firstFont = fontGroup->GetFontAt(0); - if (!firstFont) - return PR_FALSE; // OOM - const gfxFont::Metrics& metrics = firstFont->GetMetrics(); - gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); - gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent); - gfxFloat descentLimit = - ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics); - - SelectionDetails *details = GetSelectionDetails(); - for (SelectionDetails *sd = details; sd; sd = sd->mNext) { - if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations)) - continue; - - PRUint8 style; - float relativeSize; - PRInt32 index = - nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType); - if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) { - if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nsnull, - &relativeSize, &style)) { - continue; - } - } else { - // IME selections - nsTextRangeStyle& rangeStyle = sd->mTextRangeStyle; - if (rangeStyle.IsDefined()) { - if (!rangeStyle.IsLineStyleDefined() || - rangeStyle.mLineStyle == nsTextRangeStyle::LINESTYLE_NONE) { - continue; - } - style = GetTextDecorationStyle(rangeStyle); - relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f; - } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, - nsnull, &relativeSize, - &style)) { - continue; - } - } - nsRect decorationArea; - gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width), - ComputeSelectionUnderlineHeight(aPresContext, - metrics, sd->mType)); - relativeSize = PR_MAX(relativeSize, 1.0f); - size.height *= relativeSize; - decorationArea = - nsCSSRendering::GetTextDecorationRect(aPresContext, size, - ascent, underlineOffset, - NS_STYLE_TEXT_DECORATION_UNDERLINE, - style, descentLimit); - aRect.UnionRect(aRect, decorationArea); - } - DestroySelectionDetails(details); - - return !aRect.IsEmpty() && !givenRect.Contains(aRect); -} - -void -nsTextFrame::SetSelected(PRBool aSelected, - SelectionType aType) -{ - SetSelectedRange(0, mContent->GetText()->GetLength(), aSelected, aType); -} - -void -nsTextFrame::SetSelectedRange(PRUint32 aStart, - PRUint32 aEnd, - PRBool aSelected, - SelectionType aType) -{ - NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame"); - DEBUG_VERIFY_NOT_DIRTY(mState); - - // Selection is collapsed, which can't affect text frame rendering - if (aStart == aEnd) - return; - - if (aType == nsISelectionController::SELECTION_NORMAL) { - // check whether style allows selection - PRBool selectable; - IsSelectable(&selectable, nsnull); - if (!selectable) - return; - } - - PRBool anySelected = PR_FALSE; - - nsTextFrame* f = this; - while (f && f->GetContentEnd() <= PRInt32(aStart)) { - if (f->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { - anySelected = PR_TRUE; - } - f = static_cast(f->GetNextContinuation()); - } - - nsPresContext* presContext = PresContext(); - while (f && f->GetContentOffset() < PRInt32(aEnd)) { - if (aSelected) { - f->AddStateBits(NS_FRAME_SELECTED_CONTENT); - anySelected = PR_TRUE; - } else { // we need to see if any other selection is available. - SelectionDetails *details = f->GetSelectionDetails(); - if (details) { - anySelected = PR_TRUE; - DestroySelectionDetails(details); - } else { - f->RemoveStateBits(NS_FRAME_SELECTED_CONTENT); - } - } - - // We may need to reflow to recompute the overflow area for - // spellchecking or IME underline if their underline is thicker than - // the normal decoration line. - PRBool didHaveOverflowingSelection = - (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0; - nsRect r(nsPoint(0, 0), GetSize()); - PRBool willHaveOverflowingSelection = - aSelected && f->CombineSelectionUnderlineRect(presContext, r); - if (didHaveOverflowingSelection || willHaveOverflowingSelection) { - presContext->PresShell()->FrameNeedsReflow(f, - nsIPresShell::eStyleChange, - NS_FRAME_IS_DIRTY); - } - // Selection might change anything. Invalidate the overflow area. - f->InvalidateOverflowRect(); - - f = static_cast(f->GetNextContinuation()); - } - - // Scan remaining continuations to see if any are selected - while (f && !anySelected) { - if (f->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { - anySelected = PR_TRUE; - } - f = static_cast(f->GetNextContinuation()); - } - - if (anySelected) { - mContent->SetFlags(NS_TEXT_IN_SELECTION); - } else { - // This is only legal because there is only one presentation for the - // content with a selection - mContent->UnsetFlags(NS_TEXT_IN_SELECTION); - } -} - -NS_IMETHODIMP -nsTextFrame::GetPointFromOffset(PRInt32 inOffset, - nsPoint* outPoint) -{ - if (!outPoint) - return NS_ERROR_NULL_POINTER; - - outPoint->x = 0; - outPoint->y = 0; - - DEBUG_VERIFY_NOT_DIRTY(mState); - if (mState & NS_FRAME_IS_DIRTY) - return NS_ERROR_UNEXPECTED; - - if (GetContentLength() <= 0) { - return NS_OK; - } - - gfxSkipCharsIterator iter = EnsureTextRun(); - if (!mTextRun) - return NS_ERROR_FAILURE; - - PropertyProvider properties(this, iter); - // Don't trim trailing whitespace, we want the caret to appear in the right - // place if it's positioned there - properties.InitializeForDisplay(PR_FALSE); - - if (inOffset < GetContentOffset()){ - NS_WARNING("offset before this frame's content"); - inOffset = GetContentOffset(); - } else if (inOffset > GetContentEnd()) { - NS_WARNING("offset after this frame's content"); - inOffset = GetContentEnd(); - } - PRInt32 trimmedOffset = properties.GetStart().GetOriginalOffset(); - PRInt32 trimmedEnd = trimmedOffset + properties.GetOriginalLength(); - inOffset = PR_MAX(inOffset, trimmedOffset); - inOffset = PR_MIN(inOffset, trimmedEnd); - - iter.SetOriginalOffset(inOffset); - - if (inOffset < trimmedEnd && - !iter.IsOriginalCharSkipped() && - !mTextRun->IsClusterStart(iter.GetSkippedOffset())) { - NS_WARNING("GetPointFromOffset called for non-cluster boundary"); - FindClusterStart(mTextRun, trimmedOffset, &iter); - } - - gfxFloat advanceWidth = - mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(), - GetSkippedDistance(properties.GetStart(), iter), - &properties); - nscoord width = NSToCoordCeilClamped(advanceWidth); - - if (mTextRun->IsRightToLeft()) { - outPoint->x = mRect.width - width; - } else { - outPoint->x = width; - } - outPoint->y = 0; - - return NS_OK; -} - -NS_IMETHODIMP -nsTextFrame::GetChildFrameContainingOffset(PRInt32 aContentOffset, - PRBool aHint, - PRInt32* aOutOffset, - nsIFrame**aOutFrame) -{ - DEBUG_VERIFY_NOT_DIRTY(mState); -#if 0 //XXXrbs disable due to bug 310227 - if (mState & NS_FRAME_IS_DIRTY) - return NS_ERROR_UNEXPECTED; -#endif - - NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters"); - NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!"); - - nsTextFrame* f = this; - if (aContentOffset >= mContentOffset) { - while (PR_TRUE) { - nsTextFrame* next = static_cast(f->GetNextContinuation()); - if (!next || aContentOffset < next->GetContentOffset()) - break; - if (aContentOffset == next->GetContentOffset()) { - if (aHint) { - f = next; - } - break; - } - f = next; - } - } else { - while (PR_TRUE) { - nsTextFrame* prev = static_cast(f->GetPrevContinuation()); - if (!prev || aContentOffset > f->GetContentOffset()) - break; - if (aContentOffset == f->GetContentOffset()) { - if (!aHint) { - f = prev; - } - break; - } - f = prev; - } - } - - *aOutOffset = aContentOffset - f->GetContentOffset(); - *aOutFrame = f; - return NS_OK; -} - -PRBool -nsTextFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset) -{ - NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range"); - - gfxSkipCharsIterator iter = EnsureTextRun(); - if (!mTextRun) - return PR_FALSE; - - TrimmedOffsets trimmed = GetTrimmedOffsets(GetFragment(), PR_TRUE); - // Check whether there are nonskipped characters in the trimmmed range - return iter.ConvertOriginalToSkipped(trimmed.GetEnd()) > - iter.ConvertOriginalToSkipped(trimmed.mStart); -} - -/** - * This class iterates through the clusters before or after the given - * aPosition (which is a content offset). You can test each cluster - * to see if it's whitespace (as far as selection/caret movement is concerned), - * or punctuation, or if there is a word break before the cluster. ("Before" - * is interpreted according to aDirection, so if aDirection is -1, "before" - * means actually *after* the cluster content.) - */ -class NS_STACK_CLASS ClusterIterator { -public: - ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, PRInt32 aDirection, - nsString& aContext); - - PRBool NextCluster(); - PRBool IsWhitespace(); - PRBool IsPunctuation(); - PRBool HaveWordBreakBefore() { return mHaveWordBreak; } - PRInt32 GetAfterOffset(); - PRInt32 GetBeforeOffset(); - -private: - nsCOMPtr mCategories; - gfxSkipCharsIterator mIterator; - const nsTextFragment* mFrag; - nsTextFrame* mTextFrame; - PRInt32 mDirection; - PRInt32 mCharIndex; - nsTextFrame::TrimmedOffsets mTrimmed; - nsTArray mWordBreaks; - PRPackedBool mHaveWordBreak; -}; - -static PRBool -IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, gfxTextRun* aTextRun, - nsIFrame* aFrame) -{ - if (aIter.IsOriginalCharSkipped()) - return PR_FALSE; - PRUint32 index = aIter.GetSkippedOffset(); - if (!aTextRun->IsClusterStart(index)) - return PR_FALSE; - return !(aFrame->GetStyleText()->NewlineIsSignificant() && - aTextRun->GetChar(index) == '\n'); -} - -PRBool -nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset) -{ - PRInt32 contentLength = GetContentLength(); - NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range"); - - PRBool selectable; - PRUint8 selectStyle; - IsSelectable(&selectable, &selectStyle); - if (selectStyle == NS_STYLE_USER_SELECT_ALL) - return PR_FALSE; - - gfxSkipCharsIterator iter = EnsureTextRun(); - if (!mTextRun) - return PR_FALSE; - - TrimmedOffsets trimmed = GetTrimmedOffsets(GetFragment(), PR_FALSE); - - // A negative offset means "end of frame". - PRInt32 startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); - - if (!aForward) { - PRInt32 i; - for (i = PR_MIN(trimmed.GetEnd(), startOffset) - 1; - i >= trimmed.mStart; --i) { - iter.SetOriginalOffset(i); - if (IsAcceptableCaretPosition(iter, mTextRun, this)) { - *aOffset = i - mContentOffset; - return PR_TRUE; - } - } - *aOffset = 0; - } else { - PRInt32 i; - for (i = startOffset + 1; i <= trimmed.GetEnd(); ++i) { - iter.SetOriginalOffset(i); - // XXX we can't necessarily stop at the end of this frame, - // but we really have no choice right now. We need to do a deeper - // fix/restructuring of PeekOffsetCharacter - if (i == trimmed.GetEnd() || - IsAcceptableCaretPosition(iter, mTextRun, this)) { - *aOffset = i - mContentOffset; - return PR_TRUE; - } - } - *aOffset = contentLength; - } - - return PR_FALSE; -} - -PRBool -ClusterIterator::IsWhitespace() -{ - NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); - return IsSelectionSpace(mFrag, mCharIndex); -} - -PRBool -ClusterIterator::IsPunctuation() -{ - NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); - if (!mCategories) - return PR_FALSE; - nsIUGenCategory::nsUGenCategory c = mCategories->Get(mFrag->CharAt(mCharIndex)); - return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol; -} - -PRInt32 -ClusterIterator::GetBeforeOffset() -{ - NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); - return mCharIndex + (mDirection > 0 ? 0 : 1); -} - -PRInt32 -ClusterIterator::GetAfterOffset() -{ - NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); - return mCharIndex + (mDirection > 0 ? 1 : 0); -} - -PRBool -ClusterIterator::NextCluster() -{ - if (!mDirection) - return PR_FALSE; - gfxTextRun* textRun = mTextFrame->GetTextRun(); - - mHaveWordBreak = PR_FALSE; - while (PR_TRUE) { - PRBool keepGoing = PR_FALSE; - if (mDirection > 0) { - if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) - return PR_FALSE; - keepGoing = mIterator.IsOriginalCharSkipped() || - mIterator.GetOriginalOffset() < mTrimmed.mStart || - !textRun->IsClusterStart(mIterator.GetSkippedOffset()); - mCharIndex = mIterator.GetOriginalOffset(); - mIterator.AdvanceOriginal(1); - } else { - if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) - return PR_FALSE; - mIterator.AdvanceOriginal(-1); - keepGoing = mIterator.IsOriginalCharSkipped() || - mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() || - !textRun->IsClusterStart(mIterator.GetSkippedOffset()); - mCharIndex = mIterator.GetOriginalOffset(); - } - - if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) { - mHaveWordBreak = PR_TRUE; - } - if (!keepGoing) - return PR_TRUE; - } -} - -ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, - PRInt32 aDirection, nsString& aContext) - : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1) -{ - mIterator = aTextFrame->EnsureTextRun(); - if (!aTextFrame->GetTextRun()) { - mDirection = 0; // signal failure - return; - } - mIterator.SetOriginalOffset(aPosition); - - mCategories = do_GetService(NS_UNICHARCATEGORY_CONTRACTID); - - mFrag = aTextFrame->GetFragment(); - mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, PR_TRUE); - - PRInt32 textOffset = aTextFrame->GetContentOffset(); - PRInt32 textLen = aTextFrame->GetContentLength(); - if (!mWordBreaks.AppendElements(textLen + 1)) { - mDirection = 0; // signal failure - return; - } - memset(mWordBreaks.Elements(), PR_FALSE, textLen + 1); - PRInt32 textStart; - if (aDirection > 0) { - if (aContext.IsEmpty()) { - // No previous context, so it must be the start of a line or text run - mWordBreaks[0] = PR_TRUE; - } - textStart = aContext.Length(); - mFrag->AppendTo(aContext, textOffset, textLen); - } else { - if (aContext.IsEmpty()) { - // No following context, so it must be the end of a line or text run - mWordBreaks[textLen] = PR_TRUE; - } - textStart = 0; - nsAutoString str; - mFrag->AppendTo(str, textOffset, textLen); - aContext.Insert(str, 0); - } - nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker(); - PRInt32 i; - for (i = 0; i <= textLen; ++i) { - PRInt32 indexInText = i + textStart; - mWordBreaks[i] |= - wordBreaker->BreakInBetween(aContext.get(), indexInText, - aContext.get() + indexInText, - aContext.Length() - indexInText); - } -} - -PRBool -nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect, - PRInt32* aOffset, PeekWordState* aState) -{ - PRInt32 contentLength = GetContentLength(); - NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range"); - - PRBool selectable; - PRUint8 selectStyle; - IsSelectable(&selectable, &selectStyle); - if (selectStyle == NS_STYLE_USER_SELECT_ALL) - return PR_FALSE; - - PRInt32 offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); - ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext); - - if (!cIter.NextCluster()) - return PR_FALSE; - - do { - PRBool isPunctuation = cIter.IsPunctuation(); - PRBool isWhitespace = cIter.IsWhitespace(); - PRBool isWordBreakBefore = cIter.HaveWordBreakBefore(); - if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) { - aState->SetSawBeforeType(); - aState->Update(isPunctuation, isWhitespace); - continue; - } - // See if we can break before the current cluster - if (!aState->mAtStart) { - PRBool canBreak; - if (isPunctuation != aState->mLastCharWasPunctuation) { - canBreak = BreakWordBetweenPunctuation(aState, aForward, - isPunctuation, isWhitespace, aIsKeyboardSelect); - } else if (!aState->mLastCharWasWhitespace && - !isWhitespace && !isPunctuation && isWordBreakBefore) { - // if both the previous and the current character are not white - // space but this can be word break before, we don't need to eat - // a white space in this case. This case happens in some languages - // that their words are not separated by white spaces. E.g., - // Japanese and Chinese. - canBreak = PR_TRUE; - } else { - canBreak = isWordBreakBefore && aState->mSawBeforeType; - } - if (canBreak) { - *aOffset = cIter.GetBeforeOffset() - mContentOffset; - return PR_TRUE; - } - } - aState->Update(isPunctuation, isWhitespace); - } while (cIter.NextCluster()); - - *aOffset = cIter.GetAfterOffset() - mContentOffset; - return PR_FALSE; -} - - // TODO this needs to be deCOMtaminated with the interface fixed in -// nsIFrame.h, but we won't do that until the old textframe is gone. -NS_IMETHODIMP -nsTextFrame::CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, - PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *aRetval) -{ - if (!aRetval) - return NS_ERROR_NULL_POINTER; - - // Text in the range is visible if there is at least one character in the range - // that is not skipped and is mapped by this frame (which is the primary frame) - // or one of its continuations. - for (nsTextFrame* f = this; f; - f = static_cast(GetNextContinuation())) { - if (f->PeekOffsetNoAmount(PR_TRUE, nsnull)) { - *aRetval = PR_TRUE; - return NS_OK; - } - } - - *aRetval = PR_FALSE; - return NS_OK; -} - -NS_IMETHODIMP -nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const -{ - start = GetContentOffset(); - end = GetContentEnd(); - return NS_OK; -} - -static PRInt32 -FindEndOfPunctuationRun(const nsTextFragment* aFrag, - gfxTextRun* aTextRun, - gfxSkipCharsIterator* aIter, - PRInt32 aOffset, - PRInt32 aStart, - PRInt32 aEnd) -{ - PRInt32 i; - - for (i = aStart; i < aEnd - aOffset; ++i) { - if (nsContentUtils::IsPunctuationMarkAt(aFrag, aOffset + i)) { - aIter->SetOriginalOffset(aOffset + i); - FindClusterEnd(aTextRun, aEnd, aIter); - i = aIter->GetOriginalOffset() - aOffset; - } else { - break; - } - } - return i; -} - -/** - * Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE - * if it does not contain a true "letter". - * If returns PR_TRUE, then it also updates aLength to cover just the first-letter - * text. - * - * XXX :first-letter should be handled during frame construction - * (and it has a good bit in common with nextBidi) - * - * @param aLength an in/out parameter: on entry contains the maximum length to - * return, on exit returns length of the first-letter fragment (which may - * include leading and trailing punctuation, for example) - */ -static PRBool -FindFirstLetterRange(const nsTextFragment* aFrag, - gfxTextRun* aTextRun, - PRInt32 aOffset, const gfxSkipCharsIterator& aIter, - PRInt32* aLength) -{ - PRInt32 i; - PRInt32 length = *aLength; - PRInt32 endOffset = aOffset + length; - gfxSkipCharsIterator iter(aIter); - - // skip leading whitespace, then consume clusters that start with punctuation - i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, - GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1), - endOffset); - if (i == length) - return PR_FALSE; - - // If the next character is not a letter or number, there is no first-letter. - // Return PR_TRUE so that we don't go on looking, but set aLength to 0. - if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) { - *aLength = 0; - return PR_TRUE; - } - - // consume another cluster (the actual first letter) - iter.SetOriginalOffset(aOffset + i); - FindClusterEnd(aTextRun, endOffset, &iter); - i = iter.GetOriginalOffset() - aOffset; - if (i + 1 == length) - return PR_TRUE; - - // consume clusters that start with punctuation - i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset); - if (i < length) - *aLength = i; - return PR_TRUE; -} - -static PRUint32 -FindStartAfterSkippingWhitespace(PropertyProvider* aProvider, - nsIFrame::InlineIntrinsicWidthData* aData, - const nsStyleText* aTextStyle, - gfxSkipCharsIterator* aIterator, - PRUint32 aFlowEndInTextRun) -{ - if (aData->skipWhitespace) { - while (aIterator->GetSkippedOffset() < aFlowEndInTextRun && - IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) { - aIterator->AdvanceOriginal(1); - } - } - return aIterator->GetSkippedOffset(); -} - -/* virtual */ -void nsTextFrame::MarkIntrinsicWidthsDirty() -{ - ClearTextRun(); - nsFrame::MarkIntrinsicWidthsDirty(); -} - -// XXX this doesn't handle characters shaped by line endings. We need to -// temporarily override the "current line ending" settings. -void -nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext, - nsIFrame::InlineMinWidthData *aData) -{ - PRUint32 flowEndInTextRun; - gfxContext* ctx = aRenderingContext->ThebesContext(); - gfxSkipCharsIterator iter = - EnsureTextRun(ctx, aData->lineContainer, aData->line, &flowEndInTextRun); - if (!mTextRun) - return; - - // Pass null for the line container. This will disable tab spacing, but that's - // OK since we can't really handle tabs for intrinsic sizing anyway. - const nsStyleText* textStyle = GetStyleText(); - const nsTextFragment* frag = GetFragment(); - PropertyProvider provider(mTextRun, textStyle, frag, this, - iter, PR_INT32_MAX, nsnull, 0); - - PRBool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); - PRBool preformatNewlines = textStyle->NewlineIsSignificant(); - PRBool preformatTabs = textStyle->WhiteSpaceIsSignificant(); - gfxFloat tabWidth = -1; - PRUint32 start = - FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); - - // XXX Should we consider hyphenation here? - for (PRUint32 i = start, wordStart = start; i <= flowEndInTextRun; ++i) { - PRBool preformattedNewline = PR_FALSE; - PRBool preformattedTab = PR_FALSE; - if (i < flowEndInTextRun) { - // XXXldb Shouldn't we be including the newline as part of the - // segment that it ends rather than part of the segment that it - // starts? - preformattedNewline = preformatNewlines && mTextRun->GetChar(i) == '\n'; - preformattedTab = preformatTabs && mTextRun->GetChar(i) == '\t'; - if (!mTextRun->CanBreakLineBefore(i) && !preformattedNewline && - !preformattedTab) { - // we can't break here (and it's not the end of the flow) - continue; - } - } - - if (i > wordStart) { - nscoord width = - NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(wordStart, i - wordStart, &provider)); - aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); - aData->atStartOfLine = PR_FALSE; - - if (collapseWhitespace) { - PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter); - if (trimStart == start) { - // This is *all* trimmable whitespace, so whatever trailingWhitespace - // we saw previously is still trailing... - aData->trailingWhitespace += width; - } else { - // Some non-whitespace so the old trailingWhitespace is no longer trailing - aData->trailingWhitespace = - NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); - } - } else { - aData->trailingWhitespace = 0; - } - } - - if (preformattedTab) { - PropertyProvider::Spacing spacing; - provider.GetSpacing(i, 1, &spacing); - aData->currentLine += nscoord(spacing.mBefore); - gfxFloat afterTab = - AdvanceToNextTab(aData->currentLine, FindLineContainer(this), - mTextRun, &tabWidth); - aData->currentLine = nscoord(afterTab + spacing.mAfter); - wordStart = i + 1; - } else if (i < flowEndInTextRun || - (i == mTextRun->GetLength() && - (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) { - if (preformattedNewline) { - aData->ForceBreak(aRenderingContext); - } else { - aData->OptionallyBreak(aRenderingContext); - } - wordStart = i; - } - } - - if (start < flowEndInTextRun) { - // Check if we have collapsible whitespace at the end - aData->skipWhitespace = - IsTrimmableSpace(provider.GetFragment(), - iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), - textStyle); - } -} - -// XXX Need to do something here to avoid incremental reflow bugs due to -// first-line and first-letter changing min-width -/* virtual */ void -nsTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext, - nsIFrame::InlineMinWidthData *aData) -{ - nsTextFrame* f; - gfxTextRun* lastTextRun = nsnull; - // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames - // in the flow are handled right here. - for (f = this; f; f = static_cast(f->GetNextContinuation())) { - // f->mTextRun could be null if we haven't set up textruns yet for f. - // Except in OOM situations, lastTextRun will only be null for the first - // text frame. - if (f == this || f->mTextRun != lastTextRun) { - nsIFrame* lc; - if (aData->lineContainer && - aData->lineContainer != (lc = FindLineContainer(f))) { - NS_ASSERTION(f != this, "wrong InlineMinWidthData container" - " for first continuation"); - aData->line = nsnull; - aData->lineContainer = lc; - } - - // This will process all the text frames that share the same textrun as f. - f->AddInlineMinWidthForFlow(aRenderingContext, aData); - lastTextRun = f->mTextRun; - } - } -} - -// XXX this doesn't handle characters shaped by line endings. We need to -// temporarily override the "current line ending" settings. -void -nsTextFrame::AddInlinePrefWidthForFlow(nsIRenderingContext *aRenderingContext, - nsIFrame::InlinePrefWidthData *aData) -{ - PRUint32 flowEndInTextRun; - gfxContext* ctx = aRenderingContext->ThebesContext(); - gfxSkipCharsIterator iter = - EnsureTextRun(ctx, aData->lineContainer, aData->line, &flowEndInTextRun); - if (!mTextRun) - return; - - // Pass null for the line container. This will disable tab spacing, but that's - // OK since we can't really handle tabs for intrinsic sizing anyway. - - const nsStyleText* textStyle = GetStyleText(); - const nsTextFragment* frag = GetFragment(); - PropertyProvider provider(mTextRun, textStyle, frag, this, - iter, PR_INT32_MAX, nsnull, 0); - - PRBool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); - PRBool preformatNewlines = textStyle->NewlineIsSignificant(); - PRBool preformatTabs = textStyle->WhiteSpaceIsSignificant(); - gfxFloat tabWidth = -1; - PRUint32 start = - FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); - - // XXX Should we consider hyphenation here? - // If newlines and tabs aren't preformatted, nothing to do inside - // the loop so make i skip to the end - PRUint32 loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun; - for (PRUint32 i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) { - PRBool preformattedNewline = PR_FALSE; - PRBool preformattedTab = PR_FALSE; - if (i < flowEndInTextRun) { - // XXXldb Shouldn't we be including the newline as part of the - // segment that it ends rather than part of the segment that it - // starts? - NS_ASSERTION(preformatNewlines, "We can't be here unless newlines are hard breaks"); - preformattedNewline = preformatNewlines && mTextRun->GetChar(i) == '\n'; - preformattedTab = preformatTabs && mTextRun->GetChar(i) == '\t'; - if (!preformattedNewline && !preformattedTab) { - // we needn't break here (and it's not the end of the flow) - continue; - } - } - - if (i > lineStart) { - nscoord width = - NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(lineStart, i - lineStart, &provider)); - aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); - - if (collapseWhitespace) { - PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter); - if (trimStart == start) { - // This is *all* trimmable whitespace, so whatever trailingWhitespace - // we saw previously is still trailing... - aData->trailingWhitespace += width; - } else { - // Some non-whitespace so the old trailingWhitespace is no longer trailing - aData->trailingWhitespace = - NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); - } - } else { - aData->trailingWhitespace = 0; - } - } - - if (preformattedTab) { - PropertyProvider::Spacing spacing; - provider.GetSpacing(i, 1, &spacing); - aData->currentLine += nscoord(spacing.mBefore); - gfxFloat afterTab = - AdvanceToNextTab(aData->currentLine, FindLineContainer(this), - mTextRun, &tabWidth); - aData->currentLine = nscoord(afterTab + spacing.mAfter); - lineStart = i + 1; - } else if (preformattedNewline) { - aData->ForceBreak(aRenderingContext); - lineStart = i; - } - } - - // Check if we have collapsible whitespace at the end - if (start < flowEndInTextRun) { - aData->skipWhitespace = - IsTrimmableSpace(provider.GetFragment(), - iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), - textStyle); - } -} - -// XXX Need to do something here to avoid incremental reflow bugs due to -// first-line and first-letter changing pref-width -/* virtual */ void -nsTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, - nsIFrame::InlinePrefWidthData *aData) -{ - nsTextFrame* f; - gfxTextRun* lastTextRun = nsnull; - // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames - // in the flow are handled right here. - for (f = this; f; f = static_cast(f->GetNextContinuation())) { - // f->mTextRun could be null if we haven't set up textruns yet for f. - // Except in OOM situations, lastTextRun will only be null for the first - // text frame. - if (f == this || f->mTextRun != lastTextRun) { - nsIFrame* lc; - if (aData->lineContainer && - aData->lineContainer != (lc = FindLineContainer(f))) { - NS_ASSERTION(f != this, "wrong InlinePrefWidthData container" - " for first continuation"); - aData->line = nsnull; - aData->lineContainer = lc; - } - - // This will process all the text frames that share the same textrun as f. - f->AddInlinePrefWidthForFlow(aRenderingContext, aData); - lastTextRun = f->mTextRun; - } - } -} - -/* virtual */ nsSize -nsTextFrame::ComputeSize(nsIRenderingContext *aRenderingContext, - nsSize aCBSize, nscoord aAvailableWidth, - nsSize aMargin, nsSize aBorder, nsSize aPadding, - PRBool aShrinkWrap) -{ - // Inlines and text don't compute size before reflow. - return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); -} - -static nsRect -RoundOut(const gfxRect& aRect) -{ - nsRect r; - r.x = NSToCoordFloor(aRect.X()); - r.y = NSToCoordFloor(aRect.Y()); - r.width = NSToCoordCeil(aRect.XMost()) - r.x; - r.height = NSToCoordCeil(aRect.YMost()) - r.y; - return r; -} - -nsRect -nsTextFrame::ComputeTightBounds(gfxContext* aContext) const -{ - if ((GetStyleContext()->HasTextDecorations() && - eCompatibility_NavQuirks == PresContext()->CompatibilityMode()) || - (GetStateBits() & TEXT_HYPHEN_BREAK)) { - // This is conservative, but OK. - return GetOverflowRect(); - } - - gfxSkipCharsIterator iter = const_cast(this)->EnsureTextRun(); - if (!mTextRun) - return nsRect(0, 0, 0, 0); - - PropertyProvider provider(const_cast(this), iter); - // Trim trailing whitespace - provider.InitializeForDisplay(PR_TRUE); - - gfxTextRun::Metrics metrics = - mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), - ComputeTransformedLength(provider), - gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, - aContext, &provider); - // mAscent should be the same as metrics.mAscent, but it's what we use to - // paint so that's the one we'll use. - return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent); -} - -static PRBool -HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun, - PRInt32 aStartOffset, const gfxSkipCharsIterator& aIter) -{ - if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY)) - return PR_FALSE; - gfxSkipCharsIterator iter = aIter; - while (iter.GetOriginalOffset() > aStartOffset) { - iter.AdvanceOriginal(-1); - if (!iter.IsOriginalCharSkipped()) - break; - if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY) - return PR_TRUE; - } - return PR_FALSE; -} - -void -nsTextFrame::SetLength(PRInt32 aLength) -{ - mContentLengthHint = aLength; - PRInt32 end = GetContentOffset() + aLength; - nsTextFrame* f = static_cast(GetNextInFlow()); - if (!f) - return; - if (end < f->mContentOffset) { - // Our frame is shrinking. Give the text to our next in flow. - f->mContentOffset = end; - if (f->GetTextRun() != mTextRun) { - ClearTextRun(); - f->ClearTextRun(); - } - return; - } - while (f && f->mContentOffset < end) { - // Our frame is growing. Take text from our in-flow. - f->mContentOffset = end; - if (f->GetTextRun() != mTextRun) { - ClearTextRun(); - f->ClearTextRun(); - } - f = static_cast(f->GetNextInFlow()); - } -#ifdef DEBUG - f = this; - PRInt32 iterations = 0; - while (f && iterations < 10) { - f->GetContentLength(); // Assert if negative length - f = static_cast(f->GetNextContinuation()); - ++iterations; - } - f = this; - iterations = 0; - while (f && iterations < 10) { - f->GetContentLength(); // Assert if negative length - f = static_cast(f->GetPrevContinuation()); - ++iterations; - } -#endif -} - -PRBool -nsTextFrame::IsFloatingFirstLetterChild() -{ - if (!GetStateBits() & TEXT_FIRST_LETTER) - return PR_FALSE; - nsIFrame* frame = GetParent(); - if (!frame || frame->GetType() != nsGkAtoms::letterFrame) - return PR_FALSE; - return frame->GetStyleDisplay()->IsFloating(); -} - -NS_IMETHODIMP -nsTextFrame::Reflow(nsPresContext* aPresContext, - nsHTMLReflowMetrics& aMetrics, - const nsHTMLReflowState& aReflowState, - nsReflowStatus& aStatus) -{ - DO_GLOBAL_REFLOW_COUNT("nsTextFrame"); - DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); -#ifdef NOISY_REFLOW - ListTag(stdout); - printf(": BeginReflow: availableSize=%d,%d\n", - aReflowState.availableWidth, aReflowState.availableHeight); -#endif - - ///////////////////////////////////////////////////////////////////// - // Set up flags and clear out state - ///////////////////////////////////////////////////////////////////// - - // Clear out the reflow state flags in mState (without destroying - // the TEXT_BLINK_ON_OR_PRINTING bit). We also clear the whitespace flags - // because this can change whether the frame maps whitespace-only text or not. - RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); - - // Temporarily map all possible content while we construct our new textrun. - // so that when doing reflow our styles prevail over any part of the - // textrun we look at. Note that next-in-flows may be mapping the same - // content; gfxTextRun construction logic will ensure that we take priority. - PRInt32 maxContentLength = GetInFlowContentLength(); - - // XXX If there's no line layout, we shouldn't even have created this - // frame. This may happen if, for example, this is text inside a table - // but not inside a cell. For now, just don't reflow. We also don't need to - // reflow if there is no content. - if (!aReflowState.mLineLayout || !maxContentLength) { - ClearMetrics(aMetrics); - aStatus = NS_FRAME_COMPLETE; - return NS_OK; - } - - nsLineLayout& lineLayout = *aReflowState.mLineLayout; - - if (aReflowState.mFlags.mBlinks) { - if (0 == (mState & TEXT_BLINK_ON_OR_PRINTING) && PresContext()->IsDynamic()) { - mState |= TEXT_BLINK_ON_OR_PRINTING; - nsBlinkTimer::AddBlinkFrame(aPresContext, this); - } - } - else { - if (0 != (mState & TEXT_BLINK_ON_OR_PRINTING) && PresContext()->IsDynamic()) { - mState &= ~TEXT_BLINK_ON_OR_PRINTING; - nsBlinkTimer::RemoveBlinkFrame(this); - } - } - - const nsStyleText* textStyle = GetStyleText(); - - PRBool atStartOfLine = lineLayout.LineIsEmpty(); - if (atStartOfLine) { - AddStateBits(TEXT_START_OF_LINE); - } - - PRUint32 flowEndInTextRun; - nsIFrame* lineContainer = lineLayout.GetLineContainerFrame(); - gfxContext* ctx = aReflowState.rendContext->ThebesContext(); - const nsTextFragment* frag = GetFragment(); - - // DOM offsets of the text range we need to measure, after trimming - // whitespace, restricting to first-letter, and restricting preformatted text - // to nearest newline - PRInt32 length = maxContentLength; - PRInt32 offset = GetContentOffset(); - - // Restrict preformatted text to the nearest newline - PRInt32 newLineOffset = -1; // this will be -1 or a content offset - if (textStyle->NewlineIsSignificant()) { - newLineOffset = FindChar(frag, offset, length, '\n'); - if (newLineOffset >= 0) { - length = newLineOffset + 1 - offset; - } - } - if (atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) { - // Skip leading whitespace. Make sure we don't skip a 'pre-line' - // newline if there is one. - PRInt32 skipLength = newLineOffset >= 0 ? length - 1 : length; - PRInt32 whitespaceCount = - GetTrimmableWhitespaceCount(frag, offset, skipLength, 1); - offset += whitespaceCount; - length -= whitespaceCount; - } - - PRBool completedFirstLetter = PR_FALSE; - // Layout dependent styles are a problem because we need to reconstruct - // the gfxTextRun based on our layout. - if (lineLayout.GetInFirstLetter() || lineLayout.GetInFirstLine()) { - SetLength(maxContentLength); - - if (lineLayout.GetInFirstLetter()) { - // floating first-letter boundaries are significant in textrun - // construction, so clear the textrun out every time we hit a first-letter - // and have changed our length (which controls the first-letter boundary) - ClearTextRun(); - // Find the length of the first-letter. We need a textrun for this. - gfxSkipCharsIterator iter = - EnsureTextRun(ctx, lineContainer, lineLayout.GetLine(), &flowEndInTextRun); - - if (mTextRun) { - PRInt32 firstLetterLength = length; - if (lineLayout.GetFirstLetterStyleOK()) { - completedFirstLetter = - FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength); - if (newLineOffset >= 0) { - // Don't allow a preformatted newline to be part of a first-letter. - firstLetterLength = PR_MIN(firstLetterLength, length - 1); - if (length == 1) { - // There is no text to be consumed by the first-letter before the - // preformatted newline. Note that the first letter is therefore - // complete (FindFirstLetterRange will have returned false). - completedFirstLetter = PR_TRUE; - } - } - } else { - // We're in a first-letter frame's first in flow, so if there - // was a first-letter, we'd be it. However, for one reason - // or another (e.g., preformatted line break before this text), - // we're not actually supposed to have first-letter style. So - // just make a zero-length first-letter. - firstLetterLength = 0; - completedFirstLetter = PR_TRUE; - } - length = firstLetterLength; - if (length) { - AddStateBits(TEXT_FIRST_LETTER); - } - // Change this frame's length to the first-letter length right now - // so that when we rebuild the textrun it will be built with the - // right first-letter boundary - SetLength(offset + length - GetContentOffset()); - // Ensure that the textrun will be rebuilt - ClearTextRun(); - } - } - } - - gfxSkipCharsIterator iter = - EnsureTextRun(ctx, lineContainer, lineLayout.GetLine(), &flowEndInTextRun); - - if (mTextRun && iter.GetOriginalEnd() < offset + length) { - // The textrun does not map enough text for this frame. This can happen - // when the textrun was ended in the middle of a text node because a - // preformatted newline was encountered, and prev-in-flow frames have - // consumed all the text of the textrun. We need a new textrun. - ClearTextRun(); - iter = EnsureTextRun(ctx, lineContainer, - lineLayout.GetLine(), &flowEndInTextRun); - } - - if (!mTextRun) { - ClearMetrics(aMetrics); - aStatus = NS_FRAME_COMPLETE; - return NS_OK; - } - - NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length) - <= mTextRun->GetLength(), - "Text run does not map enough text for our reflow"); - - ///////////////////////////////////////////////////////////////////// - // See how much text should belong to this text frame, and measure it - ///////////////////////////////////////////////////////////////////// - - iter.SetOriginalOffset(offset); - nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ? - (lineLayout.GetCurrentFrameXDistanceFromBlock() - - lineContainer->GetUsedBorderAndPadding().left) - : -1; - PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length, - lineContainer, xOffsetForTabs); - - PRUint32 transformedOffset = provider.GetStart().GetSkippedOffset(); - - // The metrics for the text go in here - gfxTextRun::Metrics textMetrics; - gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ? - gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS : - gfxFont::LOOSE_INK_EXTENTS; -#ifdef MOZ_MATHML - NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags), - "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore"); -#endif - - PRInt32 limitLength = length; - PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent); - PRBool forceBreakAfter = PR_FALSE; - if (forceBreak >= offset + length) { - forceBreakAfter = forceBreak == offset + length; - // The break is not within the text considered for this textframe. - forceBreak = -1; - } - if (forceBreak >= 0) { - limitLength = forceBreak - offset; - NS_ASSERTION(limitLength >= 0, "Weird break found!"); - } - // This is the heart of text reflow right here! We don't know where - // to break, so we need to see how much text fits in the available width. - PRUint32 transformedLength; - if (offset + limitLength >= PRInt32(frag->GetLength())) { - NS_ASSERTION(offset + limitLength == PRInt32(frag->GetLength()), - "Content offset/length out of bounds"); - NS_ASSERTION(flowEndInTextRun >= transformedOffset, - "Negative flow length?"); - transformedLength = flowEndInTextRun - transformedOffset; - } else { - // we're not looking at all the content, so we need to compute the - // length of the transformed substring we're looking at - gfxSkipCharsIterator iter(provider.GetStart()); - iter.SetOriginalOffset(offset + limitLength); - transformedLength = iter.GetSkippedOffset() - transformedOffset; - } - PRUint32 transformedLastBreak = 0; - PRBool usedHyphenation; - gfxFloat trimmedWidth = 0; - gfxFloat availWidth = aReflowState.availableWidth; - PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant(); - PRInt32 unusedOffset; - gfxBreakPriority breakPriority; - lineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority); - PRUint32 transformedCharsFit = - mTextRun->BreakAndMeasureText(transformedOffset, transformedLength, - (GetStateBits() & TEXT_START_OF_LINE) != 0, - availWidth, - &provider, !lineLayout.LineIsBreakable(), - canTrimTrailingWhitespace ? &trimmedWidth : nsnull, - &textMetrics, boundingBoxType, ctx, - &usedHyphenation, &transformedLastBreak, - textStyle->WordCanWrap(), &breakPriority); - // The "end" iterator points to the first character after the string mapped - // by this frame. Basically, its original-string offset is offset+charsFit - // after we've computed charsFit. - gfxSkipCharsIterator end(provider.GetEndHint()); - end.SetSkippedOffset(transformedOffset + transformedCharsFit); - PRInt32 charsFit = end.GetOriginalOffset() - offset; - if (offset + charsFit == newLineOffset) { - // We broke before a trailing preformatted '\n'. The newline should - // be assigned to this frame. Note that newLineOffset will be -1 if - // there was no preformatted newline, so we wouldn't get here in that - // case. - ++charsFit; - } - // That might have taken us beyond our assigned content range (because - // we might have advanced over some skipped chars that extend outside - // this frame), so get back in. - PRInt32 lastBreak = -1; - if (charsFit >= limitLength) { - charsFit = limitLength; - if (transformedLastBreak != PR_UINT32_MAX) { - // lastBreak is needed. - // This may set lastBreak greater than 'length', but that's OK - lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak); - } - end.SetOriginalOffset(offset + charsFit); - // If we were forced to fit, and the break position is after a soft hyphen, - // note that this is a hyphenation break. - if ((forceBreak >= 0 || forceBreakAfter) && - HasSoftHyphenBefore(frag, mTextRun, offset, end)) { - usedHyphenation = PR_TRUE; - } - } - if (usedHyphenation) { - // Fix up metrics to include hyphen - AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx); - AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS); - } - - gfxFloat trimmableWidth = 0; - PRBool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; - if (canTrimTrailingWhitespace) { - // Optimization: if we trimmed trailing whitespace, and we can be sure - // this frame will be at the end of the line, then leave it trimmed off. - // Otherwise we have to undo the trimming, in case we're not at the end of - // the line. (If we actually do end up at the end of the line, we'll have - // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid - // having to re-do it.) - if (brokeText) { - // We're definitely going to break so our trailing whitespace should - // definitely be timmed. Record that we've already done it. - AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); - } else { - // We might not be at the end of the line. (Note that even if this frame - // ends in breakable whitespace, it might not be at the end of the line - // because it might be followed by breakable, but preformatted, whitespace.) - // Undo the trimming. - textMetrics.mAdvanceWidth += trimmedWidth; - trimmableWidth = trimmedWidth; - if (mTextRun->IsRightToLeft()) { - // Space comes before text, so the bounding box is moved to the - // right by trimmdWidth - textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0)); - } - } - } - - if (!brokeText && lastBreak >= 0) { - // Since everything fit and no break was forced, - // record the last break opportunity - NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aReflowState.availableWidth, - "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?"); - lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, PR_TRUE, breakPriority); - } - - PRInt32 contentLength = offset + charsFit - GetContentOffset(); - - ///////////////////////////////////////////////////////////////////// - // Compute output metrics - ///////////////////////////////////////////////////////////////////// - - // first-letter frames should use the tight bounding box metrics for ascent/descent - // for good drop-cap effects - if (GetStateBits() & TEXT_FIRST_LETTER) { - textMetrics.mAscent = PR_MAX(0, -textMetrics.mBoundingBox.Y()); - textMetrics.mDescent = PR_MAX(0, textMetrics.mBoundingBox.YMost()); - } - - // Setup metrics for caller - // Disallow negative widths - aMetrics.width = NSToCoordCeil(PR_MAX(0, textMetrics.mAdvanceWidth)); - - if (transformedCharsFit == 0 && !usedHyphenation) { - aMetrics.ascent = 0; - aMetrics.height = 0; - } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) { - // Use actual text metrics for floating first letter frame. - aMetrics.ascent = NSToCoordCeil(textMetrics.mAscent); - aMetrics.height = aMetrics.ascent + NSToCoordCeil(textMetrics.mDescent); - } else { - // Otherwise, ascent should contain the overline drawable area. - // And also descent should contain the underline drawable area. - // nsIFontMetrics::GetMaxAscent/GetMaxDescent contains them. - nscoord fontAscent, fontDescent; - nsIFontMetrics* fm = provider.GetFontMetrics(); - fm->GetMaxAscent(fontAscent); - fm->GetMaxDescent(fontDescent); - aMetrics.ascent = PR_MAX(NSToCoordCeil(textMetrics.mAscent), fontAscent); - nscoord descent = PR_MAX(NSToCoordCeil(textMetrics.mDescent), fontDescent); - aMetrics.height = aMetrics.ascent + descent; - } - - NS_ASSERTION(aMetrics.ascent >= 0, "Negative ascent???"); - NS_ASSERTION(aMetrics.height - aMetrics.ascent >= 0, "Negative descent???"); - - mAscent = aMetrics.ascent; - - // Handle text that runs outside its normal bounds. - nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent); - aMetrics.mOverflowArea.UnionRect(boundingBox, - nsRect(0, 0, aMetrics.width, aMetrics.height)); - - UnionTextDecorationOverflow(aPresContext, provider, &aMetrics.mOverflowArea); - - ///////////////////////////////////////////////////////////////////// - // Clean up, update state - ///////////////////////////////////////////////////////////////////// - - // If all our characters are discarded or collapsed, then trimmable width - // from the last textframe should be preserved. Otherwise the trimmable width - // from this textframe overrides. (Currently in CSS trimmable width can be - // at most one space so there's no way for trimmable width from a previous - // frame to accumulate with trimmable width from this frame.) - if (transformedCharsFit > 0) { - lineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth)); - AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); - } - if (charsFit > 0 && charsFit == length && - HasSoftHyphenBefore(frag, mTextRun, offset, end)) { - // Record a potential break after final soft hyphen - lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, - textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth, - eNormalBreak); - } - PRBool breakAfter = forceBreakAfter; - // length == 0 means either the text is empty or it's all collapsed away - PRBool emptyTextAtStartOfLine = atStartOfLine && length == 0; - if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine && - transformedOffset + transformedLength == mTextRun->GetLength() && - (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) { - // We placed all the text in the textrun and we have a break opportunity at - // the end of the textrun. We need to record it because the following - // content may not care about nsLineBreaker. - - // Note that because we didn't break, we can be sure that (thanks to the - // code up above) textMetrics.mAdvanceWidth includes the width of any - // trailing whitespace. So we need to subtract trimmableWidth here - // because if we did break at this point, that much width would be trimmed. - if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) { - breakAfter = PR_TRUE; - } else { - lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, PR_TRUE, - eNormalBreak); - } - } - - // Compute reflow status - aStatus = contentLength == maxContentLength - ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; - - if (charsFit == 0 && length > 0) { - // Couldn't place any text - aStatus = NS_INLINE_LINE_BREAK_BEFORE(); - } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) { - // Ends in \n - aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); - lineLayout.SetLineEndsInBR(PR_TRUE); - } else if (breakAfter) { - aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); - } - if (completedFirstLetter) { - lineLayout.SetFirstLetterStyleOK(PR_FALSE); - aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE; - } - - // Compute space and letter counts for justification, if required - if (!textStyle->WhiteSpaceIsSignificant() && - lineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY) { - AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present. - // This is corrected for in nsLineLayout::TrimWhiteSpaceIn. - PRInt32 numJustifiableCharacters = - provider.ComputeJustifiableCharacters(offset, charsFit); - - NS_ASSERTION(numJustifiableCharacters <= charsFit, - "Bad justifiable character count"); - lineLayout.SetTextJustificationWeights(numJustifiableCharacters, - charsFit - numJustifiableCharacters); - } - - SetLength(contentLength); - - if (mContent->HasFlag(NS_TEXT_IN_SELECTION)) { - // XXXroc Watch out, this could be slow!!! Speed up GetSelectionDetails? - SelectionDetails* details = GetSelectionDetails(); - if (details) { - AddStateBits(NS_FRAME_SELECTED_CONTENT); - DestroySelectionDetails(details); - } else { - RemoveStateBits(NS_FRAME_SELECTED_CONTENT); - } - } - - Invalidate(aMetrics.mOverflowArea); - -#ifdef NOISY_REFLOW - ListTag(stdout); - printf(": desiredSize=%d,%d(b=%d) status=%x\n", - aMetrics.width, aMetrics.height, aMetrics.ascent, - aStatus); -#endif - NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); - return NS_OK; -} - -/* virtual */ PRBool -nsTextFrame::CanContinueTextRun() const -{ - // We can continue a text run through a text frame - return PR_TRUE; -} - -nsTextFrame::TrimOutput -nsTextFrame::TrimTrailingWhiteSpace(nsIRenderingContext* aRC) -{ - TrimOutput result; - result.mChanged = PR_FALSE; - result.mLastCharIsJustifiable = PR_FALSE; - result.mDeltaWidth = 0; - - AddStateBits(TEXT_END_OF_LINE); - - PRInt32 contentLength = GetContentLength(); - if (!contentLength) - return result; - - gfxContext* ctx = aRC->ThebesContext(); - gfxSkipCharsIterator start = EnsureTextRun(ctx); - NS_ENSURE_TRUE(mTextRun, result); - - PRUint32 trimmedStart = start.GetSkippedOffset(); - - const nsTextFragment* frag = GetFragment(); - TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE); - gfxSkipCharsIterator trimmedEndIter = start; - const nsStyleText* textStyle = GetStyleText(); - gfxFloat delta = 0; - PRUint32 trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); - - if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) { - // We pre-trimmed this frame, so the last character is justifiable - result.mLastCharIsJustifiable = PR_TRUE; - } else if (trimmed.GetEnd() < GetContentEnd()) { - gfxSkipCharsIterator end = trimmedEndIter; - PRUint32 endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); - if (trimmedEnd < endOffset) { - // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's - // OK to pass null for the line container. - PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, - nsnull, 0); - delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider); - // non-compressed whitespace being skipped at end of line -> justifiable - // XXX should we actually *count* justifiable characters that should be - // removed from the overall count? I think so... - result.mLastCharIsJustifiable = PR_TRUE; - result.mChanged = PR_TRUE; - } - } - - if (!result.mLastCharIsJustifiable && - (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) { - // Check if any character in the last cluster is justifiable - PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, - nsnull, 0); - PRBool isCJK = IsChineseJapaneseLangGroup(this); - gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter); - provider.FindJustificationRange(&justificationStart, &justificationEnd); - - PRInt32 i; - for (i = justificationEnd.GetOriginalOffset(); i < trimmed.GetEnd(); ++i) { - if (IsJustifiableCharacter(frag, i, isCJK)) { - result.mLastCharIsJustifiable = PR_TRUE; - } - } - } - - gfxFloat advanceDelta; - mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart, - (GetStateBits() & TEXT_START_OF_LINE) != 0, PR_TRUE, - &advanceDelta, ctx); - if (advanceDelta != 0) { - result.mChanged = PR_TRUE; - } - - // aDeltaWidth is *subtracted* from our width. - // If advanceDelta is positive then setting the line break made us longer, - // so aDeltaWidth could go negative. - result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta); - // If aDeltaWidth goes negative, that means this frame might not actually fit - // anymore!!! We need higher level line layout to recover somehow. - // If it's because the frame has a soft hyphen that is now being displayed, - // this should actually be OK, because our reflow recorded the break - // opportunity that allowed the soft hyphen to be used, and we wouldn't - // have recorded the opportunity unless the hyphen fit (or was the first - // opportunity on the line). - // Otherwise this can/ really only happen when we have glyphs with special - // shapes at the end of lines, I think. Breaking inside a kerning pair won't - // do it because that would mean we broke inside this textrun, and - // BreakAndMeasureText should make sure the resulting shaped substring fits. - // Maybe if we passed a maxTextLength? But that only happens at direction - // changes (so we wouldn't kern across the boundary) or for first-letter - // (which always fits because it starts the line!). - NS_WARN_IF_FALSE(result.mDeltaWidth >= 0, - "Negative deltawidth, something odd is happening"); - -#ifdef NOISY_TRIM - ListTag(stdout); - printf(": trim => %d\n", result.mDeltaWidth); -#endif - return result; -} - -nsRect -nsTextFrame::RecomputeOverflowRect() -{ - gfxSkipCharsIterator iter = EnsureTextRun(); - if (!mTextRun) - return nsRect(nsPoint(0,0), GetSize()); - - PropertyProvider provider(this, iter); - provider.InitializeForDisplay(PR_TRUE); - - gfxTextRun::Metrics textMetrics = - mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), - ComputeTransformedLength(provider), - gfxFont::LOOSE_INK_EXTENTS, nsnull, - &provider); - - nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent); - boundingBox.UnionRect(boundingBox, - nsRect(nsPoint(0,0), GetSize())); - - UnionTextDecorationOverflow(PresContext(), provider, &boundingBox); - - return boundingBox; -} - -static PRUnichar TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun, - PRUint32 aSkippedOffset, PRUnichar aChar) -{ - if (aChar == '\n') { - return aStyle->NewlineIsSignificant() ? aChar : ' '; - } - switch (aStyle->mTextTransform) { - case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: - nsContentUtils::GetCaseConv()->ToLower(aChar, &aChar); - break; - case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: - nsContentUtils::GetCaseConv()->ToUpper(aChar, &aChar); - break; - case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: - if (aTextRun->CanBreakLineBefore(aSkippedOffset)) { - nsContentUtils::GetCaseConv()->ToTitle(aChar, &aChar); - } - break; - } - - return aChar; -} - -nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString, - gfxSkipChars* aSkipChars, - gfxSkipCharsIterator* aSkipIter, - PRUint32 aSkippedStartOffset, - PRUint32 aSkippedMaxLength) -{ - // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient... - gfxSkipCharsBuilder skipCharsBuilder; - nsTextFrame* textFrame; - const nsTextFragment* textFrag = GetFragment(); - PRUint32 keptCharsLength = 0; - PRUint32 validCharsLength = 0; - - // Build skipChars and copy text, for each text frame in this continuation block - for (textFrame = this; textFrame; - textFrame = static_cast(textFrame->GetNextContinuation())) { - // For each text frame continuation in this block ... - - // Ensure the text run and grab the gfxSkipCharsIterator for it - gfxSkipCharsIterator iter = textFrame->EnsureTextRun(); - if (!textFrame->mTextRun) - return NS_ERROR_FAILURE; - - // Skip to the start of the text run, past ignored chars at start of line - // XXX In the future we may decide to trim extra spaces before a hard line - // break, in which case we need to accurately detect those sitations and - // call GetTrimmedOffsets() with PR_TRUE to trim whitespace at the line's end - TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, PR_FALSE); - PRInt32 startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset; - if (startOfLineSkipChars > 0) { - skipCharsBuilder.SkipChars(startOfLineSkipChars); - iter.SetOriginalOffset(trimmedContentOffsets.mStart); - } - - // Keep and copy the appropriate chars withing the caller's requested range - const nsStyleText* textStyle = textFrame->GetStyleText(); - while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() && - keptCharsLength < aSkippedMaxLength) { - // For each original char from content text - if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) { - skipCharsBuilder.SkipChar(); - } else { - ++keptCharsLength; - skipCharsBuilder.KeepChar(); - if (aAppendToString) { - aAppendToString->Append( - TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(), - textFrag->CharAt(iter.GetOriginalOffset()))); - } - } - iter.AdvanceOriginal(1); - } - if (keptCharsLength >= aSkippedMaxLength) { - break; // Already past the end, don't build string or gfxSkipCharsIter anymore - } - } - - if (aSkipChars) { - aSkipChars->TakeFrom(&skipCharsBuilder); // Copy skipChars into aSkipChars - if (aSkipIter) { - // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator, - // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipCars. - *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength()); - } - } - - return NS_OK; -} - -#ifdef DEBUG -// Translate the mapped content into a string that's printable -void -nsTextFrame::ToCString(nsCString& aBuf, PRInt32* aTotalContentLength) const -{ - // Get the frames text content - const nsTextFragment* frag = GetFragment(); - if (!frag) { - return; - } - - // Compute the total length of the text content. - *aTotalContentLength = frag->GetLength(); - - PRInt32 contentLength = GetContentLength(); - // Set current fragment and current fragment offset - if (0 == contentLength) { - return; - } - PRInt32 fragOffset = GetContentOffset(); - PRInt32 n = fragOffset + contentLength; - while (fragOffset < n) { - PRUnichar ch = frag->CharAt(fragOffset++); - if (ch == '\r') { - aBuf.AppendLiteral("\\r"); - } else if (ch == '\n') { - aBuf.AppendLiteral("\\n"); - } else if (ch == '\t') { - aBuf.AppendLiteral("\\t"); - } else if ((ch < ' ') || (ch >= 127)) { - aBuf.Append(nsPrintfCString("\\u%04x", ch)); - } else { - aBuf.Append(ch); - } - } -} -#endif - -nsIAtom* -nsTextFrame::GetType() const -{ - return nsGkAtoms::textFrame; -} - -/* virtual */ PRBool -nsTextFrame::IsEmpty() -{ - NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) || - !(mState & TEXT_ISNOT_ONLY_WHITESPACE), - "Invalid state"); - - // XXXldb Should this check compatibility mode as well??? - const nsStyleText* textStyle = GetStyleText(); - if (textStyle->WhiteSpaceIsSignificant()) { - // XXX shouldn't we return true if the length is zero? - return PR_FALSE; - } - - if (mState & TEXT_ISNOT_ONLY_WHITESPACE) { - return PR_FALSE; - } - - if (mState & TEXT_IS_ONLY_WHITESPACE) { - return PR_TRUE; - } - - PRBool isEmpty = IsAllWhitespace(GetFragment(), - textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE); - mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); - return isEmpty; -} - -#ifdef DEBUG -NS_IMETHODIMP -nsTextFrame::GetFrameName(nsAString& aResult) const -{ - return MakeFrameName(NS_LITERAL_STRING("Text"), aResult); -} - -NS_IMETHODIMP_(nsFrameState) -nsTextFrame::GetDebugStateBits() const -{ - // mask out our emptystate flags; those are just caches - return nsFrame::GetDebugStateBits() & - ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS); -} - -NS_IMETHODIMP -nsTextFrame::List(FILE* out, PRInt32 aIndent) const -{ - // Output the tag - IndentBy(out, aIndent); - ListTag(out); -#ifdef DEBUG_waterson - fprintf(out, " [parent=%p]", mParent); -#endif - if (HasView()) { - fprintf(out, " [view=%p]", static_cast(GetView())); - } - - PRInt32 totalContentLength; - nsCAutoString tmp; - ToCString(tmp, &totalContentLength); - - // Output the first/last content offset and prev/next in flow info - PRBool isComplete = GetContentEnd() == totalContentLength; - fprintf(out, "[%d,%d,%c] ", - GetContentOffset(), GetContentLength(), - isComplete ? 'T':'F'); - - if (nsnull != mNextSibling) { - fprintf(out, " next=%p", static_cast(mNextSibling)); - } - nsIFrame* prevContinuation = GetPrevContinuation(); - if (nsnull != prevContinuation) { - fprintf(out, " prev-continuation=%p", static_cast(prevContinuation)); - } - if (nsnull != mNextContinuation) { - fprintf(out, " next-continuation=%p", static_cast(mNextContinuation)); - } - - // Output the rect and state - fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); - if (0 != mState) { - if (mState & NS_FRAME_SELECTED_CONTENT) { - fprintf(out, " [state=%08x] SELECTED", mState); - } else { - fprintf(out, " [state=%08x]", mState); - } - } - fprintf(out, " [content=%p]", static_cast(mContent)); - if (HasOverflowRect()) { - nsRect overflowArea = GetOverflowRect(); - fprintf(out, " [overflow=%d,%d,%d,%d]", overflowArea.x, overflowArea.y, - overflowArea.width, overflowArea.height); - } - fprintf(out, " sc=%p", static_cast(mStyleContext)); - nsIAtom* pseudoTag = mStyleContext->GetPseudoType(); - if (pseudoTag) { - nsAutoString atomString; - pseudoTag->ToString(atomString); - fprintf(out, " pst=%s", - NS_LossyConvertUTF16toASCII(atomString).get()); - } - fputs("<\n", out); - - // Output the text - aIndent++; - - IndentBy(out, aIndent); - fputs("\"", out); - fputs(tmp.get(), out); - fputs("\"\n", out); - - aIndent--; - IndentBy(out, aIndent); - fputs(">\n", out); - - return NS_OK; -} -#endif - -void -nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart, PRInt32 aEnd) -{ - AddStateBits(NS_FRAME_IS_BIDI); - - /* - * After Bidi resolution we may need to reassign text runs. - * This is called during bidi resolution from the block container, so we - * shouldn't be holding a local reference to a textrun anywhere. - */ - ClearTextRun(); - - nsTextFrame* prev = static_cast(GetPrevContinuation()); - if (prev) { - // the bidi resolver can be very evil when columns/pages are involved. Don't - // let it violate our invariants. - PRInt32 prevOffset = prev->GetContentOffset(); - aStart = PR_MAX(aStart, prevOffset); - aEnd = PR_MAX(aEnd, prevOffset); - prev->ClearTextRun(); - } - - mContentOffset = aStart; - SetLength(aEnd - aStart); -} - -/** - * @return PR_TRUE if this text frame ends with a newline character. It should return - * PR_FALSE if it is not a text frame. - */ -PRBool -nsTextFrame::HasTerminalNewline() const -{ - return ::HasTerminalNewline(this); -} - -PRBool -nsTextFrame::IsAtEndOfLine() const -{ - return (GetStateBits() & TEXT_END_OF_LINE) != 0; -} - -const nsTextFragment* -nsTextFrame::GetFragmentInternal() const -{ - return PresContext()->IsDynamic() ? mContent->GetText() : - nsLayoutUtils::GetTextFragmentForPrinting(this); -} diff --git a/tests/cpp/nsThread.cpp b/tests/cpp/nsThread.cpp deleted file mode 100644 index 9539c56..0000000 --- a/tests/cpp/nsThread.cpp +++ /dev/null @@ -1,702 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla code. - * - * The Initial Developer of the Original Code is Google Inc. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Darin Fisher - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "timelog.h" - -#include "nsThread.h" -#include "nsThreadManager.h" -#include "nsIClassInfoImpl.h" -#include "nsIProgrammingLanguage.h" -#include "nsAutoLock.h" -#include "nsAutoPtr.h" -#include "nsCOMPtr.h" -#include "prlog.h" -#include "nsThreadUtilsInternal.h" - -#ifdef PR_LOGGING -static PRLogModuleInfo *sLog = PR_NewLogModule("nsThread"); -#endif -#define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args) - -NS_DECL_CI_INTERFACE_GETTER(nsThread) - -nsIThreadObserver* nsThread::sGlobalObserver; - -//----------------------------------------------------------------------------- -// Because we do not have our own nsIFactory, we have to implement nsIClassInfo -// somewhat manually. - -class nsThreadClassInfo : public nsIClassInfo { -public: - NS_DECL_ISUPPORTS_INHERITED // no mRefCnt - NS_DECL_NSICLASSINFO - - nsThreadClassInfo() {} -}; - -static nsThreadClassInfo sThreadClassInfo; - -NS_IMETHODIMP_(nsrefcnt) nsThreadClassInfo::AddRef() { return 2; } -NS_IMETHODIMP_(nsrefcnt) nsThreadClassInfo::Release() { return 1; } -NS_IMPL_QUERY_INTERFACE1(nsThreadClassInfo, nsIClassInfo) - -NS_IMETHODIMP -nsThreadClassInfo::GetInterfaces(PRUint32 *count, nsIID ***array) -{ - return NS_CI_INTERFACE_GETTER_NAME(nsThread)(count, array); -} - -NS_IMETHODIMP -nsThreadClassInfo::GetHelperForLanguage(PRUint32 lang, nsISupports **result) -{ - *result = nsnull; - return NS_OK; -} - -NS_IMETHODIMP -nsThreadClassInfo::GetContractID(char **result) -{ - *result = nsnull; - return NS_OK; -} - -NS_IMETHODIMP -nsThreadClassInfo::GetClassDescription(char **result) -{ - *result = nsnull; - return NS_OK; -} - -NS_IMETHODIMP -nsThreadClassInfo::GetClassID(nsCID **result) -{ - *result = nsnull; - return NS_OK; -} - -NS_IMETHODIMP -nsThreadClassInfo::GetImplementationLanguage(PRUint32 *result) -{ - *result = nsIProgrammingLanguage::CPLUSPLUS; - return NS_OK; -} - -NS_IMETHODIMP -nsThreadClassInfo::GetFlags(PRUint32 *result) -{ - *result = THREADSAFE; - return NS_OK; -} - -NS_IMETHODIMP -nsThreadClassInfo::GetClassIDNoAlloc(nsCID *result) -{ - return NS_ERROR_NOT_AVAILABLE; -} - -//----------------------------------------------------------------------------- - -NS_IMPL_THREADSAFE_ADDREF(nsThread) -NS_IMPL_THREADSAFE_RELEASE(nsThread) -NS_INTERFACE_MAP_BEGIN(nsThread) - NS_INTERFACE_MAP_ENTRY(nsIThread) - NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) - NS_INTERFACE_MAP_ENTRY(nsIEventTarget) - NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) - if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { - foundInterface = static_cast(&sThreadClassInfo); - } else -NS_INTERFACE_MAP_END -NS_IMPL_CI_INTERFACE_GETTER4(nsThread, nsIThread, nsIThreadInternal, - nsIEventTarget, nsISupportsPriority) - -//----------------------------------------------------------------------------- - -class nsThreadStartupEvent : public nsRunnable { -public: - // Create a new thread startup object. - static nsThreadStartupEvent *Create() { - nsThreadStartupEvent *startup = new nsThreadStartupEvent(); - if (startup && startup->mMon) - return startup; - // Allocation failure - delete startup; - return nsnull; - } - - // This method does not return until the thread startup object is in the - // completion state. - void Wait() { - if (mInitialized) // Maybe avoid locking... - return; - nsAutoMonitor mon(mMon); - while (!mInitialized) - mon.Wait(); - } - - // This method needs to be public to support older compilers (xlC_r on AIX). - // It should be called directly as this class type is reference counted. - virtual ~nsThreadStartupEvent() { - if (mMon) - nsAutoMonitor::DestroyMonitor(mMon); - } - -private: - NS_IMETHOD Run() { - nsAutoMonitor mon(mMon); - mInitialized = PR_TRUE; - mon.Notify(); - return NS_OK; - } - - nsThreadStartupEvent() - : mMon(nsAutoMonitor::NewMonitor("xpcom.threadstartup")) - , mInitialized(PR_FALSE) { - } - - PRMonitor *mMon; - PRBool mInitialized; -}; - -//----------------------------------------------------------------------------- - -// This event is responsible for notifying nsThread::Shutdown that it is time -// to call PR_JoinThread. -class nsThreadShutdownAckEvent : public nsRunnable { -public: - nsThreadShutdownAckEvent(nsThreadShutdownContext *ctx) - : mShutdownContext(ctx) { - } - NS_IMETHOD Run() { - mShutdownContext->shutdownAck = PR_TRUE; - return NS_OK; - } -private: - nsThreadShutdownContext *mShutdownContext; -}; - -// This event is responsible for setting mShutdownContext -class nsThreadShutdownEvent : public nsRunnable { -public: - nsThreadShutdownEvent(nsThread *thr, nsThreadShutdownContext *ctx) - : mThread(thr), mShutdownContext(ctx) { - } - NS_IMETHOD Run() { - mThread->mShutdownContext = mShutdownContext; - return NS_OK; - } -private: - nsRefPtr mThread; - fprintf(logfp, "%s.%09ld: New Thread (%p)\n", out.str, out.nsec, (void *)self); - - // Inform the ThreadManager - nsThreadManager::get()->RegisterCurrentThread(self); - - // Wait for and process startup event - nsCOMPtr event; - if (!self->GetEvent(PR_TRUE, getter_AddRefs(event))) { - NS_WARNING("failed waiting for thread startup event"); - return; - } - event->Run(); // unblocks nsThread::Init - event = nsnull; - - // Now, process incoming events... - while (!self->ShuttingDown()) - NS_ProcessNextEvent(self); - - // Do NS_ProcessPendingEvents but with special handling to set - // mEventsAreDoomed atomically with the removal of the last event. The key - // invariant here is that we will never permit PutEvent to succeed if the - // event would be left in the queue after our final call to - // NS_ProcessPendingEvents. - while (PR_TRUE) { - { - nsAutoLock lock(self->mLock); - if (!self->mEvents->HasPendingEvent()) { - // No events in the queue, so we will stop now. Don't let any more - // events be added, since they won't be processed. It is critical - // that no PutEvent can occur between testing that the event queue is - // empty and setting mEventsAreDoomed! - self->mEventsAreDoomed = PR_TRUE; - break; - } - } - NS_ProcessPendingEvents(self); - } - - // Inform the threadmanager that this thread is going away - nsThreadManager::get()->UnregisterCurrentThread(self); - - // Dispatch shutdown ACK - event = new nsThreadShutdownAckEvent(self->mShutdownContext); - self->mShutdownContext->joiningThread->Dispatch(event, NS_DISPATCH_NORMAL); - - // Release any observer of the thread here. - self->SetObserver(nsnull); - - NS_RELEASE(self); -} - -//----------------------------------------------------------------------------- - -nsThread::nsThread() - : mLock(PR_NewLock()) - , mEvents(&mEventsRoot) - , mPriority(PRIORITY_NORMAL) - , mThread(nsnull) - , mRunningEvent(0) - , mShutdownContext(nsnull) - , mShutdownRequired(PR_FALSE) - , mEventsAreDoomed(PR_FALSE) -{ -} - -nsThread::~nsThread() -{ - if (mLock) - PR_DestroyLock(mLock); -} - -nsresult -nsThread::Init() -{ - NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY); - - struct logtime out; - get_log_time(&out); - fprintf(logfp, "%s.%09ld: Thread (%p) Init() start\n", out.str, out.nsec, (void *)this); - - // spawn thread and wait until it is fully setup - nsRefPtr startup = nsThreadStartupEvent::Create(); - NS_ENSURE_TRUE(startup, NS_ERROR_OUT_OF_MEMORY); - - NS_ADDREF_THIS(); - - mShutdownRequired = PR_TRUE; - - // ThreadFunc is responsible for setting mThread - PRThread *thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, - PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, - PR_JOINABLE_THREAD, 0); - if (!thr) { - NS_RELEASE_THIS(); - return NS_ERROR_OUT_OF_MEMORY; - } - - // ThreadFunc will wait for this event to be run before it tries to access - // mThread. By delaying insertion of this event into the queue, we ensure - // that mThread is set properly. - { - nsAutoLock lock(mLock); - mEvents->PutEvent(startup); - } - - // Wait for thread to call ThreadManager::SetupCurrentThread, which completes - // initialization of ThreadFunc. - startup->Wait(); - - get_log_time(&out); - fprintf(logfp, "%s.%09ld: Thread (%p) Init() end\n", out.str, out.nsec, (void *)this); - - return NS_OK; -} - -nsresult -nsThread::InitCurrentThread() -{ - NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY); - - mThread = PR_GetCurrentThread(); - - nsThreadManager::get()->RegisterCurrentThread(this); - return NS_OK; -} - -nsresult -nsThread::PutEvent(nsIRunnable *event) -{ - { - nsAutoLock lock(mLock); - if (mEventsAreDoomed) { - NS_WARNING("An event was posted to a thread that will never run it (rejected)"); - return NS_ERROR_UNEXPECTED; - } - if (!mEvents->PutEvent(event)) - return NS_ERROR_OUT_OF_MEMORY; - } - - nsCOMPtr obs = GetObserver(); - if (obs) - obs->OnDispatchedEvent(this); - - return NS_OK; -} - -//----------------------------------------------------------------------------- -// nsIEventTarget - -NS_IMETHODIMP -nsThread::Dispatch(nsIRunnable *event, PRUint32 flags) -{ - LOG(("THRD(%p) Dispatch [%p %x]\n", this, event, flags)); - - NS_ENSURE_ARG_POINTER(event); - - if (flags & DISPATCH_SYNC) { - nsThread *thread = nsThreadManager::get()->GetCurrentThread(); - NS_ENSURE_STATE(thread); - - // XXX we should be able to do something better here... we should - // be able to monitor the slot occupied by this event and use - // that to tell us when the event has been processed. - - nsRefPtr wrapper = - new nsThreadSyncDispatch(thread, event); - if (!wrapper) - return NS_ERROR_OUT_OF_MEMORY; - nsresult rv = PutEvent(wrapper); - // Don't wait for the event to finish if we didn't dispatch it... - if (NS_FAILED(rv)) - return rv; - - while (wrapper->IsPending()) - NS_ProcessNextEvent(thread); - return rv; - } - - NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); - return PutEvent(event); -} - -NS_IMETHODIMP -nsThread::IsOnCurrentThread(PRBool *result) -{ - *result = (PR_GetCurrentThread() == mThread); - return NS_OK; -} - -//----------------------------------------------------------------------------- -// nsIThread - -NS_IMETHODIMP -nsThread::GetPRThread(PRThread **result) -{ - *result = mThread; - return NS_OK; -} - -NS_IMETHODIMP -nsThread::Shutdown() -{ - LOG(("THRD(%p) shutdown\n", this)); - - // XXX If we make this warn, then we hit that warning at xpcom shutdown while - // shutting down a thread in a thread pool. That happens b/c the thread - // in the thread pool is already shutdown by the thread manager. - if (!mThread) - return NS_OK; - - NS_ENSURE_STATE(mThread != PR_GetCurrentThread()); - - // Prevent multiple calls to this method - { - nsAutoLock lock(mLock); - if (!mShutdownRequired) - return NS_ERROR_UNEXPECTED; - mShutdownRequired = PR_FALSE; - } - - nsThreadShutdownContext context; - context.joiningThread = nsThreadManager::get()->GetCurrentThread(); - context.shutdownAck = PR_FALSE; - - // Set mShutdownContext and wake up the thread in case it is waiting for - // events to process. - nsCOMPtr event = new nsThreadShutdownEvent(this, &context); - if (!event) - return NS_ERROR_OUT_OF_MEMORY; - // XXXroc What if posting the event fails due to OOM? - PutEvent(event); - - // We could still end up with other events being added after the shutdown - // task, but that's okay because we process pending events in ThreadFunc - // after setting mShutdownContext just before exiting. - - // Process events on the current thread until we receive a shutdown ACK. - while (!context.shutdownAck) - NS_ProcessNextEvent(context.joiningThread); - - // Now, it should be safe to join without fear of dead-locking. - - PR_JoinThread(mThread); - mThread = nsnull; - -#ifdef DEBUG - { - nsAutoLock lock(mLock); - NS_ASSERTION(!mObserver, "Should have been cleared at shutdown!"); - } -#endif - - return NS_OK; -} - -NS_IMETHODIMP -nsThread::HasPendingEvents(PRBool *result) -{ - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); - - *result = mEvents->GetEvent(PR_FALSE, nsnull); - return NS_OK; -} - -NS_IMETHODIMP -nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result) -{ - struct logtime out; - get_log_time(&out); - fprintf(logfp, "%s.%09ld: Thread (%p) ProcessNextEvent [%u %u]\n", - out.str, - out.nsec, - (void *)this, - mayWait, - mRunningEvent); - - LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait, mRunningEvent)); - - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); - - PRBool notifyGlobalObserver = (sGlobalObserver != nsnull); - if (notifyGlobalObserver) - sGlobalObserver->OnProcessNextEvent(this, mayWait && !ShuttingDown(), - mRunningEvent); - - nsCOMPtr obs = mObserver; - if (obs) - obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent); - - ++mRunningEvent; - - nsresult rv = NS_OK; - - { - // Scope for |event| to make sure that its destructor fires while - // mRunningEvent has been incremented, since that destructor can - // also do work. - - // If we are shutting down, then do not wait for new events. - nsCOMPtr event; - mEvents->GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event)); - - *result = (event.get() != nsnull); - - if (event) { - get_log_time(&out); - fprintf(logfp, "%s.%09ld: Thread (%p) running [%p]\n", - out.str, - out.nsec, - (void *)this, - (void *)event.get()); - LOG(("THRD(%p) running [%p]\n", this, event.get())); - event->Run(); - } else if (mayWait) { - NS_ASSERTION(ShuttingDown(), - "This should only happen when shutting down"); - rv = NS_ERROR_UNEXPECTED; - } - get_log_time(&out); - fprintf(logfp, "%s.%09ld: Thread (%p) running finished [%p]\n", - out.str, - out.nsec, - (void *)this, - (void *)event.get()); - } - - --mRunningEvent; - if (obs) - obs->AfterProcessNextEvent(this, mRunningEvent); - - if (notifyGlobalObserver && sGlobalObserver) - sGlobalObserver->AfterProcessNextEvent(this, mRunningEvent); - - return rv; -} - -//----------------------------------------------------------------------------- -// nsISupportsPriority - -NS_IMETHODIMP -nsThread::GetPriority(PRInt32 *priority) -{ - *priority = mPriority; - return NS_OK; -} - -NS_IMETHODIMP -nsThread::SetPriority(PRInt32 priority) -{ - NS_ENSURE_STATE(mThread); - - // NSPR defines the following four thread priorities: - // PR_PRIORITY_LOW - // PR_PRIORITY_NORMAL - // PR_PRIORITY_HIGH - // PR_PRIORITY_URGENT - // We map the priority values defined on nsISupportsPriority to these values. - - mPriority = priority; - - PRThreadPriority pri; - if (mPriority <= PRIORITY_HIGHEST) { - pri = PR_PRIORITY_URGENT; - } else if (mPriority < PRIORITY_NORMAL) { - pri = PR_PRIORITY_HIGH; - } else if (mPriority > PRIORITY_NORMAL) { - pri = PR_PRIORITY_LOW; - } else { - pri = PR_PRIORITY_NORMAL; - } - PR_SetThreadPriority(mThread, pri); - - return NS_OK; -} - -NS_IMETHODIMP -nsThread::AdjustPriority(PRInt32 delta) -{ - return SetPriority(mPriority + delta); -} - -//----------------------------------------------------------------------------- -// nsIThreadInternal - -NS_IMETHODIMP -nsThread::GetObserver(nsIThreadObserver **obs) -{ - nsAutoLock lock(mLock); - NS_IF_ADDREF(*obs = mObserver); - return NS_OK; -} - -NS_IMETHODIMP -nsThread::SetObserver(nsIThreadObserver *obs) -{ - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); - - nsAutoLock lock(mLock); - mObserver = obs; - return NS_OK; -} - -NS_IMETHODIMP -nsThread::PushEventQueue(nsIThreadEventFilter *filter) -{ - nsChainedEventQueue *queue = new nsChainedEventQueue(filter); - if (!queue || !queue->IsInitialized()) { - delete queue; - return NS_ERROR_OUT_OF_MEMORY; - } - - nsAutoLock lock(mLock); - queue->mNext = mEvents; - mEvents = queue; - return NS_OK; -} - -NS_IMETHODIMP -nsThread::PopEventQueue() -{ - nsAutoLock lock(mLock); - - // Make sure we do not pop too many! - NS_ENSURE_STATE(mEvents != &mEventsRoot); - - nsChainedEventQueue *queue = mEvents; - mEvents = mEvents->mNext; - - nsCOMPtr event; - while (queue->GetEvent(PR_FALSE, getter_AddRefs(event))) - mEvents->PutEvent(event); - - delete queue; - - return NS_OK; -} - -PRBool -nsThread::nsChainedEventQueue::PutEvent(nsIRunnable *event) -{ - PRBool val; - if (!mFilter || mFilter->AcceptEvent(event)) { - val = mQueue.PutEvent(event); - } else { - val = mNext->PutEvent(event); - } - return val; -} - -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsThreadSyncDispatch::Run() -{ - if (mSyncTask) { - mSyncTask->Run(); - mSyncTask = nsnull; - // unblock the origin thread - mOrigin->Dispatch(this, NS_DISPATCH_NORMAL); - } - return NS_OK; -} - -nsresult -NS_SetGlobalThreadObserver(nsIThreadObserver* aObserver) -{ - if (aObserver && nsThread::sGlobalObserver) { - return NS_ERROR_NOT_AVAILABLE; - } - - if (!NS_IsMainThread()) { - return NS_ERROR_UNEXPECTED; - } - - nsThread::sGlobalObserver = aObserver; - return NS_OK; -} diff --git a/tests/cpp/nsThread.h b/tests/cpp/nsThread.h deleted file mode 100644 index 4ad1032..0000000 --- a/tests/cpp/nsThread.h +++ /dev/null @@ -1,170 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla code. - * - * The Initial Developer of the Original Code is Google Inc. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Darin Fisher - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef nsThread_h__ -#define nsThread_h__ - -#include "nsIThreadInternal.h" -#include "nsISupportsPriority.h" -#include "nsEventQueue.h" -#include "nsThreadUtils.h" -#include "nsString.h" -#include "nsAutoLock.h" -#include "nsAutoPtr.h" - -// A native thread -class nsThread : public nsIThreadInternal, public nsISupportsPriority -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIEVENTTARGET - NS_DECL_NSITHREAD - NS_DECL_NSITHREADINTERNAL - NS_DECL_NSISUPPORTSPRIORITY - - nsThread(); - - // Initialize this as a wrapper for a new PRThread. - nsresult Init(); - - // Initialize this as a wrapper for the current PRThread. - nsresult InitCurrentThread(); - - // The PRThread corresponding to this thread. - PRThread *GetPRThread() { return mThread; } - - // If this flag is true, then the nsThread was created using - // nsIThreadManager::NewThread. - PRBool ShutdownRequired() { return mShutdownRequired; } - - // The global thread observer - static nsIThreadObserver* sGlobalObserver; - -private: - friend class nsThreadShutdownEvent; - - ~nsThread(); - - PRBool ShuttingDown() { return mShutdownContext != nsnull; } - - static void ThreadFunc(void *arg); - - // Helper - already_AddRefed GetObserver() { - nsIThreadObserver *obs; - nsThread::GetObserver(&obs); - return already_AddRefed(obs); - } - - // Wrappers for event queue methods: - PRBool GetEvent(PRBool mayWait, nsIRunnable **event) { - return mEvents->GetEvent(mayWait, event); - } - nsresult PutEvent(nsIRunnable *event); - - // Wrapper for nsEventQueue that supports chaining. - class nsChainedEventQueue { - public: - nsChainedEventQueue(nsIThreadEventFilter *filter = nsnull) - : mNext(nsnull), mFilter(filter) { - } - - PRBool IsInitialized() { - return mQueue.IsInitialized(); - } - - PRBool GetEvent(PRBool mayWait, nsIRunnable **event) { - return mQueue.GetEvent(mayWait, event); - } - - PRBool PutEvent(nsIRunnable *event); - - PRBool HasPendingEvent() { - return mQueue.HasPendingEvent(); - } - - class nsChainedEventQueue *mNext; - private: - nsCOMPtr mFilter; - nsEventQueue mQueue; - }; - - // This lock protects access to mObserver, mEvents and mEventsAreDoomed. - // All of those fields are only modified on the thread itself (never from - // another thread). This means that we can avoid holding the lock while - // using mObserver and mEvents on the thread itself. When calling PutEvent - // on mEvents, we have to hold the lock to synchronize with PopEventQueue. - PRLock *mLock; - - nsCOMPtr mObserver; - - nsChainedEventQueue *mEvents; // never null - nsChainedEventQueue mEventsRoot; - - PRInt32 mPriority; - PRThread *mThread; - PRUint32 mRunningEvent; // counter - - struct nsThreadShutdownContext *mShutdownContext; - - PRPackedBool mShutdownRequired; - PRPackedBool mShutdownPending; - // Set to true when events posted to this thread will never run. - PRPackedBool mEventsAreDoomed; -}; - -//----------------------------------------------------------------------------- - -class nsThreadSyncDispatch : public nsRunnable { -public: - nsThreadSyncDispatch(nsIThread *origin, nsIRunnable *task) - : mOrigin(origin), mSyncTask(task) { - } - - PRBool IsPending() { - return mSyncTask != nsnull; - } - -private: - NS_DECL_NSIRUNNABLE - - nsCOMPtr mOrigin; - nsCOMPtr mSyncTask; -}; - -#endif // nsThread_h__ diff --git a/tests/cpp/nsThread_part.cpp b/tests/cpp/nsThread_part.cpp deleted file mode 100644 index 6921941..0000000 --- a/tests/cpp/nsThread_part.cpp +++ /dev/null @@ -1,46 +0,0 @@ -namespace { -struct nsThreadShutdownContext { - nsThread *joiningThread; - PRBool shutdownAck; - union { - int a; - int b; - union { - char f; - char g; - }; - }; -}; -}; - -struct teststruct { - char foo; - union { - int x; - int y; - }; -}; - -namespace -{ - short int i = 0; - - class Testclass - { - int j; - - public: - - Testclass(); - ~Testclass(); - }; - - namespace - { - string str = ""; - class FooClass - { - FooClass(); - }; - } -}; diff --git a/tests/cpp/nsXPComInit.cpp b/tests/cpp/nsXPComInit.cpp deleted file mode 100644 index 86839c2..0000000 --- a/tests/cpp/nsXPComInit.cpp +++ /dev/null @@ -1,1023 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* vim:set ts=4 sw=4 sts=4 ci et: */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Benjamin Smedberg - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifdef MOZ_IPC -#include "base/basictypes.h" -#endif - -#include "mozilla/XPCOM.h" -#include "nsXULAppAPI.h" - -#include "nsXPCOMPrivate.h" -#include "nsXPCOMCIDInternal.h" - -#include "nsStaticComponents.h" -#include "prlink.h" - -#include "nsObserverList.h" -#include "nsObserverService.h" -#include "nsProperties.h" -#include "nsPersistentProperties.h" -#include "nsScriptableInputStream.h" -#include "nsBinaryStream.h" -#include "nsStorageStream.h" -#include "nsPipe.h" - -#include "nsMemoryImpl.h" -#include "nsDebugImpl.h" -#include "nsTraceRefcntImpl.h" -#include "nsErrorService.h" -#include "nsByteBuffer.h" - -#include "nsSupportsArray.h" -#include "nsArray.h" -#include "nsINIParserImpl.h" -#include "nsSupportsPrimitives.h" -#include "nsConsoleService.h" -#include "nsExceptionService.h" - -#include "nsComponentManager.h" -#include "nsCategoryManagerUtils.h" -#include "nsIServiceManager.h" -#include "nsGenericFactory.h" - -#include "nsThreadManager.h" -#include "nsThreadPool.h" - -#include "nsIProxyObjectManager.h" -#include "nsProxyEventPrivate.h" // access to the impl of nsProxyObjectManager for the generic factory registration. - -#include "xptinfo.h" -#include "nsIInterfaceInfoManager.h" -#include "xptiprivate.h" - -#include "nsTimerImpl.h" -#include "TimerThread.h" - -#include "nsThread.h" -#include "nsProcess.h" -#include "nsEnvironment.h" -#include "nsVersionComparatorImpl.h" - -#include "nsILocalFile.h" -#include "nsLocalFile.h" -#if defined(XP_UNIX) || defined(XP_OS2) -#include "nsNativeCharsetUtils.h" -#endif -#include "nsDirectoryService.h" -#include "nsDirectoryServiceDefs.h" -#include "nsCategoryManager.h" -#include "nsICategoryManager.h" -#include "nsMultiplexInputStream.h" - -#include "nsStringStream.h" -extern NS_METHOD nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **); -NS_DECL_CLASSINFO(nsStringInputStream) - -#include "nsFastLoadService.h" - -#include "nsAtomService.h" -#include "nsAtomTable.h" -#include "nsTraceRefcnt.h" -#include "nsTimelineService.h" - -#include "nsHashPropertyBag.h" - -#include "nsUnicharInputStream.h" -#include "nsVariant.h" - -#include "nsUUIDGenerator.h" - -#include "nsIOUtil.h" - -#ifdef GC_LEAK_DETECTOR -#include "nsLeakDetector.h" -#endif -#include "nsRecyclingAllocator.h" - -#include "SpecialSystemDirectory.h" - -#if defined(XP_WIN) -#include "nsWindowsRegKey.h" -#endif - -#ifdef XP_MACOSX -#include "nsMacUtilsImpl.h" -#endif - -#include "nsSystemInfo.h" -#include "nsMemoryReporterManager.h" - -#include - -#ifdef MOZ_IPC -#include "base/at_exit.h" -#include "base/command_line.h" -#include "base/message_loop.h" - -#include "mozilla/ipc/BrowserProcessSubThread.h" - -using base::AtExitManager; -using mozilla::ipc::BrowserProcessSubThread; - -namespace { - -static AtExitManager* sExitManager; -static MessageLoop* sMessageLoop; -static bool sCommandLineWasInitialized; -static BrowserProcessSubThread* sIOThread; - -} /* anonymous namespace */ -#endif - -using mozilla::TimeStamp; - -// Registry Factory creation function defined in nsRegistry.cpp -// We hook into this function locally to create and register the registry -// Since noone outside xpcom needs to know about this and nsRegistry.cpp -// does not have a local include file, we are putting this definition -// here rather than in nsIRegistry.h -extern nsresult NS_RegistryGetFactory(nsIFactory** aFactory); -extern nsresult NS_CategoryManagerGetFactory( nsIFactory** ); - -#ifdef DEBUG -extern void _FreeAutoLockStatics(); -#endif - -static NS_DEFINE_CID(kComponentManagerCID, NS_COMPONENTMANAGER_CID); -static NS_DEFINE_CID(kMemoryCID, NS_MEMORY_CID); -static NS_DEFINE_CID(kINIParserFactoryCID, NS_INIPARSERFACTORY_CID); -static NS_DEFINE_CID(kSimpleUnicharStreamFactoryCID, NS_SIMPLE_UNICHAR_STREAM_FACTORY_CID); - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsProcess) - -#define NS_ENVIRONMENT_CLASSNAME "Environment Service" - -// ds/nsISupportsPrimitives -#define NS_SUPPORTS_ID_CLASSNAME "Supports ID" -#define NS_SUPPORTS_CSTRING_CLASSNAME "Supports String" -#define NS_SUPPORTS_STRING_CLASSNAME "Supports WString" -#define NS_SUPPORTS_PRBOOL_CLASSNAME "Supports PRBool" -#define NS_SUPPORTS_PRUINT8_CLASSNAME "Supports PRUint8" -#define NS_SUPPORTS_PRUINT16_CLASSNAME "Supports PRUint16" -#define NS_SUPPORTS_PRUINT32_CLASSNAME "Supports PRUint32" -#define NS_SUPPORTS_PRUINT64_CLASSNAME "Supports PRUint64" -#define NS_SUPPORTS_PRTIME_CLASSNAME "Supports PRTime" -#define NS_SUPPORTS_CHAR_CLASSNAME "Supports Char" -#define NS_SUPPORTS_PRINT16_CLASSNAME "Supports PRInt16" -#define NS_SUPPORTS_PRINT32_CLASSNAME "Supports PRInt32" -#define NS_SUPPORTS_PRINT64_CLASSNAME "Supports PRInt64" -#define NS_SUPPORTS_FLOAT_CLASSNAME "Supports float" -#define NS_SUPPORTS_DOUBLE_CLASSNAME "Supports double" -#define NS_SUPPORTS_VOID_CLASSNAME "Supports void" -#define NS_SUPPORTS_INTERFACE_POINTER_CLASSNAME "Supports interface pointer" - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsIDImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsStringImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsCStringImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRBoolImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint8Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint16Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint32Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint64Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRTimeImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsCharImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt16Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt32Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt64Impl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsFloatImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsDoubleImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsVoidImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsInterfacePointerImpl) - -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsConsoleService, Init) -NS_DECL_CLASSINFO(nsConsoleService) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsAtomService) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsExceptionService) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimerImpl) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryOutputStream) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryInputStream) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsStorageStream) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsVersionComparatorImpl) - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsVariant) - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsRecyclingAllocatorImpl) - -#ifdef MOZ_TIMELINE -NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimelineService) -#endif - -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHashPropertyBag, Init) - -NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsProperties, Init) - -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUUIDGenerator, Init) - -#ifdef XP_MACOSX -NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacUtilsImpl) -#endif - -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSystemInfo, Init) - -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMemoryReporterManager, Init) - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsIOUtil) - -static NS_METHOD -nsThreadManagerGetSingleton(nsISupports* outer, - const nsIID& aIID, - void* *aInstancePtr) -{ - NS_ASSERTION(aInstancePtr, "null outptr"); - NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION); - - return nsThreadManager::get()->QueryInterface(aIID, aInstancePtr); -} -NS_DECL_CLASSINFO(nsThreadManager) - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsThreadPool) -NS_DECL_CLASSINFO(nsThreadPool) - -static NS_METHOD -nsXPTIInterfaceInfoManagerGetSingleton(nsISupports* outer, - const nsIID& aIID, - void* *aInstancePtr) -{ - NS_ASSERTION(aInstancePtr, "null outptr"); - NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION); - - nsCOMPtr iim - (xptiInterfaceInfoManager::GetInterfaceInfoManagerNoAddRef()); - if (!iim) - return NS_ERROR_FAILURE; - - return iim->QueryInterface(aIID, aInstancePtr); -} - - -static nsresult -RegisterGenericFactory(nsIComponentRegistrar* registrar, - const nsModuleComponentInfo *info) -{ - nsresult rv; - nsIGenericFactory* fact; - rv = NS_NewGenericFactory(&fact, info); - if (NS_FAILED(rv)) return rv; - - rv = registrar->RegisterFactory(info->mCID, - info->mDescription, - info->mContractID, - fact); - NS_RELEASE(fact); - return rv; -} - -// In order to support the installer, we need -// to be told out of band if we should cause -// an autoregister. If the file ".autoreg" exists in the binary -// directory, we check its timestamp against the timestamp of the -// compreg.dat file. If the .autoreg file is newer, we autoregister. -static PRBool CheckUpdateFile() -{ - nsresult rv; - nsCOMPtr compregFile; - rv = nsDirectoryService::gService->Get(NS_XPCOM_COMPONENT_REGISTRY_FILE, - NS_GET_IID(nsIFile), - getter_AddRefs(compregFile)); - - if (NS_FAILED(rv)) { - NS_WARNING("Getting NS_XPCOM_COMPONENT_REGISTRY_FILE failed"); - return PR_FALSE; - } - - PRInt64 compregModTime; - rv = compregFile->GetLastModifiedTime(&compregModTime); - if (NS_FAILED(rv)) - return PR_TRUE; - - nsCOMPtr file; - rv = nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, - NS_GET_IID(nsIFile), - getter_AddRefs(file)); - - if (NS_FAILED(rv)) { - NS_WARNING("Getting NS_XPCOM_CURRENT_PROCESS_DIR failed"); - return PR_FALSE; - } - - file->AppendNative(nsDependentCString(".autoreg")); - - // superfluous cast - PRInt64 nowTime = PR_Now() / PR_USEC_PER_MSEC; - PRInt64 autoregModTime; - rv = file->GetLastModifiedTime(&autoregModTime); - if (NS_FAILED(rv)) - goto next; - - if (autoregModTime > compregModTime) { - if (autoregModTime < nowTime) { - return PR_TRUE; - } else { - NS_WARNING("Screwy timestamps, ignoring .autoreg"); - } - } - -next: - nsCOMPtr greFile; - rv = nsDirectoryService::gService->Get(NS_GRE_DIR, - NS_GET_IID(nsIFile), - getter_AddRefs(greFile)); - - if (NS_FAILED(rv)) { - NS_WARNING("Getting NS_GRE_DIR failed"); - return PR_FALSE; - } - - greFile->AppendNative(nsDependentCString(".autoreg")); - - PRBool equals; - rv = greFile->Equals(file, &equals); - if (NS_SUCCEEDED(rv) && equals) - return PR_FALSE; - - rv = greFile->GetLastModifiedTime(&autoregModTime); - if (NS_FAILED(rv)) - return PR_FALSE; - - if (autoregModTime > nowTime) { - NS_WARNING("Screwy timestamps, ignoring .autoreg"); - return PR_FALSE; - } - return autoregModTime > compregModTime; -} - - -nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = NULL; -PRBool gXPCOMShuttingDown = PR_FALSE; - -// For each class that wishes to support nsIClassInfo, add a line like this -// NS_DECL_CLASSINFO(nsMyClass) - -#define COMPONENT(NAME, Ctor) \ - { NS_##NAME##_CLASSNAME, NS_##NAME##_CID, NS_##NAME##_CONTRACTID, Ctor } - -#define COMPONENT_CI(NAME, Ctor, Class) \ - { NS_##NAME##_CLASSNAME, NS_##NAME##_CID, NS_##NAME##_CONTRACTID, Ctor, \ - NULL, NULL, NULL, NS_CI_INTERFACE_GETTER_NAME(Class), NULL, \ - &NS_CLASSINFO_NAME(Class) } - -#define COMPONENT_CI_FLAGS(NAME, Ctor, Class, Flags) \ - { NS_##NAME##_CLASSNAME, NS_##NAME##_CID, NS_##NAME##_CONTRACTID, Ctor, \ - NULL, NULL, NULL, NS_CI_INTERFACE_GETTER_NAME(Class), NULL, \ - &NS_CLASSINFO_NAME(Class), Flags } - -static const nsModuleComponentInfo components[] = { - COMPONENT(MEMORY, nsMemoryImpl::Create), - COMPONENT(DEBUG, nsDebugImpl::Create), -#define NS_ERRORSERVICE_CLASSNAME NS_ERRORSERVICE_NAME - COMPONENT(ERRORSERVICE, nsErrorService::Create), - - COMPONENT(BYTEBUFFER, ByteBufferImpl::Create), - COMPONENT(SCRIPTABLEINPUTSTREAM, nsScriptableInputStream::Create), - COMPONENT(BINARYINPUTSTREAM, nsBinaryInputStreamConstructor), - COMPONENT(BINARYOUTPUTSTREAM, nsBinaryOutputStreamConstructor), - COMPONENT(STORAGESTREAM, nsStorageStreamConstructor), - COMPONENT(VERSIONCOMPARATOR, nsVersionComparatorImplConstructor), - COMPONENT(PIPE, nsPipeConstructor), - -#define NS_PROPERTIES_CLASSNAME "Properties" - COMPONENT(PROPERTIES, nsPropertiesConstructor), - -#define NS_PERSISTENTPROPERTIES_CID NS_IPERSISTENTPROPERTIES_CID /* sigh */ - COMPONENT(PERSISTENTPROPERTIES, nsPersistentProperties::Create), - - COMPONENT(SUPPORTSARRAY, nsSupportsArray::Create), - COMPONENT(ARRAY, nsArrayConstructor), - COMPONENT_CI_FLAGS(CONSOLESERVICE, nsConsoleServiceConstructor, - nsConsoleService, - nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON), - COMPONENT(EXCEPTIONSERVICE, nsExceptionServiceConstructor), - COMPONENT(ATOMSERVICE, nsAtomServiceConstructor), -#ifdef MOZ_TIMELINE - COMPONENT(TIMELINESERVICE, nsTimelineServiceConstructor), -#endif - COMPONENT(OBSERVERSERVICE, nsObserverService::Create), - COMPONENT(GENERICFACTORY, nsGenericFactory::Create), - -#define NS_XPCOMPROXY_CID NS_PROXYEVENT_MANAGER_CID - COMPONENT(XPCOMPROXY, nsProxyObjectManager::Create), - - COMPONENT(TIMER, nsTimerImplConstructor), - -#define COMPONENT_SUPPORTS(TYPE, Type) \ - COMPONENT(SUPPORTS_##TYPE, nsSupports##Type##ImplConstructor) - - COMPONENT_SUPPORTS(ID, ID), - COMPONENT_SUPPORTS(STRING, String), - COMPONENT_SUPPORTS(CSTRING, CString), - COMPONENT_SUPPORTS(PRBOOL, PRBool), - COMPONENT_SUPPORTS(PRUINT8, PRUint8), - COMPONENT_SUPPORTS(PRUINT16, PRUint16), - COMPONENT_SUPPORTS(PRUINT32, PRUint32), - COMPONENT_SUPPORTS(PRUINT64, PRUint64), - COMPONENT_SUPPORTS(PRTIME, PRTime), - COMPONENT_SUPPORTS(CHAR, Char), - COMPONENT_SUPPORTS(PRINT16, PRInt16), - COMPONENT_SUPPORTS(PRINT32, PRInt32), - COMPONENT_SUPPORTS(PRINT64, PRInt64), - COMPONENT_SUPPORTS(FLOAT, Float), - COMPONENT_SUPPORTS(DOUBLE, Double), - COMPONENT_SUPPORTS(VOID, Void), - COMPONENT_SUPPORTS(INTERFACE_POINTER, InterfacePointer), - -#undef COMPONENT_SUPPORTS -#define NS_LOCAL_FILE_CLASSNAME "Local File Specification" - COMPONENT(LOCAL_FILE, nsLocalFile::nsLocalFileConstructor), -#define NS_DIRECTORY_SERVICE_CLASSNAME "nsIFile Directory Service" - COMPONENT(DIRECTORY_SERVICE, nsDirectoryService::Create), - COMPONENT(PROCESS, nsProcessConstructor), - COMPONENT(ENVIRONMENT, nsEnvironment::Create), - - COMPONENT_CI_FLAGS(THREADMANAGER, nsThreadManagerGetSingleton, - nsThreadManager, - nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON), - COMPONENT_CI_FLAGS(THREADPOOL, nsThreadPoolConstructor, - nsThreadPool, nsIClassInfo::THREADSAFE), - - COMPONENT_CI_FLAGS(STRINGINPUTSTREAM, nsStringInputStreamConstructor, - nsStringInputStream, nsIClassInfo::THREADSAFE), - COMPONENT(MULTIPLEXINPUTSTREAM, nsMultiplexInputStreamConstructor), - -#ifndef MOZ_NO_FAST_LOAD - COMPONENT(FASTLOADSERVICE, nsFastLoadService::Create), -#endif - - COMPONENT(VARIANT, nsVariantConstructor), - COMPONENT(INTERFACEINFOMANAGER_SERVICE, nsXPTIInterfaceInfoManagerGetSingleton), - - COMPONENT(RECYCLINGALLOCATOR, nsRecyclingAllocatorImplConstructor), - -#define NS_HASH_PROPERTY_BAG_CLASSNAME "Hashtable Property Bag" - COMPONENT(HASH_PROPERTY_BAG, nsHashPropertyBagConstructor), - - COMPONENT(UUID_GENERATOR, nsUUIDGeneratorConstructor), - -#if defined(XP_WIN) - COMPONENT(WINDOWSREGKEY, nsWindowsRegKeyConstructor), -#endif - -#ifdef XP_MACOSX - COMPONENT(MACUTILSIMPL, nsMacUtilsImplConstructor), -#endif - - COMPONENT(SYSTEMINFO, nsSystemInfoConstructor), -#define NS_MEMORY_REPORTER_MANAGER_CLASSNAME "Memory Reporter Manager" - COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor), - COMPONENT(IOUTIL, nsIOUtilConstructor), -}; - -#undef COMPONENT - -const int components_length = sizeof(components) / sizeof(components[0]); - -// gDebug will be freed during shutdown. -static nsIDebug* gDebug = nsnull; - -EXPORT_XPCOM_API(nsresult) -NS_GetDebug(nsIDebug** result) -{ - return nsDebugImpl::Create(nsnull, - NS_GET_IID(nsIDebug), - (void**) result); -} - -EXPORT_XPCOM_API(nsresult) -NS_GetTraceRefcnt(nsITraceRefcnt** result) -{ - return nsTraceRefcntImpl::Create(nsnull, - NS_GET_IID(nsITraceRefcnt), - (void**) result); -} - -EXPORT_XPCOM_API(nsresult) -NS_InitXPCOM(nsIServiceManager* *result, - nsIFile* binDirectory) -{ - return NS_InitXPCOM3(result, binDirectory, nsnull, nsnull, 0); -} - -EXPORT_XPCOM_API(nsresult) -NS_InitXPCOM2(nsIServiceManager* *result, - nsIFile* binDirectory, - nsIDirectoryServiceProvider* appFileLocationProvider) -{ - return NS_InitXPCOM3(result, binDirectory, appFileLocationProvider, nsnull, 0); -} - -#include "timelog.h" - -void get_log_time(struct logtime *out) -{ - char timeformat[] = "%Y-%m-%d %H:%M:%S"; - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - strftime(out->str, TIMELEN, timeformat, localtime(&ts.tv_sec)); - out->nsec = ts.tv_nsec; -} - -FILE *logfp; - -EXPORT_XPCOM_API(nsresult) -NS_InitXPCOM3(nsIServiceManager* *result, - nsIFile* binDirectory, - nsIDirectoryServiceProvider* appFileLocationProvider, - nsStaticModuleInfo const *staticComponents, - PRUint32 componentCount) -{ - nsresult rv = NS_OK; - -#ifdef MOZ_ENABLE_LIBXUL - if (!staticComponents) { - staticComponents = kPStaticModules; - componentCount = kStaticModuleCount; - } -#endif - - // We are not shutting down - gXPCOMShuttingDown = PR_FALSE; - -#ifdef MOZ_IPC - // Set up chromium libs - NS_ASSERTION(!sExitManager && !sMessageLoop, "Bad logic!"); - - if (!AtExitManager::AlreadyRegistered()) { - sExitManager = new AtExitManager(); - NS_ENSURE_STATE(sExitManager); - } - - if (!MessageLoop::current()) { - sMessageLoop = new MessageLoopForUI(MessageLoop::TYPE_MOZILLA_UI); - NS_ENSURE_STATE(sMessageLoop); - } - - if (XRE_GetProcessType() == GeckoProcessType_Default && - !BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO)) { - scoped_ptr ioThread( - new BrowserProcessSubThread(BrowserProcessSubThread::IO)); - NS_ENSURE_TRUE(ioThread.get(), NS_ERROR_OUT_OF_MEMORY); - - base::Thread::Options options; - options.message_loop_type = MessageLoop::TYPE_IO; - NS_ENSURE_TRUE(ioThread->StartWithOptions(options), NS_ERROR_FAILURE); - - sIOThread = ioThread.release(); - } -#endif - - NS_LogInit(); - - logfp = fopen("time.log", "w"); -// logfp = stdout; - - // Set up TimeStamp - rv = TimeStamp::Startup(); - NS_ENSURE_SUCCESS(rv, rv); - - // Establish the main thread here. - struct logtime out; - get_log_time(&out); - fprintf(logfp, "%s.%09ld: Establish main thread\n", out.str, out.nsec); - rv = nsThreadManager::get()->Init(); - if (NS_FAILED(rv)) return rv; - - // Set up the timer globals/timer thread - rv = nsTimerImpl::Startup(); - NS_ENSURE_SUCCESS(rv, rv); - -#ifndef WINCE - // If the locale hasn't already been setup by our embedder, - // get us out of the "C" locale and into the system - if (strcmp(setlocale(LC_ALL, NULL), "C") == 0) - setlocale(LC_ALL, ""); -#endif - -#if defined(XP_UNIX) || defined(XP_OS2) - NS_StartupNativeCharsetUtils(); -#endif - NS_StartupLocalFile(); - - StartupSpecialSystemDirectory(); - - rv = nsDirectoryService::RealInit(); - if (NS_FAILED(rv)) - return rv; - - nsCOMPtr xpcomLib; - - PRBool value; - if (binDirectory) - { - rv = binDirectory->IsDirectory(&value); - - if (NS_SUCCEEDED(rv) && value) { - nsDirectoryService::gService->Set(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, binDirectory); - binDirectory->Clone(getter_AddRefs(xpcomLib)); - } - } - else { - nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, - NS_GET_IID(nsIFile), - getter_AddRefs(xpcomLib)); - } - - if (xpcomLib) { - xpcomLib->AppendNative(nsDependentCString(XPCOM_DLL)); - nsDirectoryService::gService->Set(NS_XPCOM_LIBRARY_FILE, xpcomLib); - } - - if (appFileLocationProvider) { - rv = nsDirectoryService::gService->RegisterProvider(appFileLocationProvider); - if (NS_FAILED(rv)) return rv; - } - -#ifdef MOZ_IPC - if ((sCommandLineWasInitialized = !CommandLine::IsInitialized())) { -#ifdef OS_WIN - CommandLine::Init(0, nsnull); -#else - nsCOMPtr binaryFile; - nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, - NS_GET_IID(nsIFile), - getter_AddRefs(binaryFile)); - NS_ENSURE_STATE(binaryFile); - - rv = binaryFile->AppendNative(NS_LITERAL_CSTRING("nonexistent-executable")); - NS_ENSURE_SUCCESS(rv, rv); - - nsCString binaryPath; - rv = binaryFile->GetNativePath(binaryPath); - NS_ENSURE_SUCCESS(rv, rv); - - static char const *const argv = { strdup(binaryPath.get()) }; - CommandLine::Init(1, &argv); -#endif - } -#endif - - NS_ASSERTION(nsComponentManagerImpl::gComponentManager == NULL, "CompMgr not null at init"); - - // Create the Component/Service Manager - nsComponentManagerImpl *compMgr = new nsComponentManagerImpl(); - if (compMgr == NULL) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(compMgr); - - rv = compMgr->Init(staticComponents, componentCount); - if (NS_FAILED(rv)) - { - NS_RELEASE(compMgr); - return rv; - } - - nsComponentManagerImpl::gComponentManager = compMgr; - - if (result) { - nsIServiceManager *serviceManager = - static_cast(compMgr); - - NS_ADDREF(*result = serviceManager); - } - - nsCOMPtr memory; - NS_GetMemoryManager(getter_AddRefs(memory)); - rv = compMgr->RegisterService(kMemoryCID, memory); - if (NS_FAILED(rv)) return rv; - - rv = compMgr->RegisterService(kComponentManagerCID, static_cast(compMgr)); - if (NS_FAILED(rv)) return rv; - -#ifdef GC_LEAK_DETECTOR - rv = NS_InitLeakDetector(); - if (NS_FAILED(rv)) return rv; -#endif - - rv = nsCycleCollector_startup(); - if (NS_FAILED(rv)) return rv; - - // 2. Register the global services with the component manager so that - // clients can create new objects. - - // Category Manager - { - nsCOMPtr categoryManagerFactory; - if ( NS_FAILED(rv = NS_CategoryManagerGetFactory(getter_AddRefs(categoryManagerFactory))) ) - return rv; - - NS_DEFINE_CID(kCategoryManagerCID, NS_CATEGORYMANAGER_CID); - - rv = compMgr->RegisterFactory(kCategoryManagerCID, - NS_CATEGORYMANAGER_CLASSNAME, - NS_CATEGORYMANAGER_CONTRACTID, - categoryManagerFactory, - PR_TRUE); - if ( NS_FAILED(rv) ) return rv; - } - - nsCOMPtr registrar = do_QueryInterface( - static_cast(compMgr), &rv); - if (registrar) { - for (int i = 0; i < components_length; i++) - RegisterGenericFactory(registrar, &components[i]); - - nsCOMPtr iniParserFactory(new nsINIParserFactory()); - if (iniParserFactory) - registrar->RegisterFactory(kINIParserFactoryCID, - "nsINIParserFactory", - NS_INIPARSERFACTORY_CONTRACTID, - iniParserFactory); - - registrar-> - RegisterFactory(kSimpleUnicharStreamFactoryCID, - "nsSimpleUnicharStreamFactory", - NS_SIMPLE_UNICHAR_STREAM_FACTORY_CONTRACTID, - nsSimpleUnicharStreamFactory::GetInstance()); - } - - // Pay the cost at startup time of starting this singleton. - nsIInterfaceInfoManager* iim = - xptiInterfaceInfoManager::GetInterfaceInfoManagerNoAddRef(); - - if (CheckUpdateFile() || NS_FAILED( - nsComponentManagerImpl::gComponentManager->ReadPersistentRegistry())) { - // If the component registry is out of date, malformed, or incomplete, - // autoregister the default component directories. - (void) iim->AutoRegisterInterfaces(); - nsComponentManagerImpl::gComponentManager->AutoRegister(nsnull); - } - - // After autoreg, but before we actually instantiate any components, - // add any services listed in the "xpcom-directory-providers" category - // to the directory service. - nsDirectoryService::gService->RegisterCategoryProviders(); - - // Notify observers of xpcom autoregistration start - NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, - nsnull, - NS_XPCOM_STARTUP_OBSERVER_ID); - - return NS_OK; -} - - -// -// NS_ShutdownXPCOM() -// -// The shutdown sequence for xpcom would be -// -// - Notify "xpcom-shutdown" for modules to release primary (root) references -// - Shutdown XPCOM timers -// - Notify "xpcom-shutdown-threads" for thread joins -// - Shutdown the event queues -// - Release the Global Service Manager -// - Release all service instances held by the global service manager -// - Release the Global Service Manager itself -// - Release the Component Manager -// - Release all factories cached by the Component Manager -// - Notify module loaders to shut down -// - Unload Libraries -// - Release Contractid Cache held by Component Manager -// - Release dll abstraction held by Component Manager -// - Release the Registry held by Component Manager -// - Finally, release the component manager itself -// -EXPORT_XPCOM_API(nsresult) -NS_ShutdownXPCOM(nsIServiceManager* servMgr) -{ - return mozilla::ShutdownXPCOM(servMgr); -} - -namespace mozilla { - -nsresult -ShutdownXPCOM(nsIServiceManager* servMgr) -{ - NS_ENSURE_STATE(NS_IsMainThread()); - - nsresult rv; - nsCOMPtr moduleLoaders; - - // Notify observers of xpcom shutting down - { - // Block it so that the COMPtr will get deleted before we hit - // servicemanager shutdown - - nsCOMPtr thread = do_GetCurrentThread(); - NS_ENSURE_STATE(thread); - - nsRefPtr observerService; - CallGetService("@mozilla.org/observer-service;1", - (nsObserverService**) getter_AddRefs(observerService)); - - if (observerService) - { - (void) observerService-> - NotifyObservers(nsnull, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, - nsnull); - - nsCOMPtr mgr; - rv = NS_GetServiceManager(getter_AddRefs(mgr)); - if (NS_SUCCEEDED(rv)) - { - (void) observerService-> - NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID, - nsnull); - } - } - - NS_ProcessPendingEvents(thread); - - if (observerService) - (void) observerService-> - NotifyObservers(nsnull, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, - nsnull); - - NS_ProcessPendingEvents(thread); - - // Shutdown the timer thread and all timers that might still be alive before - // shutting down the component manager - nsTimerImpl::Shutdown(); - - NS_ProcessPendingEvents(thread); - - // Shutdown all remaining threads. This method does not return until - // all threads created using the thread manager (with the exception of - // the main thread) have exited. - nsThreadManager::get()->Shutdown(); - - NS_ProcessPendingEvents(thread); - - // We save the "xpcom-shutdown-loaders" observers to notify after - // the observerservice is gone. - if (observerService) { - observerService-> - EnumerateObservers(NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, - getter_AddRefs(moduleLoaders)); - - observerService->Shutdown(); - } - } - - // XPCOM is officially in shutdown mode NOW - // Set this only after the observers have been notified as this - // will cause servicemanager to become inaccessible. - gXPCOMShuttingDown = PR_TRUE; - -#ifdef DEBUG_dougt - fprintf(stderr, "* * * * XPCOM shutdown. Access will be denied * * * * \n"); -#endif - // We may have AddRef'd for the caller of NS_InitXPCOM, so release it - // here again: - NS_IF_RELEASE(servMgr); - - // Shutdown global servicemanager - if (nsComponentManagerImpl::gComponentManager) { - nsComponentManagerImpl::gComponentManager->FreeServices(); - } - - nsProxyObjectManager::Shutdown(); - - // Release the directory service - NS_IF_RELEASE(nsDirectoryService::gService); - - nsCycleCollector_shutdown(); - - if (moduleLoaders) { - PRBool more; - nsCOMPtr el; - while (NS_SUCCEEDED(moduleLoaders->HasMoreElements(&more)) && - more) { - moduleLoaders->GetNext(getter_AddRefs(el)); - - // Don't worry about weak-reference observers here: there is - // no reason for weak-ref observers to register for - // xpcom-shutdown-loaders - - nsCOMPtr obs(do_QueryInterface(el)); - if (obs) - (void) obs->Observe(nsnull, - NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, - nsnull); - } - - moduleLoaders = nsnull; - } - - // Shutdown nsLocalFile string conversion - NS_ShutdownLocalFile(); -#ifdef XP_UNIX - NS_ShutdownNativeCharsetUtils(); -#endif - - // Shutdown xpcom. This will release all loaders and cause others holding - // a refcount to the component manager to release it. - if (nsComponentManagerImpl::gComponentManager) { - rv = (nsComponentManagerImpl::gComponentManager)->Shutdown(); - NS_ASSERTION(NS_SUCCEEDED(rv), "Component Manager shutdown failed."); - } else - NS_WARNING("Component Manager was never created ..."); - - // Release our own singletons - // Do this _after_ shutting down the component manager, because the - // JS component loader will use XPConnect to call nsIModule::canUnload, - // and that will spin up the InterfaceInfoManager again -- bad mojo - xptiInterfaceInfoManager::FreeInterfaceInfoManager(); - - // Finally, release the component manager last because it unloads the - // libraries: - if (nsComponentManagerImpl::gComponentManager) { - nsrefcnt cnt; - NS_RELEASE2(nsComponentManagerImpl::gComponentManager, cnt); - NS_ASSERTION(cnt == 0, "Component Manager being held past XPCOM shutdown."); - } - nsComponentManagerImpl::gComponentManager = nsnull; - -#ifdef DEBUG - // FIXME BUG 456272: this should disappear - _FreeAutoLockStatics(); -#endif - - ShutdownSpecialSystemDirectory(); - - NS_PurgeAtomTable(); - - NS_IF_RELEASE(gDebug); - - TimeStamp::Shutdown(); - - NS_LogTerm(); - -#ifdef MOZ_IPC - if (sIOThread) { - delete sIOThread; - sIOThread = nsnull; - } - if (sMessageLoop) { - delete sMessageLoop; - sMessageLoop = nsnull; - } - if (sCommandLineWasInitialized) { - CommandLine::Terminate(); - sCommandLineWasInitialized = false; - } - if (sExitManager) { - delete sExitManager; - sExitManager = nsnull; - } -#endif - -#ifdef GC_LEAK_DETECTOR - // Shutdown the Leak detector. - NS_ShutdownLeakDetector(); -#endif - - return NS_OK; -} - -} // namespace mozilla diff --git a/tests/cpp/proxytests.cpp b/tests/cpp/proxytests.cpp deleted file mode 100644 index 7f95dc1..0000000 --- a/tests/cpp/proxytests.cpp +++ /dev/null @@ -1,558 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Pierre Phaneuf - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include - -#include "nsXPCOM.h" -#include "nsXPCOMCIDInternal.h" -#include "nsIComponentManager.h" -#include "nsIComponentRegistrar.h" -#include "nsIServiceManager.h" -#include "nsAutoPtr.h" -#include "nsCOMPtr.h" -#include "nsCOMArray.h" - -#include "nscore.h" -#include "nspr.h" -#include "prmon.h" - -#include "nsITestProxy.h" - -#include "nsIRunnable.h" -#include "nsIProxyObjectManager.h" -#include "nsIThreadPool.h" -#include "nsXPCOMCIDInternal.h" -#include "nsComponentManagerUtils.h" -#include "nsServiceManagerUtils.h" -#include "nsThreadUtils.h" - -#include "prlog.h" -#ifdef PR_LOGGING -static PRLogModuleInfo *sLog = PR_NewLogModule("Test"); -#define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args) -#else -#define LOG(args) printf args -#endif - -namespace proxytests { - -static nsresult -GetThreadFromPRThread(PRThread *prthread, nsIThread **result) -{ - LOG(("TEST: GetThreadFromPRThread [%p]\n", prthread)); - - nsCOMPtr tm = do_GetService(NS_THREADMANAGER_CONTRACTID); - NS_ENSURE_STATE(tm); - return tm->GetThreadFromPRThread(prthread, result); -} - -/***************************************************************************/ -/* nsTestXPCFoo */ -/***************************************************************************/ -class nsTestXPCFoo : public nsITestProxy -{ - NS_DECL_ISUPPORTS - NS_IMETHOD Test(PRInt32 p1, PRInt32 p2, PRInt32* retval); - NS_IMETHOD Test2(); - NS_IMETHOD Test3(nsISupports *p1, nsISupports **p2); - - nsTestXPCFoo(); -}; - -nsTestXPCFoo::nsTestXPCFoo() -{ - NS_ADDREF_THIS(); -} - -NS_IMPL_ISUPPORTS1(nsTestXPCFoo, nsITestProxy) - -NS_IMETHODIMP nsTestXPCFoo::Test(PRInt32 p1, PRInt32 p2, PRInt32* retval) -{ - LOG(("TEST: Thread (%d) Test Called successfully! Party on...\n", p1)); - *retval = p1+p2; - return NS_OK; -} - - -NS_IMETHODIMP nsTestXPCFoo::Test2() -{ - LOG(("TEST: The quick brown netscape jumped over the old lazy ie..\n")); - - return NS_OK; -} - -NS_IMETHODIMP nsTestXPCFoo::Test3(nsISupports *p1, nsISupports **p2) -{ - if (p1 != nsnull) - { - nsITestProxy *test; - - p1->QueryInterface(NS_GET_IID(nsITestProxy), (void**)&test); - - test->Test2(); - PRInt32 a; - test->Test( 1, 2, &a); - LOG(("TEST: \n1+2=%d\n",a)); - } - - - *p2 = new nsTestXPCFoo(); - return NS_OK; -} - -/***************************************************************************/ -/* nsTestXPCFoo2 */ -/***************************************************************************/ -class nsTestXPCFoo2 : public nsITestProxy -{ - NS_DECL_ISUPPORTS - NS_IMETHOD Test(PRInt32 p1, PRInt32 p2, PRInt32* retval); - NS_IMETHOD Test2(); - NS_IMETHOD Test3(nsISupports *p1, nsISupports **p2); - - nsTestXPCFoo2(); -}; - -nsTestXPCFoo2::nsTestXPCFoo2() -{ - NS_ADDREF_THIS(); -} - -NS_IMPL_THREADSAFE_ISUPPORTS1(nsTestXPCFoo2, nsITestProxy) - -NS_IMETHODIMP nsTestXPCFoo2::Test(PRInt32 p1, PRInt32 p2, PRInt32* retval) -{ - LOG(("TEST: calling back to caller!\n")); - - nsCOMPtr manager = - do_GetService(NS_XPCOMPROXY_CONTRACTID); - - LOG(("TEST: ProxyObjectManager: %p \n", (void *) manager.get())); - - PR_ASSERT(manager); - - nsCOMPtr thread; - GetThreadFromPRThread((PRThread *) p1, getter_AddRefs(thread)); - NS_ENSURE_STATE(thread); - - nsCOMPtr proxyObject; - manager->GetProxyForObject(thread, NS_GET_IID(nsITestProxy), this, NS_PROXY_SYNC, (void**)&proxyObject); - proxyObject->Test3(nsnull, nsnull); - - LOG(("TEST: Deleting Proxy Object\n")); - return NS_OK; -} - - -NS_IMETHODIMP nsTestXPCFoo2::Test2() -{ - LOG(("TEST: nsTestXPCFoo2::Test2() called\n")); - - return NS_OK; -} - - -NS_IMETHODIMP nsTestXPCFoo2::Test3(nsISupports *p1, nsISupports **p2) -{ - LOG(("TEST: Got called")); - return NS_OK; -} - - - -#if 0 -struct ArgsStruct { - nsIThread* thread; - PRInt32 threadNumber; -}; - - - -// This will create two objects both descendants of a single IID. -void TestCase_TwoClassesOneInterface(void *arg) -{ - ArgsStruct *argsStruct = (ArgsStruct*) arg; - - - nsCOMPtr manager = - do_GetService(NS_XPCOMPROXY_CONTRACTID); - - printf("ProxyObjectManager: %p \n", (void *) manager.get()); - - PR_ASSERT(manager); - - nsITestProxy *proxyObject; - nsITestProxy *proxyObject2; - - nsTestXPCFoo* foo = new nsTestXPCFoo(); - nsTestXPCFoo2* foo2 = new nsTestXPCFoo2(); - - PR_ASSERT(foo); - PR_ASSERT(foo2); - - - manager->GetProxyForObject(argsStruct->thread, NS_GET_IID(nsITestProxy), foo, NS_PROXY_SYNC, (void**)&proxyObject); - - manager->GetProxyForObject(argsStruct->thread, NS_GET_IID(nsITestProxy), foo2, NS_PROXY_SYNC, (void**)&proxyObject2); - - - - if (proxyObject && proxyObject2) - { - // release ownership of the real object. - - PRInt32 a; - nsresult rv; - PRInt32 threadNumber = argsStruct->threadNumber; - - printf("Deleting real Object (%d)\n", threadNumber); - NS_RELEASE(foo); - - printf("Deleting real Object 2 (%d)\n", threadNumber); - NS_RELEASE(foo2); - - - printf("Thread (%d) Prior to calling proxyObject->Test.\n", threadNumber); - rv = proxyObject->Test(threadNumber, 0, &a); - printf("Thread (%d) error: %d.\n", threadNumber, rv); - - - printf("Thread (%d) Prior to calling proxyObject->Test2.\n", threadNumber); - rv = proxyObject->Test2(); - printf("Thread (%d) error: %d.\n", threadNumber, rv); - - printf("Thread (%d) Prior to calling proxyObject2->Test2.\n", threadNumber); - rv = proxyObject2->Test2(); - printf("Thread (%d) proxyObject2 error: %d.\n", threadNumber, rv); - - printf("Deleting Proxy Object (%d)\n", threadNumber ); - NS_RELEASE(proxyObject); - - printf("Deleting Proxy Object 2 (%d)\n", threadNumber ); - NS_RELEASE(proxyObject2); - } - - PR_Sleep( PR_MillisecondsToInterval(1000) ); // If your thread goes away, your stack goes away. Only use ASYNC on calls that do not have out parameters -} -#endif - - - -void TestCase_NestedLoop(nsIThread *thread, PRInt32 index) -{ - nsCOMPtr manager = - do_GetService(NS_XPCOMPROXY_CONTRACTID); - - LOG(("TEST: ProxyObjectManager: %p\n", (void *) manager.get())); - - PR_ASSERT(manager); - - nsITestProxy *proxyObject; - nsTestXPCFoo2* foo = new nsTestXPCFoo2(); - - PR_ASSERT(foo); - - - manager->GetProxyForObject(thread, NS_GET_IID(nsITestProxy), foo, NS_PROXY_SYNC, (void**)&proxyObject); - - if (proxyObject) - { - // release ownership of the real object. - - nsresult rv; - - LOG(("TEST: Deleting real Object (%d)\n", index)); - NS_RELEASE(foo); - - PRInt32 retval; - - LOG(("TEST: Getting EventThread...\n")); - - //nsCOMPtr curThread = do_GetCurrentThread(); - PRThread *curThread = PR_GetCurrentThread(); - if (curThread) - { - LOG(("TEST: Thread (%d) Prior to calling proxyObject->Test.\n", index)); - rv = proxyObject->Test(NS_PTR_TO_INT32((void*)curThread), 0, &retval); // XXX broken on 64-bit arch - LOG(("TEST: Thread (%d) proxyObject error: %x.\n", index, rv)); - - LOG(("TEST: Deleting Proxy Object (%d)\n", index)); - NS_RELEASE(proxyObject); - } - - PR_Sleep( PR_MillisecondsToInterval(1000) ); // If your thread goes away, your stack goes away. Only use ASYNC on calls that do not have out parameters - } -} - - -#if 0 -void TestCase_nsISupports(void *arg) -{ - - ArgsStruct *argsStruct = (ArgsStruct*) arg; - - nsCOMPtr manager = - do_GetService(NS_XPCOMPROXY_CONTRACTID); - - PR_ASSERT(manager); - - nsITestProxy *proxyObject; - nsTestXPCFoo* foo = new nsTestXPCFoo(); - - PR_ASSERT(foo); - - manager->GetProxyForObject(argsStruct->thread, NS_GET_IID(nsITestProxy), foo, NS_PROXY_SYNC, (void**)&proxyObject); - - if (proxyObject != nsnull) - { - nsISupports *bISupports = nsnull, *cISupports = nsnull; - - proxyObject->Test3(foo, &bISupports); - proxyObject->Test3(bISupports, &cISupports); - - nsITestProxy *test; - bISupports->QueryInterface(NS_GET_IID(nsITestProxy), (void**)&test); - - test->Test2(); - - NS_RELEASE(foo); - NS_RELEASE(proxyObject); - } -} -#endif - -/***************************************************************************/ -/* ProxyTest */ -/***************************************************************************/ - -class ProxyTest : public nsIRunnable -{ -public: - NS_DECL_ISUPPORTS - - ProxyTest(PRThread *eventLoopThread, PRInt32 index) - : mEventLoopThread(eventLoopThread) - , mIndex(index) - {} - - NS_IMETHOD Run() - { - //TestCase_TwoClassesOneInterface(arg); - //TestCase_nsISupports(arg); - nsCOMPtr thread; - GetThreadFromPRThread(mEventLoopThread, getter_AddRefs(thread)); - TestCase_NestedLoop(thread, mIndex); - - return NS_OK; - } - -private: - PRThread *mEventLoopThread; - PRInt32 mIndex; -}; -NS_IMPL_THREADSAFE_ISUPPORTS1(ProxyTest, nsIRunnable) - -class TestSyncProxyToSelf : public nsIRunnable -{ -public: - NS_DECL_ISUPPORTS - - NS_IMETHOD Run() - { - LOG(("TEST: Verifing calling Proxy on eventQ thread.\n")); - - nsCOMPtr thread = do_GetCurrentThread(); - - nsITestProxy *proxyObject; - nsTestXPCFoo *foo = new nsTestXPCFoo(); - NS_ENSURE_STATE(foo); - - nsCOMPtr manager = - do_GetService(NS_XPCOMPROXY_CONTRACTID); - - manager->GetProxyForObject(thread, - NS_GET_IID(nsITestProxy), foo, - NS_PROXY_SYNC, (void**)&proxyObject); - - PRInt32 a; - proxyObject->Test(1, 2, &a); - proxyObject->Test2(); - - NS_RELEASE(proxyObject); - delete foo; - - LOG(("TEST: End of Verification calling Proxy on eventQ thread.\n")); - - return NS_OK; - } -}; -NS_IMPL_THREADSAFE_ISUPPORTS1(TestSyncProxyToSelf, nsIRunnable) - -//--------------------------------------------------------------------------- -// Test to make sure we can call methods on a "main thread only" object from -// a background thread. - -class MainThreadOnly : public nsIRunnable { -public: - NS_DECL_ISUPPORTS - NS_IMETHOD Run() { - NS_ASSERTION(NS_IsMainThread(), "method called on wrong thread"); - *mNumRuns -= 1; - return NS_OK; - } - MainThreadOnly(PRUint32 *numRuns) : mNumRuns(numRuns) {} - ~MainThreadOnly() { - NS_ASSERTION(NS_IsMainThread(), "method called on wrong thread"); - } - PRBool IsDone() { return mNumRuns == 0; } -private: - PRUint32 *mNumRuns; -}; -NS_IMPL_ISUPPORTS1(MainThreadOnly, nsIRunnable) // not threadsafe! - -static nsresult -RunApartmentTest() -{ - LOG(("RunApartmentTest: start\n")); - - const PRUint32 numDispatched = 160; - - PRUint32 numCompleted = 0; - nsCOMPtr obj = new MainThreadOnly(&numCompleted); - - nsCOMPtr manager = - do_GetService(NS_XPCOMPROXY_CONTRACTID); - - nsCOMPtr objProxy; - manager->GetProxyForObject(NS_PROXY_TO_CURRENT_THREAD, - NS_GET_IID(nsIRunnable), - obj, - NS_PROXY_ASYNC, - getter_AddRefs(objProxy)); - nsCOMPtr thread; - NS_NewThread(getter_AddRefs(thread)); - - obj = nsnull; - - nsCOMPtr pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); - - pool->SetThreadLimit(8); - for (PRUint32 i = 0; i < numDispatched; ++i) - pool->Dispatch(objProxy, NS_DISPATCH_NORMAL); - - objProxy = nsnull; - - nsCOMPtr curThread = do_GetCurrentThread(); - while (numCompleted < numDispatched) { - NS_ProcessNextEvent(curThread); - } - - pool->Shutdown(); - - LOG(("RunApartmentTest: end\n")); - return NS_OK; -} - -} // namespace - -using namespace proxytests; - -int -main(int argc, char **argv) -{ - int numberOfThreads = 1; - - if (argc > 1) - numberOfThreads = atoi(argv[1]); - - NS_InitXPCOM2(nsnull, nsnull, nsnull); - - // Scope code so everything is destroyed before we run call NS_ShutdownXPCOM - { - nsCOMPtr registrar; - NS_GetComponentRegistrar(getter_AddRefs(registrar)); - registrar->AutoRegister(nsnull); - - RunApartmentTest(); - - nsCOMPtr eventLoopThread; - NS_NewThread(getter_AddRefs(eventLoopThread)); - - nsCOMPtr test = new TestSyncProxyToSelf(); - eventLoopThread->Dispatch(test, NS_DISPATCH_NORMAL); - - PRThread *eventLoopPRThread; - eventLoopThread->GetPRThread(&eventLoopPRThread); - PR_ASSERT(eventLoopPRThread); - - LOG(("TEST: Spawn Threads:\n")); - nsCOMArray threads; - for (PRInt32 spawn = 0; spawn < numberOfThreads; spawn++) - { - test = new ProxyTest(eventLoopPRThread, spawn); - - nsCOMPtr thread; - NS_NewThread(getter_AddRefs(thread), test); - - threads.AppendObject(thread); - - LOG(("TEST: \tThread (%d) spawned\n", spawn)); - - PR_Sleep( PR_MillisecondsToInterval(250) ); - } - - LOG(("TEST: All Threads Spawned.\n")); - - LOG(("TEST: Wait for threads.\n")); - for (PRInt32 i = 0; i < numberOfThreads; i++) - { - LOG(("TEST: Thread (%d) Join...\n", i)); - nsresult rv = threads[i]->Shutdown(); - LOG(("TEST: Thread (%d) Joined. (error: %x).\n", i, rv)); - } - - LOG(("TEST: Shutting down event loop thread\n")); - eventLoopThread->Shutdown(); - } - - LOG(("TEST: Calling Cleanup.\n")); - NS_ShutdownXPCOM(nsnull); - - LOG(("TEST: Return zero.\n")); - return 0; -} diff --git a/tests/cpp/test.cpp b/tests/cpp/test.cpp deleted file mode 100644 index c076e67..0000000 --- a/tests/cpp/test.cpp +++ /dev/null @@ -1,33 +0,0 @@ -namespace Parser -{ - namespace XXX - { - class Foobar - { - class Blah - { - class Bar - { - class Lalala - { - public: - Foobar(); - } - } - } - - }; - }; -}; - -void* -Parser::XXX::Foobar::wait(int i, const char const * const * p) -{ - return; -} - -void -Foobar::non_nil() -{ - return; -} diff --git a/tests/cpp/testsubclass.cpp b/tests/cpp/testsubclass.cpp deleted file mode 100644 index 4c5e9ae..0000000 --- a/tests/cpp/testsubclass.cpp +++ /dev/null @@ -1,12 +0,0 @@ -int animal::moose::getFeet() //^2^ -{ - return fFeet; -} - -void animal::moose::doNothing() //^3^ -{ - animal::moose foo(); - - fFeet = N// -15- - ; // #15# ( "NAME1" "NAME2" "NAME3" ) -} diff --git a/tests/cs/ICoder.cs b/tests/cs/ICoder.cs deleted file mode 100644 index 875cb27..0000000 --- a/tests/cs/ICoder.cs +++ /dev/null @@ -1,157 +0,0 @@ -// ICoder.h - -using System; - -namespace SevenZip -{ - /// - /// The exception that is thrown when an error in input stream occurs during decoding. - /// - class DataErrorException : ApplicationException - { - public DataErrorException(): base("Data Error") { } - } - - /// - /// The exception that is thrown when the value of an argument is outside the allowable range. - /// - class InvalidParamException : ApplicationException - { - public InvalidParamException(): base("Invalid Parameter") { } - } - - public interface ICodeProgress - { - /// - /// Callback progress. - /// - /// - /// input size. -1 if unknown. - /// - /// - /// output size. -1 if unknown. - /// - void SetProgress(Int64 inSize, Int64 outSize); - }; - - public interface ICoder - { - /// - /// Codes streams. - /// - /// - /// input Stream. - /// - /// - /// output Stream. - /// - /// - /// input Size. -1 if unknown. - /// - /// - /// output Size. -1 if unknown. - /// - /// - /// callback progress reference. - /// - /// - /// if input stream is not valid - /// - void Code(System.IO.Stream inStream, System.IO.Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress); - }; - - /* - public interface ICoder2 - { - void Code(ISequentialInStream []inStreams, - const UInt64 []inSizes, - ISequentialOutStream []outStreams, - UInt64 []outSizes, - ICodeProgress progress); - }; - */ - - /// - /// Provides the fields that represent properties idenitifiers for compressing. - /// - public enum CoderPropID - { - /// - /// Specifies default property. - /// - DefaultProp = 0, - /// - /// Specifies size of dictionary. - /// - DictionarySize, - /// - /// Specifies size of memory for PPM*. - /// - UsedMemorySize, - /// - /// Specifies order for PPM methods. - /// - Order, - /// - /// Specifies Block Size. - /// - BlockSize, - /// - /// Specifies number of postion state bits for LZMA (0 <= x <= 4). - /// - PosStateBits, - /// - /// Specifies number of literal context bits for LZMA (0 <= x <= 8). - /// - LitContextBits, - /// - /// Specifies number of literal position bits for LZMA (0 <= x <= 4). - /// - LitPosBits, - /// - /// Specifies number of fast bytes for LZ*. - /// - NumFastBytes, - /// - /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". - /// - MatchFinder, - /// - /// Specifies the number of match finder cyckes. - /// - MatchFinderCycles, - /// - /// Specifies number of passes. - /// - NumPasses, - /// - /// Specifies number of algorithm. - /// - Algorithm, - /// - /// Specifies the number of threads. - /// - NumThreads, - /// - /// Specifies mode with end marker. - /// - EndMarker - }; - - - public interface ISetCoderProperties - { - void SetCoderProperties(CoderPropID[] propIDs, object[] properties); - }; - - public interface IWriteCoderProperties - { - void WriteCoderProperties(System.IO.Stream outStream); - } - - public interface ISetDecoderProperties - { - void SetDecoderProperties(byte[] properties); - } -} diff --git a/tests/cs/RangeCoder.cs b/tests/cs/RangeCoder.cs deleted file mode 100644 index 4ced247..0000000 --- a/tests/cs/RangeCoder.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; - -namespace SevenZip.Compression.RangeCoder -{ - class Encoder - { - public const uint kTopValue = (1 << 24); - - System.IO.Stream Stream; - - public UInt64 Low; - public uint Range; - uint _cacheSize; - byte _cache; - - long StartPosition; - - public void SetStream(System.IO.Stream stream) - { - Stream = stream; - } - - public void ReleaseStream() - { - Stream = null; - } - - public void Init() - { - StartPosition = Stream.Position; - - Low = 0; - Range = 0xFFFFFFFF; - _cacheSize = 1; - _cache = 0; - } - - public void FlushData() - { - for (int i = 0; i < 5; i++) - ShiftLow(); - } - - public void FlushStream() - { - Stream.Flush(); - } - - public void CloseStream() - { - Stream.Close(); - } - - public void Encode(uint start, uint size, uint total) - { - Low += start * (Range /= total); - Range *= size; - while (Range < kTopValue) - { - Range <<= 8; - ShiftLow(); - } - } - - public void ShiftLow() - { - if ((uint)Low < (uint)0xFF000000 || (uint)(Low >> 32) == 1) - { - byte temp = _cache; - do - { - Stream.WriteByte((byte)(temp + (Low >> 32))); - temp = 0xFF; - } - while (--_cacheSize != 0); - _cache = (byte)(((uint)Low) >> 24); - } - _cacheSize++; - Low = ((uint)Low) << 8; - } - - public void EncodeDirectBits(uint v, int numTotalBits) - { - for (int i = numTotalBits - 1; i >= 0; i--) - { - Range >>= 1; - if (((v >> i) & 1) == 1) - Low += Range; - if (Range < kTopValue) - { - Range <<= 8; - ShiftLow(); - } - } - } - - public void EncodeBit(uint size0, int numTotalBits, uint symbol) - { - uint newBound = (Range >> numTotalBits) * size0; - if (symbol == 0) - Range = newBound; - else - { - Low += newBound; - Range -= newBound; - } - while (Range < kTopValue) - { - Range <<= 8; - ShiftLow(); - } - } - - public long GetProcessedSizeAdd() - { - return _cacheSize + - Stream.Position - StartPosition + 4; - // (long)Stream.GetProcessedSize(); - } - } - - class Decoder - { - public const uint kTopValue = (1 << 24); - public uint Range; - public uint Code; - // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); - public System.IO.Stream Stream; - - public void Init(System.IO.Stream stream) - { - // Stream.Init(stream); - Stream = stream; - - Code = 0; - Range = 0xFFFFFFFF; - for (int i = 0; i < 5; i++) - Code = (Code << 8) | (byte)Stream.ReadByte(); - } - - public void ReleaseStream() - { - // Stream.ReleaseStream(); - Stream = null; - } - - public void CloseStream() - { - Stream.Close(); - } - - public void Normalize() - { - while (Range < kTopValue) - { - Code = (Code << 8) | (byte)Stream.ReadByte(); - Range <<= 8; - } - } - - public void Normalize2() - { - if (Range < kTopValue) - { - Code = (Code << 8) | (byte)Stream.ReadByte(); - Range <<= 8; - } - } - - public uint GetThreshold(uint total) - { - return Code / (Range /= total); - } - - public void Decode(uint start, uint size, uint total) - { - Code -= start * Range; - Range *= size; - Normalize(); - } - - public uint DecodeDirectBits(int numTotalBits) - { - uint range = Range; - uint code = Code; - uint result = 0; - for (int i = numTotalBits; i > 0; i--) - { - range >>= 1; - /* - result <<= 1; - if (code >= range) - { - code -= range; - result |= 1; - } - */ - uint t = (code - range) >> 31; - code -= range & (t - 1); - result = (result << 1) | (1 - t); - - if (range < kTopValue) - { - code = (code << 8) | (byte)Stream.ReadByte(); - range <<= 8; - } - } - Range = range; - Code = code; - return result; - } - - public uint DecodeBit(uint size0, int numTotalBits) - { - uint newBound = (Range >> numTotalBits) * size0; - uint symbol; - if (Code < newBound) - { - symbol = 0; - Range = newBound; - } - else - { - symbol = 1; - Code -= newBound; - Range -= newBound; - } - Normalize(); - return symbol; - } - - // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } - } -} diff --git a/tests/cs/RangeCoderBitTree.cs b/tests/cs/RangeCoderBitTree.cs deleted file mode 100644 index 3309c14..0000000 --- a/tests/cs/RangeCoderBitTree.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; - -namespace SevenZip.Compression.RangeCoder -{ - struct BitTreeEncoder - { - BitEncoder[] Models; - int NumBitLevels; - - public BitTreeEncoder(int numBitLevels) - { - NumBitLevels = numBitLevels; - Models = new BitEncoder[1 << numBitLevels]; - } - - public void Init() - { - for (uint i = 1; i < (1 << NumBitLevels); i++) - Models[i].Init(); - } - - public void Encode(Encoder rangeEncoder, UInt32 symbol) - { - UInt32 m = 1; - for (int bitIndex = NumBitLevels; bitIndex > 0; ) - { - bitIndex--; - UInt32 bit = (symbol >> bitIndex) & 1; - Models[m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - } - } - - public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol) - { - UInt32 m = 1; - for (UInt32 i = 0; i < NumBitLevels; i++) - { - UInt32 bit = symbol & 1; - Models[m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - symbol >>= 1; - } - } - - public UInt32 GetPrice(UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int bitIndex = NumBitLevels; bitIndex > 0; ) - { - bitIndex--; - UInt32 bit = (symbol >> bitIndex) & 1; - price += Models[m].GetPrice(bit); - m = (m << 1) + bit; - } - return price; - } - - public UInt32 ReverseGetPrice(UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int i = NumBitLevels; i > 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += Models[m].GetPrice(bit); - m = (m << 1) | bit; - } - return price; - } - - public static UInt32 ReverseGetPrice(BitEncoder[] Models, UInt32 startIndex, - int NumBitLevels, UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int i = NumBitLevels; i > 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += Models[startIndex + m].GetPrice(bit); - m = (m << 1) | bit; - } - return price; - } - - public static void ReverseEncode(BitEncoder[] Models, UInt32 startIndex, - Encoder rangeEncoder, int NumBitLevels, UInt32 symbol) - { - UInt32 m = 1; - for (int i = 0; i < NumBitLevels; i++) - { - UInt32 bit = symbol & 1; - Models[startIndex + m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - symbol >>= 1; - } - } - } - - struct BitTreeDecoder - { - BitDecoder[] Models; - int NumBitLevels; - - public BitTreeDecoder(int numBitLevels) - { - NumBitLevels = numBitLevels; - Models = new BitDecoder[1 << numBitLevels]; - } - - public void Init() - { - for (uint i = 1; i < (1 << NumBitLevels); i++) - Models[i].Init(); - } - - public uint Decode(RangeCoder.Decoder rangeDecoder) - { - uint m = 1; - for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--) - m = (m << 1) + Models[m].Decode(rangeDecoder); - return m - ((uint)1 << NumBitLevels); - } - - public uint ReverseDecode(RangeCoder.Decoder rangeDecoder) - { - uint m = 1; - uint symbol = 0; - for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) - { - uint bit = Models[m].Decode(rangeDecoder); - m <<= 1; - m += bit; - symbol |= (bit << bitIndex); - } - return symbol; - } - - public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex, - RangeCoder.Decoder rangeDecoder, int NumBitLevels) - { - uint m = 1; - uint symbol = 0; - for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) - { - uint bit = Models[startIndex + m].Decode(rangeDecoder); - m <<= 1; - m += bit; - symbol |= (bit << bitIndex); - } - return symbol; - } - } -} diff --git a/tests/eiffel/poly.e b/tests/eiffel/poly.e deleted file mode 100644 index fc14381..0000000 --- a/tests/eiffel/poly.e +++ /dev/null @@ -1,64 +0,0 @@ -class - POINT -inherit - ANY - redefine - out - end -create - make, make_origin - -feature -- Initialization - - make (a_x, a_y: INTEGER) - -- Create with values `a_x' and `a_y' - do - set_x (a_x) - set_y (a_y) - ensure - x_set: x = a_x - y_set: y = a_y - end - - make_origin - -- Create at origin - do - ensure - x_set: x = 0 - y_set: y = 0 - end - -feature -- Access - - x: INTEGER assign set_x - -- Horizontal axis coordinate - - y: INTEGER assign set_y - -- Vertical axis coordinate - -feature -- Element change - - set_x (a_x: INTEGER) - -- Set `x' coordinate to `a_x' - do - x := a_x - ensure - x_set: x = a_x - end - - set_y (a_y: INTEGER) - -- Set `y' coordinate to `a_y' - do - y := a_y - ensure - y_set: y = a_y - end - -feature -- Output - - out: STRING - -- Display as string - do - Result := "Point: x = " + x.out + " y = " + y.out - end -end diff --git a/tests/erlang/examples-2.0/ebnf.ecc b/tests/erlang/examples-2.0/ebnf.ecc deleted file mode 100644 index ba70aae..0000000 --- a/tests/erlang/examples-2.0/ebnf.ecc +++ /dev/null @@ -1,34 +0,0 @@ -COMPILER ebnf. - -CHARACTERS - small = "abcdefghijklmnopqrstuvwxyz"; - big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - alpha = small + big; - dig = "0123456789"; - blank = CHR(9) + CHR(10) + CHR(32); - noQuote = ANY - '"'. - -COMMENTS - FROM "(*" TO "*)" NESTED. - -TOKENS - Nonterminal = small {alpha | dig}; - Terminal = big {alpha | dig}; - White = blank {blank}; - String = '"' { noQuote } '"'. - -IGNORE - White + Comment. - -PRODUCTIONS - ebnf = {production} "."; - production = Nonterminal "=" expr ";" ; - expr = term {"|" term}; - term = factor {factor}; - factor = "{" expr "}" - | "[" expr "]" - | "(" expr ")" - | Nonterminal | Terminal | String. - -END ebnf. - diff --git a/tests/erlang/examples-2.0/ecc.xrl b/tests/erlang/examples-2.0/ecc.xrl deleted file mode 100644 index 5f5c2d6..0000000 --- a/tests/erlang/examples-2.0/ecc.xrl +++ /dev/null @@ -1,117 +0,0 @@ -Definitions. - -Dig = [0-9] -Big = [A-Z] -Small = [a-z] -WS = [\000-\s] - -COMMENT = \(\*\(*([^*)]|[^*]\)|\*[^)])*\**\*\) -STRING = "(\\\^.|\\.|[^"])*" -QUOTE = '(\\\^.|\\.|[^'])*' - -Rules. - -({Small}({Small}|{Big}|{Dig}|_)*) : {token, {atom,YYline, YYtext}}. - -({Big}({Small}|{Big}|{Dig}|_)*) : {token, special(YYtext, YYline)}. - -({Dig}{Dig}*) : {token, {integer, YYline, list_to_integer(YYtext)}}. - -%% string - -{STRING} : %% Strip quotes. - S = lists:sublist(YYtext, 2, length(YYtext) - 2), - {token,{string,YYline,string_gen(S)}}. - -{QUOTE} : %% Strip quotes. - S = lists:sublist(YYtext, 2, length(YYtext) - 2), - {token,{quote,YYline,string_gen(S)}}. - - -{COMMENT} : . - - -%%--------------------------------------------------------- -%% Ignore stuff -%%--------------------------------------------------------- -%% "{WHITE}". %% whitespace -%% "#.*". %% Ignore Macro stuff for now -%% "{COMMENT}". %% Ignore Comments - -%% C comments are /* ... */ -%% Our comments are (* ... *) {we have to quote ( and * yuck -%% i.e. write \* and \( } -%% - -%% COMMENT "/\\*/*([^*/]|[^*]/|\\*[^/])*\\**\\*/". (tobbe) -%% COMMENT "(\\*/*([^*)]|[^*])|\\*[^)])*\\**\\*)". (modified) -%% COMMENT "\(\\*/*([^*\)]|[^*]\)|\\*[^\)])*\\**\\*\)". (quoted) - -= : {token, {'=', YYline}}. -\+ : {token, {'+', YYline}}. -\- : {token, {'-', YYline}}. -\; : {token, {';', YYline}}. -} : {token, {'}', YYline}}. -{ : {token, {'{', YYline}}. -\[ : {token, {'[', YYline}}. -\] : {token, {']', YYline}}. -\( : {token, {'(', YYline}}. -\) : {token, {')', YYline}}. -\| : {token, {'|', YYline}}. -\: : {token, {':', YYline}}. - -(.|\n) : skip_token. - -\.[\s\t\n] : {end_token,{'$end', YYline}}. - -Erlang code. - -string_gen([$\\|Cs]) -> - string_escape(Cs); -string_gen([C|Cs]) -> - [C|string_gen(Cs)]; -string_gen([]) -> []. - -string_escape([O1,O2,O3|S]) when - O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> - [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)]; -string_escape([$^,C|Cs]) -> - [C band 31|string_gen(Cs)]; -string_escape([C|Cs]) when C >= 0, C =< $ -> - string_gen(Cs); -string_escape([C|Cs]) -> - [escape_char(C)|string_gen(Cs)]. - -escape_char($n) -> $\n; %\n = LF -escape_char($r) -> $\r; %\r = CR -escape_char($t) -> $\t; %\t = TAB -escape_char($v) -> $\v; %\v = VT -escape_char($b) -> $\b; %\b = BS -escape_char($f) -> $\f; %\f = FF -escape_char($e) -> $\e; %\e = ESC -escape_char($s) -> $ ; %\s = SPC -escape_char($d) -> $\d; %\d = DEL -escape_char(C) -> C. - -remove_brackets([_,_|T]) -> - [_,_|T1] = lists:reverse(T), - lists:reverse(T1). - -special("COMPILER", Line) -> {'COMPILER', Line}; -special("CHARACTERS", Line) -> {'CHARACTERS', Line}; -special("COMMENTS", Line) -> {'COMMENTS', Line}; -special("FROM", Line) -> {'FROM', Line}; -special("TO", Line) -> {'TO', Line}; -special("TOKENS", Line) -> {'TOKENS', Line}; -special("IGNORE", Line) -> {'IGNORE', Line}; -special("PRODUCTIONS", Line) -> {'PRODUCTIONS', Line}; -special("END", Line) -> {'END', Line}; -special("NESTED", Line) -> {'NESTED', Line}; -special("EOL", Line) -> {'EOL', Line}; -special("CHR", Line) -> {'CHR', Line}; -special("ANY", Line) -> {'ANY', Line}; -special(Other, Line) -> {var, Line, Other}. - - - - diff --git a/tests/erlang/examples-2.0/ecc.yrl b/tests/erlang/examples-2.0/ecc.yrl deleted file mode 100644 index ad03c44..0000000 --- a/tests/erlang/examples-2.0/ecc.yrl +++ /dev/null @@ -1,105 +0,0 @@ -Nonterminals - -production -form lhs factor -nested syntax char_prods charline char_rhs char_prim -ignore moreignore expr term. - -Terminals -atom var string quote '|' '=' '}' '{' '(' ')' '[' ']' -'COMPILER' 'CHARACTERS' 'COMMENTS' 'FROM' 'TO' 'TOKENS' -'IGNORE' 'PRODUCTIONS' 'END' 'NESTED' 'EOL' 'CHR' 'ANY' integer comment -'+' '-' ';'. - - -Rootsymbol form. - -form -> 'COMPILER' atom : {compiler, unwrap('$2')}. -form -> 'CHARACTERS' char_prods : {characters, '$2'}. -form -> 'COMMENTS' 'FROM' string - 'TO' string nested : {comments,unwrap('$3'),unwrap('$5'), - '$6'}. -form -> 'TOKENS' syntax : {tokens, '$2'}. -form -> 'IGNORE' ignore : {ignore, '$2'}. -form -> 'PRODUCTIONS' syntax : {syntax, '$2'}. -form -> 'END' atom : {theend, '$2'}. -form -> comment. - -nested -> 'NESTED' : nested. -nested -> 'EOL' : eol. -nested -> '$empty' : not_nested. - -%% Character syntax - -char_prods -> charline ';' char_prods : ['$1'|'$3']. -char_prods -> charline : ['$1']. - -charline -> atom '=' char_rhs : {unwrap('$1'), '$3'}. - -char_rhs -> char_prim '+' char_rhs : {plus, '$1', '$3'}. -char_rhs -> char_prim '-' char_rhs : {minus, '$1', '$3'}. -char_rhs -> char_prim : '$1'. - -char_prim -> 'CHR' '(' integer ')' : {chr, unwrap('$3')}. -char_prim -> string : {string, unwrap('$1')}. -char_prim -> quote : {string, unwrap('$1')}. -char_prim -> atom : {atom, unwrap('$1')}. -char_prim -> 'ANY' : any. - -ignore -> var moreignore : [unwrap('$1')|'$2']. - -moreignore -> '+' ignore : '$2'. -moreignore -> '$empty' : []. - -%% The following deifinitions are taken from [WIR82] -%% WIR82 Programming in Modular2 -%% Springer Verlag 1982 - -%% statement : A syntactic form -%% expression : A list of alternatives -%% term : A concatination of factors -%% factor : A single syntactoc entity or a parenthesized expression - -%% Construct -%% ========= -%% [ A ] = zero or more A's -%% { A } = any number of A's -%% "A" = a string parse tree -%% A | B = A or B parse tree -%% A B = sequence of A followed by B -%% identifier = a name - -%% syntax = {production} -%% production = id "=" expr ";" -%% expr = term {"|" term} -%% term = factor {factor} -%% factor = id | string "{" expr "} - -syntax -> production ';' syntax : ['$1'|'$3']. -syntax -> production : ['$1']. - -production -> lhs '=' expr : {prod, '$1', '$3'}. - -lhs -> var : unwrap('$1'). -lhs -> atom : unwrap('$1'). - -expr -> term : '$1'. -expr -> term '|' expr : {alt, '$1', '$3'}. - -term -> factor : '$1'. -term -> factor term : {seq, '$1', '$2'}. - -factor -> atom : {nt, unwrap('$1')}. -factor -> var : {ta, unwrap('$1')}. -factor -> string : {ts, unwrap('$1')}. -factor -> quote : {tq, unwrap('$1')}. -factor -> '[' expr ']' : {one, '$2'}. -factor -> '{' expr '}' : {star, '$2'}. -factor -> '(' expr ')' : {bracket, '$2'}. - -Erlang code. - -unwrap({_,_,V}) -> V. - -simplify({Tag,A,nil}) -> A; -simplify(X) -> X. diff --git a/tests/erlang/examples-2.0/ecc_parse.erl b/tests/erlang/examples-2.0/ecc_parse.erl deleted file mode 100644 index dae192e..0000000 --- a/tests/erlang/examples-2.0/ecc_parse.erl +++ /dev/null @@ -1,58 +0,0 @@ --module(ecc_parse). - --doc([{author, 'Joe Armstrong'}, - {title, "Parser for the ecc language."}, - {keywords,[ecc,parser,yecc,leex]}, - {date, 891106}]). - --export([make/0, file/1]). - -%% usage -%% ecc_parse:file(File) -%% Converts File.ebnf -> File.xbin -%% ecc_parse:make() -%% Makes the parser - -make() -> - %% The parser is made from - %% ecc.yrl and ecc.xrl - yecc:yecc("ecc", "ecc_yecc"), - c:c(ecc_yecc), - leex:gen(ecc, ecc_lex), - c:c(ecc_lex). - -file(F) -> - io:format("Parsing ~s.ecc~n", [F]), - {ok, Stream} = file:open(F ++ ".ecc", read), - Parse = handle(Stream, 1, [], 0), - file:close(Stream), - Parse. - -handle(Stream, LineNo, L, NErrors) -> - handle1(io:requests(Stream, [{get_until,foo,ecc_lex, - tokens,[LineNo]}]), Stream, L, NErrors). - -handle1({ok, Toks, Next}, Stream, L, Nerrs) -> - case ecc_yecc:parse(Toks) of - {ok, Parse} -> - handle(Stream, Next, [Parse|L], Nerrs); - {error, {Line, Mod, What}} -> - Str = apply(Mod, format_error, [What]), - io:format("** ~w ~s~n", [Line, Str]), - handle(Stream, Next, L, Nerrs+1); - Other -> - io:format("Bad_parse:~p\n", [Other]), - handle(Stream, Next, L, Nerrs+1) - end; -handle1({eof, _}, Stream, L, 0) -> - {ok, lists:reverse(L)}; -handle1({eof, _}, Stream, L, N) -> - {error, N}; -handle1(What, Stream, L, Nerrs) -> - io:format("Here:~p\n", [What]), - handle(Stream, 1, L, Nerrs+1). - -first([H]) -> []; -first([H|T]) -> [H|first(T)]; -first([]) -> []. - diff --git a/tests/erlang/examples-2.0/ermake.erl b/tests/erlang/examples-2.0/ermake.erl deleted file mode 100644 index a8ffd21..0000000 --- a/tests/erlang/examples-2.0/ermake.erl +++ /dev/null @@ -1,236 +0,0 @@ --module(ermake). - --doc([{author,'Joe Armstrong'}, - {title,"Erlang make utility."}, - {keywords, [make]}, - {date,981103}]). - --export([all/0, target/1, file/1, file/2]). - --import(lists, [delete/2, filter/2, foldl/3, map/2, max/1, member/2, zf/2]). - -%% all() -> Makes first target in EMakefile -%% target(target::string()) -> Makes Target in EMakefile -%% file(file()) -> Makes first target in File -%% file(file(), target::string()) -> Makes Target in File - -all() -> file("EMakefile"). - -target(T) -> file("EMakefile", T). - -file(File) -> make(File, top). - -file(File, Target) -> make(File, {target, Target}). - -make(File, Target) -> - Lines = ermake_parse:parse(File), - Rules = filter(fun(X) -> element(1, X) == make end, Lines), - Suffix = filter(fun(X) -> element(1, X) == suffix end, Lines), - Rules1 = add_extra_rules(Rules, Suffix), - case Target of - top -> - case hd(Rules) of - {make, Ts, _, _} -> - make_everything(Ts, Rules1); - _ -> - nothing_to_do - end; - {target, T} -> - make_everything([T], Rules1) - end. - -add_extra_rules(Rules, Suffix) -> - %% If a dependent is mentioned and - %% there is no explicit rule for how to make the dependent then - %% add an extra rule if possible - Targets = [T || {make,Ts,_,_} <- Rules, T <- Ts], - Dependents = [D || {make, _, Ds, _} <- Rules, D <- Ds], - Missing = filter(fun(I) -> not member(I, Targets) end, Dependents), - Missing1 = remove_duplicates(Missing), - Extra = zf(fun(I) -> find_suffix_rule(I, Suffix) end, Missing1), - Rules ++ Extra. - -make_everything(Targets, Rules) -> - Ps = [{T, D} || {make, Ts, Ds, _} <- Rules, T <- Ts, D <- Ds], - %% trace all the rules we can reach from the root set - L0 = transitive:closure(Targets, Ps), - L = delete(true, L0), - %% keep those rules that are mentioned in targets or destinations - Ps1 = filter(fun({D,T}) -> - member(D, L) or member(T, L) - end, Ps), - %% reverse the order to build the bottom up tree - Ps2 = map(fun({I,J}) -> {J, I} end, Ps1), - %% order the result - case topological_sort:sort(Ps2) of - {ok, Order0} -> - Order = delete(true, Order0), - %% Order is the absolute order to build things - Cmds = map(fun(I) -> select_rule(I, Rules) end, Order), - foldl(fun do_cmd/2, [], Cmds), - true; - {cycle, Cycle} -> - exit({makefile,contains,cycle,Cycle}) - end. - -%% find which rule is needed to build Target - -select_rule(Target, Rules) -> - Matches = [{make, Ts,Ds,Fun}|| {make,Ts,Ds,Fun} <- Rules, - member(Target, Ts)], - case length(Matches) of - 0 -> {file, Target}; - 1 -> hd(Matches); - _ -> exit({multiple,rules,to,make,Target}) - end. - -%% do_cmd(cmd(), made()) -> make()' -%% cmd() = {make, Targets, Dependents, Fun} | {file, Target} -%% made() = [Target, time()]. - -do_cmd({make, Bins, Srcs, Fun}, Made) -> - case target_time(Bins, Made) of - none -> - eval(Bins, Fun); - {missing, M} -> - eval(Bins, Fun); - {max, TBin} -> - case target_time(Srcs, Made) of - {missing, M} -> - exit({'I don\'t know how to make',M}); - {max, TSrc} when TSrc > TBin -> - eval(Bins, Fun); - {max, _} -> - true; - none -> - exit({no,src,Srcs}) - end - end, - update_times(Srcs ++ Bins, this_time(), Made); -do_cmd({file,H}, Made) -> - update_times([H], this_time(), Made). - -%% target_time(Targets, Made) -> {max, Time} | {missing,M} -%% none -%% if no targets found -%% {missing, M} -%% if target M is missing -%% {max, Time} -%% Time is the last modified time of all the targets -%% the limes are derived from either the Made list -%% or from the time stamp of the file. - -target_time(Targets, Made) -> - target_time(Targets, Made, []). - -target_time([H|T], Made, Times) -> - case make_time(H, Made) of - {yes, Time} -> - target_time(T, Made, [Time|Times]); - no -> - case is_file(H) of - true -> - target_time(T, Made, [last_modified(H)|Times]); - false -> - {missing, H} - end - end; -target_time([], Made, []) -> - none; -target_time([], Made, Times) -> - {max, max(Times)}. - -make_time(X, [{X,Time}|_]) -> {yes, Time}; -make_time(X, [_|T]) -> make_time(X, T); -make_time(X, []) -> no. - -update_times([H|T], Now, Made) -> - case make_time(H, Made) of - {yes, _} -> update_times(T, Now, Made); - no -> - case is_file(H) of - true -> - update_times(T, Now, [{H, last_modified(H)}|Made]); - false -> - update_times(T, Now, [{H, Now}|Made]) - end - end; -update_times([], _, Made) -> - Made. - -%% see if a suffix rule can be applied to the file D - -find_suffix_rule(D, Suffix) -> - Ext = filename:extension(D), - find_suffix_rule(Ext, D, Suffix). - -find_suffix_rule(To, D, [{suffix, [From, To], Fun}|_]) -> - Root = filename:rootname(D), - Fun1 = expand_cmd(Fun, Root), - {true, {make, [D], [Root ++ From], Fun1}}; -find_suffix_rule(To, D, [_|T]) -> - find_suffix_rule(To, D, T); -find_suffix_rule(_, _, []) -> - false. - -expand_cmd([$$,$>|T], Root) -> - Root ++ expand_cmd(T, Root); -expand_cmd([H|T], Root) -> - [H|expand_cmd(T, Root)]; -expand_cmd([], _) -> - []. - -eval(_, []) -> - true; -eval(Target, Str) -> - io:format("make ~p ->~n~s~n",[Target, Str]), - case erl_scan:tokens([], "fun() -> " ++ Str ++ " end. ", 1) of - {done, {ok, Toks, _},_} -> - case erl_parse:parse_exprs(Toks) of - {ok, [Parse]} -> - %% io:format("Parse = ~p~n",[Parse]), - Env0 = erl_eval:new_bindings(), - Call = [{call,9999,Parse,[]}], - case erl_eval:exprs(Call, Env0) of - {value, Val, _} -> - Val; - O3 -> - exit({eval,error,O3}) - end; - O1 -> - exit({parse,error,o1,O1}) - end; - O2 -> - exit({tokenisation,error,O2}) - end. - -%% Stuff that should have been in the libraries (sigh :-) - -last_modified(F) -> - case file:file_info(F) of - {ok, {_, _, _, _, Time, _, _}} -> - Time; - _ -> - exit({last_modified, F}) - end. - -is_file(File) -> - case file:file_info(File) of - {ok, _} -> - true; - _ -> - false - end. - -remove_duplicates(L) -> - foldl(fun(I, Acc) -> - case member(I, Acc) of - true -> Acc; - false -> [I|Acc] - end - end, [], L). - -this_time() -> - {Y,M,D} = date(), - {H,Min,S} = time(), - {Y,M,D,H,Min,S}. diff --git a/tests/erlang/examples-2.0/ermake_line_reader.erl b/tests/erlang/examples-2.0/ermake_line_reader.erl deleted file mode 100644 index 3473815..0000000 --- a/tests/erlang/examples-2.0/ermake_line_reader.erl +++ /dev/null @@ -1,246 +0,0 @@ --module(ermake_line_reader). - --doc([{author,'Joe Armstrong'}, - {title,"Provide character level input for utilities such as make, lex, yacc etc. This module reads lines up to dot whitespace. Include files and named macros are expanded in place."}, - {keywords, [read,line,make,dot,whitespace,forms]}, - {date,981028}]). - -%% This module provides a common *character level input* -%% For Erlang look-alike utilities such as make, yecc, lex -%% It provides -%% 1) Multiple line input terminated by dot white space -%% 2) Variables VAR = Val, or VAR += Val -%% 3) Include files include("File") -%% 4) comment stripping %... are removed - --export([test/1, read_file/1, read_file/2]). - --import(lists, [keyreplace/4, keysearch/3, member/2, reverse/1, reverse/2]). - -test(1) -> read_file("EMakefile"); -test(2) -> read_file("test1"). - -read_file(File) -> - read_file(File, []). - -read_file(File, Macros) -> - {Macros1, Lines} = read_lines(File, Macros, [File]), - %% io:format("Macros were:~p~n",[Macros1]), - trim(Lines). - -trim([{{File,Line},Str}|T]) -> - Leading = count_leading_nls(Str, Line), - case trim_line(Str) of - [] -> trim(T); - Str1 -> [{File,Leading,Str1}|trim(T)] - end; -trim([]) -> []. - -trim_line(Str) -> - Str1 = skip_white(Str), - trim_end_of_line(Str1). - -trim_end_of_line(Str1) -> - case reverse(Str1) of - [X,$.|Tmp] -> - reverse(Tmp); - [] -> - []; - Other -> - exit({oops,Other}) - end. - -read_lines(File, Macros0, Stack) -> - case file:read_file(File) of - {ok, Bin} -> - Lines = gather_lines(binary_to_list(Bin), File, 1, []), - %% io:format("Lines=~p~n",[Lines]), - expand(Lines, Macros0, Stack, []); - _ -> - exit({cannot,read,file,File}) - end. - -expand([{Where, H}|T], Macros, Stack, L) -> - %% first expand any macros - H1 = expand_macro(H, Macros, Where), - %% now add any macro definitions - case is_macro_defn(H1) of - {new, Var, Val} -> - case keysearch(Var,1,Macros) of - {value,{_,Val}} -> - %% same value no problem - expand(T, Macros, Stack, L); - {value,{_,Replacement}} -> - %% some other value - exit({error, Where, cannot,redefine,macro, - Var,was,Replacement,is,Val}); - false -> - %% new definition - expand(T, [{Var,Val}|Macros], Stack, L) - end; - {plus, Var, Val} -> - case keysearch(Var,1,Macros) of - {value,{_,Old}} -> - %% some other value - Macros1 = keyreplace(Var,1,Macros,{Var,Old++Val}), - expand(T, Macros1, Stack, L); - false -> - exit({error, Where, no,previous,defn,for,Var}) - end; - no -> - case is_include(H1, Where) of - {yes, File} -> - case member(File, Stack) of - true -> - exit({error, Where, recursive_include, File}); - false -> - {Macros1, Lines1} = read_lines(File,Macros,Stack), - expand(T, Macros1, Stack, reverse(Lines1, L)) - end; - no -> - expand(T, Macros, Stack, [{Where, H1}|L]) - end - end; -expand([], Macros, Stack, L) -> - {Macros, reverse(L)}. - -expand_macro([$$,$(|T], Macros, Where) -> - case is_var(T) of - {yes, Var, [$)|T1]} -> - case keysearch(Var,1,Macros) of - {value,{_,Replacement}} -> - Replacement ++ expand_macro(T1, Macros, Where); - false -> - exit({error,Where,undefined,macro,Var}) - end; - no -> - [$$,$(|expand_macro(T, Macros, Where)] - end; -expand_macro([H|T], Macros, Where) -> - [H|expand_macro(T, Macros, Where)]; -expand_macro([], Macros, _) -> - []. - -is_include(Line, Where) -> - case skip_white(Line) of - [$i,$n,$c,$l,$u,$d,$e,$(,$"|T] -> - {File, T1} = get_quoted([$"|T], Where), - case skip_white(T1) of - [$)|_] -> - {yes, File}; - _ -> - exit({Where,bad,include,syntax}) - end; - _ -> - no - end. - -is_macro_defn(Line) -> - Str1 = skip_white(Line), - case is_var(Str1) of - {yes, Var, Str2} -> - case skip_white(Str2) of - [$=|T] -> - {new, Var, trim_end_of_line(T)}; - [$+,$=|T] -> - {plus, Var, trim_end_of_line(T)}; - _ -> - no - end; - no -> - no - end. - -is_var([H|T]) when $A =< H, H =< $Z -> - collect_var(T, [H]); -is_var(_) -> - no. - -collect_var([H|T], L) when $A =< H, H =< $Z -> - collect_var(T, [H|L]); -collect_var([H|T], L) when $1 =< H, H =< $9 -> - collect_var(T, [H|L]); -collect_var([H|T], L) when $a =< H, H =< $z -> - collect_var(T, [H|L]); -collect_var(X, L) -> - {yes, reverse(L), X}. - -skip_white([$ |T]) -> skip_white(T); -skip_white([$\n|T]) -> skip_white(T); -skip_white([$\t|T]) -> skip_white(T); -skip_white(T) -> T. - -gather_lines([], File, N, L) -> - reverse(L); -gather_lines(Str, File, N, L) -> - {Line, Str1} = get_line(Str, {File, N}, []), - Width = count_nls(Line, 0), - gather_lines(Str1, File, N + Width, [{{File,N},Line}|L]). - -count_nls([$\n|T], N) -> count_nls(T, N+1); -count_nls([_|T], N) -> count_nls(T, N); -count_nls([], N) -> N. - -count_leading_nls([$\n|T], N) -> count_leading_nls(T, N+1); -count_leading_nls(_, N) -> N. - -%% get_line collects a line up to . - -get_line([$.,X|T], Where, L) -> - case X of - $\n -> - {reverse(L, [$.,$\n]), T}; - $ -> - {reverse(L, [". "]), T}; - $\t -> - {reverse(L, [$.,$\t]), T}; - _ -> - get_line(T, Where, [X,$.|L]) - end; -get_line([$"|T], Where, L) -> - {Str, T1} = get_quoted([$"|T], Where), - get_line(T1, Where, [$"|reverse(Str, [$"|L])]); -get_line([$'|T], Where, L) -> - {Str, T1} = get_quoted([$'|T], Where), - get_line(T1, Where,[$'|reverse(Str, [$'|L])]); -get_line([$%|T], Where, L) -> - %% remove the comment - T1 = skip_to_eol(T), - get_line(T1, Where, L); -get_line([H|T], Where, L) -> - get_line(T, Where, [H|L]); -get_line([], Where, L) -> - {reverse(L), []}. - -skip_to_eol([$\n|T]) -> [$\n|T]; -skip_to_eol([_|T]) -> skip_to_eol(T); -skip_to_eol([]) -> []. - - -%% get_quoted(string(), {file(),line()}) -> {quoted(), rest()} -%% The " ' is not included - -get_quoted([End|T], Where) -> - get_quoted(T, Where, End, []). - -get_quoted([End|T], Where, End, Acc) -> - {reverse(Acc), T}; -get_quoted([$\\,C|T], Where, End, Acc) -> - get_quoted(T, Where, End, [quoted(C)|Acc]); -get_quoted([$\n|_], {File,Line}, _, _) -> - exit({error, file, File, line, Line, - "newline not allowed in string"}); -get_quoted([H|T], Where, End, Acc) -> - get_quoted(T, Where, End, [H|Acc]); -get_quoted([], {File,Line}, _, _) -> - exit({error, file, File, line, Line, - "end of line not allowed in string"}). - -%% Quoted characters - -quoted($n) -> $\n; -quoted($t) -> $\t; -quoted($r) -> $\r; -quoted($b) -> $\b; -quoted($v) -> $\v; -quoted(C) -> C. diff --git a/tests/erlang/examples-2.0/ermake_parse.erl b/tests/erlang/examples-2.0/ermake_parse.erl deleted file mode 100644 index 32698de..0000000 --- a/tests/erlang/examples-2.0/ermake_parse.erl +++ /dev/null @@ -1,73 +0,0 @@ --module(ermake_parse). - --doc([{author,'Joe Armstrong'}, - {title,"Parser used by ermake."}, - {keywords, [parser,make]}, - {date,981029}]). - --export([parse/1]). - --import(lists, [reverse/1, prefix/2, map/2]). - -parse(File) -> - Dir = filename:dirname(code:which(?MODULE)), - Lines = ermake_line_reader:read_file(File, [{"MAKEDIR", Dir}]), - map(fun parse_line/1, Lines). - -parse_line({File,Line,Str}) -> - parse_str(Str). - -parse_str([$S,$u,$f,$f,$i,$x,$ |T]) -> - %% io:format("Suffix:~s~n",[T]), - case split("->", T) of - {yes, Pre, Fun} -> - Tmp = string:tokens(Pre,". "), - Tmp1 = map(fun(I) -> [$.|I] end, Tmp), - {suffix, Tmp1, Fun}; - no -> - exit({'No -> in suffix rule', T}) - end; -parse_str(Str) -> - case split("when", Str) of - {yes, As, BsF} -> - case split("->", BsF) of - %% As when Bs -> F - {yes, Bs, F} -> - {make, parse_files(As), parse_files(Bs), - parse_fun(F)}; - no -> - %% As when Bs. - {make, parse_files(As), parse_files(BsF), []} - end; - no -> - %% A -> F - case split("->", Str) of - no -> - exit({'No "when" or "->" in rule', Str}); - {yes, As, F} -> - {make, parse_files(As), [true], parse_fun(F)} - end - end. - -%% split(Prefix, String) -> {yes, Before, After} | no -%% splits String at Prefix - -split(Prefix, L) -> split(Prefix, L, []). - -split(_, [], L) -> - no; -split(Prefix, L, L1) -> - case prefix(Prefix, L) of - true -> - {yes, reverse(L1), string:substr(L, length(Prefix)+1)}; - false -> - split(Prefix, tl(L), [hd(L)|L1]) - end. - -parse_files(Str) -> - Files = string:tokens(Str, [$ ,$,,$\n,$\t]). - -parse_fun([$\n|F]) -> F; -parse_fun(F) -> F. - - diff --git a/tests/erlang/examples-2.0/error_handler.erl b/tests/erlang/examples-2.0/error_handler.erl deleted file mode 100644 index 95ac11c..0000000 --- a/tests/erlang/examples-2.0/error_handler.erl +++ /dev/null @@ -1,30 +0,0 @@ --module(error_handler). - --doc([{author,joe}, - {title,"Special version of error handler used by sos.erl"}, - {date,981012}]). - --export([undefined_function/3,undefined_global_name/2]). - -undefined_function(sos, F, A) -> - erlang:display({error_handler,undefined_function, - sos,F,A}), - exit(oops); -undefined_function(M, F, A) -> - case sos:load_module(M) of - {ok, M} -> - case erlang:function_exported(M,F,length(A)) of - true -> - apply(M, F, A); - false -> - sos:stop_system({undef,{M,F,A}}) - end; - {ok, Other} -> - sos:stop_system({undef,{M,F,A}}); - already_loaded -> - sos:stop_system({undef,{M,F,A}}); - {error, What} -> - sos:stop_system({load,error,What}) - end. -undefined_global_name(Name, Message) -> - exit({badarg,{Name,Message}}). diff --git a/tests/erlang/examples-2.0/find.erl b/tests/erlang/examples-2.0/find.erl deleted file mode 100644 index bb0ea0c..0000000 --- a/tests/erlang/examples-2.0/find.erl +++ /dev/null @@ -1,170 +0,0 @@ --module(find). - --doc([{author,'Joe Armstrong'}, - {title,"Find all files. Find all out of date files. -

A find utility which finds all files (and directories) -relative to a given root directory"}, - {keywords, [find,make]}, - {api,["find:files(\".\", \"*.erl\", false) finds all -entries in the current directory. -Recursive scan of sub-directories is also allowed.", -"find:out_of_date(\".\",\".erl\",\".jam\") finds all out of date -Erlang files in the current directory"]}, - {date,970203}]). - --export([files/3, out_of_date/3]). - --import(lists, [suffix/2, sublist/3, map/2, filter/2]). - - -%% files(Dir, ReExpr, Recursive) -> [File] -%% Find regular files starting from Dir -%% Which match ReExpr -%% If Recursive is true do recursivly on all sub-directories -%% Example find(".", "*.erl", false) will find all erlang files in the -%% Current directory -%% -%% out_of_date(Dir, SrcExt, ObjExt) find all "out of date files" in -%% Dir. -%% Example: -%% out_of_date(".", ".erl", ".jam") -%% Finds all out of date files in the current directory - -files(Dir, Re, Flag) -> - Re1 = string:re_sh_to_awk(Re), - find_files(Dir, Re1, Flag, []). - -%% +type find_files(dirname(), Regexp, bool(), [filename()]) -> [filename()] -%% when Regexp = string(). - -find_files(Dir, Re, Flag, L) -> - case file:list_dir(Dir) of - {ok, Files} -> find_files(Files, Dir, Re, Flag, L); - {error, _} -> L - end. - -%% +type find_files([filename()], dirname(), Regexp, bool(), [filename()]) -> -%% [filename()] when Regexp = string(). - -find_files([File|T], Dir, Re, Recursive, L) -> - FullName = Dir ++ [$/|File], - case file_type(FullName) of - regular -> - case string:re_match(FullName, Re) of - {match, _, _} -> - find_files(T, Dir, Re, Recursive, [FullName|L]); - _ -> - find_files(T, Dir, Re, Recursive, L) - end; - directory -> - case Recursive of - true -> - L1 = find_files(FullName, Re, Recursive, L), - find_files(T, Dir, Re, Recursive, L1); - false -> - find_files(T, Dir, Re, Recursive, L) - end; - error -> - find_files(T, Dir, Re, Recursive, L) - end; -find_files([], _, _, _, L) -> - L. - -%% +type file_type(string()) -> regular | directory | error. - -file_type(File) -> - case file:file_info(File) of - {ok, Facts} -> - case element(2, Facts) of - regular -> regular; - directory -> directory; - _ -> error - end; - _ -> - error - end. - - -%%______________________________________________________________________ -%% outofdate(Dir, InExtension, OutExtension) -%% scans Dir for all files with the extension "InExtension" -%% If a file with this extension is found then "OutExtension" is checked -%% -%% returns a list of files in

where *.OutExtension is -%% "out of date" with respect to *.InExtension -%% in the sence of "make" - -out_of_date(Dir, In, Out) -> - case file:list_dir(Dir) of - {ok, Files0} -> - Files1 = filter(fun(F) -> - suffix(In, F) - end, Files0), - Files2 = map(fun(F) -> - sublist(F, 1, - length(F)-length(In)) - end, Files1), - filter(fun(F) -> update(F, In, Out) end,Files2); - _ -> - [] - end. - -%% +type update(string(), string(), string()) -> bool(). - -update(File, In, Out) -> - InFile = File ++ In, - OutFile = File ++ Out, - case is_file(OutFile) of - true -> - case writeable(OutFile) of - true -> - outofdate(InFile, OutFile); - false -> - %% can't write so we can't update - false - end; - false -> - %% doesn't exist - true - end. - -%% +type is_file(string()) -> bool(). - -is_file(File) -> - case file:file_info(File) of - {ok, _} -> - true; - _ -> - false - end. - -%% +type outofdate(string(), string()) -> bool(). - -outofdate(In, Out) -> - case {last_modified(In), last_modified(Out)} of - {T1, T2} when T1 > T2 -> - true; - _ -> - false - end. - -%% +type last_modified(string()) -> {int(), int(),int(), int(),int(), int()} -%% | 'EXIT'({last_modified, string()}). - -last_modified(F) -> - case file:file_info(F) of - {ok, {_, _, _, _, Time, _, _}} -> - Time; - _ -> - exit({last_modified, F}) - end. - -%% +type writeable(string()) -> bool(). - -writeable(F) -> - case file:file_info(F) of - {ok, {_,_,read_write,_,_,_,_}} -> true; - {ok, {_,_,write ,_,_,_,_}} -> true; - _ -> false - end. - diff --git a/tests/erlang/examples-2.0/ftp_client.erl b/tests/erlang/examples-2.0/ftp_client.erl deleted file mode 100644 index 30506e6..0000000 --- a/tests/erlang/examples-2.0/ftp_client.erl +++ /dev/null @@ -1,52 +0,0 @@ --module(ftp_client). - --doc([{author, joe}, - {title, "FTP client in pure Erlang -- i.e. an FTP client as it might -have been written, i.e. not according to RFC 959"}, - {keywords,[ftp, client]}, - {date, 981014}]). - --export([connect/3, pwd/1, cd/2, ls/1, put/2, get/2, lcd/1, lpwd/0, lls/0, - quit/1]). - -connect(Host, User, Password) -> - {ftp_server, Host} ! {connect,self(),User,Password}, - receive - {ftp_server, Reply} -> Reply; - Other -> Other - after 10000 -> - timeout - end. - -%S tag1 -pwd(Handle) -> remote(Handle, pwd). -cd(Handle, Dir) -> remote(Handle, {cd, Dir}). -ls(Handle) -> remote(Handle, ls). -get(Handle, File) -> remote(Handle, {get, File}). -quit(Handle) -> remote(Handle, quit). -%E tag1 - -%S tag2 -lcd(Dir) -> file:set_cwd(Dir), lpwd(). -lpwd() -> cwd(). -lls() -> element(2, file:list_dir(cwd())). -%E tag2 - -cwd() -> element(2, file:get_cwd()). - -remote(Handle, Op) -> - Handle ! {self(), Op}, - receive - {ftp_server, Any} -> - Any - after 1000 -> - timeout - end. - -put(Handle, File) -> - case file:read_file(File) of - {ok, Contents} -> - remote(Handle, {put, File, Contents}); - Other -> - Other - end. diff --git a/tests/erlang/examples-2.0/ftp_server.erl b/tests/erlang/examples-2.0/ftp_server.erl deleted file mode 100644 index 98d2f4f..0000000 --- a/tests/erlang/examples-2.0/ftp_server.erl +++ /dev/null @@ -1,123 +0,0 @@ --module(ftp_server). - -%% Look in ~tony/erlang/ftpd/ftpd.erl -%% For filename stuff - --doc([{author, joe}, - {title, "FTP server in pure Erlang -- i.e. an FTP server as it -might have been written, i.e. not according to RFC 959"}, - {keywords,[ftp, server]}, - {date, 981014}]). - --compile(export_all). - --export([start/0, internal/0, handler/1]). --import(lists, [member/2, reverse/1]). - -start() -> - case (catch register(ftp_server, - spawn(?MODULE, internal, []))) of - {'EXIT', _} -> - already_started; - Pid -> - ok - end. - -internal() -> - case file:consult("users") of - {ok, Users} -> - process_flag(trap_exit, true), - loop(Users, 0); - _ -> - exit(no_users_allowed) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -loop(Users, N) -> - receive - {connect, Pid, User, Password} -> - io:format("connection request from:~p ~p ~p~n", - [Pid, User, Password]), - case member({User, Password}, Users) of - true -> - Max = max_connections(), - if - N > Max -> - Pid ! {ftp_server, - {error, too_many_connections}}, - loop(Users, N); - true -> - New = spawn_link(?MODULE, handler, [Pid]), - Pid ! {ftp_server, {ok, New}}, - loop(Users, N + 1) - end; - false -> - Pid ! {ftp_server, {error, rejected}}, - loop(Users, N) - end; - {'EXIT', Pid} -> - io:format("Handler ~p died~n", [Pid]), - loop(Users, lists:max(N-1, 0)); - Any -> - io:format("received:~p~n",[Any]), - loop(Users, N) - end. - -handler(Pid) -> - receive - {Pid, quit} -> - Pid ! {ftp_server, ok}; - {Pid, Op} -> - io:format("got:~p ~p~n",[Pid, Op]), - Pid ! {ftp_server, do_op(Op)}, - handler(Pid) - end. - -do_op({cd, Dir}) -> file:set_cwd(Dir), cwd(); -do_op(ls) -> element(2, file:list_dir(cwd())); -do_op(pwd) -> cwd(); -do_op({get_file, File}) -> file:read_file(File). - -max_connections() -> 10. - -cwd() -> element(2, file:get_cwd()). - -%% This was taken from Tony - -%% -%% Compose file/directory names -%% -rel_name(Name, Wd) -> - case filename:pathtype(Name) of - relative -> - rel_path(filename:join(Wd, Name)); - absolute -> - rel_path(Name); - volumerelative -> - rel_path(filename:join(Wd,Name)) - end. -%% -%% We sometime need a simulated root, then call abs_name -%% -abs_name(Name) -> - filename:join("/", Name). - -%% -%% rel_path returns a relative path i.e remove -%% and root or volume relative start components -%% -rel_path(Path) -> - rel_path(filename:split(Path),[]). - -%% remove absolute or volume relative stuff -rel_path([Root|Path], RP) -> - case filename:pathtype(Root) of - relative -> rpath(Path, [Root|RP]); - _ -> rpath(Path, RP) - end. - -rpath([".."|P], [_|RP]) -> rpath(P, RP); -rpath(["."|P], RP) -> rpath(P, RP); -rpath([F|P], RP) -> rpath(P, [F|RP]); -rpath([],[]) -> ""; -rpath([], RP) -> filename:join(reverse(RP)). diff --git a/tests/erlang/examples-2.0/leex.erl b/tests/erlang/examples-2.0/leex.erl deleted file mode 100644 index ae1cc89..0000000 --- a/tests/erlang/examples-2.0/leex.erl +++ /dev/null @@ -1,591 +0,0 @@ -%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE -%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE -%% AND BETTER INTERFACE - BE WARNED -%% PLEASE REPORT ALL BUGS TO THE AUTHOR. - -%% Copyright (C) 1996, Ellemtel Telecommunications Systems Laboratories -%% File : leex.erl -%% Author : Robert Virding (rv@cslab.ericsson.se) -%% Purpose : A Lexical Analyser Generator for Erlang. - -%% Most of the algorithms used here are taken pretty much as -%% described in the "Dragon Book" by Aho, Sethi and Ullman. Some -%% completing details were taken from "Compiler Design in C" by -%% Hollub. - --module(leex). - --doc([{author,'Robert Virding'}, - {title,"A Lexical Analyser Generator for Erlang"}, - {keywords, [lex]}, - {date,981012}]). - --copyright('Copyright (c) 1996 Ericsson Telecommunications AB'). - --author('rv@cslab.ericsson.se'). - --export([gen/2,format_error/1]). - --import(lists, [member/2,reverse/1,seq/2,keymember/3,keysearch/3,keysort/2, - foreach/2]). --import(ordsets, [is_element/2,add_element/2,union/2,subtract/2]). - -%%-compile([export_all]). - --record(nfa_state, {no,edges=[],accept=noaccept}). --record(dfa_state, {no,nfa=[],trans=[],accept=noaccept}). - -gen(In, Out) -> - InFile = lists:concat([In,".xrl"]), - OutFile = lists:concat([Out,".erl"]), - case parse_file(InFile) of - {ok,REAs,Actions,Code} -> - %% io:fwrite("REAs = ~p\n", [REAs]), - %% io:fwrite("Actions = ~p\n", [Actions]), - {NFA,NF} = build_combined_nfa(REAs), - io:fwrite("NFA contains ~w states, ", [size(NFA)]), - %% io:fwrite("NFA = ~p~n ", [NFA]), - {DFA0,DF0} = build_dfa(NFA, NF), - io:fwrite("DFA contains ~w states, ", [length(DFA0)]), - %% io:fwrite("DFA = ~p~n ", [DFA0]), - {DFA,DF} = minimise_dfa(DFA0, DF0), - io:fwrite("minimised to ~w states.~n", [length(DFA)]), - out_file(OutFile, Out, DFA, DF, Actions, Code); - {error,Error} -> - io:put_chars([$\n,gcc_error(InFile, Error),$\n]), - error - end. - -format_error({open,F}) -> ["error opening ",io_lib:write_string(F)]; -format_error(missing_rules) -> "missing rules"; -format_error(bad_rule) -> "bad rule"; -format_error({regexp,E}) -> ["bad regexp `",regexp:format_error(E),"'"]; -format_error({after_regexp,S}) -> - ["bad code after regexp ",io_lib:write_string(S)]. - -gcc_error(File, {Line,Mod,Error}) -> - io_lib:format("~s:~w: ~s", [File,Line,apply(Mod, format_error, [Error])]); -gcc_error(File, {Mod,Error}) -> - io_lib:format("~s: ~s", [File,apply(Mod, format_error, [Error])]). - -%% parse_file(InFile) -> {[REA],[Action],Code} | {error,Error} -%% when -%% REA = {RegExp,ActionNo}; -%% Action = {ActionNo,ActionString}; -%% Code = [char()]. -%% -%% Read and parse the file InFile. -%% After each section of the file has been parsed we directly call the -%% next section. This is done when we detect a line we don't recognise -%% in the current section. The file format is very simple and Erlang -%% token based, we allow empty lines and Erlang style comments. - -parse_file(InFile) -> - case file:open(InFile, read) of - {ok,Ifile} -> - io:fwrite("Parsing file ~s, ", [InFile]), - case parse_head(Ifile) of - {ok,REAs,Actions,Code} -> - io:fwrite("contained ~w rules.~n", [length(REAs)]), - file:close(Ifile), - {ok,REAs,Actions,Code}; - Error -> - file:close(Ifile), - Error - end; - {error,R} -> - {error,{leex,{open,InFile}}} - end. - -%% parse_head(File) -%% Parse the head of the file. - -parse_head(Ifile) -> - parse_defs(Ifile, nextline(Ifile, 0)). - -%% parse_defs(File, Line) -%% Parse the macro definition section of a file. Allow no definitions. - -parse_defs(Ifile, {ok,[$D,$e,$f,$i,$n,$i,$t,$i,$o,$n,$s,$.|_Rest],L}) -> - parse_defs(Ifile, nextline(Ifile, L), []); -parse_defs(Ifile, Line) -> - parse_rules(Ifile, Line, []). - -parse_defs(Ifile, {ok,Chars,L}, Ms) -> - case string:tokens(Chars, " \t\n") of - [Name,"=",Def] -> - parse_defs(Ifile, nextline(Ifile, L), [{Name,Def}|Ms]); - Other -> - parse_rules(Ifile, {ok,Chars,L}, Ms) - end; -parse_defs(Ifile, Line, Ms) -> - parse_rules(Ifile, Line, Ms). - -%% parse_rules(File, Line, Macros) -%% Parse the RE rules section of the file. This must exist. - -parse_rules(Ifile, {ok,[$R,$u,$l,$e,$s,$.|_Rest],L}, Ms) -> - parse_rules(Ifile, nextline(Ifile, L), Ms, [], [], 0); -parse_rules(Ifile, {ok,Other,L}, Ms) -> - {error,{L,leex,missing_rules}}; -parse_rules(Ifile, {eof,L}, Ms) -> - {error,{L,leex,missing_rules}}. - -collect_rule(Ifile, Chars, L0) -> - {match,St,Len} = regexp:first_match(Chars, "[^ \t]+"), - %% io:fwrite("RE = ~p~n", [string:substr(Chars, St, Len)]), - case collect_rule(Ifile, string:substr(Chars, St+Len), L0, []) of - {ok,[{':',Lc}|Toks],L1} -> {ok,string:substr(Chars, St, Len),Toks,L1}; - {ok,Toks,L1} -> {error,{L0,leex,bad_rule}}; - {eof,L1} -> {error,{L1,leex,bad_rule}}; - {error,E,L1} -> {error,E} - end. - -collect_rule(Ifile, Chars, L0, Cont0) -> - case erl_scan:tokens(Cont0, Chars, L0) of - {done,{ok,Toks,L1},Rest} -> {ok,Toks,L0}; - {done,{eof,L1},Rest} -> {eof,L0}; - {done,{error,E,L1},Rest} -> {error,E,L0}; - {more,Cont1} -> - collect_rule(Ifile, io:get_line(Ifile, leex), L0+1, Cont1) - end. - -parse_rules(Ifile, {ok,[$E,$r,$l,$a,$n,$g,$ ,$c,$o,$d,$e,$.|_Rest],L}, - Ms, REAs, As, N) -> - %% Must be careful to put rules in correct order! - parse_code(Ifile, L, reverse(REAs), reverse(As)); -parse_rules(Ifile, {ok,Chars,L0}, Ms, REAs, As, N) -> - %% io:fwrite("~w: ~p~n", [L0,Chars]), - case collect_rule(Ifile, Chars, L0) of - {ok,Re,Atoks,L1} -> - case parse_rule(Re, L0, Atoks, Ms, N) of - {ok,REA,A} -> - parse_rules(Ifile, nextline(Ifile, L1), Ms, - [REA|REAs], [A|As], N+1); - {error,E} -> {error,E} - end; - {error,E} -> {error,E} - end; -parse_rules(Ifile, {eof,Line}, Ms, REAs, As, N) -> - %% Must be careful to put rules in correct order! - {ok,reverse(REAs),reverse(As),[]}. - -%% parse_rule(RegExpString, RegExpLine, ActionTokens, Macros, Counter) -%% Parse one regexp after performing macro substition. - -parse_rule(S, Line, [{dot,Ld}], Ms, N) -> - case parse_rule_regexp(S, Ms) of - {ok,R} -> - {ok,{R,N},{N,empty_action}}; - {error,E} -> - {error,{Line,leex,{regexp,E}}} - end; -parse_rule(S, Line, Atoks, Ms, N) -> - case parse_rule_regexp(S, Ms) of - {ok,R} -> - case erl_parse:parse_exprs(Atoks) of - {ok,Aes} -> - YYtext = keymember('YYtext', 3, Atoks), - {ok,{R,N},{N,Aes,YYtext}}; - {error,E} -> - {error,{Line,leex,{after_regexp,S}}} - end; - {error,E} -> - {error,{Line,leex,{regexp,E}}} - end. - -parse_rule_regexp(RE0, [{M,Exp}|Ms]) -> - case regexp:gsub(RE0, "{" ++ M ++ "}", Exp) of - {ok,RE,N} -> parse_rule_regexp(RE, Ms); - {error,E} -> parse_rule_regexp(RE0, Ms) - end; -parse_rule_regexp(RE, []) -> - %% io:fwrite("RE = ~p~n", [RE]), - regexp:parse(RE). - -%% parse_code(File, Line, REAs, Actions) -%% Parse the code section of the file. - -parse_code(Ifile, Line, REAs, As) -> - {ok,REAs,As,io:get_chars(Ifile, leex, 102400)}. - -%% nextline(InputFile, PrevLineNo) -> {ok,Chars,LineNo} | {eof,LineNo}. -%% Get the next line skipping comment lines and blank lines. - -nextline(Ifile, L) -> - case io:get_line(Ifile, leex) of - eof -> {eof,L}; - Chars -> - case skip(Chars, " \t\n") of - [$%|_Rest] -> nextline(Ifile, L+1); - [] -> nextline(Ifile, L+1); - Other -> {ok,Chars,L+1} - end - end. - -%% skip(Str, Cs) -> lists:dropwhile(fun (C) -> member(C, Cs) end, Str). - -skip([C|Str], Cs) -> - case member(C, Cs) of - true -> skip(Str, Cs); - false -> [C|Str] - end; -skip([], Cs) -> []. - -%% build_combined_nfa(RegExpActionList) -> {NFA,FirstState}. Build -%% the combined NFA using Thompson's construction straight out of the -%% book. Build the separate NFAs in the same order as the rules so -%% that the accepting have ascending states have ascending state -%% numbers. Start numbering the states from 1 as we put the states -%% in a tuple with the state number as the index. - -build_combined_nfa(REAs) -> - {NFA0,Firsts,Free} = build_nfa_list(REAs, [], [], 1), - F = #nfa_state{no=Free,edges=epsilon_trans(Firsts)}, - {list_to_tuple(keysort(#nfa_state.no, [F|NFA0])),Free}. - -build_nfa_list([{RE,Action}|REAs], NFA0, Firsts, Free0) -> - {NFA1,Free1,First} = build_nfa(RE, Free0, Action), - build_nfa_list(REAs, NFA1 ++ NFA0, [First|Firsts], Free1); -build_nfa_list([], NFA, Firsts, Free) -> - {NFA,reverse(Firsts),Free}. - -epsilon_trans(Firsts) -> [ {epsilon,F} || F <- Firsts ]. - -%% {NFA,NextFreeState,FirstState} = build_nfa(RegExp, FreeState, Action) -%% When building the NFA states for a ??? we don't build the end -%% state, just allocate a State for it and return this state -%% number. This allows us to avoid building unnecessary states for -%% concatenation which would then have to be removed by overwriting -%% an existing state. - -build_nfa(RE, FreeState, Action) -> - {NFA,N,Es} = build_nfa(RE, FreeState+1, FreeState, []), - {[#nfa_state{no=Es,accept={accept,Action}}|NFA],N,FreeState}. - -%% build_nfa(RegExp, NextState, FirstState, NFA) -> {NFA,NextState,EndState}. -%% The NFA is a list of nfa_state is no predefined order. The state -%% number of the returned EndState is already allocated! - -build_nfa({'or',RE1,RE2}, N0, Fs, NFA0) -> - {NFA1,N1,Es1} = build_nfa(RE1, N0+1, N0, NFA0), - {NFA2,N2,Es2} = build_nfa(RE2, N1+1, N1, NFA1), - Es = N2, - {[#nfa_state{no=Fs,edges=[{epsilon,N0},{epsilon,N1}]}, - #nfa_state{no=Es1,edges=[{epsilon,Es}]}, - #nfa_state{no=Es2,edges=[{epsilon,Es}]}|NFA2], - N2+1,Es}; -build_nfa({concat,RE1, RE2}, N0, Fs, NFA0) -> - {NFA1,N1,Es1} = build_nfa(RE1, N0, Fs, NFA0), - {NFA2,N2,Es2} = build_nfa(RE2, N1, Es1, NFA1), - {NFA2,N2,Es2}; -build_nfa({kclosure,RE}, N0, Fs, NFA0) -> - {NFA1,N1,Es1} = build_nfa(RE, N0+1, N0, NFA0), - Es = N1, - {[#nfa_state{no=Fs,edges=[{epsilon,N0},{epsilon,Es}]}, - #nfa_state{no=Es1,edges=[{epsilon,N0},{epsilon,Es}]}|NFA1], - N1+1,Es}; -build_nfa({pclosure,RE}, N0, Fs, NFA0) -> - {NFA1,N1,Es1} = build_nfa(RE, N0+1, N0, NFA0), - Es = N1, - {[#nfa_state{no=Fs,edges=[{epsilon,N0}]}, - #nfa_state{no=Es1,edges=[{epsilon,N0},{epsilon,Es}]}|NFA1], - N1+1,Es}; -build_nfa({optional,RE}, N0, Fs, NFA0) -> - {NFA1,N1,Es1} = build_nfa(RE, N0+1, N0, NFA0), - Es = N1, - {[#nfa_state{no=Fs,edges=[{epsilon,N0},{epsilon,Es}]}, - #nfa_state{no=Es1,edges=[{epsilon,Es}]}|NFA1], - N1+1,Es}; -build_nfa({char_class,Cc}, N, Fs, NFA) -> - {[#nfa_state{no=Fs,edges=[{char_class(Cc),N}]}|NFA],N+1,N}; -build_nfa({comp_class,Cc}, N, Fs, NFA) -> - {[#nfa_state{no=Fs,edges=[{comp_class(Cc),N}]}|NFA],N+1,N}; -build_nfa(C, N, Fs, NFA) when integer(C) -> - {[#nfa_state{no=Fs,edges=[{[C],N}]}|NFA],N+1,N}. - -char_class(Cc) -> - lists:foldl(fun ({C1,C2}, Set) -> union(seq(C1, C2), Set); - (C, Set) -> add_element(C, Set) end, [], Cc). - -comp_class(Cc) -> subtract(seq(0, 255), char_class(Cc)). - -%% build_dfa(NFA, NfaFirstState) -> {DFA,DfaFirstState}. -%% Build a DFA from an NFA using "subset construction". The major -%% difference from the book is that we keep the marked and unmarked -%% DFA states in seperate lists. New DFA states are added to the -%% unmarked list and states are marked by moving them to the marked -%% list. We assume that the NFA accepting state numbers are in -%% ascending order for the rules and use ordsets to keep this order. - -build_dfa(NFA, Nf) -> - D = #dfa_state{no=0,nfa=eclosure([Nf], NFA)}, - {build_dfa([D], 1, [], NFA),0}. - -%% build_dfa([UnMarked], NextState, [Marked], NFA) -> DFA. -%% Traverse the unmarked states. Temporarily add the current unmarked -%% state to the marked list before calculating translation, this is -%% to avoid adding too many duplicate states. Add it properly to the -%% marked list afterwards with correct translations. - -build_dfa([U|Us0], N0, Ms, NFA) -> - {Ts,Us1,N1} = build_dfa(255, U#dfa_state.nfa, Us0, N0, [], [U|Ms], NFA), - M = U#dfa_state{trans=Ts,accept=accept(U#dfa_state.nfa, NFA)}, - build_dfa(Us1, N1, [M|Ms], NFA); -build_dfa([], N, Ms, NFA) -> Ms. - -%% build_dfa(Char, [NfaState], [Unmarked], NextState, [Transition], [Marked], NFA) -> -%% [Marked]. -%% Foreach NFA state set calculate the legal translations. N.B. must -%% search *BOTH* the unmarked and marked lists to check if DFA state -%% already exists. By test characters downwards and prepending -%% transitions we get the transition lists in ascending order. - -build_dfa(C, Set, Us, N, Ts, Ms, NFA) when C >= 0 -> - case eclosure(move(Set, C, NFA), NFA) of - S when S /= [] -> - case keysearch(S, #dfa_state.nfa, Us) of - {value,#dfa_state{no=T}} -> - build_dfa(C-1, Set, Us, N, [{C,T}|Ts], Ms, NFA); - false -> - case keysearch(S, #dfa_state.nfa, Ms) of - {value,#dfa_state{no=T}} -> - build_dfa(C-1, Set, Us, N, [{C,T}|Ts], Ms, NFA); - false -> - U = #dfa_state{no=N,nfa=S}, - build_dfa(C-1, Set, [U|Us], N+1, [{C,N}|Ts], Ms, NFA) - end - end; - [] -> - build_dfa(C-1, Set, Us, N, Ts, Ms, NFA) - end; -build_dfa(-1, Set, Us, N, Ts, Ms, NFA) -> - {Ts,Us,N}. - -%% eclosure([State], NFA) -> [State]. -%% move([State], Char, NFA) -> [State]. -%% These are straight out of the book. As eclosure uses ordsets then -%% the generated state sets are in ascending order. - -eclosure(Sts, NFA) -> eclosure(Sts, NFA, []). - -eclosure([St|Sts], NFA, Ec) -> - #nfa_state{edges=Es} = element(St, NFA), - eclosure([ N || {epsilon,N} <- Es, - nnot(is_element(N, Ec)) ] ++ Sts, - NFA, add_element(St, Ec)); -eclosure([], NFA, Ec) -> Ec. - -nnot(true) -> false; -nnot(false) -> true. - -move(Sts, C, NFA) -> - [St || N <- Sts, - {C1,St} <- (element(N, NFA))#nfa_state.edges, - list(C1), - member(C, C1) ]. - -%% accept([State], NFA) -> {accept,A} | noaccept. -%% Scan down the state list until we find an accepting state. - -accept([St|Sts], NFA) -> - case element(St, NFA) of - #nfa_state{accept={accept,A}} -> {accept,A}; - #nfa_state{accept=noaccept} -> accept(Sts, NFA) - end; -accept([], NFA) -> noaccept. - -%% minimise_dfa(DFA, DfaFirst) -> {DFA,DfaFirst}. -%% Minimise the DFA by removing equivalent states. We consider a -%% state if both the transitions and the their accept state is the -%% same. First repeatedly run throught the DFA state list removing -%% equivalent states and updating remaining transitions with -%% remaining equivalent state numbers. When no more reductions are -%% possible then pack the remaining state numbers to get consecutive -%% states. - -minimise_dfa(DFA0, Df0) -> - case min_dfa(DFA0) of - {DFA1,[]} -> %No reduction! - {DFA2,Rs} = pack_dfa(DFA1), - {min_update(DFA2, Rs),min_use(Df0, Rs)}; - {DFA1,Rs} -> - minimise_dfa(min_update(DFA1, Rs), min_use(Df0, Rs)) - end. - -min_dfa(DFA) -> min_dfa(DFA, [], []). - -min_dfa([D|DFA0], Rs0, MDFA) -> - {DFA1,Rs1} = min_delete(DFA0, D#dfa_state.trans, D#dfa_state.accept, - D#dfa_state.no, Rs0, []), - min_dfa(DFA1, Rs1, [D|MDFA]); -min_dfa([], Rs, MDFA) -> {MDFA,Rs}. - -min_delete([#dfa_state{no=N,trans=T,accept=A}|DFA], T, A, NewN, Rs, MDFA) -> - min_delete(DFA, T, A, NewN, [{N,NewN}|Rs], MDFA); -min_delete([D|DFA], T, A, NewN, Rs, MDFA) -> - min_delete(DFA, T, A, NewN, Rs, [D|MDFA]); -min_delete([], T, A, NewN, Rs, MDFA) -> {MDFA,Rs}. - -min_update(DFA, Rs) -> - [ D#dfa_state{trans=min_update_trans(D#dfa_state.trans, Rs)} || D <- DFA ]. - -min_update_trans(Tr, Rs) -> - [ {C,min_use(S, Rs)} || {C,S} <- Tr ]. - -min_use(Old, [{Old,New}|Reds]) -> New; -min_use(Old, [R|Reds]) -> min_use(Old, Reds); -min_use(Old, []) -> Old. - -pack_dfa(DFA) -> pack_dfa(DFA, 0, [], []). - -pack_dfa([D|DFA], NewN, Rs, PDFA) -> - pack_dfa(DFA, NewN+1, [{D#dfa_state.no,NewN}|Rs], [D#dfa_state{no=NewN}|PDFA]); -pack_dfa([], NewN, Rs, PDFA) -> {PDFA,Rs}. - -%% out_file(FileName, Module, DFA, DfaStart, [Action], Code) -> ok | error. - -out_file(OutFile, Out, DFA, DF, Actions, Code) -> - io:fwrite("Writing file ~s, ", [OutFile]), - case file:path_open([".", [code:lib_dir(),"/tools/include"]], - "leex.hrl", read) of - {ok,Ifile,Iname} -> - case file:open(OutFile, write) of - {ok,Ofile} -> - out_file(Ifile, Ofile, Out, DFA, DF, Actions, Code), - file:close(Ifile), - file:close(Ofile), - io:fwrite("ok~n"), - ok; - {error,E} -> - file:close(Ifile), - io:fwrite("open error~n"), - error - end; - {error,R} -> - io:fwrite("open error~n"), - error - end. - -%% out_file(IncFile, OutFile, DFA, DfaStart, Actions, Code) -> ok. -%% Copy the include file line by line substituting special lines with -%% generated code. We cheat by only looking at the first 5 -%% characters. - -out_file(Ifile, Ofile, Out, DFA, DF, Actions, Code) -> - case io:get_line(Ifile, leex) of - eof -> ok; - Line -> - case string:substr(Line, 1, 5) of - "##mod" -> io:fwrite(Ofile, "-module(~w).~n", [Out]); - "##cod" -> io:put_chars(Ofile, Code); - "##dfa" -> out_dfa(Ofile, DFA, DF); - "##act" -> out_actions(Ofile, Actions); - Other -> io:put_chars(Ofile, Line) - end, - out_file(Ifile, Ofile, Out, DFA, DF, Actions, Code) - end. - -out_dfa(File, DFA, DF) -> - io:fwrite(File, "yystate() -> ~w.~n~n", [DF]), - foreach(fun (S) -> out_trans(File, S) end, DFA), - io:fwrite(File, "yystate(S, Ics, Line, Tlen, Action, Alen) ->~n", []), - io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,S}.~n~n", []). - -out_trans(File, #dfa_state{no=N,trans=[],accept={accept,A}}) -> - io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]), - io:fwrite(File, " {~w,Tlen,Ics,Line};~n", [A]); -out_trans(File, #dfa_state{no=N,trans=Tr,accept={accept,A}}) -> - foreach(fun (T) -> out_tran(File, N, A, T) end, pack_trans(Tr)), - io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]), - io:fwrite(File, " {~w,Tlen,Ics,Line,~w};~n", [A,N]); -out_trans(File, #dfa_state{no=N,trans=Tr,accept=noaccept}) -> - foreach(fun (T) -> out_tran(File, N, T) end, pack_trans(Tr)), - io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]), - io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,~w};~n", [N]). - -out_tran(File, N, A, {{Cf,Cl},S}) -> - out_head(File, N, io_lib:write_char(Cf), io_lib:write_char(Cl)), - out_body(File, S, "Line", "C", A); -out_tran(File, N, A, {$\n,S}) -> - out_head(File, N, "$\\n"), - out_body(File, S, "Line+1", "$\\n", A); -out_tran(File, N, A, {C,S}) -> - Char = io_lib:write_char(C), - out_head(File, N, Char), - out_body(File, S, "Line", Char, A). - -out_tran(File, N, {{Cf,Cl},S}) -> - out_head(File, N, io_lib:write_char(Cf), io_lib:write_char(Cl)), - out_body(File, S, "Line", "C"); -out_tran(File, N, {$\n,S}) -> - out_head(File, N, "$\\n"), - out_body(File, S, "Line+1", "$\\n"); -out_tran(File, N, {C,S}) -> - Char = io_lib:write_char(C), - out_head(File, N, Char), - out_body(File, S, "Line", Char). - -out_head(File, State, Char) -> - io:fwrite(File, "yystate(~w, [~s|Ics], Line, Tlen, Action, Alen) ->\n", - [State,Char]). - -out_head(File, State, Min, Max) -> - io:fwrite(File, "yystate(~w, [C|Ics], Line, Tlen, Action, Alen) when C >= ~s, C =< ~s ->\n", - [State,Min,Max]). - -out_body(File, Next, Line, C, Action) -> - io:fwrite(File, " yystate(~w, Ics, ~s, Tlen+1, ~w, Tlen);\n", - [Next,Line,Action]). - -out_body(File, Next, Line, C) -> - io:fwrite(File, " yystate(~w, Ics, ~s, Tlen+1, Action, Alen);\n", - [Next,Line]). - -%% pack_tran([{Char,State}]) -> [{Crange,State}] when -%% Crange = {Char,Char} | Char. -%% Pack the translation table into something more suitable for -%% generating code. Ranges of characters with the same State are -%% packed together, while solitary characters are left "as is". We -%% KNOW how the pattern matching compiler works so solitary -%% characters are stored before ranges. We do this using ordsets for -%% for the packed table. Always break out $\n as solitary character. - -pack_trans([{C,S}|Tr]) -> pack_trans(Tr, C, C, S, []); -pack_trans([]) -> []. - -pack_trans([{$\n,S1}|Tr], Cf, Cl, S, Pt) -> - pack_trans(Cf, Cl, S, add_element({$\n,S1}, pack_trans(Tr))); -pack_trans([{C,S}|Tr], Cf, Cl, S, Pt) when C == Cl + 1 -> - pack_trans(Tr, Cf, C, S, Pt); -pack_trans([{C,S1}|Tr], Cf, Cl, S, Pt) -> - pack_trans(Tr, C, C, S1, pack_trans(Cf, Cl, S, Pt)); -pack_trans([], Cf, Cl, S, Pt) -> pack_trans(Cf, Cl, S, Pt). - -pack_trans(Cf, Cf, S, Pt) -> add_element({Cf,S}, Pt); -pack_trans(Cf, Cl, S, Pt) when Cl == Cf + 1 -> - add_element({Cf,S}, add_element({Cl,S}, Pt)); -pack_trans(Cf, Cl, S, Pt) -> add_element({{Cf,Cl},S}, Pt). - -out_actions(File, As) -> - foreach(fun (A) -> out_action(File, A) end, As), - io:fwrite(File, "yyaction(_, _, _, _) -> error.~n", []). - -out_action(File, {A,empty_action}) -> - io:fwrite(File, "yyaction(~w, YYlen, YYtcs, YYline) -> skip_token;~n", [A]); -out_action(File, {A,Code,YYtext}) -> - io:fwrite(File, "yyaction(~w, YYlen, YYtcs, YYline) ->~n", [A]), - if - YYtext == true -> - io:fwrite(File, " YYtext = yypre(YYtcs, YYlen),~n", []); - YYtext == false -> ok - end, - io:fwrite(File, " ~s;~n", [erl_pp:exprs(Code, 4, none)]). - - - - - - - - diff --git a/tests/erlang/examples-2.0/leex.hrl b/tests/erlang/examples-2.0/leex.hrl deleted file mode 100644 index e83fe4b..0000000 --- a/tests/erlang/examples-2.0/leex.hrl +++ /dev/null @@ -1,200 +0,0 @@ -%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE -%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE -%% AND BETTER INTERFACE - BE WARNED -%% PLEASE REPORT ALL BUGS TO THE AUTHOR. - -##module - --export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). --export([format_error/1]). - -%% User code. This is placed here to allow extra attributes. -##code - -format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; -format_error({user,S}) -> S. - -string(String) -> string(String, 1). - -string(String, Line) -> string(String, Line, String, []). - -%% string(InChars, Line, TokenChars, Tokens) -> -%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. - -string([], L, [], Ts) -> %No partial tokens! - {ok,yyrev(Ts),L}; -string(Ics0, L0, Tcs, Ts) -> - case yystate(yystate(), Ics0, L0, 0, reject, 0) of - {A,Alen,Ics1,L1} -> %Accepting end state - string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); - {A,Alen,Ics1,L1,S1} -> %After an accepting state - string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); - {reject,Alen,Tlen,Ics1,L1,S1} -> - {error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; - {A,Alen,Tlen,Ics1,L1,S1} -> - string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L1), Ts) - end. - -%% string_cont(RestChars, Line, Token, Tokens) -%% Test for and remove the end token wrapper. - -string_cont(Rest, Line, {token,T}, Ts) -> - string(Rest, Line, Rest, [T|Ts]); -string_cont(Rest, Line, {end_token,T}, Ts) -> - string(Rest, Line, Rest, [T|Ts]); -string_cont(Rest, Line, skip_token, Ts) -> - string(Rest, Line, Rest, Ts); -string_cont(Rest, Line, {error,S}, Ts) -> - {error,{Line,?MODULE,{user,S}},Line}. - -%% token(Continuation, Chars, Line) -> -%% {more,Continuation} | {done,ReturnVal,RestChars}. -%% Must be careful when re-entering to append the latest characters to the -%% after characters in an accept. - -token(Cont, Chars) -> token(Cont, Chars, 1). - -token([], Chars, Line) -> - token(Chars, Line, yystate(), Chars, 0, reject, 0); -token({Line,State,Tcs,Tlen,Action,Alen}, Chars, _) -> - token(Chars, Line, State, Tcs ++ Chars, Tlen, Action, Alen). - -%% token(InChars, Line, State, TokenChars, TokenLen, Accept) -> -%% {more,Continuation} | {done,ReturnVal,RestChars}. - -token(Ics0, L0, S0, Tcs, Tlen0, A0, Alen0) -> - case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of - {A1,Alen1,Ics1,L1} -> %Accepting end state - token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); - {A1,Alen1,[],L1,S1} -> %After an accepting state - {more,{L1,S1,Tcs,Alen1,A1,Alen1}}; - {A1,Alen1,Ics1,L1,S1} -> - token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); - {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state - {more,{L1,S1,Tcs,Tlen1,A1,Alen1}}; - {reject,Alen1,Tlen1,eof,L1,S1} -> - {done,{eof,L1},[]}; - {reject,Alen1,Tlen1,Ics1,L1,S1} -> - {done,{error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},L1},Ics1}; - {A1,Alen1,Tlen1,Ics1,L1,S1} -> - token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1)) - end. - -%% tokens_cont(RestChars, Line, Token) -%% Test if we have detected the end token, if so return done else continue. - -token_cont(Rest, Line, {token,T}) -> - {done,{ok,T,Line},Rest}; -token_cont(Rest, Line, {end_token,T}) -> - {done,{ok,T,Line},Rest}; -token_cont(Rest, Line, skip_token) -> - token(Rest, Line, yystate(), Rest, 0, reject, 0); -token_cont(Rest, Line, {error,S}) -> - {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. - -%% tokens(Continuation, Chars, Line) -> -%% {more,Continuation} | {done,ReturnVal,RestChars}. -%% Must be careful when re-entering to append the latest characters to the -%% after characters in an accept. - -tokens(Cont, Chars) -> tokens(Cont, Chars, 1). - -tokens([], Chars, Line) -> - tokens(Chars, Line, yystate(), Chars, 0, [], reject, 0); -tokens({tokens,Line,State,Tcs,Tlen,Ts,Action,Alen}, Chars, _) -> - tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Ts, Action, Alen); -tokens({skip_tokens,Line,State,Tcs,Tlen,Error,Action,Alen}, Chars, _) -> - skip_tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Error, Action, Alen). - -%% tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> -%% {more,Continuation} | {done,ReturnVal,RestChars}. - -tokens(Ics0, L0, S0, Tcs, Tlen0, Ts, A0, Alen0) -> - case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of - {A1,Alen1,Ics1,L1} -> %Accepting end state - tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); - {A1,Alen1,[],L1,S1} -> %After an accepting state - {more,{tokens,L1,S1,Tcs,Alen1,Ts,A1,Alen1}}; - {A1,Alen1,Ics1,L1,S1} -> - tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); - {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state - {more,{tokens,L1,S1,Tcs,Tlen1,Ts,A1,Alen1}}; - {reject,Alen1,Tlen1,eof,L1,S1} -> - {done,if Ts == [] -> {eof,L1}; - true -> {ok,yyrev(Ts),L1} end,[]}; - {reject,Alen1,Tlen1,Ics1,L1,S1} -> - skip_tokens(yysuf(Tcs, Tlen1+1), L1, - {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}); - {A1,Alen1,Tlen1,Ics1,L1,S1} -> - tokens_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Ts) - end. - -%% tokens_cont(RestChars, Line, Token, Tokens) -%% Test if we have detected the end token, if so return done else continue. - -tokens_cont(Rest, Line, {token,T}, Ts) -> - tokens(Rest, Line, yystate(), Rest, 0, [T|Ts], reject, 0); -tokens_cont(Rest, Line, {end_token,T}, Ts) -> - {done,{ok,yyrev(Ts, [T]),Line},Rest}; -tokens_cont(Rest, Line, skip_token, Ts) -> - tokens(Rest, Line, yystate(), Rest, 0, Ts, reject, 0); -tokens_cont(Rest, Line, {error,S}, Ts) -> - skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). - -%% token_skip(InChars, Line, Error) -> {done,ReturnVal,RestChars}. -%% Skip tokens until an end token, junk everything and return the error. - -%%skip_tokens(Ics, Line, Error) -> {done,{error,Error,Line},Ics}. - -skip_tokens(Ics, Line, Error) -> - skip_tokens(Ics, Line, yystate(), Ics, 0, Error, reject, 0). - -%% skip_tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> -%% {more,Continuation} | {done,ReturnVal,RestChars}. - -skip_tokens(Ics0, L0, S0, Tcs, Tlen0, Error, A0, Alen0) -> - case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of - {A1,Alen1,Ics1,L1} -> %Accepting end state - skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); - {A1,Alen1,[],L1,S1} -> %After an accepting state - {more,{skip_tokens,L1,S1,Tcs,Alen1,Error,A1,Alen1}}; - {A1,Alen1,Ics1,L1,S1} -> - skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); - {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state - {more,{skip_tokens,L1,S1,Tcs,Tlen1,Error,A1,Alen1}}; - {reject,Alen1,Tlen1,eof,L1,S1} -> - {done,{error,Error,L1},[]}; - {reject,Alen1,Tlen1,Ics1,L1,S1} -> - skip_tokens(yysuf(Tcs, Tlen1), L1, Error); - {A1,Alen1,Tlen1,Ics1,L1,S1} -> - skip_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Error) - end. - -%% skip_cont(RestChars, Line, Token, Error) -%% Test if we have detected the end token, if so return done else continue. - -skip_cont(Rest, Line, {token,T}, Error) -> - skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); -skip_cont(Rest, Line, {end_token,T}, Error) -> - {done,{error,Error,Line},Rest}; -skip_cont(Rest, Line, {error,S}, Error) -> - skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); -skip_cont(Rest, Line, skip_token, Error) -> - skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0). - -yyrev(L) -> yyrev(L, []). - -yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); -yyrev([], Acc) -> Acc. - -yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; -yypre(L, N) -> []. - -yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); -yysuf(L, 0) -> L. - -%% Generated state transition function. -##dfa - -%% Generated action function. -##actions diff --git a/tests/erlang/examples-2.0/lin.erl b/tests/erlang/examples-2.0/lin.erl deleted file mode 100644 index d3a7d45..0000000 --- a/tests/erlang/examples-2.0/lin.erl +++ /dev/null @@ -1,95 +0,0 @@ --module(lin). - --doc([{author,'Joe Armstrong'}, - {title,"Linear algebra utilities."}, - {keywords, [linear,algebra]}, - {date,981103}]). - --export([pow/3, inv/2, solve/2, str2int/1, int2str/1, gcd/2]). - -%% pow(A, B, M) => (A^B) mod M -%% examples pow(9726,3533,11413) = 5761 -%% pow(5971,6597,11413) = 9726 - -pow(A, 1, M) -> - A rem M; -pow(A, 2, M) -> - A*A rem M; -pow(A, B, M) -> - B1 = B div 2, - B2 = B - B1, - %% B2 = B1 or B1+1 - P = pow(A, B1, M), - case B2 of - B1 -> (P*P) rem M; - _ -> (P*P*A) rem M - end. - -%% inv(A, B) = C | no_inverse -%% computes C such that -%% A*C mod B = 1 -%% computes A^-1 mod B -%% examples inv(28, 75) = 67. -%% inv(3533, 11200) = 6597 -%% inv(6597, 11200) = 3533 - -inv(A, B) -> - case solve(A, B) of - {X, Y} -> - if X < 0 -> X + B; - true -> X - end; - _ -> - no_inverse - end. - -%% solve(A, B) => {X, Y} | insoluble -%% solve the linear congruence -%% A * X - B * Y = 1 - -%S tag1 -solve(A, B) -> - case catch s(A,B) of - insoluble -> insoluble; - {X, Y} -> - case A * X - B * Y of - 1 -> {X, Y}; - Other -> error - end - end. - -s(A, 0) -> throw(insoluble); -s(A, 1) -> {0, -1}; -s(A, -1) -> {0, 1}; -s(A, B) -> - K1 = A div B, - K2 = A - K1*B, - {Tmp, X} = s(B, -K2), - {X, K1 * X - Tmp}. -%E tag1 - - -%% converts a string to a base 256 integer -%% converts a base 256 integer to a string - -%S tag2 -str2int(Str) -> str2int(Str, 0). - -str2int([H|T], N) -> str2int(T, N*256+H); -str2int([], N) -> N. - -int2str(N) -> int2str(N, []). - -int2str(N, L) when N =< 0 -> L; -int2str(N, L) -> - N1 = N div 256, - H = N - N1 * 256, - int2str(N1, [H|L]). -%E tag2 - -%% greatest common denominator - -gcd(A, B) when A < B -> gcd(B, A); -gcd(A, 0) -> A; -gcd(A, B) -> - gcd(B, A rem B). diff --git a/tests/erlang/examples-2.0/primes.erl b/tests/erlang/examples-2.0/primes.erl deleted file mode 100644 index 0af3c4e..0000000 --- a/tests/erlang/examples-2.0/primes.erl +++ /dev/null @@ -1,92 +0,0 @@ --module(primes). - --doc([{author,'Joe Armstrong'}, - {title,"Prime number utilities."}, - {keywords, [prime, numbers]}, - {date,981103}]). - --export([make/1, make_prime/1, is_prime/1]). --compile(export_all). - -%% make a prime with at least K decimal digits -%% Here we use 'Bertrand's postulate, is that for every N > 3, -%% there is a prime P satisfying N < P < 2N - 2 -%% This was proved by Tchebychef in 1850 (Erdos improved this proof -%% in 1932) - -%S tag4 - -make_prime(K) when K > 0 -> - new_seed(), - N = make(K), - if N > 3 -> - io:format("Generating a ~w digit prime ",[K]), - MaxTries = N - 3, - P1 = make_prime(MaxTries, N+1), - io:format("~n",[]), - P1; - true -> - make_prime(K) - end. - -make_prime(0, _) -> - exit(impossible); -make_prime(K, P) -> - io:format(".",[]), - case is_prime(P) of - true -> P; - false -> make_prime(K-1, P+1) - end. -%E tag4 - -%% make(N) -> a random integer with N digits. - -%S tag1 -make(N) -> new_seed(), make(N, 0). - -make(0, D) -> D; -make(N, D) -> - make(N-1, D*10 + (random:uniform(10)-1)). -%E tag1 - -%% Fermat's little theorem says that if -%% N is a prime and if A < N then -%% A^N mod N = A - -%S tag3 -is_prime(D) -> - new_seed(), - is_prime(D, 100). - -is_prime(D, Ntests) -> - N = length(integer_to_list(D)) -1, - is_prime(Ntests, D, N). - -is_prime(0, _, _) -> true; -is_prime(Ntest, N, Len) -> - K = random:uniform(Len), - %% A is a random number less than N - A = make(K), - if - A < N -> - case lin:pow(A,N,N) of - A -> is_prime(Ntest-1,N,Len); - _ -> false - end; - true -> - is_prime(Ntest, N, Len) - end. -%E tag3 - -new_seed() -> - {_,_,X} = erlang:now(), - {H,M,S} = time(), - H1 = H * X rem 32767, - M1 = M * X rem 32767, - S1 = S * X rem 32767, - put(random_seed, {H1,M1,S1}). - - - - - diff --git a/tests/erlang/examples-2.0/rsa_key.erl b/tests/erlang/examples-2.0/rsa_key.erl deleted file mode 100644 index 3c72c07..0000000 --- a/tests/erlang/examples-2.0/rsa_key.erl +++ /dev/null @@ -1,56 +0,0 @@ --module(rsa_key). - --export([make_sig/1, make_sig/2]). - -make_sig(Who, Len) when Len > 79 -> - {Public, Private} = make_sig(Len), - file:write_file(Who ++ ".pub", term_to_binary(Public)), - file:write_file(Who ++ ".pri", term_to_binary(Private)), - {keyfilecreated,for,Who}. - -%% The "book" says ... -%% 1. Bob generates two primes p and q -%% 2. Bob computes n = pq and phi(n) = (p-1)*(q-1) -%% 3. Bob chooses a random b(0 < b < phi(n)) such that -%% gcd(b, phi(n)) = 1 -%% 4. Bob computes a = b^(-1) mod phi(n) using the Euclidean algorithm -%% 5. Bob publishes n and b in a directory as his public key. - -%S tag1 -make_sig(Len) -> - %% generate two digit prime numbers - P = primes:make_prime(Len), - io:format("P = ~p~n", [P]), - Q = primes:make_prime(Len), - io:format("Q = ~p~n", [Q]), - N = P*Q, - io:format("N = ~p~n", [N]), - Phi = (P-1)*(Q-1), - %% now make B such that B < Phi and gcd(B, Phi) = 1 - B = b(Phi), - io:format("Public key (B) = ~p~n", [B]), - A = lin:inv(B, Phi), - io:format("Private key (A) = ~p~n", [A]), - {{B,N},{A,N}}. - -b(Phi) -> - io:format("Generating a public key B "), - K = length(integer_to_list(Phi)) - 1, - B = b(1, K, Phi), - io:format("~n", []), - B. - -b(Try, K, Phi) -> - io:format("."), - B = primes:make(K), - if - B < Phi -> - case lin:gcd(B, Phi) of - 1 -> B; - _ -> b(Try+1, K, Phi) - end; - true -> - b(Try, K, Phi) - end. -%E tag1 - diff --git a/tests/erlang/examples-2.0/sos b/tests/erlang/examples-2.0/sos deleted file mode 100755 index 31e817e..0000000 --- a/tests/erlang/examples-2.0/sos +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -erl -boot /home/joe/erl/example_programs-2.0/examples-2.0/sos -environment `printenv` -load $1 diff --git a/tests/erlang/examples-2.0/sos.erl b/tests/erlang/examples-2.0/sos.erl deleted file mode 100644 index 0d2bbc3..0000000 --- a/tests/erlang/examples-2.0/sos.erl +++ /dev/null @@ -1,344 +0,0 @@ --module(sos). - --doc([{author,joe}, - {title,"Simple OS written entirely in Erlang"}, - {keywords, [os]}, - {htmldoc, "sos.html"}, - {date,981012}]). - --export([main/0, % starts the system - load_module/1, % - log_error/1, - make_server/3, - cast/2, - rpc/2, - change_behaviour/2, - keep_alive/2, - make_global/2, - on_exit/2, - on_halt/1, - stop_system/1, - every/3, - spawn_fun/1, - spawn_link_fun/1, - lookup/2, - map/2, - reverse/1, - read/0, - write/1, - env/1, - make_scripts/0 - ]). - --export([internal_call/1]). - -main() -> - make_server(io, - fun start_io/0, fun handle_io/2), - make_server(code, - const([init,erl_prim_loader]), - fun handle_code/2), - make_server(error_logger, - const(0), fun handle_error_logger/2), - make_server(halt_demon, - const([]), fun handle_halt_demon/2), - make_server(env, - fun start_env/0, fun handle_env/2), - load_module(error_handler), - Mod = get_module_name(), - load_module(Mod), - run(Mod). - -run(Mod) -> - Pid = spawn_link(Mod, main, []), - on_exit(Pid, fun(Why) -> stop_system(Why) end). - -load_module(Mod) -> rpc(code, {load_module, Mod}). - -handle_code({load_module, Mod}, Mods) -> - case member(Mod, Mods) of - true -> - {already_loaded, Mods}; - false -> - case primLoad(Mod) of - ok -> - {{ok,Mod}, [Mod|Mods]}; - Error -> - {Error, Mods} - end - end. - -primLoad(Module) -> - Str = atom_to_list(Module), - case erl_prim_loader:get_file(Str ++ ".jam") of - {ok, Bin, FullName} -> - case erlang:load_module(Module, Bin) of - {module, Module} -> - ok; - {module, _} -> - {error, wrong_module_in_binary}; - Other -> - {error, {bad_object_code, Module}} - end; - error -> - {error, {cannot_locate, Module}} - - end. - -log_error(Error) -> cast(error_logger, {log, Error}). - -handle_error_logger({log, Error}, N) -> - erlang:display({error, Error}), - {ok, N+1}. - -%S tag4 -on_halt(Fun) -> cast(halt_demon,{on_halt,Fun}). -stop_system(Why) -> cast(halt_demon,{stop_system,Why}). -%E tag4 - -handle_halt_demon({on_halt, Fun}, Funs) -> - {ok, [Fun|Funs]}; -handle_halt_demon({stop_system, Why}, Funs) -> - case Why of - normal -> true; - _ -> erlang:display({stopping_system,Why}) - end, - map(fun(F) -> F() end, Funs), - erlang:halt(), - {ok, []}. - -%S tag5 -read() -> rpc(io, read). -write(X) -> rpc(io, {write, X}). -%E tag5 - -start_io() -> - Port = open_port({fd,0,1}, [eof, binary]), - process_flag(trap_exit, true), - {false, Port}. - -handle_io(read, {true, Port}) -> - {eof, {true, Port}}; -handle_io(read, {false, Port}) -> - receive - {Port, {data, Bytes}} -> - {{ok, Bytes}, {false, Port}}; - {Port, eof} -> - {eof, {true,Port}}; - {'EXIT', Port, badsig} -> - handle_io(read, {false, Port}); - {'EXIT', Port, Why} -> - {eof, {true, Port}} - end; -handle_io({write,X}, {Flag,Port}) -> - Port ! {self(), {command, X}}, - {ok, {Flag, Port}}. - -env(Key) -> rpc(env, {lookup, Key}). - -handle_env({lookup, Key}, Dict) -> - {lookup(Key, Dict), Dict}. - -start_env() -> - Env = case init:get_argument(environment) of - {ok, [L]} -> - L; - error -> - fatal({missing, '-environment ...'}) - end, - map(fun split_env/1, Env). - -split_env(Str) -> split_env(Str, []). - -split_env([$=|T], L) -> {reverse(L), T}; -split_env([], L) -> {reverse(L), []}; -split_env([H|T], L) -> split_env(T, [H|L]). - -make_server(Name, FunD, FunH) -> - make_global(Name, - fun() -> - Data = FunD(), - server_loop(Name, Data, FunH) - end). - -server_loop(Name, Data, Fun) -> - receive - {rpc, Pid, Q} -> - case (catch Fun(Q, Data)) of - {'EXIT', Why} -> - Pid ! {Name, exit, Why}, - server_loop(Name, Data, Fun); - {Reply, Data1} -> - Pid ! {Name, Reply}, - server_loop(Name, Data1, Fun) - end; - {cast, Pid, Q} -> - case (catch Fun(Q, Data)) of - {'EXIT', Why} -> - exit(Pid, Why), - server_loop(Name, Data, Fun); - Data1 -> - server_loop(Name, Data1, Fun) - end; - {eval, Fun1} -> - server_loop(Name, Data, Fun1) - end. - -rpc(Name, Q) -> - Name ! {rpc, self(), Q}, - receive - {Name, Reply} -> - Reply; - {Name, exit, Why} -> - exit(Why) - end. - -cast(Name, Q) -> - Name ! {cast, self(), Q}. - -change_behaviour(Name, Fun) -> - Name ! {eval, Fun}. - -const(C) -> fun() -> C end. - -keep_alive(Name, Fun) -> - Pid = make_global(Name, Fun), - on_exit(Pid, - fun(Exit) -> keep_alive(Name, Fun) end). - -make_global(Name, Fun) -> - case whereis(Name) of - undefined -> - Self = self(), - Pid = spawn_fun(fun() -> - make_global(Self,Name,Fun) - end), - receive - {Pid, ack} -> - Pid - end; - Pid -> - Pid - end. - -make_global(Pid, Name, Fun) -> - case register(Name, self()) of - {'EXIT', _} -> - Pid ! {self(), ack}; - _ -> - Pid ! {self(), ack}, - Fun() - end. - -spawn_fun({'fun',Mod,Arity,Chksum,Env}) -> - spawn(?MODULE, internal_call, - [[Mod,Arity,Chksum,[],Env]]). - -spawn_link_fun({'fun',Mod,Arity,Chksum,Env}) -> - spawn(?MODULE, internal_call, - [[Mod,Arity,Chksum,[],Env]]). - -internal_call([Mod|Args]) -> - apply(Mod, module_lambdas, Args). - -on_exit(Pid, Fun) -> - spawn_fun(fun() -> - process_flag(trap_exit, true), - link(Pid), - receive - {'EXIT', Pid, Why} -> - Fun(Why) - end - end). - -every(Pid, Time, Fun) -> - spawn_fun(fun() -> - process_flag(trap_exit, true), - link(Pid), - every_loop(Pid, Time, Fun) - end). - -every_loop(Pid, Time, Fun) -> - receive - {'EXIT', Pid, Why} -> - true - after Time -> - Fun(), - every_loop(Pid, Time, Fun) - end. - -get_module_name() -> - case init:get_argument(load) of - {ok, [[Arg]]} -> - module_name(Arg); - error -> - fatal({missing, '-load Mod'}) - end. - -%S tag7 -lookup(Key, [{Key,Val}|_]) -> {found, Val}; -lookup(Key, [_|T]) -> lookup(Key, T); -lookup(Key, []) -> not_found. - -member(X, [X|_]) -> true; -member(H, [_|T]) -> member(H, T); -member(_, []) -> false. - -map(F, [H|T]) -> [F(H)|map(F, T)]; -map(F, []) -> []. - -reverse(X) -> reverse(X, []). - -reverse([H|T], L) -> reverse(T, [H|L]); -reverse([], L) -> L. - -module_name(Str) -> - case (catch list_to_atom(Str)) of - {'EXIT', _} -> - log_error({bad_module_name,Str}), - stop_system(bad_start_module); - Mod -> Mod - end. -%E tag7 - -fatal(Term) -> - log_error({fatal, Term}), - stop_system({fatal, Term}). - -%S tag8 -make_scripts() -> - {ok, Cwd} = file:get_cwd(), - Script = - {script,{"sos","1.0"}, - [{preLoaded,[init,erl_prim_loader]}, - {progress,preloaded}, - {path, - [".", - Cwd, - "$ROOT/lib/" ++ lib_location(kernel) ++ "/ebin", - "$ROOT/lib/" ++ lib_location(stdlib) ++ "/ebin"]}, - {primLoad, - [erl_open_port, - erlang, - error_handler, - sos - ]}, - {kernel_load_completed}, - {progress,kernel_load_completed}, - {progress,started}, - {apply,{sos,main,[]}} - ]}, - file:write_file("sos.boot", term_to_binary(Script)), - - {ok, Stream} = file:open("sos", write), - io:format(Stream, "#!/bin/sh~nerl -boot ~s/sos " - " -environment `printenv` -load $1~n", - [Cwd]), - file:close(Stream), - unix:cmd("chmod a+x sos"), - true. - -lib_location(Lib) -> - filename:basename(code:lib_dir(Lib)). -%E tag8 - diff --git a/tests/erlang/examples-2.0/sos_err1.erl b/tests/erlang/examples-2.0/sos_err1.erl deleted file mode 100644 index 382da01..0000000 --- a/tests/erlang/examples-2.0/sos_err1.erl +++ /dev/null @@ -1,7 +0,0 @@ --module(sos_err1). --doc(none). --export([main/0]). - -main() -> - listsforeaack:abc(123), - sos:write("Hello world\n"). diff --git a/tests/erlang/examples-2.0/sos_err2.erl b/tests/erlang/examples-2.0/sos_err2.erl deleted file mode 100644 index 110f16f..0000000 --- a/tests/erlang/examples-2.0/sos_err2.erl +++ /dev/null @@ -1,7 +0,0 @@ --module(sos_err2). --doc(none). --export([main/0]). - -main() -> - lists:abc(123), - sos:write("Hello world\n"). diff --git a/tests/erlang/examples-2.0/sos_test1.erl b/tests/erlang/examples-2.0/sos_test1.erl deleted file mode 100644 index 0f3846c..0000000 --- a/tests/erlang/examples-2.0/sos_test1.erl +++ /dev/null @@ -1,6 +0,0 @@ --module(sos_test1). --doc(none). --export([main/0]). - -main() -> - sos:write("Hello world\n"). diff --git a/tests/erlang/examples-2.0/sos_test2.erl b/tests/erlang/examples-2.0/sos_test2.erl deleted file mode 100644 index a0dfa7c..0000000 --- a/tests/erlang/examples-2.0/sos_test2.erl +++ /dev/null @@ -1,7 +0,0 @@ --module(sos_test2). --doc(none). --export([main/0]). - -main() -> - X = lists:reverse("Hellow world"), - sos:write([X,"\n"]). diff --git a/tests/erlang/examples-2.0/sos_test3.erl b/tests/erlang/examples-2.0/sos_test3.erl deleted file mode 100644 index 11e3c31..0000000 --- a/tests/erlang/examples-2.0/sos_test3.erl +++ /dev/null @@ -1,16 +0,0 @@ --module(sos_test3). --doc(none). --export([main/0]). --import(sos, [read/0, write/1]). - -main() -> - loop(). - -loop() -> - case read() of - eof -> - true; - {ok, X} -> - write([X]), - loop() - end. diff --git a/tests/erlang/examples-2.0/sos_test4.erl b/tests/erlang/examples-2.0/sos_test4.erl deleted file mode 100644 index 0806097..0000000 --- a/tests/erlang/examples-2.0/sos_test4.erl +++ /dev/null @@ -1,8 +0,0 @@ --module(sos_test4). --doc(none). --export([main/0]). - -main() -> - sos:write("I will crash now\n"), - 1 = 2, - sos:write("This line will not be printed\n"). diff --git a/tests/erlang/examples-2.0/suffix_rules b/tests/erlang/examples-2.0/suffix_rules deleted file mode 100644 index ea3743d..0000000 --- a/tests/erlang/examples-2.0/suffix_rules +++ /dev/null @@ -1,8 +0,0 @@ -Suffix .erl.jam -> - c:c($>). - -Suffix .tex.dvi -> - unix:cmd("latex $>.tex"). - -Suffix .ehtml.html -> - ehtml:file("$>"). diff --git a/tests/erlang/examples-2.0/test1 b/tests/erlang/examples-2.0/test1 deleted file mode 100644 index 1bda09b..0000000 --- a/tests/erlang/examples-2.0/test1 +++ /dev/null @@ -1,17 +0,0 @@ -%% This is a macro - -OBJS = a.jam, b.jam. - -OBJS += ,c.jam. - -include("suffix_rules"). - -all when a.dvi $(OBJS). - -a.dvi when a.tex -> - - io:format("touching a.dvi~n"), - unix:cmd("touch a.dvi"), - io:format("done~n"). - - diff --git a/tests/erlang/examples-2.0/test2 b/tests/erlang/examples-2.0/test2 deleted file mode 100644 index 87d5310..0000000 --- a/tests/erlang/examples-2.0/test2 +++ /dev/null @@ -1,13 +0,0 @@ -%% This is a macro - -OBJS = abc.jam, a.jam, b.jam. - -include("$(MAKEDIR)/suffix_rules"). - -all when $(OBJS) a.dvi. - -a.dvi when a.tex -> - unix:cmd("touch a.dvi"). - - - diff --git a/tests/erlang/examples-2.0/test3 b/tests/erlang/examples-2.0/test3 deleted file mode 100644 index 4e71484..0000000 --- a/tests/erlang/examples-2.0/test3 +++ /dev/null @@ -1,52 +0,0 @@ -%% -%% Make the tk library -%% - -Jamdir=../ebin. - -CC = gcc. - -tk when $(Jamdir)/tk.jam, $(Jamdir)/tkfocus.jam, - tkbutton.jam, tkentry.jam, - tkscrlbar.jam, tkdialog.jam, tklib.jam -> - X = 12, - Y = 14, - tk:stop(X, Y, X+Y). - -$(Jamdir)/tk.jam when tk.erl, tk.hrl -> - c:c(tk, [{outdir, "$(Jamdir)"}]). - -$(Jamdir)/tkfocus.jam when tkfocus.erl, tk.hrl -> - c:c(tkfocus, [{outdir, "$(Jamdir)"}]). - -$(Jamdir)/tkbutton.jam when tkbutton.erl, tk.hrl -> - c:c(tkbutton, [{outdir, "$(Jamdir)"}]). - -$(Jamdir)/tkentry.jam when tkentry.erl, tk.hrl -> - c:c(tkentry, [{outdir, "$(Jamdir)"}]). - -tkscrlbar.jam when tkscrlbar.erl, tk.hrl -> - c:c(tkscrlbar, [{outdir, "$(Jamdir)"}]). - -tkdialog.jam when tkdialog.erl -> - c:c(tkdialog, [{outdir, "$(Jamdir)"}]). - -tklib.jam when tklib.erl -> - io:format("compiling: $@~n"), - c:c(tklib, [{outdir, "$(Jamdir)"}]). - -foo.o when foo.c -> - unix:cmd("$(CC) -o $@ -c foo.c"). - -foo when bar -> - X = case Jamdir of - "../ebin" -> 1; - ".." -> 2; - "." -> 3 - end, - io:format("this = ~w", [X]). - - - - - diff --git a/tests/erlang/examples-2.0/test4 b/tests/erlang/examples-2.0/test4 deleted file mode 100644 index c1cda9f..0000000 --- a/tests/erlang/examples-2.0/test4 +++ /dev/null @@ -1,7 +0,0 @@ -all when a.jam -> - p,q,r. - -all -> a,b,c. - -all,b,c when a,b,c. - diff --git a/tests/erlang/examples-2.0/title_page.tex b/tests/erlang/examples-2.0/title_page.tex deleted file mode 100644 index c4589b5..0000000 --- a/tests/erlang/examples-2.0/title_page.tex +++ /dev/null @@ -1,22 +0,0 @@ -\begin{titlepage} -\begin{center} -\vspace*{1.0cm} -{\Huge\bf -The Erlang Cookbook\\ -} - -\vskip 1.0 true cm - - -\vskip 3.0 true cm - -{\large\bf -Joe Armstrong\\ -} - -\vskip 1.0 true cm - -\end{center} -\eject -\end{titlepage} - diff --git a/tests/erlang/examples-2.0/topological_sort.erl b/tests/erlang/examples-2.0/topological_sort.erl deleted file mode 100644 index 247f7a5..0000000 --- a/tests/erlang/examples-2.0/topological_sort.erl +++ /dev/null @@ -1,66 +0,0 @@ --module(topological_sort). - -%% Copyright (C) 1998, Ericsson Computer Science Laboratory - --doc([{author,'Joe Armstrong'}, - {title,"Topological sort of a partial order."}, - {keywords, [topological,sort,partial,order]}, - {date,981102}]). - - --export([sort/1, test/0]). - --import(lists, [map/2, member/2, filter/2]). - -%% -type([{X, X}]) -> {ok, [{X,Y}]} | {cycle, [{X,Y}]} -%% topological_sort:pairs(L) - -%% A partial order on the set S is a set of pairs {Xi,Xj} such that -%% some relation between Xi and Xj is obeyed. - -%% A topological sort of a partial order is a sequence of elements -%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial -%% order i < j - -test() -> - Pairs = [{1,2},{2,4},{4,6},{2,10},{4,8},{6,3},{1,3}, - {3,5},{5,8},{7,5},{7,9},{9,4},{9,10}], - sort(Pairs). - -%% [7,1,9,2,4,6,3,5,8,10] -%S tag1 -sort(Pairs) -> - iterate(Pairs, [], all(Pairs)). - -iterate([], L, All) -> - {ok, remove_duplicates(L ++ subtract(All, L))}; -iterate(Pairs, L, All) -> - case subtract(lhs(Pairs), rhs(Pairs)) of - [] -> - {cycle, Pairs}; - Lhs -> - iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) - end. - -all(L) -> lhs(L) ++ rhs(L). -lhs(L) -> map(fun({X,_}) -> X end, L). -rhs(L) -> map(fun({_,Y}) -> Y end, L). - -%% subtract(L1, L2) -> all the elements in L1 which are not in L2 - -subtract(L1, L2) -> filter(fun(X) -> not member(X, L2) end, L1). - -remove_duplicates([H|T]) -> - case member(H, T) of - true -> remove_duplicates(T); - false -> [H|remove_duplicates(T)] - end; -remove_duplicates([]) -> - []. - -%% remove_pairs(L1, L2) -> L2' L1 = [X] L2 = [{X,Y}] -%% removes all pairs from L2 where the first element -%% of each pair is a member of L1 - -remove_pairs(L1, L2) -> filter(fun({X,Y}) -> not member(X, L1) end, L2). -%E tag1 diff --git a/tests/erlang/examples-2.0/transitive.erl b/tests/erlang/examples-2.0/transitive.erl deleted file mode 100644 index 9ececb2..0000000 --- a/tests/erlang/examples-2.0/transitive.erl +++ /dev/null @@ -1,39 +0,0 @@ --module(transitive). -%% Copyright (C) 1997, Ericsson Telecom AB - --doc([{author,'Joe Armstrong'}, - {title,"Transitive closure of a graph."}, - {keywords, [transitive,closure]}, - {date,981102}]). - -%% warning slow on big graphs - --export([closure/2]). - -%S tag1 -closure(RootSet, Pairs) -> - closure_list(RootSet, Pairs, RootSet). - -closure(Start, [], L) -> - L; -closure(Start, Pairs, Reachable) -> - {Next, Rest} = next(Start, Pairs), - closure_list(Next, Rest, Next ++ Reachable). - -closure_list([], Pairs, Reachable) -> - Reachable; -closure_list([H|T], Pairs, Reachable) -> - Reachable1 = closure(H, Pairs, Reachable), - closure_list(T, Pairs, Reachable1). - -next(Start, Pairs) -> - next(Start, Pairs, [], []). - -next(Start, [], Reach, NoReach) -> - {Reach, NoReach}; -next(Start, [{Start,Next}|T], Reach, NoReach) -> - next(Start, T, [Next|Reach], NoReach); -next(Start, [H|T], Reach, NoReach) -> - next(Start, T, Reach, [H|NoReach]). -%E tag1 - diff --git a/tests/erlang/examples-2.0/users b/tests/erlang/examples-2.0/users deleted file mode 100644 index b11ba0b..0000000 --- a/tests/erlang/examples-2.0/users +++ /dev/null @@ -1,3 +0,0 @@ -{"joe", "zapme"}. -{"ftp","ftp"}. -{"jane","hospan"}. diff --git a/tests/fortran/block.f b/tests/fortran/block.f deleted file mode 100644 index 94b3a11..0000000 --- a/tests/fortran/block.f +++ /dev/null @@ -1,7 +0,0 @@ - block data - integer nmax - parameter (nmax=20) - real v(nmax), alpha, beta - common /vector/v,alpha,beta - data v/20*100.0/, alpha/3.14/, beta/2.71/ - end diff --git a/tests/fortran/fall1.f b/tests/fortran/fall1.f deleted file mode 100644 index f37d3ce..0000000 --- a/tests/fortran/fall1.f +++ /dev/null @@ -1,150 +0,0 @@ -c -c -c
-      module constants
-         integer, parameter :: np=2000, dbl=selected_real_kind(14,100)
-         real(dbl) :: g=9.807,dtmin=.001
-      end module constants
-c
-      program fall
-      use constants
-      implicit none
-c
-c   Program to calculate the dynamics of a falling body
-c
-c   John Mahaffy    4/15/95
-c
-c   Arrays:
-c     v   -   velocity at each time integration step
-c     z   -   height at  each time integration step
-c     t   -   time for each corresponding v and z
-c     zreal - Actual height at time t(i) for comparison with computed z
-c
-c   In this program, I am using allocatate just to save space in the
-c   executable program file (a.out).  No attempt is made to estimate a size.
-c   Module "constants" communicates between subroutines.
-c
-c
-      real(dbl), allocatable :: v(:),z(:),t(:), zreal(:)
-c
-      real(dbl) dt
-      integer nsteps
-c
-      allocate (v(np),z(np),t(np),zreal(np))
-c
-      call input(z,dt)
-      call odesolve(v,z,t,dt,nsteps)
-      call realans(t,z,nsteps,zreal)
-      call output (t,z,zreal,v,nsteps)
-      stop
-      end
-c
-      subroutine input (z,dt)
-      use constants
-      implicit none
-c
-c   Obtain user input for initial height and time step
-c
-c   John Mahaffy    4/15/95
-c
-c  Output Arguments:
-c     z(1)   -  initial height
-c     dt     -  integration time step
-c
-      real(dbl) z(*),dt
-c
-      write(6,'(a)',advance='no') ' Initial height (meters): '
-      read *, z(1)
-      write(6,'(a)',advance='no') 'Time step size (seconds): '
-      read *, dt
-      if(dt.le.0.) dt=dtmin
-      return
-      end
-c
-      subroutine odesolve(v,z,t,dt,nsteps)
-      use constants
-c
-c   Solve the Ordinary Differential Equation of motion for the body
-c
-c   John Mahaffy    4/15/95
-c
-c   Arguments:
-c   Input
-c     dt     -   timestep size
-c   Output:
-c     v    -   velocity
-c     z    -   height
-c     t    -   time
-c     nsteps - last step in the integration
-c
-      real (dbl) v(*),z(*),t(*),dt
-      integer i, nsteps
-c
-c   Solve the equation for a falling body
-c
-c     d v                    d z
-c     ---    =  - g          ---   =  v
-c     d t                    d t
-c
-c   Set remaining initial conditions:
-c
-      t(1)=0.
-      v(1)=0.
-c
-c     Now loop through time steps until z goes negative or we run out of space
-c
-      do 100 i=2,np
-         v(i)= v(i-1)-dt*g
-         z(i)= z(i-1)+dt*.5*(v(i)+v(i-1))
-         t(i)=t(i-1)+dt
-         if(z(i).lt.0.) go to 200
-c
- 100     continue
-c
-      write(6,*) 'Ran out of space to continue integration'
-      write(6,*) ' Last height was ',z(np),' meters'
-      i=np
- 200  nsteps=i
-c     return
-      end
-c
-      subroutine realans(t,z,nsteps,zreal)
-      use constants
-c
-c     Get the values of the analytic solution to the differential equation
-c     for each time point to check the numerical accuracy.
-c
-c   John Mahaffy    4/15/95
-c
-      real(dbl) t(*),z(*),zreal(*)
-      integer i,nsteps
-c
-      do 10 i=1,nsteps
-  10  zreal(i)=z(1)-.5*g*t(i)**2
-      return
-      end
-c
-      subroutine output(t,z,zreal,v,nsteps)
-      use constants, only : dbl
-      implicit none
-c
-c   Outputs the full results of the time integration
-c
-c   John Mahaffy    4/15/95
-c
-      real(dbl) v(*),z(*),t(*), zreal(*)
-      integer nsteps,i
-      print *, 'Results are in fall.output'
-      open (11,file='fall.output')
-      write(11,2000)
-      do 300 i=1,nsteps
-      write(11,2001) t(i),v(i),z(i),zreal(i)
- 300  continue
- 2000 format (33x,'Computed',8x,'Actual',/,
-     &        6x,'Time',9x,'Velocity', 8x,'Height',8x,'Height')
- 2001 format (1x,1p,4e15.7)
-      return
-      end
-c
-c -c diff --git a/tests/fortran/htcoef.f b/tests/fortran/htcoef.f deleted file mode 100644 index 7e15598..0000000 --- a/tests/fortran/htcoef.f +++ /dev/null @@ -1,55 +0,0 @@ - program htcoef -c -c John Mahaffy, Penn State University, CmpSc 201 Example -c 1/26/96 -c - implicit none - real k,D,Pr,h,Nulam,Nuturb - real Re1,Re2,Re3,Re4 -c -c Calculate an approximation for heat transfer coefficients -c in a 1 inch pipe for several different Reynolds numbers -c -c An example of why you should learn to use subprograms -c -c h - heat transfer coefficient ( w/m**2/K)' -c Nulam - laminar Nusselt number -c Nuturb - Turbulent Nusselt number (Dittus-Boelter correlation) -c k - conductivity ( w/m/K)' -c D - hydraulic diameter (m) -c Re - Reynolds number -c Pr - Prandl number -c - data k,D,Pr/0.617,0.0254,1.0/, Nulam/4.0/ -c -c Each of the following blocks assigns a Reynolds number, calculates -c an associated Turbulent Nusselt number and calculates the heat -c transfer coefficient based on the maximum of Turbulent and Laminar -c Nusselt Numbers -c - Re1=10. - Nuturb=0.023*Re1**0.8*Pr**0.4 - h=k/D*max(Nulam,Nuturb) - print *, 'For Reynolds Number = ',Re1 - print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' -c - Re2=100. - Nuturb=0.023*Re2**0.8*Pr**0.4 - h=k/D*max(Nulam,Nuturb) - print *, 'For Reynolds Number = ',Re2 - print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' -c - Re3=1000. - Nuturb=0.023*Re3**0.8*Pr**0.4 - h=k/D*max(Nulam,Nuturb) - print *, 'For Reynolds Number = ',Re3 - print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' -c - Re4=10000. - Nuturb=0.023*Re4**0.8*Pr**0.4 - h=k/D*max(Nulam,Nuturb) - print *, 'For Reynolds Number = ',Re4 - print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' -c - stop - end diff --git a/tests/fortran/linint1.f b/tests/fortran/linint1.f deleted file mode 100644 index c565643..0000000 --- a/tests/fortran/linint1.f +++ /dev/null @@ -1,22 +0,0 @@ - subroutine linint(x,y) -c -c Given a value of x return a value of y based on interpolation -c within a table of y values (ytab) evaluated at evenly spaced -c x points between xmin and xmax. -c -c John Mahaffy 2/12/95 -c - real ytab(11) - parameter (xmin=300.,dx=100.,xmax=xmin+10.*dx,rdx=1./dx) - data ytab/1.0,2.1,3.2,4.4,5.7,7.1,8.6,10.2,11.9,13.7,15.8/ - if(x.ge.xmin.and.x.le.xmax) then - i1= int((x-xmin)*rdx)+1 - x1=xmin+(i1-1)*dx - wx=(x-x1)*rdx - y=(1-wx)*ytab(i1)+wx*ytab(i1+1) - else - write(6,*) 'x = ', x, ' is out of table range' - stop - endif - return - end diff --git a/tests/fortran/plot2.f b/tests/fortran/plot2.f deleted file mode 100644 index e57988d..0000000 --- a/tests/fortran/plot2.f +++ /dev/null @@ -1,209 +0,0 @@ -c -cplot2.f -c -c
-
-      program plotit
-c
-c    Program to provide plots of Sin(x)
-c    Ascii Character plots go to terminal and file 'pplot.out'
-c
-c     John Mahaffy   2/1/95
-c
-      implicit none
-c
-c    The following line tells Fortran that "func" is a function or subroutine
-c    and not a variable
-c
-c
-      external func
-c
-c
-c   The next line is necessary to pass the location of the intrinsic sin
-c   function as an argument to a subprogram
-c
-c
-      intrinsic sin
-c
-c
-      call pplot(func,0.,6.)
-      print *, 'Hit the Enter key to continue'
-c
-      pause
-c
-      call gnuplot('x','y','sin(x)',func,0.0,6.0)
-      call gnuplot('x','y','Intrinsic sin(x)',sin,0.0,6.0)
-      stop
-      end
-      subroutine pplot(f,xmin,xmax)
-c
-c   Generate ASCII character plot to screen and file 'pplot.out'
-c
-      implicit none
-      character line*72
-      real x, f , xmin , xmax
-      integer ip,i,imax
-c
-c    The following line tells Fortran that f is a function or subroutine
-c    and not a variable
-c
-      external f
-c
-c   INPUT Arguments
-c
-c   f       -   function to be ploted
-c   xmin    -   minimum x value
-c   xmax    -   maximum x value
-c
-c   OTHER key variables
-c
-c   line    -   Character string loaded with a line of output
-c   ip      -   Position in line for a function value
-c
-      open (11,file='pplot.out')
-c
-c   Label values of the y axis
-c
-      line=' '
-      line(14:15)='-1'
-      line(65:65)='1'
-      write(*,*) line
-      write(11,*) line
-      line=' '
-      write(line(10:13),'(f4.1)') xmin
-c
-c   Draw the y axis
-c
-      line(15:40)='+----+----+----+----+----+'
-      line(41:65)=line(16:40)
-c
-c   Plot the value at x=0
-c
-      ip= nint(25*f(0.0))+40
-      line(ip:ip)='*'
-      write(*,*) line
-      write(11,*) line
-      line=' '
-      imax=nint((xmax-xmin)*10)
-c
-c     Limit output
-c
-      imax=min(1000,imax)
-c
-c    Loop through and plot points for other x values
-c
-      do 50 i=1,imax
-         x=.1*i
-         ip=nint(25*f(x))+40
-c 
-         if(mod(i,10).eq.0) then
-            write(line(10:13),'(f4.1)') x
-            line(40:40)='+'
-c
-         else
-c
-            line(10:13)=' '
-            line(40:40)='|'
-         endif
-         line(ip:ip)='*'
-         write(*,*) line
-         write(11,*) line
- 50      line(ip:ip)=' '
-c
-      close (11)
-c
-      return
-      end
-      subroutine gnuplot(xlabel,ylabel,title,f,xmin,xmax)
-c
-c   Ship data to the public domain program "gnuplot" for plotting
-c
-      implicit none
-      character line*72,sq*1
-      real x,f,xmin,xmax,fx
-      character*(*) xlabel,ylabel,title
-      integer i,imax,lc
-      external f
-      data sq/''''/
-c
-c   INPUT Arguments
-c
-c   f       -   function to be ploted
-c   xmin    -   minimum x value
-c   xmax    -   maximum x value
-c   xlabel  -   Contains a label for the x-axis
-c   ylabel  -   Contains a label for the y-axis
-c   title   -   Contains a title for the plot
-c
-c   OTHER key variables
-c
-c   line   -   Contains a line of character data
-c
-c   Drive a separate true graphics program (gnuplot)
-c
-c   First set up the command file for gnuplot
-c   Run gnuplot interactively and use the "help" command to learn more
-c   about what I am doing.
-c
-      open (12,file='gnuxy')
-c
-c  UnComment the next line if you are on a NCSA/BYU Telnet Session
-c
-c     write(12,*) 'set terminal tek40xx'
-c
-      write(12,*) 'set data style lines'
-c     
-      lc=len(xlabel)
-c     
-      line='set xlabel '''//xlabel(1:lc)//sq
-      write(12,*)line
-c
-c   You don't really need to calculate the character variable length
-c   here.  The following works just fine because of the character*(*)
-c
-      line='set ylabel '''//ylabel//sq
-      write(12,*)line
-      line='set title '''//title//sq
-      write(12,*)line
-      write(12,*)'set nokey'
-      write(12,2000) xmin,xmax
- 2000 format('set xrange [',f3.0,':',f3.0,']')
-      write(12,*) 'plot ''dataxy'' using 1:2'
-      write(12,*) 'pause 10'
-      close(12)
-c
-c   Generate x-y pairs for the graph
-c
-      open (12,file='dataxy')
-      imax=nint((xmax-xmin)*10)
-c
-c     Limit output
-c
-      imax=min(1000,imax)
-c
-      do 100 i=0,imax
-         x=.1*i
-         fx=f(x)
-         write(12,*) x,fx
-  100 continue
-      close(12)
-c
-c   Tell the system to run the program gnuplot
-c   This call works on either IBM RS6000 or Sun, but is not part of
-c   the Fortran standard.
-c   Comment out the line if you aren't at a terminal with graphics
-c
-      call system('gnuplot gnuxy')
-c
-      return
-c
-      end
-c
-      real function func(x)
-c
-      func=sin(x)
-      return
-      end
-c
-c -c diff --git a/tests/fortran/thcl.f b/tests/fortran/thcl.f deleted file mode 100644 index 72f0438..0000000 --- a/tests/fortran/thcl.f +++ /dev/null @@ -1,73 +0,0 @@ - function thcl(temp) -c -c function thcl evaluates the freon liquid thermal conductivity -c as a function of liquid enthalpy -c -c liquid temperature temp in (j/kg) -c thermal conductivity thcl in (w/m/k) -c -c -c - dimension tabl(4,26) - save ilast - data ilast/15/,ntab/26/ - data tabl/ - & 1.99826700E+02, 1.15267000E-01,-3.03660304E-04, 6.96601393E-07, - & 2.10937800E+02, 1.11979000E-01,-2.88180288E-04, 6.96601393E-07, - & 2.22048900E+02, 1.08863000E-01,-2.72700273E-04,-6.88501377E-07, - & 2.33160000E+02, 1.05748000E-01,-2.88000288E-04, 6.88501377E-07, - & 2.44271100E+02, 1.02633000E-01,-2.72700273E-04,-6.96601393E-07, - & 2.55382200E+02, 9.95170000E-02,-2.88180288E-04, 7.04701409E-07, - & 2.66493300E+02, 9.64020000E-02,-2.72520273E-04,-7.04701409E-07, - & 2.77604400E+02, 9.32870000E-02,-2.88180288E-04, 6.96822277E-07, - & 2.88715600E+02, 9.01710000E-02,-2.72695225E-04,-6.88955687E-07, - & 2.99826700E+02, 8.70560000E-02,-2.88005336E-04, 6.88955687E-07, - & 3.10937800E+02, 8.39410000E-02,-2.72695225E-04,-6.97055703E-07, - & 3.22048900E+02, 8.08250000E-02,-2.88185336E-04, 7.05155719E-07, - & 3.33160000E+02, 7.77100000E-02,-2.72515225E-04,-7.05155719E-07, - & 3.44271100E+02, 7.45950000E-02,-2.88185336E-04, 6.97055703E-07, - & 3.55382200E+02, 7.14790000E-02,-2.72695225E-04,-6.88955687E-07, - & 3.66493300E+02, 6.83640000E-02,-2.88005336E-04, 6.88955687E-07, - & 3.77604400E+02, 6.52490000E-02,-2.72695225E-04,-6.96822277E-07, - & 3.88715600E+02, 6.21330000E-02,-2.88180288E-04, 7.04701409E-07, - & 3.99826700E+02, 5.90180000E-02,-2.72520273E-04,-7.04701409E-07, - & 4.10937800E+02, 5.59030000E-02,-2.88180288E-04, 6.96601393E-07, - & 4.22048900E+02, 5.27870000E-02,-2.72700273E-04,-4.89240978E-06, - & 4.33160000E+02, 4.91530000E-02,-3.81420381E-04, 6.80401361E-07, - & 4.44271100E+02, 4.49990000E-02,-3.66300366E-04,-6.28561257E-06, - & 4.55382200E+02, 4.01530000E-02,-5.05980506E-04,-2.45592491E-05, - & 4.66493300E+02, 3.14990000E-02,-1.05174105E-03,-2.18924207E-04, - & 4.70937800E+02, 2.25000000E-02, 0.00000000E+00, 0.00000000E+00/ - x=temp -c Start the search from the last point of table use index -c - if (x.le.tabl(1,ilast+1)) then -c -c Search down the table from point of last use -c - do 20 i1=ilast,1,-1 - if(x.ge.tabl(1,i1)) go to 60 - 20 continue -c write(6,*) 'x = ', x, ' is below the table range' - i1=1 - go to 60 - else -c -c Search up the table from point of last use -c - do 40 i1=ilast+1,ntab-1 - if(x.le.tabl(1,i1+1)) go to 60 - 40 continue -c write(6,*) 'x = ', x, ' is above the table range' - i1=ntab-1 - go to 60 - endif -c -c Bounding points found, interpolate -c - 60 dx=(x-tabl(1,i1)) - thcl=tabl(2,i1)+tabl(3,i1)*dx+tabl(4,i1)*dx**2 - ilast=i1 - 120 continue - return - end diff --git a/tests/java/FlowSet.java b/tests/java/FlowSet.java deleted file mode 100644 index f185522..0000000 --- a/tests/java/FlowSet.java +++ /dev/null @@ -1,46 +0,0 @@ -// This file is part of the Java Compiler Kit (JKit) -// -// The Java Compiler Kit is free software; you can -// redistribute it and/or modify it under the terms of the -// GNU General Public License as published by the Free Software -// Foundation; either version 2 of the License, or (at your -// option) any later version. -// -// The Java Compiler Kit is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without -// even the implied warranty of MERCHANTABILITY or FITNESS FOR -// A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public -// License along with the Java Compiler Kit; if not, -// write to the Free Software Foundation, Inc., 59 Temple Place, -// Suite 330, Boston, MA 02111-1307 USA -// -// (C) David James Pearce, 2009. - -package jkit.jil.dfa; - -public interface FlowSet { - - /** - * FlowSets must be cloneable to facilitate multiple flows of execution - * from conditionals - * - * @return A Clone of the current FlowSet - */ - public Object clone(); - - /** - * Computes the least upper bound of this flowset and that provided. NOTE - * the join operation has a subtle, yet important, requirement. If the - * result of the join must be equivalent to *this* flowset, then it must be - * the *same* flowset. - * - * @param s - * Another FlowSet to join with this - * @return true if this FlowSet has changed due to the computation, false - * otherwise - */ - public FlowSet join(FlowSet s); -} diff --git a/tests/java/JilBuilder.java b/tests/java/JilBuilder.java deleted file mode 100644 index 214ca12..0000000 --- a/tests/java/JilBuilder.java +++ /dev/null @@ -1,1933 +0,0 @@ -// This file is part of the Java Compiler Kit (JKit) -// -// The Java Compiler Kit is free software; you can -// redistribute it and/or modify it under the terms of the -// GNU General Public License as published by the Free Software -// Foundation; either version 2 of the License, or (at your -// option) any later version. -// -// The Java Compiler Kit is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without -// even the implied warranty of MERCHANTABILITY or FITNESS FOR -// A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. -// -// You should have received a copy of the GNU General Public -// License along with the Java Compiler Kit; if not, -// write to the Free Software Foundation, Inc., 59 Temple Place, -// Suite 330, Boston, MA 02111-1307 USA -// -// (C) David James Pearce, 2009. - -package jkit.java.stages; - -import java.util.*; - -import jkit.compiler.FieldNotFoundException; -import static jkit.compiler.SyntaxError.*; -import static jkit.jil.util.Exprs.*; -import static jkit.jil.util.Types.*; -import jkit.compiler.ClassLoader; -import jkit.compiler.Clazz; -import jkit.compiler.SyntacticAttribute; -import jkit.java.io.JavaFile; -import jkit.java.tree.*; -import jkit.jil.tree.*; -import jkit.jil.tree.Type; -import jkit.jil.util.Types; -import jkit.util.Pair; -import jkit.util.Triple; - -/** - *

- * The aim of this stage is to generate Jil code representing this class. - *

- * - * @author djp - * - */ -public class JilBuilder { - private ClassLoader loader = null; - private TypeSystem types = null; - - /** - * This class is used to annotate an invoke expression with the list of - * checked (and possibly unchecked) exceptions it has declared to throw, as - * well as the type of the method to be invoked. It's sole purpose is to - * prevent us from having to re-traverse the class heirarchy during code - * generation. - * - * @author djp - * - */ - public static class MethodInfo implements SyntacticAttribute { - public final ArrayList exceptions; - public Type.Function type; - - public MethodInfo(List e, Type.Function type) { - exceptions = new ArrayList(e); - this.type = type; - } - } - - private static class Scope {} - - private static class LabelScope extends Scope { - public String label; - public LabelScope(String lab) { - label = lab; - } - } - - private static class SwitchScope extends Scope { - public String exitLab; - public SwitchScope(String el) { - exitLab = el; - } - } - - private static class LoopScope extends SwitchScope { - public String continueLab; - public LoopScope(String cl, String el) { - super(el); - continueLab = cl; - } - } - - private static class ClassScope extends Scope { - public Type.Clazz type; - - public ClassScope(Type.Clazz type) { - this.type = type; - } - } - - private final Stack scopes = new Stack(); - - public JilBuilder(ClassLoader loader, TypeSystem types) { - this.loader = loader; - this.types = types; - } - - public void apply(JavaFile file) { - // Now, traverse the declarations - for(Decl d : file.declarations()) { - doDeclaration(d, null); - } - } - - protected void doDeclaration(Decl d, JilClass parent) { - try { - if(d instanceof Decl.JavaInterface) { - doInterface((Decl.JavaInterface)d); - } else if(d instanceof Decl.JavaEnum) { - doEnum((Decl.JavaEnum)d); - } else if(d instanceof Decl.JavaClass) { - doClass((Decl.JavaClass)d); - } else if(d instanceof Decl.JavaMethod) { - doMethod((Decl.JavaMethod)d, parent); - } else if(d instanceof Decl.JavaField) { - doField((Decl.JavaField)d, parent); - } else if (d instanceof Decl.InitialiserBlock) { - doInitialiserBlock((Decl.InitialiserBlock)d , parent); - } else if (d instanceof Decl.StaticInitialiserBlock) { - doStaticInitialiserBlock((Decl.StaticInitialiserBlock) d, parent); - } else { - syntax_error("internal failure (unknown declaration \"" + d - + "\" encountered)", d); - } - } catch(Exception ex) { - internal_error(d,ex); - } - } - - protected void doInterface(Decl.JavaInterface d) throws ClassNotFoundException { - doClass(d); - } - - protected void doEnum(Decl.JavaEnum en) throws ClassNotFoundException { - doClass(en); - } - - protected void doClass(Decl.JavaClass c) throws ClassNotFoundException { - Type.Clazz type = c.attribute(Type.Clazz.class); - scopes.push(new ClassScope(type)); - // We, need to update the skeleton so that any methods and fields - // discovered below this are attributed to this class! - JilClass skeleton = (JilClass) loader.loadClass(type); - - // I do fields after everything else, so as to simplify the process - // of adding field initialisers to constructors. This is because it - // means I can be sure that the constructor has been otherwise - // completely generated, so all I need is to add initialisers at - // beginning, after super call (if there is one). - ArrayList fields = new ArrayList(); - for(Decl d : c.declarations()) { - if (!(d instanceof Decl.JavaField) - && !(d instanceof Decl.InitialiserBlock) - && !(d instanceof Decl.StaticInitialiserBlock)) { - doDeclaration(d, skeleton); - } else { - fields.add(d); - } - } - - // Note, I iterate the field declarations in reverse order to ensure - // that field initialisers are added to constructors in the right - // order. - for(int i=fields.size();i>0;--i) { - Decl d = fields.get(i-1); - doDeclaration(d, skeleton); - } - - scopes.pop(); - } - - protected void doMethod(Decl.JavaMethod d, JilClass parent) { - Type.Function type = d.attribute(Type.Function.class); - List stmts = doStatement(d.body()); - - // simple hack here, for case when no return statement is provided. - if (type.returnType() instanceof Type.Void - && (stmts.size() == 0 || !(stmts.get(stmts.size() - 1) instanceof JilStmt.Return))) { - stmts.add(new JilStmt.Return(null)); - } - - // First, off. If this is a constructor, then check whether there is an - // explicit super constructor call or not. If not, then add one. - if (d instanceof Decl.JavaConstructor) { - if(findSuperCall(stmts) == -1) { - stmts.add(0, new JilExpr.SpecialInvoke(new JilExpr.Variable("super", parent - .superClass()), "super", new ArrayList(), - new Type.Function(T_VOID), T_VOID)); - } - } - - // Now, add this statement list to the jil method representing this java - // method. - String name = d instanceof Decl.JavaConstructor ? parent.name() : d.name(); - for (JilMethod m : parent.methods()) { - if (m.name().equals(name) && m.type().equals(type)) { - m.body().addAll(stmts); - } - } - } - - protected void doField(Decl.JavaField d, JilClass parent) { - Pair> tmp = doExpression(d.initialiser()); - Type fieldT = d.type().attribute(Type.class); - boolean isStatic = d.isStatic(); - - if(tmp != null) { - if(d.isStatic()) { - if(!(d.isFinal() && d.isConstant())) { - // This is a static field with an non-constant initialiser. - // Therefore, we need to add it to the static initialiser. - JilMethod staticInit = createStaticInitialiser(parent); - JilExpr.Deref df = new JilExpr.Deref(new JilExpr.ClassVariable( - parent.type()), d.name(), isStatic, fieldT, d - .attributes()); - JilStmt.Assign ae = new JilStmt.Assign(df, tmp.first(), d - .attributes()); - // add them at the beginning to get the right ordering. - staticInit.body().add(0,ae); - staticInit.body().addAll(0,tmp.second()); - } - } else { - // This is a non-static field with an initialiser. Therefore, we - // need to add it to the beginning of all constructors. One issue is - // that, if the first statement of a constructor is a super call - // (which is should normally be), then we need to put the statements - // after that. - for(JilMethod m : parent.methods()) { - if(m.name().equals(parent.name())) { - List body = m.body(); - JilExpr.Deref df = new JilExpr.Deref(new JilExpr.Variable("this", - parent.type()), d.name(), isStatic, fieldT, d - .attributes()); - JilStmt.Assign ae = new JilStmt.Assign(df, tmp.first(), d - .attributes()); - int sc = findSuperCall(body)+1; - body.add(sc,ae); - body.addAll(sc,tmp.second()); - } - } - } - } - } - - protected void doInitialiserBlock(Decl.InitialiserBlock d, JilClass parent) { - ArrayList stmts = new ArrayList(); - - for (Stmt s : d.statements()) { - stmts.addAll(doStatement(s)); - } - - // This is a non-static initialiser block. Therefore, we - // need to add it to the beginning of all constructors. One issue is - // that, if the first statement of a constructor is a super call - // (which is should normally be), then we need to put the statements - // after that. - for(JilMethod m : parent.methods()) { - if(m.name().equals(parent.name())) { - List body = m.body(); - body.addAll(findSuperCall(body)+1,stmts); - } - } - } - - protected void doStaticInitialiserBlock(Decl.StaticInitialiserBlock d, JilClass parent) { - ArrayList stmts = new ArrayList(); - for (Stmt s : d.statements()) { - stmts.addAll(doStatement(s)); - } - - JilMethod m = createStaticInitialiser(parent); - // Again, add at beginning to ensure the right order. - m.body().addAll(0,stmts); - } - - protected List doStatement(Stmt e) { - try { - if(e instanceof Stmt.SynchronisedBlock) { - return doSynchronisedBlock((Stmt.SynchronisedBlock)e); - } else if(e instanceof Stmt.TryCatchBlock) { - return doTryCatchBlock((Stmt.TryCatchBlock)e); - } else if(e instanceof Stmt.Block) { - return doBlock((Stmt.Block)e); - } else if(e instanceof Stmt.VarDef) { - return doVarDef((Stmt.VarDef) e); - } else if(e instanceof Stmt.AssignmentOp) { - return doAssignmentOp((Stmt.AssignmentOp) e).second(); - } else if(e instanceof Stmt.Assignment) { - return doAssignment((Stmt.Assignment) e).second(); - } else if(e instanceof Stmt.Return) { - return doReturn((Stmt.Return) e); - } else if(e instanceof Stmt.Throw) { - return doThrow((Stmt.Throw) e); - } else if(e instanceof Stmt.Assert) { - return doAssert((Stmt.Assert) e); - } else if(e instanceof Stmt.Break) { - return doBreak((Stmt.Break) e); - } else if(e instanceof Stmt.Continue) { - return doContinue((Stmt.Continue) e); - } else if(e instanceof Stmt.Label) { - return doLabel((Stmt.Label) e); - } else if(e instanceof Stmt.If) { - return doIf((Stmt.If) e); - } else if(e instanceof Stmt.For) { - return doFor((Stmt.For) e); - } else if(e instanceof Stmt.ForEach) { - return doForEach((Stmt.ForEach) e); - } else if(e instanceof Stmt.While) { - return doWhile((Stmt.While) e); - } else if(e instanceof Stmt.DoWhile) { - return doDoWhile((Stmt.DoWhile) e); - } else if(e instanceof Stmt.Switch) { - return doSwitch((Stmt.Switch) e); - } else if(e instanceof Expr.Invoke) { - Pair> r = doInvoke((Expr.Invoke) e); - r.second().add((JilExpr.Invoke) r.first()); - return r.second(); - } else if(e instanceof Expr.New) { - Pair> r = doNew((Expr.New) e); - r.second().add((JilExpr.New) r.first()); - return r.second(); - } else if(e instanceof Decl.JavaClass) { - doClass((Decl.JavaClass)e); - return new ArrayList(); - } else if(e instanceof Stmt.PrePostIncDec) { - Pair> r = doExpression((Stmt.PrePostIncDec) e); - return r.second(); - } - } catch(Exception ex) { - internal_error(e,ex); - } - - if (e != null) { - syntax_error("Invalid statement encountered: " + e.getClass(), e); - } - - return new ArrayList(); - } - - protected List doBlock(Stmt.Block block) { - ArrayList r = new ArrayList(); - if(block != null) { - // now process every statement in this block. - for(Stmt s : block.statements()) { - r.addAll(doStatement(s)); - } - } - return r; - } - - protected List doCatchBlock(Stmt.CatchBlock block) { - ArrayList r = new ArrayList(); - if(block != null) { - // now process every statement in this block. - for(Stmt s : block.statements()) { - r.addAll(doStatement(s)); - } - } - return r; - } - - protected List doSynchronisedBlock(Stmt.SynchronisedBlock block) { - ArrayList r = new ArrayList(); - r.addAll(doBlock(block)); - doExpression(block.expr()); - // need to add synch enter and leave here ? - return r; - } - - protected int tryexit_label = 0; - protected int tryhandler_label = 0; - - protected List doTryCatchBlock(Stmt.TryCatchBlock block) { - String exitLab = "tryexit" + tryexit_label++; - ArrayList r = new ArrayList(); - r.addAll(doBlock(block)); - - int i = tryhandler_label; - for(Stmt.CatchBlock cb : block.handlers()) { - String handlerLab = "tryhandler" + i++; - for(int j=0;j!=r.size();++j) { - JilStmt s = r.get(j); - Type.Clazz ct = cb.type().attribute(Type.Clazz.class); - if(!(s instanceof JilStmt.Goto || s instanceof JilStmt.Label)) { - // This is very pedantic. Almost certainly, we could rule out - // certain kinds of exception branches. However, it's difficult - // to do this properly and, therefore, we remain quite - // conservative at this point. See the following paper for an - // excellent discussion of this: - // - // "Improving the precision and correctness of exception - // analysis in Soot", John Jorgensen, 2003. - r.set(j,s.addException(ct, handlerLab)); - } - } - } - - r.add(new JilStmt.Goto(exitLab,block.attributes())); - - for(Stmt.CatchBlock cb : block.handlers()) { - String handlerLab = "tryhandler" + tryhandler_label++; - Type.Clazz ct = cb.type().attribute(Type.Clazz.class); - r.add(new JilStmt.Label(handlerLab,cb.attributes())); - r.add(new JilStmt.Assign(new JilExpr.Variable(cb.variable(), ct, cb - .attributes()), new JilExpr.Variable("$", ct, - cb.attributes()))); - // At this point, we have a slightly interesting problem. We need to - // search - r.addAll(doCatchBlock(cb)); - r.add(new JilStmt.Goto(exitLab,cb.attributes())); - } - - r.add(new JilStmt.Label(exitLab,block.attributes())); - - List finallyBlock = doBlock(block.finaly()); - if(!finallyBlock.isEmpty()) { - // Now, we add the finally block. This is done in a separate method - // because it's actually quite challenging. - addFinallyBlock(r,finallyBlock); - } - - return r; - } - - protected static int finallyex_label = 0; - - protected void addFinallyBlock(List block, List finallyBlk) { - // So, to add the finally block properly, we need to iterate through the - // block and find any situations where we exit the block. This includes - // return statements, branches to labels which are not in the block, - // exceptional flow, and simply executing the last statement of the - // block. - - // First, collect all local labels, so we can distinguish local from - // non-local branches. - HashSet labels = new HashSet(); - for(JilStmt s : block) { - if(s instanceof JilStmt.Label) { - JilStmt.Label l = (JilStmt.Label) s; - labels.add(l.label()); - } - } - - String exceptionLabel = "finally" + finallyex_label++; - - // Now, iterate the block looking for non-local branches. - boolean lastNonBranch = false; - for(int i=0;i!=block.size();++i) { - lastNonBranch = true; - JilStmt stmt = block.get(i); - if(stmt instanceof JilStmt.Return) { - block.addAll(i,copyBlock(finallyBlk)); - i += finallyBlk.size(); - lastNonBranch = false; - } else if(stmt instanceof JilStmt.Goto) { - JilStmt.Goto g = (JilStmt.Goto) stmt; - if(!labels.contains(g.label())) { - // this is a non-local branch statement. - block.addAll(i,copyBlock(finallyBlk)); - i += finallyBlk.size(); - } - lastNonBranch = false; - } else if(stmt instanceof JilStmt.IfGoto) { - JilStmt.IfGoto g = (JilStmt.IfGoto) stmt; - - if(!labels.contains(g.label())) { - // houston, we have a problem. I'm not really sure how we - // can actually get here though. - throw new RuntimeException( - "An internal failure has occurred in JilBuilder. Please report it, along with the generating code!"); - } - } else if(!(stmt instanceof JilStmt.Label)){ - // Add the default exceptional edge for exceptional flow. - stmt = stmt.addException(Types.JAVA_LANG_THROWABLE, exceptionLabel); - block.set(i, stmt); - - // just for the (unlikely) case when last statement is a throw. - if(stmt instanceof JilStmt.Throw) { lastNonBranch = false; } - } - } - - String exitLabel = "finallyexit" + (finallyex_label-1); - if(lastNonBranch) { - block.addAll(finallyBlk); - block.add(new JilStmt.Goto(exitLabel)); - } - - // Now, process exceptional exit - block.add(new JilStmt.Label(exceptionLabel)); - block.add(new JilStmt.Assign(new JilExpr.Variable(exceptionLabel + "$", - Types.JAVA_LANG_THROWABLE), new JilExpr.Variable("$", - Types.JAVA_LANG_THROWABLE))); - block.addAll(copyBlock(finallyBlk)); - block.add(new JilStmt.Throw(new JilExpr.Variable(exceptionLabel + "$", - Types.JAVA_LANG_THROWABLE))); - - if(lastNonBranch) { - block.add(new JilStmt.Label(exitLabel)); - } - } - - protected static int copy_label = 0; - protected List copyBlock(List block) { - // The purpose of this method is to create a copy of the block. - // In particular, labels within the block must be copied. - - HashSet labels = new HashSet(); - for(JilStmt stmt : block) { - if(stmt instanceof JilStmt.Label) { - JilStmt.Label lab = (JilStmt.Label) stmt; - labels.add(lab.label()); - } - } - - ArrayList nblock = new ArrayList(); - for(JilStmt stmt : block) { - ArrayList> nexceptions = new ArrayList(); - - for(Pair p : stmt.exceptions()) { - String target = p.second(); - if(labels.contains(target)) { - target = target + "$copy" + copy_label; - } - nexceptions.add(new Pair(p.first(),target)); - } - - if(stmt instanceof JilStmt.Goto) { - JilStmt.Goto gto = (JilStmt.Goto) stmt; - String target = gto.label(); - if(labels.contains(target)) { - target = target + "$copy" + copy_label; - } - nblock.add(new JilStmt.Goto(target, nexceptions, - new ArrayList(stmt.attributes()))); - } else if(stmt instanceof JilStmt.IfGoto) { - JilStmt.IfGoto igto = (JilStmt.IfGoto) stmt; - String target = igto.label(); - if(labels.contains(target)) { - target = target + "$copy" + copy_label; - } - nblock.add(new JilStmt.IfGoto(igto.condition(), target, - nexceptions, - new ArrayList(stmt.attributes()))); - } else if(stmt instanceof JilStmt.Switch) { - JilStmt.Switch swt = (JilStmt.Switch) stmt; - ArrayList> ncases = new ArrayList(); - for(Pair c : swt.cases()) { - String target = c.second(); - if(labels.contains(target)) { - target = target + "$copy" + copy_label; - } - ncases.add(new Pair(c.first(),target)); - } - // And, don't forget the default label! - String deftarget = swt.defaultLabel(); - if(labels.contains(deftarget)) { - deftarget = deftarget + "$copy" + copy_label; - } - nblock.add(new JilStmt.Switch(swt.condition(), ncases, - deftarget, new ArrayList(swt.attributes()))); - } else if(stmt instanceof JilStmt.Label) { - JilStmt.Label lab = (JilStmt.Label) stmt; - String target = lab.label(); - if(labels.contains(target)) { - target = target + "$copy" + copy_label; - } - nblock.add(new JilStmt.Label(target, - new ArrayList(stmt.attributes()))); - } else { - // there is a bug relating to switch statements. - JilStmt nstmt = stmt.clearAddExceptions(nexceptions); - nblock.add(nstmt); - } - } - - copy_label = copy_label + 1; - - return nblock; - } - - protected List doVarDef(Stmt.VarDef def) { - Type type = def.type().attribute(Type.class); - List> defs = def.definitions(); - ArrayList r = new ArrayList(); - for(int i=0;i!=defs.size();++i) { - Triple d = defs.get(i); - Type nt = type; - - for(int j=0;j!=d.second();++j) { - nt = new Type.Array(nt); - } - - if(d.third() != null) { - Pair> e = doExpression(d.third()); - r.addAll(e.second()); - JilExpr lhs = new JilExpr.Variable(d.first(), nt, def - .attributes()); - r.add(new JilStmt.Assign(lhs, e.first())); - } - } - - return r; - } - - protected Pair> doAssignmentOp(Stmt.AssignmentOp def) { - ArrayList r = new ArrayList(); - - Pair> lhs = doExpression(def.lhs()); - Pair> rhs = doExpression(new Expr.BinOp( - def.op(), def.lhs(), def.rhs(), def.attributes())); - - Type lhs_t = lhs.first().type(); - - if (rhs.second().isEmpty() || lhs.first() instanceof JilExpr.Variable) { - JilExpr tmpVar = new JilExpr.Variable(getTempVar(), lhs_t, def - .attributes()); - r.add(new JilStmt.Assign(tmpVar, lhs.first(), def.attributes())); - r.addAll(rhs.second()); - r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def - .attributes())); - return new Pair(lhs.first(), r); - } else if(lhs.first() instanceof JilExpr.Deref) { - JilExpr.Deref deref1 = (JilExpr.Deref) lhs.first(); - - if(deref1.target() instanceof JilExpr.ClassVariable) { - // Slightly awkward case, since this corresponds to a static - // class access and, hence, there are no possible side-effects. - // However, we cannot assign the target to a tmp variable, since - // it will not compile down to anything in practice. - r.addAll(rhs.second()); - r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def - .attributes())); - return new Pair(lhs.first(), r); - } else { - - JilExpr tmpVar = new JilExpr.Variable(getTempVar(), deref1.target() - .type(), def.attributes()); - r.add(new JilStmt.Assign(tmpVar, deref1.target(), def.attributes())); - r.addAll(rhs.second()); - JilExpr.Deref deref2 = new JilExpr.Deref(tmpVar, deref1.name(), - deref1.isStatic(), deref1.type(), deref1.attributes()); - r - .add(new JilStmt.Assign(deref2, rhs.first(), def - .attributes())); - return new Pair(deref2, r); - } - } else if(lhs.first() instanceof JilExpr.ArrayIndex) { - JilExpr.ArrayIndex aindex1 = (JilExpr.ArrayIndex) lhs.first(); - - JilExpr targetVar = new JilExpr.Variable(getTempVar(), aindex1 - .target().type(), def.attributes()); - JilExpr indexVar = new JilExpr.Variable(getTempVar(), aindex1 - .index().type(), def.attributes()); - r.add(new JilStmt.Assign(targetVar, aindex1.target(), def - .attributes())); - r.add(new JilStmt.Assign(indexVar, aindex1.index(), def - .attributes())); - r.addAll(rhs.second()); - JilExpr.ArrayIndex aindex2 = new JilExpr.ArrayIndex(targetVar, - indexVar, aindex1.type(), aindex1.attributes()); - r - .add(new JilStmt.Assign(aindex2, rhs.first(), def - .attributes())); - return new Pair(aindex2, r); - } else { - syntax_error( - "unknown l-value encountered on assignment with side-effects", - def); - return null; // unreachable. - } - } - - protected Pair> doAssignment(Stmt.Assignment def) { - ArrayList r = new ArrayList(); - Pair> lhs = doExpression(def.lhs()); - Pair> rhs = doExpression(def.rhs()); - r.addAll(lhs.second()); - - if (rhs.second().isEmpty() || lhs.first() instanceof JilExpr.Variable) { - // easy case, no side-effects in rhs or trivial assignment. - r.addAll(rhs.second()); - r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def - .attributes())); - return new Pair(lhs.first(), r); - } else if(lhs.first() instanceof JilExpr.Deref) { - - JilExpr.Deref deref1 = (JilExpr.Deref) lhs.first(); - - if(deref1.target() instanceof JilExpr.ClassVariable) { - // Slightly awkward case, since this corresponds to a static - // class access and, hence, there are no possible side-effects. - // However, we cannot assign the target to a tmp variable, since - // it will not compile down to anything in practice. - r.addAll(rhs.second()); - r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def - .attributes())); - return new Pair(lhs.first(), r); - } else { - JilExpr tmpVar = new JilExpr.Variable(getTempVar(), deref1.target() - .type(), def.attributes()); - r.add(new JilStmt.Assign(tmpVar, deref1.target(), def.attributes())); - r.addAll(rhs.second()); - JilExpr.Deref deref2 = new JilExpr.Deref(tmpVar, deref1.name(), - deref1.isStatic(), deref1.type(), deref1.attributes()); - r.add(new JilStmt.Assign(deref2, rhs.first(), def - .attributes())); - return new Pair(deref2, r); - } - } else if(lhs.first() instanceof JilExpr.ArrayIndex) { - JilExpr.ArrayIndex aindex1 = (JilExpr.ArrayIndex) lhs.first(); - - JilExpr targetVar = new JilExpr.Variable(getTempVar(), aindex1 - .target().type(), def.attributes()); - JilExpr indexVar = new JilExpr.Variable(getTempVar(), aindex1 - .index().type(), def.attributes()); - r.add(new JilStmt.Assign(targetVar, aindex1.target(), def - .attributes())); - r.add(new JilStmt.Assign(indexVar, aindex1.index(), def - .attributes())); - r.addAll(rhs.second()); - JilExpr.ArrayIndex aindex2 = new JilExpr.ArrayIndex(targetVar, - indexVar, aindex1.type(), aindex1.attributes()); - r.add(new JilStmt.Assign(aindex2, rhs.first(), def.attributes())); - return new Pair(aindex2, r); - } else { - syntax_error( - "unknown l-value encountered on assignment with side-effects", - def); - return null; // unreachable. - } - } - - protected List doReturn(Stmt.Return ret) { - ArrayList r = new ArrayList(); - if(ret.expr() != null) { - Pair> expr = doExpression(ret.expr()); - r.addAll(expr.second()); - r.add(new JilStmt.Return(expr.first(),ret.attributes())); - } else { - r.add(new JilStmt.Return(null,ret.attributes())); - } - return r; - } - - protected List doThrow(Stmt.Throw ret) { - ArrayList r = new ArrayList(); - Pair> expr = doExpression(ret.expr()); - r.addAll(expr.second()); - r.add(new JilStmt.Throw(expr.first(),ret.attributes())); - return r; - } - - protected List doAssert(Stmt.Assert ret) { - ArrayList r = new ArrayList(); - Pair> expr = doExpression(ret.expr()); - - // need to do some real code generation here. - - return r; - } - - protected List doBreak(Stmt.Break brk) { - ArrayList r = new ArrayList(); - - if(brk.label() == null) { - SwitchScope ls = (SwitchScope) findEnclosingScope(SwitchScope.class); - if(ls == null) { - syntax_error("break used outside of loop or switch",brk); - } - r.add(new JilStmt.Goto(ls.exitLab,brk.attributes())); - } else { - String label = brk.label(); - LoopScope lastLoop = null; - for(int i=scopes.size()-1;i>=0;--i) { - Scope s = scopes.get(i); - if(s instanceof LoopScope) { - lastLoop = (LoopScope) s; - } else if(s instanceof LabelScope) { - LabelScope lab = (LabelScope) s; - if(lab.label.equals(label)) { - break; - } - } - } - if(lastLoop == null) { - syntax_error("no enclosing loop instance",brk); - } - r.add(new JilStmt.Goto(lastLoop.exitLab,brk.attributes())); - } - - return r; - } - - protected List doContinue(Stmt.Continue brk) { - ArrayList r = new ArrayList(); - - if(brk.label() == null) { - LoopScope ls = (LoopScope) findEnclosingScope(LoopScope.class); - - if(ls == null) { - syntax_error("continue used outside loop",brk); - } - - r.add(new JilStmt.Goto(ls.continueLab,brk.attributes())); - } else { - String label = brk.label(); - LoopScope lastLoop = null; - for(int i=scopes.size()-1;i>=0;--i) { - Scope s = scopes.get(i); - if(s instanceof LoopScope) { - lastLoop = (LoopScope) s; - } else if(s instanceof LabelScope) { - LabelScope lab = (LabelScope) s; - if(lab.label.equals(label)) { - break; - } - } - } - if(lastLoop == null) { - syntax_error("no enclosing loop instance",brk); - } - r.add(new JilStmt.Goto(lastLoop.continueLab,brk.attributes())); - } - - return r; - } - - protected List doLabel(Stmt.Label lab) { - scopes.push(new LabelScope(lab.label())); - List r = doStatement(lab.statement()); - scopes.pop(); - r.add(0, new JilStmt.Label(lab.label(), lab.attributes())); - return r; - } - - static protected int ifexit_label = 0; - static protected int iftrue_label = 0; - - protected List doIf(Stmt.If stmt) { - ArrayList r = new ArrayList(); - - Pair> cond = doExpression(stmt.condition()); - List tbranch = doStatement(stmt.trueStatement()); - List fbranch = doStatement(stmt.falseStatement()); - - r.addAll(cond.second()); - - if(stmt.falseStatement() == null) { - r.add(new JilStmt.IfGoto( - new JilExpr.UnOp(cond.first(), JilExpr.UnOp.NOT, T_BOOL, - stmt.condition().attributes()), "ifexit" + ifexit_label, stmt.attributes())); - r.addAll(tbranch); - } else if(stmt.trueStatement() == null) { - r.add(new JilStmt.IfGoto(cond.first(),"ifexit" + ifexit_label,stmt.attributes())); - r.addAll(fbranch); - } else { - r.add(new JilStmt.IfGoto(cond.first(),"iftrue" + iftrue_label,stmt.attributes())); - r.addAll(fbranch); - r.add(new JilStmt.Goto("ifexit" + ifexit_label,stmt.attributes())); - r.add(new JilStmt.Label("iftrue" + iftrue_label++,stmt.attributes())); - r.addAll(tbranch); - } - - r.add(new JilStmt.Label("ifexit" + ifexit_label++,stmt.attributes())); - return r; - - } - - static protected int whileheader_label = 0; - static protected int whileexit_label = 0; - - protected List doWhile(Stmt.While stmt) { - String headerLab = "whileheader" + whileheader_label++; - String exitLab = "whileexit" + whileexit_label++; - ArrayList r = new ArrayList(); - - r.add(new JilStmt.Label(headerLab, stmt - .attributes())); - Pair> cond = doExpression(stmt.condition()); - r.addAll(cond.second()); - r.add(new JilStmt.IfGoto(new JilExpr.UnOp(cond.first(), JilExpr.UnOp.NOT, - T_BOOL, stmt.condition().attributes()), exitLab, stmt - .attributes())); - scopes.push(new LoopScope(headerLab,exitLab)); - r.addAll(doStatement(stmt.body())); - scopes.pop(); - r.add(new JilStmt.Goto(headerLab, stmt - .attributes())); - r.add(new JilStmt.Label(exitLab, stmt - .attributes())); - - return r; - } - - static protected int dowhileheader_label = 0; - static protected int dowhileexit_label = 0; - - protected List doDoWhile(Stmt.DoWhile stmt) { - String headerLab = "dowhileheader" + dowhileheader_label++; - String exitLab = "dowhileexit" + dowhileexit_label++; - - ArrayList r = new ArrayList(); - - r.add(new JilStmt.Label(headerLab, stmt - .attributes())); - scopes.push(new LoopScope(headerLab,exitLab)); - r.addAll(doStatement(stmt.body())); - scopes.pop(); - Pair> cond = doExpression(stmt.condition()); - r.addAll(cond.second()); - r.add(new JilStmt.IfGoto(cond.first(), headerLab, stmt.attributes())); - r.add(new JilStmt.Label(exitLab, stmt - .attributes())); - return r; - } - - static protected int forheader_label = 0; - static protected int forinc_label = 0; - static protected int forexit_label = 0; - - protected List doFor(Stmt.For stmt) { - String headerLab = "forheader" + forheader_label++; - String exitLab = "forexit" + forexit_label++; - String incLab = "forinc" + forinc_label++; - - ArrayList r = new ArrayList(); - - if(stmt.initialiser() != null) { - r.addAll(doStatement(stmt.initialiser())); - } - - r.add(new JilStmt.Label(headerLab, stmt - .attributes())); - - if(stmt.condition() != null) { - Pair> cond = doExpression(stmt.condition()); - r.addAll(cond.second()); - r.add(new JilStmt.IfGoto(new JilExpr.UnOp(cond.first(), JilExpr.UnOp.NOT, - T_BOOL, stmt.condition().attributes()), exitLab, - stmt.attributes())); - } - - if(stmt.increment() != null) { - scopes.push(new LoopScope(incLab,exitLab)); - } else { - // this is a minor optimisation in the case that no increment is - // provided. - scopes.push(new LoopScope(headerLab,exitLab)); - } - r.addAll(doStatement(stmt.body())); - scopes.pop(); - - if(stmt.increment() != null) { - r.add(new JilStmt.Label(incLab)); - r.addAll(doStatement(stmt.increment())); - } - - r.add(new JilStmt.Goto(headerLab, stmt - .attributes())); - r.add(new JilStmt.Label(exitLab, stmt - .attributes())); - - return r; - } - - static protected int forallheader_label = 0; - static protected int forallexit_label = 0; - static protected int foralliter_label = 0; - static protected int forallinc_label = 0; - - protected List doForEach(Stmt.ForEach stmt) { - String headerLab = "forallheader" + forallheader_label++; - String exitLab = "forallexit" + forallexit_label++; - String iterLab = "foralliter" + foralliter_label++; - String incLab = "forallinc" + forallinc_label++; - - ArrayList stmts = new ArrayList(); - - Pair> src = doExpression(stmt.source()); - JilExpr.Variable loopVar = new JilExpr.Variable(stmt.var(), stmt.type() - .attribute(Type.class), stmt.attributes()); - - Type srcType = src.first().type(); - - stmts.addAll(src.second()); - JilExpr.Variable iter; - - if (srcType instanceof Type.Array) { - iter = new JilExpr.Variable(iterLab, T_INT); - stmts - .add(new JilStmt.Assign(iter, new JilExpr.Int(0), stmt - .attributes())); - } else { - // the following needs to be expanded upon, so as to include generic - // information on the iterator. The easiest way to do this is to - // look up the iterator() method in the src class, and use it's - // return type. - iter = new JilExpr.Variable(iterLab, JAVA_UTIL_ITERATOR); - - stmts.add(new JilStmt.Assign(iter, new JilExpr.Invoke(src.first(), - "iterator", new ArrayList(), new Type.Function( - JAVA_UTIL_ITERATOR), JAVA_UTIL_ITERATOR), stmt - .attributes())); - } - - stmts.add(new JilStmt.Label(headerLab, stmt - .attributes())); - - // Second, do condition - - if (srcType instanceof Type.Array) { - Type.Array arrType = (Type.Array) srcType; - JilExpr arrlength = new JilExpr.Deref(src.first(),"length",false,T_INT, stmt - .attributes()); - JilExpr gecmp = new JilExpr.BinOp(iter,arrlength,JilExpr.BinOp.GTEQ,T_BOOL, stmt - .attributes()); - stmts.add(new JilStmt.IfGoto(gecmp,exitLab, stmt - .attributes())); - - stmts.add(new JilStmt.Assign(loopVar, implicitCast(new JilExpr.ArrayIndex(src.first(), - iter, arrType.element()),loopVar.type()))); - } else { - JilExpr hasnext = new JilExpr.Invoke(iter, "hasNext", - new ArrayList(), new Type.Function(T_BOOL), - T_BOOL, stmt.attributes()); - stmts.add(new JilStmt.IfGoto(new JilExpr.UnOp(hasnext, JilExpr.UnOp.NOT, - T_BOOL), exitLab)); - - JilExpr cast; - if(loopVar.type() instanceof Type.Primitive) { - // In this case, we have to deal with casting and implicit - // conversion. - JilExpr next = new JilExpr.Invoke(iter, "next", new ArrayList(), - new Type.Function(JAVA_LANG_OBJECT), - boxedType((Type.Primitive) loopVar.type()), stmt.attributes()); - - cast = implicitCast(next, loopVar.type()); - } else { - JilExpr next = new JilExpr.Invoke(iter, "next", new ArrayList(), - new Type.Function(JAVA_LANG_OBJECT), - loopVar.type(), stmt.attributes()); - cast = new JilExpr.Cast(next, loopVar.type()); - } - stmts.add(new JilStmt.Assign(loopVar, cast, stmt.attributes())); - } - - // Third, do body - - if(srcType instanceof Type.Array) { - scopes.push(new LoopScope(incLab,exitLab)); - } else { - scopes.push(new LoopScope(headerLab,exitLab)); - } - stmts.addAll(doStatement(stmt.body())); - scopes.pop(); - - // Fourth, do increment - if (srcType instanceof Type.Array) { - stmts.add(new JilStmt.Label(incLab)); - forallinc_label++; - JilExpr.BinOp rhs = new JilExpr.BinOp(iter, new JilExpr.Int(1), - JilExpr.BinOp.ADD, T_INT, stmt.attributes()); - stmts.add(new JilStmt.Assign(iter,rhs,stmt.attributes())); - } - - stmts.add(new JilStmt.Goto(headerLab,stmt.attributes())); - - stmts.add(new JilStmt.Label(exitLab, stmt - .attributes())); - - return stmts; - } - - protected int switchcase_label = 0; - protected int switchexit_label = 0; - protected List doSwitch(Stmt.Switch sw) { - String switchExitLab = "switchexit" + switchexit_label++; - ArrayList r = new ArrayList(); - - Pair> cond = doExpression(sw.condition()); - ArrayList> cases = new ArrayList(); - ArrayList caseStmts = new ArrayList(); - String defaultLab = null; - for(Stmt.Case c : sw.cases()) { - Pair> ce = doExpression(c.condition()); - String caseLab = "switchcase" + switchcase_label++; - caseStmts.add(new JilStmt.Label(caseLab)); - scopes.push(new SwitchScope(switchExitLab)); - for(Stmt s : c.statements()) { - caseStmts.addAll(doStatement(s)); - } - scopes.pop(); - if(c.condition() != null) { - JilExpr ce_first = ce.first(); - if(ce_first instanceof JilExpr.Number) { - cases.add(new Pair(ce_first,caseLab)); - } else { - syntax_error("constant expression required",c.condition()); - } - } else { - defaultLab = caseLab; - } - } - if(defaultLab == null) { defaultLab = switchExitLab; } - r.addAll(cond.second()); - r.add(new JilStmt.Switch(cond.first(),cases,defaultLab)); - r.addAll(caseStmts); - r.add(new JilStmt.Label(switchExitLab)); - return r; - } - - protected Pair> doExpression(Expr e) { - try { - if(e instanceof Value.Bool) { - return doBoolVal((Value.Bool)e); - } else if(e instanceof Value.Byte) { - return doByteVal((Value.Byte)e); - } else if(e instanceof Value.Char) { - return doCharVal((Value.Char)e); - } else if(e instanceof Value.Short) { - return doShortVal((Value.Short)e); - } else if(e instanceof Value.Int) { - return doIntVal((Value.Int)e); - } else if(e instanceof Value.Long) { - return doLongVal((Value.Long)e); - } else if(e instanceof Value.Float) { - return doFloatVal((Value.Float)e); - } else if(e instanceof Value.Double) { - return doDoubleVal((Value.Double)e); - } else if(e instanceof Value.String) { - return doStringVal((Value.String)e); - } else if(e instanceof Value.Null) { - return doNullVal((Value.Null)e); - } else if(e instanceof Value.TypedArray) { - return doTypedArrayVal((Value.TypedArray)e); - } else if(e instanceof Value.Array) { - return doArrayVal((Value.Array)e); - } else if(e instanceof Value.Class) { - return doClassVal((Value.Class) e); - } else if(e instanceof Expr.LocalVariable) { - return doLocalVariable((Expr.LocalVariable)e); - } else if(e instanceof Expr.NonLocalVariable) { - return doNonLocalVariable((Expr.NonLocalVariable)e); - } else if(e instanceof Expr.ClassVariable) { - return doClassVariable((Expr.ClassVariable)e); - } else if(e instanceof Expr.UnOp) { - return doUnOp((Expr.UnOp)e); - } else if(e instanceof Expr.BinOp) { - return doBinOp((Expr.BinOp)e); - } else if(e instanceof Expr.TernOp) { - return doTernOp((Expr.TernOp)e); - } else if(e instanceof Expr.Cast) { - return doCast((Expr.Cast)e); - } else if(e instanceof Expr.Convert) { - return doConvert((Expr.Convert)e); - } else if(e instanceof Expr.InstanceOf) { - return doInstanceOf((Expr.InstanceOf)e); - } else if(e instanceof Expr.Invoke) { - return doInvoke((Expr.Invoke) e); - } else if(e instanceof Expr.New) { - return doNew((Expr.New) e); - } else if(e instanceof Expr.ArrayIndex) { - return doArrayIndex((Expr.ArrayIndex) e); - } else if(e instanceof Expr.Deref) { - return doDeref((Expr.Deref) e); - } else if(e instanceof Stmt.AssignmentOp) { - // force brackets - return doAssignmentOp((Stmt.AssignmentOp) e); - } else if(e instanceof Stmt.Assignment) { - // force brackets - return doAssignment((Stmt.Assignment) e); - } - } catch(Exception ex) { - internal_error(e,ex); - } - - if(e != null) { - syntax_error("Invalid expression encountered: " - + e.getClass(),e); - } - - return null; - } - - protected Pair> doDeref(Expr.Deref e) - throws ClassNotFoundException, FieldNotFoundException { - Pair> target = doExpression(e.target()); - Type type = e.attribute(Type.class); - Type.Reference _targetT = e.target().attribute(Type.Reference.class); - - if(_targetT instanceof Type.Clazz) { - Type.Clazz targetT = (Type.Clazz) _targetT; - if(e.name().equals("this")) { - // This is a special case, where we're trying to look up a field - // called "this". No such field can exist! What this means is that - // we're inside an inner class, and we're trying to access the this - // pointer of an enclosing class. - ClassScope cs = (ClassScope) findEnclosingScope(ClassScope.class); - int level = cs.type.components().size() - targetT.components().size(); - JilExpr r = new JilExpr.Variable("this",cs.type); - Type.Clazz t = cs.type; - while(level > 0) { - t = parentType(t); - r = new JilExpr.Deref(r,"this$0",false,t); - level = level - 1; - } - return new Pair(r, new ArrayList()); - } else { - Triple r = types - .resolveField(targetT, e.name(), loader); - - return new Pair>(new JilExpr.Deref(target.first(), e - .name(),r.second().isStatic(), type, e.attributes()), - target.second()); - } - } else if(_targetT instanceof Type.Array && e.name().equals("length")) { - return new Pair>(new JilExpr.Deref(target.first(), e - .name(), false, type, e.attributes()), - target.second()); - } else { - syntax_error("cannot dereference type " + _targetT,e); - } - return null; // dead code - } - - protected Pair> doArrayIndex(Expr.ArrayIndex e) { - Pair> target = doExpression(e.target()); - Pair> index = doExpression(e.index()); - Type type = e.attribute(Type.class); - - List r = target.second(); - - if(index.second().isEmpty()) { - // easy case when no side-effects in index expression. - - return new Pair>(new JilExpr.ArrayIndex(target.first(), - index.first(), type, e.attributes()), r); - } else { - // harder case, there are side-effects in the index expression. - JilExpr.Variable tmpVar = new JilExpr.Variable(getTempVar(), - target.first().type(), e.attributes()); - r.add(new JilStmt.Assign(tmpVar,target.first(),e.attributes())); - r.addAll(index.second()); - return new Pair>(new JilExpr.ArrayIndex(tmpVar, - index.first(), type, e.attributes()), r); - } - } - - protected Pair> doNew(Expr.New e) { - // Second, recurse through any parameters supplied ... - ArrayList r = new ArrayList(); - Type.Reference type = e.type().attribute(Type.Reference.class); - - MethodInfo mi = e.attribute(MethodInfo.class); - - Pair> context = doExpression(e.context()); - Pair,List> params = doExpressionList(e.parameters()); - - if(context != null) { - r.addAll(context.second()); - } - - r.addAll(params.second()); - - if(mi != null) { - return new Pair>(new JilExpr.New(type, params - .first(), mi.type, e.attributes()), r); - } else if(type instanceof Type.Array){ - return new Pair>(new JilExpr.New(type, - params.first(), new Type.Function(Types.T_VOID), e - .attributes()), r); - } else { - syntax_error("internal failure --- unable to find method information",e); - return null; - } - } - - protected Pair> doInvoke(Expr.Invoke e) { - ArrayList r = new ArrayList(); - Type type = e.attribute(Type.class); - MethodInfo mi = e.attribute(MethodInfo.class); - - Pair> target = doExpression(e.target()); - r.addAll(target.second()); - - Pair,List> params = doExpressionList(e.parameters()); - r.addAll(params.second()); - - JilExpr rec = target.first(); - - if (rec instanceof JilExpr.ClassVariable) { - return new Pair>(new JilExpr.Invoke(target.first(), e - .name(), params.first(), mi.type, type, e - .attributes()), r); - } else if (rec instanceof JilExpr.Variable - && ((JilExpr.Variable) rec).value().equals("super")) { - return new Pair>(new JilExpr.SpecialInvoke(target - .first(), e.name(), params.first(), mi.type, type, e - .attributes()), r); - } else if (rec.type() instanceof Type.Array && e.name().equals("clone")) { - // this is a special case for array cloning. It should really be in - // TypePropagation, but the problem is that implicit cast needs to - // be on the returned expression, which type propagation does not - // support. - JilExpr ie = new JilExpr.Invoke(target.first(), e.name(), params - .first(), mi.type, type, e.attributes()); - ie = new JilExpr.Cast(ie, rec.type()); - return new Pair>(ie, r); - } else { - return new Pair>(new JilExpr.Invoke(target.first(), e - .name(), params.first(), mi.type, type, e - .attributes()), r); - } - } - - protected Pair> doInstanceOf(Expr.InstanceOf e) { - Pair> lhs = doExpression(e.lhs()); - Type type = e.attribute(Type.class); - Type.Reference rhs = e.rhs().attribute(Type.Reference.class); - return new Pair>(new JilExpr.InstanceOf(lhs.first(), rhs, - type, e.attributes()), lhs.second()); - } - - protected Pair> doCast(Expr.Cast e) { - Pair> expr = doExpression(e.expr()); - Type type = e.attribute(Type.class); - return new Pair>(new JilExpr.Cast(expr.first(), - type, e.attributes()), expr.second()); - } - - protected Pair> doConvert(Expr.Convert e) { - Pair> expr = doExpression(e.expr()); - Type.Primitive type = e.attribute(Type.Primitive.class); - return new Pair>(new JilExpr.Convert(type, expr.first(), - e.attributes()), expr.second()); - } - - protected Pair> doBoolVal(Value.Bool e) { - return new Pair>(new JilExpr.Bool(e.value()), - new ArrayList()); - } - - protected Pair> doCharVal(Value.Char e) { - return new Pair>(new JilExpr.Char(e.value()), new ArrayList()); - } - - protected Pair> doByteVal(Value.Byte e) { - return new Pair>(new JilExpr.Byte(e.value()), new ArrayList()); - } - - protected Pair> doShortVal(Value.Short e) { - return new Pair>(new JilExpr.Short(e.value()), new ArrayList()); - } - - protected Pair> doIntVal(Value.Int e) { - return new Pair>(new JilExpr.Int(e.value()), new ArrayList()); - } - - protected Pair> doLongVal(Value.Long e) { - return new Pair>(new JilExpr.Long(e.value()), new ArrayList()); - } - - protected Pair> doFloatVal(Value.Float e) { - return new Pair>(new JilExpr.Float(e.value()), new ArrayList()); - } - - protected Pair> doDoubleVal(Value.Double e) { - return new Pair>(new JilExpr.Double(e.value()), new ArrayList()); - } - - protected Pair> doStringVal(Value.String e) { - return new Pair>(new JilExpr.StringVal(e.value()), new ArrayList()); - } - - protected Pair> doNullVal(Value.Null e) { - return new Pair>(new JilExpr.Null(), new ArrayList()); - } - - protected Pair> doTypedArrayVal(Value.TypedArray e) { - ArrayList r = new ArrayList(); - Type.Array type = e.attribute(Type.Array.class); - Pair,List> exprs = doExpressionList(e.values()); - r.addAll(exprs.second()); - return new Pair>(new JilExpr.Array( - exprs.first(), type, e.attributes()), r); - } - - protected Pair> doArrayVal(Value.Array e) { - ArrayList r = new ArrayList(); - Type.Array type = e.attribute(Type.Array.class); - Pair,List> exprs = doExpressionList(e.values()); - r.addAll(exprs.second()); - return new Pair>(new JilExpr.Array( - exprs.first(), type, e.attributes()), r); - } - - protected Pair> doClassVal(Value.Class e) { - Type classType = e.value().attribute(Type.class); - Type.Clazz type = e.attribute(Type.Clazz.class); - - if(type instanceof Type.Clazz) { - return new Pair>(new JilExpr.Class( - classType, type, e.attributes()), - new ArrayList()); - } else { - return new Pair>(new JilExpr.Class( - classType, JAVA_LANG_OBJECT, e.attributes()), - new ArrayList()); - } - } - - protected Pair> doLocalVariable( - Expr.LocalVariable e) { - Type type = e.attribute(Type.class); - return new Pair>(new JilExpr.Variable(e.value(), type, e - .attributes()), new ArrayList()); - } - - protected Pair> doNonLocalVariable( - Expr.NonLocalVariable e) { - syntax_error( - "internal failure (support for non-local variables not implemented!)", - e); - return null; - } - - protected Pair> doClassVariable(Expr.ClassVariable e) { - Type.Clazz type = e.attribute(Type.Clazz.class); - return new Pair>(new JilExpr.ClassVariable(type, e.attributes()), - new ArrayList()); - } - - protected Pair> doUnOp(Expr.UnOp e) { - Pair> r = doExpression(e.expr()); - Type.Primitive type = e.attribute(Type.Primitive.class); - List stmts = r.second(); - - switch (e.op()) { - case Expr.UnOp.PREDEC: - { - JilExpr lhs = r.first(); - JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.SUB, - type, e.attributes()); - stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); - return new Pair>(r.first(),stmts); - } - case Expr.UnOp.PREINC: - { - JilExpr lhs = r.first(); - JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.ADD, - type, e.attributes()); - stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); - return new Pair>(lhs,stmts); - } - case Expr.UnOp.POSTINC: - { - JilExpr lhs = r.first(); - JilExpr.Variable tmp = new JilExpr.Variable(getTempVar(),type,new ArrayList(lhs.attributes())); - stmts.add(new JilStmt.Assign(tmp,lhs,e.attributes())); - JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.ADD, - type, e.attributes()); - stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); - return new Pair>(tmp,stmts); - } - case Expr.UnOp.POSTDEC: - { - JilExpr lhs = r.first(); - JilExpr.Variable tmp = new JilExpr.Variable(getTempVar(),type,new ArrayList(lhs.attributes())); - stmts.add(new JilStmt.Assign(tmp,lhs,e.attributes())); - JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.SUB, - type, e.attributes()); - stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); - return new Pair>(tmp,stmts); - } - default: - return new Pair>(new JilExpr.UnOp(r.first(), e.op(), - type, e.attributes()), r.second()); - } - } - - protected Pair> doBinOp(Expr.BinOp e) { - - // First, check for special string concatenation operator. - if(e.op() == Expr.BinOp.CONCAT) { - return doStringConcat(e); - } - - Pair> lhs = doExpression(e.lhs()); - Pair> rhs = doExpression(e.rhs()); - - Type type = e.attribute(Type.class); - - if(type instanceof Type.Primitive) { - Type.Primitive ptype = (Type.Primitive) type; - List r = lhs.second(); - - if(rhs.second().isEmpty()) { - // This is the easy case: there are no side-effects in the rhs. - r.addAll(rhs.second()); - - return new Pair>(new JilExpr.BinOp(lhs.first(), rhs - .first(), e.op(), ptype, e.attributes()), r); - } else { - // This is the harder case: we have side-effects in the rhs. - // Now, to deal with this i'm going to be a little conservative - // and use a temporary variable. In some cases, it may be - // possible to avoid the temporary variable, but for simplicity - // I don't do this yet. [NOTE, IT's HARD TO AVOID THE TMP] - JilExpr.Variable tmpVar = new JilExpr.Variable(getTempVar(), - lhs.first().type(), e.attributes()); - r.add(new JilStmt.Assign(tmpVar,lhs.first(),e.attributes())); - r.addAll(rhs.second()); - return new Pair>(new JilExpr.BinOp(tmpVar, rhs - .first(), e.op(), ptype, e.attributes()), r); - } - } else { - syntax_error( - "internal failure --- problem processing binary operator (" - + lhs.first().type() + ")", e); - return null; - } - } - - protected static int stringbuilder_label = 0; - - protected Pair> doStringConcat(Expr.BinOp bop){ - - // This method is evidence as to why Java sucks as a programming - // language. It should be easy to construct datatypes, as I'm doing - // here, but lack of good notation makes it awkward in Java. Sure, there - // are some hacks to can do to improve the situation but basically it's - // screwed. - String builderLab = "$builder" + stringbuilder_label++; - Pair> lhs = doExpression(bop.lhs()); - Pair> rhs = doExpression(bop.rhs()); - - List stmts = lhs.second(); - stmts.addAll(rhs.second()); - - Type.Clazz builder = new Type.Clazz("java.lang", - "StringBuilder"); - - stmts.add(new JilStmt.Assign(new JilExpr.Variable(builderLab, builder), - new JilExpr.New(builder, new ArrayList(), - new Type.Function(T_VOID), bop.attributes()), bop - .attributes())); - - Type lhs_t = lhs.first().type(); - if (lhs_t instanceof Type.Primitive || isString(lhs_t)) { - ArrayList params = new ArrayList(); - params.add(lhs.first()); - stmts.add(new JilExpr.Invoke(new JilExpr.Variable(builderLab, - builder), "append", params, new Type.Function( - new Type.Clazz("java.lang", "StringBuilder"), lhs.first() - .type()), new Type.Clazz("java.lang", - "StringBuilder"), bop.attributes())); - } else { - ArrayList params = new ArrayList(); - params.add(lhs.first()); - stmts.add(new JilExpr.Invoke(new JilExpr.Variable(builderLab, - builder), "append", params, new Type.Function( - new Type.Clazz("java.lang", "StringBuilder"), - JAVA_LANG_OBJECT), new Type.Clazz("java.lang", - "StringBuilder"), bop.attributes())); - } - - // Now, do the right hand side - JilExpr r; - Type rhs_t = rhs.first().type(); - if(rhs_t instanceof Type.Primitive || isString(rhs_t)) { - ArrayList params = new ArrayList(); - params.add(rhs.first()); - r = new JilExpr.Invoke(new JilExpr.Variable(builderLab, builder), - "append", params, new Type.Function(new Type.Clazz( - "java.lang", "StringBuilder"), rhs_t), - new Type.Clazz("java.lang", "StringBuilder"), bop - .attributes()); - } else { - ArrayList params = new ArrayList(); - params.add(rhs.first()); - r = new JilExpr.Invoke(new JilExpr.Variable(builderLab, builder), - "append", params, new Type.Function(new Type.Clazz( - "java.lang", "StringBuilder"), JAVA_LANG_OBJECT), - new Type.Clazz("java.lang", "StringBuilder"), bop - .attributes()); - } - - r = new JilExpr.Invoke(r, "toString", new ArrayList(), - new Type.Function(JAVA_LANG_STRING), JAVA_LANG_STRING, bop - .attributes()); - - return new Pair>(r,stmts); - } - - protected int ternop_label = 0; - protected Pair> doTernOp(Expr.TernOp e) { - String trueLab = "$ternoptrue" + ternop_label; - String exitLab = "$ternopexit" + ternop_label++; - Type r_t = e.attribute(Type.class); - Pair> cond = doExpression(e.condition()); - Pair> tbranch = doExpression(e.trueBranch()); - Pair> fbranch = doExpression(e.falseBranch()); - ArrayList r = new ArrayList(); - JilExpr.Variable tmp = new JilExpr.Variable(getTempVar(),r_t,e.attributes()); - r.addAll(cond.second()); - r.add(new JilStmt.IfGoto(cond.first(),trueLab,e.attributes())); - r.addAll(fbranch.second()); - r.add(new JilStmt.Assign(tmp,fbranch.first(),e.attributes())); - r.add(new JilStmt.Goto(exitLab,e.attributes())); - r.add(new JilStmt.Label(trueLab,e.attributes())); - r.addAll(tbranch.second()); - r.add(new JilStmt.Assign(tmp,tbranch.first(),e.attributes())); - r.add(new JilStmt.Label(exitLab,e.attributes())); - return new Pair(tmp,r); - } - - /** - * The purpose of this method is to simplify the processing of an expression - * list. This is particular complex in the case of side-effecting - * statements. - */ - protected Pair,List> doExpressionList(List exprs) { - ArrayList nexprs = new ArrayList(); - ArrayList nstmts = new ArrayList(); - boolean hasSideEffects = false; - for(int i=exprs.size()-1;i>=0;--i) { - Expr p = exprs.get(i); - Pair> tmp = doExpression(p); - if(hasSideEffects) { - JilExpr.Variable var = new JilExpr.Variable(getTempVar(), tmp - .first().type(), p.attributes()); - nstmts.add(0,new JilStmt.Assign(var,tmp.first(),p.attributes())); - nexprs.add(0,var); - } else { - nexprs.add(0,tmp.first()); - } - nstmts.addAll(0,tmp.second()); - if(!tmp.second().isEmpty()) { - hasSideEffects=true; - } - } - return new Pair,List>(nexprs,nstmts); - } - - /** - * This method determines whether or not it is possible for a statement to - * throw a particular exception. Observe that this determination is from a - * static point of view; thus, it may be possible with more complex - * reasoning to determine that a statement cannot throw an exception. - * However, we need to be somewhat conservative here to ensure that the - * bytecode produced will in fact pass the JVM bytecode verifier. - * - * @param stmt - * @param exception - * @return - */ - protected boolean canThrowException(JilStmt stmt, Type.Clazz exception) - throws ClassNotFoundException { - - if (types.subtype(JAVA_LANG_VIRTUALMACHINEERROR, - exception, loader)) { - // must treat these exceptions very conservatively, spec JVM spec - // dictates they can occur at any point during exceptions. - return true; - } - // now, try to eliminate all statements which definitely cannot - // throw an exception. - if (stmt instanceof JilStmt.Goto || stmt instanceof JilStmt.Label - || stmt instanceof JilStmt.Nop) { - return false; - } - // Now, tackle the easier ones. - if (stmt instanceof JilStmt.Lock || stmt instanceof JilStmt.Unlock) { - // these statements can only throw null pointer exceptions. - // Actually, can also throw IllegaMonitorStateException - return types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, - loader); - } - - if (stmt instanceof JilStmt.Throw) { - // this can throw null pointer exception if the argument is null. - // can also throw IllegalMonitorStateException (see JVM spec athrow) - JilStmt.Throw tr = (JilStmt.Throw) stmt; - return types.subtype(exception, tr.expr().type(), loader) - || types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, - loader); - } - - ArrayList exprs = new ArrayList(); - if (stmt instanceof JilExpr.Invoke) { - // Some possible issue with respect to throwing Errors if - // this causes class loading. See JVM Section 2.17.5. - // - // Also, can throw IncompatibleClassChangeError, IllegalAccessError, - // AbstractMethodError, UnsatisfiedLinkError. - if (types.subtype(exception, JAVA_LANG_RUNTIMEEXCEPTION, loader) - || types.subtype(JAVA_LANG_RUNTIMEEXCEPTION, exception, - loader)) { - return true; - } - - // check declared exceptions - MethodInfo mi = stmt.attribute(MethodInfo.class); - for(Type.Clazz ex : mi.exceptions) { - if (types.subtype(exception, ex, loader)) { - return true; - } - } - - JilExpr.Invoke ivk = (JilExpr.Invoke) stmt; - exprs.addAll(ivk.parameters()); - } else if (stmt instanceof JilExpr.New) { - if (types.subtype(exception, JAVA_LANG_RUNTIMEEXCEPTION, loader) - || types.subtype(JAVA_LANG_RUNTIMEEXCEPTION, exception, loader)) { - return true; - } - JilExpr.New ivk = (JilExpr.New) stmt; - if (ivk.type() instanceof Type.Array - && types.subtype(exception, new Type.Clazz("java.lang", - "NegativeArraySizeException"), loader)) { - // In some cases, we can certain figure out that this cannot - // happen. - return true; - } else if(!(ivk.type() instanceof Type.Array)) { - // check declared exceptions - MethodInfo mi = ivk.attribute(MethodInfo.class); - for(Type.Clazz ex : mi.exceptions) { - if (types.subtype(exception, ex, loader)) { - return true; - } - } - } - - // Need to do something about checked exceptions. Also, if static - // method then cannot throw NullPointException - exprs.addAll(ivk.parameters()); - } else if(stmt instanceof JilStmt.Return) { - JilStmt.Return r = (JilStmt.Return) stmt; - // can also throw IllegalMonitorStateException (see JVM spec areturn) - if(r.expr() == null) { - return false; - } else { - exprs.add(r.expr()); - } - } else if(stmt instanceof JilStmt.Assign) { - JilStmt.Assign r = (JilStmt.Assign) stmt; - if (r.lhs() instanceof JilExpr.ArrayIndex - && types.subtype(exception, new Type.Clazz("java.lang", - "ArrayStoreException"), loader)) { - return true; - } - exprs.add(r.lhs()); - exprs.add(r.rhs()); - } else if(stmt instanceof JilStmt.IfGoto) { - JilStmt.IfGoto r = (JilStmt.IfGoto) stmt; - exprs.add(r.condition()); - } else if(stmt instanceof JilStmt.Switch) { - JilStmt.Switch r = (JilStmt.Switch) stmt; - exprs.add(r.condition()); - } - // Right, at this point, we have a bunch of expressions and we need to - // check whether or not any of these could throw the exception in - // question. - for(JilExpr e : exprs) { - if(canThrowException(e,exception)) { - return true; - } - } - return false; - } - - protected boolean canThrowException(JilExpr expr, Type.Clazz exception) - throws ClassNotFoundException { - // reuse code above if possible - if(expr instanceof JilExpr.Invoke || expr instanceof JilExpr.New) { - return canThrowException((JilStmt)expr,exception); - } - - // Again, build up an expression list to check - ArrayList exprs = new ArrayList(); - - if(expr instanceof JilExpr.Cast) { - JilExpr.Cast ec = (JilExpr.Cast) expr; - if ((ec.type() instanceof Type.Reference || ec.type() instanceof Type.Null) - && types.subtype(exception, new Type.Clazz("java.lang", - "ClassCastException"), loader)) { - return true; - } - exprs.add(ec.expr()); - } else if(expr instanceof JilExpr.BinOp) { - JilExpr.BinOp bop = (JilExpr.BinOp) expr; - if (!(bop.type() instanceof Type.Float || bop.type() instanceof Type.Double) - && (bop.op() == JilExpr.BinOp.DIV || bop.op() == JilExpr.BinOp.MOD) - && types.subtype(exception, JAVA_LANG_ARITHMETICEXCEPTION, - loader)) { - // Curiously, divide-by-zero is only a problem for integer types. - return true; - } - exprs.add(bop.lhs()); - exprs.add(bop.rhs()); - } else if(expr instanceof JilExpr.UnOp) { - JilExpr.UnOp bop = (JilExpr.UnOp) expr; - exprs.add(bop.expr()); - } else if(expr instanceof JilExpr.Deref) { - // Some possible issue with respect to throwing Errors if this - // instruction causes class loading. See JVM Section 2.17.5. - JilExpr.Deref def = (JilExpr.Deref) expr; - if (types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, loader)) { - return true; - } - exprs.add(def.target()); - } else if(expr instanceof JilExpr.Array) { - JilExpr.Array arr = (JilExpr.Array) expr; - exprs.addAll(arr.values()); - } else if(expr instanceof JilExpr.ArrayIndex) { - JilExpr.ArrayIndex ai = (JilExpr.ArrayIndex) expr; - if (types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, loader) - || types.subtype(exception, new Type.Clazz("java.lang", - "ArrayIndexOutOfBoundsException"), loader)) { - return true; - } - exprs.add(ai.target()); - exprs.add(ai.index()); - } else if(expr instanceof JilExpr.InstanceOf) { - JilExpr.InstanceOf iof = (JilExpr.InstanceOf) expr; - exprs.add(iof.lhs()); - } - - // Right, at this point, we have a bunch of expressions and we need to - // check whether or not any of these could throw the exception in - // question. - for (JilExpr e : exprs) { - if (canThrowException(e, exception)) { - return true; - } - } - - return false; - } - - protected Scope findEnclosingScope(Class c) { - for(int i=scopes.size()-1;i>=0;--i) { - Scope s = scopes.get(i); - if(c.isInstance(s)) { - return s; - } - } - return null; - } - - protected int findSuperCall(List stmts) { - int r = 0; - for(JilStmt stmt : stmts) { - if(stmt instanceof JilExpr.Invoke) { - JilExpr.Invoke sc = (JilExpr.Invoke) stmt; - if (sc.name().equals("super") || sc.name().equals("this")) { - return r; - } - } - r=r+1; - } - return -1; - } - - public JilMethod createStaticInitialiser(JilClass parent) { - List si = parent.methods(""); - if(si.size() == 0) { - ArrayList mods = new ArrayList(); - mods.add(Modifier.ACC_STATIC); - JilMethod r = new JilMethod("", new Type.Function( - Types.T_VOID), new ArrayList(), mods, - new ArrayList()); - r.body().add(new JilStmt.Return(null)); - parent.methods().add(r); - return r; - } else { - // It should be impossible to have more than one. - return si.get(0); - } - } - - protected JilExpr constant(int constant, Type t) { - if(t instanceof Type.Byte) { - return new JilExpr.Byte((byte)constant); - } else if(t instanceof Type.Char) { - return new JilExpr.Char((char)constant); - } else if(t instanceof Type.Short) { - return new JilExpr.Short((short)constant); - } else if(t instanceof Type.Int) { - return new JilExpr.Int(constant); - } else if(t instanceof Type.Long) { - return new JilExpr.Long((long)constant); - } else if(t instanceof Type.Float) { - return new JilExpr.Float((float)constant); - } else { - return new JilExpr.Double((double)constant); - } - } - - protected int tmp_label = 0; - protected String getTempVar() { - return "$tmp" + tmp_label++; - } - - /** - * Check wither a given type is a reference to java.lang.String or not. - * - * @param t - * @return - */ - protected static boolean isString(Type t) { - if(t instanceof Type.Clazz) { - Type.Clazz c = (Type.Clazz) t; - return c.pkg().equals("java.lang") && c.components().size() == 1 - && c.components().get(0).first().equals("String"); - } - return false; - } -} diff --git a/tests/java/ShipSquare.java b/tests/java/ShipSquare.java deleted file mode 100755 index 59a0267..0000000 --- a/tests/java/ShipSquare.java +++ /dev/null @@ -1,40 +0,0 @@ -import javax.swing.ImageIcon; - -/** - * A Ship square represents a square that contains a battle ship, but has not - * yet been bombed. Ship Squares can be either visible or invisible (depending - * on which side they are). - * - * @author djp - */ -public class ShipSquare extends GridSquare { - private BattleShip ship; - private Type type; - - public enum Type { - VERTICAL_TOP_END, - HORIZONTAL_MIDDLE - }; - - /** - * Construct a ShipSquare representing part of a battle ship (either a - * middle or end piece). - * - */ - public ShipSquare(Type type, BattleShip ship) { - this.type = type; - this.ship = ship; - } - - /** - * Get the ship that this square is part of. - * @return - */ - public BattleShip getShip() { return ship; } - - /** - * Determine what part of the ship this piece represents. - * @return - */ - public Type getType() { return type; } -} diff --git a/tests/javascript/backboje.js b/tests/javascript/backboje.js deleted file mode 100644 index 70e7d5d..0000000 --- a/tests/javascript/backboje.js +++ /dev/null @@ -1,16 +0,0 @@ -$(function(){ - window.Todo = Backbone.Model.extend({ - defaults: function() { - return { - done: false, - order: Todos.nextOrder() - }; - }, - - // Toggle the `done` state of this todo item. - toggle: function() { - this.save({done: !this.get("done")}); - } - - }); -}); diff --git a/tests/javascript/class.js b/tests/javascript/class.js deleted file mode 100644 index f0dae41..0000000 --- a/tests/javascript/class.js +++ /dev/null @@ -1,83 +0,0 @@ -// base class -var Animal = Class.create({ - initialize: function(name) { - this.name = name; - }, - name: "", - eat: function() { - return this.say("Yum!"); - }, - say: function(message) { - return this.name + ": " + message; - } -}); - -// subclass that augments a method -var Cat = Class.create(Animal, { - eat: function($super, food) { - if (food instanceof Mouse) return $super(); - else return this.say("Yuk! I only eat mice."); - } -}); - -// empty subclass -var Mouse = Class.create(Animal, {}); - -//mixins -var Sellable = { - getValue: function(pricePerKilo) { - return this.weight * pricePerKilo; - }, - - inspect: function() { - return '#'.interpolate(this); - } -}; - -var Reproduceable = { - reproduce: function(partner) { - if (partner.constructor != this.constructor || partner.sex == this.sex) - return null; - var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female'; - return new this.constructor('baby', weight, sex); - } -}; - -// base class with mixin -var Plant = Class.create(Sellable, { - initialize: function(name, weight) { - this.name = name; - this.weight = weight; - }, - - inspect: function() { - return '#'.interpolate(this); - } -}); - -// subclass with mixin -var Dog = Class.create(Animal, Reproduceable, { - initialize: function($super, name, weight, sex) { - this.weight = weight; - this.sex = sex; - $super(name); - } -}); - -// subclass with mixins -var Ox = Class.create(Animal, Sellable, Reproduceable, { - initialize: function($super, name, weight, sex) { - this.weight = weight; - this.sex = sex; - $super(name); - }, - - eat: function(food) { - if (food instanceof Plant) - this.weight += food.weight; - }, - - inspect: function() { - return '#'.interpolate(this); - } -}); diff --git a/tests/javascript/jquery.ui.progressbar.js b/tests/javascript/jquery.ui.progressbar.js deleted file mode 100644 index e3b25cf..0000000 --- a/tests/javascript/jquery.ui.progressbar.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * jQuery UI Progressbar @VERSION - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Progressbar - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - */ -(function( $, undefined ) { - -$.widget( "ui.progressbar", { - options: { - value: 0, - max: 100 - }, - - min: 0, - - _create: function() { - this.element - .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) - .attr({ - role: "progressbar", - "aria-valuemin": this.min, - "aria-valuemax": this.options.max, - "aria-valuenow": this._value() - }); - - this.valueDiv = $( "
" ) - .appendTo( this.element ); - - this.oldValue = this._value(); - this._refreshValue(); - }, - - _destroy: function() { - this.element - .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) - .removeAttr( "role" ) - .removeAttr( "aria-valuemin" ) - .removeAttr( "aria-valuemax" ) - .removeAttr( "aria-valuenow" ); - - this.valueDiv.remove(); - }, - - value: function( newValue ) { - if ( newValue === undefined ) { - return this._value(); - } - - this._setOption( "value", newValue ); - return this; - }, - - _setOption: function( key, value ) { - if ( key === "value" ) { - this.options.value = value; - this._refreshValue(); - if ( this._value() === this.options.max ) { - this._trigger( "complete" ); - } - } - - this._super( "_setOption", key, value ); - }, - - _value: function() { - var val = this.options.value; - // normalize invalid value - if ( typeof val !== "number" ) { - val = 0; - } - return Math.min( this.options.max, Math.max( this.min, val ) ); - }, - - _percentage: function() { - return 100 * this._value() / this.options.max; - }, - - _refreshValue: function() { - var value = this.value(); - var percentage = this._percentage(); - - if ( this.oldValue !== value ) { - this.oldValue = value; - this._trigger( "change" ); - } - - this.valueDiv - .toggle( value > this.min ) - .toggleClass( "ui-corner-right", value === this.options.max ) - .width( percentage.toFixed(0) + "%" ); - this.element.attr( "aria-valuenow", value ); - } -}); - -$.extend( $.ui.progressbar, { - version: "@VERSION" -}); - -})( jQuery ); diff --git a/tests/javascript/namespaces.js b/tests/javascript/namespaces.js deleted file mode 100644 index c2eb486..0000000 --- a/tests/javascript/namespaces.js +++ /dev/null @@ -1,27 +0,0 @@ -/* Object Literal Notation */ -var objectLiteral = { - str: '1', - func: function() { return 1; } -}; - -/* Module Pattern 1 */ -var module = (function(){ - var private = 1; - return { - method: function() { private++; } - }; -})(); - -/* Module Pattern 2 */ -var module2 = {}; -(function(context){ - var private = 1; - context.method = function() { private++; } -})(module2); - -/* Module Pattern 3 */ -var module3 = {}; -(function(){ - var private = 1; - this.method = function() { private++; } -}).apply(module3); diff --git a/tests/javascript/oreilly1.js b/tests/javascript/oreilly1.js deleted file mode 100644 index 441a829..0000000 --- a/tests/javascript/oreilly1.js +++ /dev/null @@ -1,42 +0,0 @@ -// This example is from the book _JavaScript: The Definitive Guide_. -// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates. -// This example is provided WITHOUT WARRANTY either expressed or implied. -// You may study, use, modify, and distribute it for any purpose. - -function Circle(radius) { // the constructor defines the class itself - // r is an instance variable; defined and initialized in the constructor - this.r = radius; -} - -// Circle.PI is a class variable--it is a property of the constructor function -Circle.PI = 3.14159; - -// Here is a function that computes a circle area. -function Circle_area() { return Circle.PI * this.r * this.r; } - -// Here we make the function into an instance method by assigning it -// to the prototype object of the constructor. Remember that we have to -// create and discard one object before the prototype object exists -new Circle(0); -Circle.prototype.area = Circle_area; - -// Here's another function. It takes two circle objects are arguments and -// returns the one that is larger (has the larger radius). -function Circle_max(a,b) { - if (a.r > b.r) return a; - else return b; -} - -// Since this function compares two circle objects, it doesn't make sense as -// an instance method operating on a single circle object. But we don't want -// it to be a stand-alone function either, so we make it into a class method -// by assigning it to the constructor function: -Circle.max = Circle_max; - -// Here is some code that uses each of these fields: -c = new Circle(1.0); // create an instance of the Circle class -c.r = 2.2; // set the r instance variable -a = c.area(); // invoke the area() instance method -x = Math.exp(Circle.PI); // use the PI class variable in our own computation. -d = new Circle(1.2); // create another Circle instance -bigger = Circle.max(c,d); // use the max() class method. diff --git a/tests/javascript/prototype.js b/tests/javascript/prototype.js deleted file mode 100644 index 04a4779..0000000 --- a/tests/javascript/prototype.js +++ /dev/null @@ -1,6081 +0,0 @@ -/* Prototype JavaScript framework, version 1.7 - * (c) 2005-2010 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see the Prototype web site: http://www.prototypejs.org/ - * - *--------------------------------------------------------------------------*/ - -var Prototype = { - - Version: '1.7', - - Browser: (function(){ - var ua = navigator.userAgent; - var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; - return { - IE: !!window.attachEvent && !isOpera, - Opera: isOpera, - WebKit: ua.indexOf('AppleWebKit/') > -1, - Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, - MobileSafari: /Apple.*Mobile/.test(ua) - } - })(), - - BrowserFeatures: { - XPath: !!document.evaluate, - - SelectorsAPI: !!document.querySelector, - - ElementExtensions: (function() { - var constructor = window.Element || window.HTMLElement; - return !!(constructor && constructor.prototype); - })(), - SpecificElementExtensions: (function() { - if (typeof window.HTMLDivElement !== 'undefined') - return true; - - var div = document.createElement('div'), - form = document.createElement('form'), - isSupported = false; - - if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { - isSupported = true; - } - - div = form = null; - - return isSupported; - })() - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - emptyFunction: function() { }, - - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; -/* Based on Alex Arnell's inheritance implementation. */ - -var Class = (function() { - - var IS_DONTENUM_BUGGY = (function(){ - for (var p in { toString: 1 }) { - if (p === 'toString') return false; - } - return true; - })(); - - function subclass() {}; - function create() { - var parent = null, properties = $A(arguments); - if (Object.isFunction(properties[0])) - parent = properties.shift(); - - function klass() { - this.initialize.apply(this, arguments); - } - - Object.extend(klass, Class.Methods); - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - subclass.prototype = parent.prototype; - klass.prototype = new subclass; - parent.subclasses.push(klass); - } - - for (var i = 0, length = properties.length; i < length; i++) - klass.addMethods(properties[i]); - - if (!klass.prototype.initialize) - klass.prototype.initialize = Prototype.emptyFunction; - - klass.prototype.constructor = klass; - return klass; - } - - function addMethods(source) { - var ancestor = this.superclass && this.superclass.prototype, - properties = Object.keys(source); - - if (IS_DONTENUM_BUGGY) { - if (source.toString != Object.prototype.toString) - properties.push("toString"); - if (source.valueOf != Object.prototype.valueOf) - properties.push("valueOf"); - } - - for (var i = 0, length = properties.length; i < length; i++) { - var property = properties[i], value = source[property]; - if (ancestor && Object.isFunction(value) && - value.argumentNames()[0] == "$super") { - var method = value; - value = (function(m) { - return function() { return ancestor[m].apply(this, arguments); }; - })(property).wrap(method); - - value.valueOf = method.valueOf.bind(method); - value.toString = method.toString.bind(method); - } - this.prototype[property] = value; - } - - return this; - } - - return { - create: create, - Methods: { - addMethods: addMethods - } - }; -})(); -(function() { - - var _toString = Object.prototype.toString, - NULL_TYPE = 'Null', - UNDEFINED_TYPE = 'Undefined', - BOOLEAN_TYPE = 'Boolean', - NUMBER_TYPE = 'Number', - STRING_TYPE = 'String', - OBJECT_TYPE = 'Object', - FUNCTION_CLASS = '[object Function]', - BOOLEAN_CLASS = '[object Boolean]', - NUMBER_CLASS = '[object Number]', - STRING_CLASS = '[object String]', - ARRAY_CLASS = '[object Array]', - DATE_CLASS = '[object Date]', - NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && - typeof JSON.stringify === 'function' && - JSON.stringify(0) === '0' && - typeof JSON.stringify(Prototype.K) === 'undefined'; - - function Type(o) { - switch(o) { - case null: return NULL_TYPE; - case (void 0): return UNDEFINED_TYPE; - } - var type = typeof o; - switch(type) { - case 'boolean': return BOOLEAN_TYPE; - case 'number': return NUMBER_TYPE; - case 'string': return STRING_TYPE; - } - return OBJECT_TYPE; - } - - function extend(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; - } - - function inspect(object) { - try { - if (isUndefined(object)) return 'undefined'; - if (object === null) return 'null'; - return object.inspect ? object.inspect() : String(object); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; - } - } - - function toJSON(value) { - return Str('', { '': value }, []); - } - - function Str(key, holder, stack) { - var value = holder[key], - type = typeof value; - - if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - - var _class = _toString.call(value); - - switch (_class) { - case NUMBER_CLASS: - case BOOLEAN_CLASS: - case STRING_CLASS: - value = value.valueOf(); - } - - switch (value) { - case null: return 'null'; - case true: return 'true'; - case false: return 'false'; - } - - type = typeof value; - switch (type) { - case 'string': - return value.inspect(true); - case 'number': - return isFinite(value) ? String(value) : 'null'; - case 'object': - - for (var i = 0, length = stack.length; i < length; i++) { - if (stack[i] === value) { throw new TypeError(); } - } - stack.push(value); - - var partial = []; - if (_class === ARRAY_CLASS) { - for (var i = 0, length = value.length; i < length; i++) { - var str = Str(i, value, stack); - partial.push(typeof str === 'undefined' ? 'null' : str); - } - partial = '[' + partial.join(',') + ']'; - } else { - var keys = Object.keys(value); - for (var i = 0, length = keys.length; i < length; i++) { - var key = keys[i], str = Str(key, value, stack); - if (typeof str !== "undefined") { - partial.push(key.inspect(true)+ ':' + str); - } - } - partial = '{' + partial.join(',') + '}'; - } - stack.pop(); - return partial; - } - } - - function stringify(object) { - return JSON.stringify(object); - } - - function toQueryString(object) { - return $H(object).toQueryString(); - } - - function toHTML(object) { - return object && object.toHTML ? object.toHTML() : String.interpret(object); - } - - function keys(object) { - if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } - var results = []; - for (var property in object) { - if (object.hasOwnProperty(property)) { - results.push(property); - } - } - return results; - } - - function values(object) { - var results = []; - for (var property in object) - results.push(object[property]); - return results; - } - - function clone(object) { - return extend({ }, object); - } - - function isElement(object) { - return !!(object && object.nodeType == 1); - } - - function isArray(object) { - return _toString.call(object) === ARRAY_CLASS; - } - - var hasNativeIsArray = (typeof Array.isArray == 'function') - && Array.isArray([]) && !Array.isArray({}); - - if (hasNativeIsArray) { - isArray = Array.isArray; - } - - function isHash(object) { - return object instanceof Hash; - } - - function isFunction(object) { - return _toString.call(object) === FUNCTION_CLASS; - } - - function isString(object) { - return _toString.call(object) === STRING_CLASS; - } - - function isNumber(object) { - return _toString.call(object) === NUMBER_CLASS; - } - - function isDate(object) { - return _toString.call(object) === DATE_CLASS; - } - - function isUndefined(object) { - return typeof object === "undefined"; - } - - extend(Object, { - extend: extend, - inspect: inspect, - toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, - toQueryString: toQueryString, - toHTML: toHTML, - keys: Object.keys || keys, - values: values, - clone: clone, - isElement: isElement, - isArray: isArray, - isHash: isHash, - isFunction: isFunction, - isString: isString, - isNumber: isNumber, - isDate: isDate, - isUndefined: isUndefined - }); -})(); -Object.extend(Function.prototype, (function() { - var slice = Array.prototype.slice; - - function update(array, args) { - var arrayLength = array.length, length = args.length; - while (length--) array[arrayLength + length] = args[length]; - return array; - } - - function merge(array, args) { - array = slice.call(array, 0); - return update(array, args); - } - - function argumentNames() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] - .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') - .replace(/\s+/g, '').split(','); - return names.length == 1 && !names[0] ? [] : names; - } - - function bind(context) { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = slice.call(arguments, 1); - return function() { - var a = merge(args, arguments); - return __method.apply(context, a); - } - } - - function bindAsEventListener(context) { - var __method = this, args = slice.call(arguments, 1); - return function(event) { - var a = update([event || window.event], args); - return __method.apply(context, a); - } - } - - function curry() { - if (!arguments.length) return this; - var __method = this, args = slice.call(arguments, 0); - return function() { - var a = merge(args, arguments); - return __method.apply(this, a); - } - } - - function delay(timeout) { - var __method = this, args = slice.call(arguments, 1); - timeout = timeout * 1000; - return window.setTimeout(function() { - return __method.apply(__method, args); - }, timeout); - } - - function defer() { - var args = update([0.01], arguments); - return this.delay.apply(this, args); - } - - function wrap(wrapper) { - var __method = this; - return function() { - var a = update([__method.bind(this)], arguments); - return wrapper.apply(this, a); - } - } - - function methodize() { - if (this._methodized) return this._methodized; - var __method = this; - return this._methodized = function() { - var a = update([this], arguments); - return __method.apply(null, a); - }; - } - - return { - argumentNames: argumentNames, - bind: bind, - bindAsEventListener: bindAsEventListener, - curry: curry, - delay: delay, - defer: defer, - wrap: wrap, - methodize: methodize - } -})()); - - - -(function(proto) { - - - function toISOString() { - return this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z'; - } - - - function toJSON() { - return this.toISOString(); - } - - if (!proto.toISOString) proto.toISOString = toISOString; - if (!proto.toJSON) proto.toJSON = toJSON; - -})(Date.prototype); - - -RegExp.prototype.match = RegExp.prototype.test; - -RegExp.escape = function(str) { - return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); -}; -var PeriodicalExecuter = Class.create({ - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - execute: function() { - this.callback(this); - }, - - stop: function() { - if (!this.timer) return; - clearInterval(this.timer); - this.timer = null; - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.execute(); - this.currentlyExecuting = false; - } catch(e) { - this.currentlyExecuting = false; - throw e; - } - } - } -}); -Object.extend(String, { - interpret: function(value) { - return value == null ? '' : String(value); - }, - specialChar: { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '\\': '\\\\' - } -}); - -Object.extend(String.prototype, (function() { - var NATIVE_JSON_PARSE_SUPPORT = window.JSON && - typeof JSON.parse === 'function' && - JSON.parse('{"test": true}').test; - - function prepareReplacement(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; - } - - function gsub(pattern, replacement) { - var result = '', source = this, match; - replacement = prepareReplacement(replacement); - - if (Object.isString(pattern)) - pattern = RegExp.escape(pattern); - - if (!(pattern.length || pattern.source)) { - replacement = replacement(''); - return replacement + source.split('').join(replacement) + replacement; - } - - while (source.length > 0) { - if (match = source.match(pattern)) { - result += source.slice(0, match.index); - result += String.interpret(replacement(match)); - source = source.slice(match.index + match[0].length); - } else { - result += source, source = ''; - } - } - return result; - } - - function sub(pattern, replacement, count) { - replacement = prepareReplacement(replacement); - count = Object.isUndefined(count) ? 1 : count; - - return this.gsub(pattern, function(match) { - if (--count < 0) return match[0]; - return replacement(match); - }); - } - - function scan(pattern, iterator) { - this.gsub(pattern, iterator); - return String(this); - } - - function truncate(length, truncation) { - length = length || 30; - truncation = Object.isUndefined(truncation) ? '...' : truncation; - return this.length > length ? - this.slice(0, length - truncation.length) + truncation : String(this); - } - - function strip() { - return this.replace(/^\s+/, '').replace(/\s+$/, ''); - } - - function stripTags() { - return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); - } - - function stripScripts() { - return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - } - - function extractScripts() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), - matchOne = new RegExp(Prototype.ScriptFragment, 'im'); - return (this.match(matchAll) || []).map(function(scriptTag) { - return (scriptTag.match(matchOne) || ['', ''])[1]; - }); - } - - function evalScripts() { - return this.extractScripts().map(function(script) { return eval(script) }); - } - - function escapeHTML() { - return this.replace(/&/g,'&').replace(//g,'>'); - } - - function unescapeHTML() { - return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); - } - - - function toQueryParams(separator) { - var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return { }; - - return match[1].split(separator || '&').inject({ }, function(hash, pair) { - if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()), - value = pair.length > 1 ? pair.join('=') : pair[0]; - - if (value != undefined) value = decodeURIComponent(value); - - if (key in hash) { - if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; - hash[key].push(value); - } - else hash[key] = value; - } - return hash; - }); - } - - function toArray() { - return this.split(''); - } - - function succ() { - return this.slice(0, this.length - 1) + - String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - } - - function times(count) { - return count < 1 ? '' : new Array(count + 1).join(this); - } - - function camelize() { - return this.replace(/-+(.)?/g, function(match, chr) { - return chr ? chr.toUpperCase() : ''; - }); - } - - function capitalize() { - return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - } - - function underscore() { - return this.replace(/::/g, '/') - .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') - .replace(/([a-z\d])([A-Z])/g, '$1_$2') - .replace(/-/g, '_') - .toLowerCase(); - } - - function dasherize() { - return this.replace(/_/g, '-'); - } - - function inspect(useDoubleQuotes) { - var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { - if (character in String.specialChar) { - return String.specialChar[character]; - } - return '\\u00' + character.charCodeAt().toPaddedString(2, 16); - }); - if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; - return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - } - - function unfilterJSON(filter) { - return this.replace(filter || Prototype.JSONFilter, '$1'); - } - - function isJSON() { - var str = this; - if (str.blank()) return false; - str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); - str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); - str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); - return (/^[\],:{}\s]*$/).test(str); - } - - function evalJSON(sanitize) { - var json = this.unfilterJSON(), - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - if (cx.test(json)) { - json = json.replace(cx, function (a) { - return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - try { - if (!sanitize || json.isJSON()) return eval('(' + json + ')'); - } catch (e) { } - throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - } - - function parseJSON() { - var json = this.unfilterJSON(); - return JSON.parse(json); - } - - function include(pattern) { - return this.indexOf(pattern) > -1; - } - - function startsWith(pattern) { - return this.lastIndexOf(pattern, 0) === 0; - } - - function endsWith(pattern) { - var d = this.length - pattern.length; - return d >= 0 && this.indexOf(pattern, d) === d; - } - - function empty() { - return this == ''; - } - - function blank() { - return /^\s*$/.test(this); - } - - function interpolate(object, pattern) { - return new Template(this, pattern).evaluate(object); - } - - return { - gsub: gsub, - sub: sub, - scan: scan, - truncate: truncate, - strip: String.prototype.trim || strip, - stripTags: stripTags, - stripScripts: stripScripts, - extractScripts: extractScripts, - evalScripts: evalScripts, - escapeHTML: escapeHTML, - unescapeHTML: unescapeHTML, - toQueryParams: toQueryParams, - parseQuery: toQueryParams, - toArray: toArray, - succ: succ, - times: times, - camelize: camelize, - capitalize: capitalize, - underscore: underscore, - dasherize: dasherize, - inspect: inspect, - unfilterJSON: unfilterJSON, - isJSON: isJSON, - evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, - include: include, - startsWith: startsWith, - endsWith: endsWith, - empty: empty, - blank: blank, - interpolate: interpolate - }; -})()); - -var Template = Class.create({ - initialize: function(template, pattern) { - this.template = template.toString(); - this.pattern = pattern || Template.Pattern; - }, - - evaluate: function(object) { - if (object && Object.isFunction(object.toTemplateReplacements)) - object = object.toTemplateReplacements(); - - return this.template.gsub(this.pattern, function(match) { - if (object == null) return (match[1] + ''); - - var before = match[1] || ''; - if (before == '\\') return match[2]; - - var ctx = object, expr = match[3], - pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; - - match = pattern.exec(expr); - if (match == null) return before; - - while (match != null) { - var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; - ctx = ctx[comp]; - if (null == ctx || '' == match[3]) break; - expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); - match = pattern.exec(expr); - } - - return before + String.interpret(ctx); - }); - } -}); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; - -var $break = { }; - -var Enumerable = (function() { - function each(iterator, context) { - var index = 0; - try { - this._each(function(value) { - iterator.call(context, value, index++); - }); - } catch (e) { - if (e != $break) throw e; - } - return this; - } - - function eachSlice(number, iterator, context) { - var index = -number, slices = [], array = this.toArray(); - if (number < 1) return array; - while ((index += number) < array.length) - slices.push(array.slice(index, index+number)); - return slices.collect(iterator, context); - } - - function all(iterator, context) { - iterator = iterator || Prototype.K; - var result = true; - this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw $break; - }); - return result; - } - - function any(iterator, context) { - iterator = iterator || Prototype.K; - var result = false; - this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) - throw $break; - }); - return result; - } - - function collect(iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); - return results; - } - - function detect(iterator, context) { - var result; - this.each(function(value, index) { - if (iterator.call(context, value, index)) { - result = value; - throw $break; - } - }); - return result; - } - - function findAll(iterator, context) { - var results = []; - this.each(function(value, index) { - if (iterator.call(context, value, index)) - results.push(value); - }); - return results; - } - - function grep(filter, iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - - if (Object.isString(filter)) - filter = new RegExp(RegExp.escape(filter)); - - this.each(function(value, index) { - if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); - return results; - } - - function include(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; - - var found = false; - this.each(function(value) { - if (value == object) { - found = true; - throw $break; - } - }); - return found; - } - - function inGroupsOf(number, fillWith) { - fillWith = Object.isUndefined(fillWith) ? null : fillWith; - return this.eachSlice(number, function(slice) { - while(slice.length < number) slice.push(fillWith); - return slice; - }); - } - - function inject(memo, iterator, context) { - this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); - return memo; - } - - function invoke(method) { - var args = $A(arguments).slice(1); - return this.map(function(value) { - return value[method].apply(value, args); - }); - } - - function max(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value >= result) - result = value; - }); - return result; - } - - function min(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value < result) - result = value; - }); - return result; - } - - function partition(iterator, context) { - iterator = iterator || Prototype.K; - var trues = [], falses = []; - this.each(function(value, index) { - (iterator.call(context, value, index) ? - trues : falses).push(value); - }); - return [trues, falses]; - } - - function pluck(property) { - var results = []; - this.each(function(value) { - results.push(value[property]); - }); - return results; - } - - function reject(iterator, context) { - var results = []; - this.each(function(value, index) { - if (!iterator.call(context, value, index)) - results.push(value); - }); - return results; - } - - function sortBy(iterator, context) { - return this.map(function(value, index) { - return { - value: value, - criteria: iterator.call(context, value, index) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }).pluck('value'); - } - - function toArray() { - return this.map(); - } - - function zip() { - var iterator = Prototype.K, args = $A(arguments); - if (Object.isFunction(args.last())) - iterator = args.pop(); - - var collections = [this].concat(args).map($A); - return this.map(function(value, index) { - return iterator(collections.pluck(index)); - }); - } - - function size() { - return this.toArray().length; - } - - function inspect() { - return '#'; - } - - - - - - - - - - return { - each: each, - eachSlice: eachSlice, - all: all, - every: all, - any: any, - some: any, - collect: collect, - map: collect, - detect: detect, - findAll: findAll, - select: findAll, - filter: findAll, - grep: grep, - include: include, - member: include, - inGroupsOf: inGroupsOf, - inject: inject, - invoke: invoke, - max: max, - min: min, - partition: partition, - pluck: pluck, - reject: reject, - sortBy: sortBy, - toArray: toArray, - entries: toArray, - zip: zip, - size: size, - inspect: inspect, - find: detect - }; -})(); - -function $A(iterable) { - if (!iterable) return []; - if ('toArray' in Object(iterable)) return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; -} - - -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} - -Array.from = $A; - - -(function() { - var arrayProto = Array.prototype, - slice = arrayProto.slice, - _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available - - function each(iterator, context) { - for (var i = 0, length = this.length >>> 0; i < length; i++) { - if (i in this) iterator.call(context, this[i], i, this); - } - } - if (!_each) _each = each; - - function clear() { - this.length = 0; - return this; - } - - function first() { - return this[0]; - } - - function last() { - return this[this.length - 1]; - } - - function compact() { - return this.select(function(value) { - return value != null; - }); - } - - function flatten() { - return this.inject([], function(array, value) { - if (Object.isArray(value)) - return array.concat(value.flatten()); - array.push(value); - return array; - }); - } - - function without() { - var values = slice.call(arguments, 0); - return this.select(function(value) { - return !values.include(value); - }); - } - - function reverse(inline) { - return (inline === false ? this.toArray() : this)._reverse(); - } - - function uniq(sorted) { - return this.inject([], function(array, value, index) { - if (0 == index || (sorted ? array.last() != value : !array.include(value))) - array.push(value); - return array; - }); - } - - function intersect(array) { - return this.uniq().findAll(function(item) { - return array.detect(function(value) { return item === value }); - }); - } - - - function clone() { - return slice.call(this, 0); - } - - function size() { - return this.length; - } - - function inspect() { - return '[' + this.map(Object.inspect).join(', ') + ']'; - } - - function indexOf(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; - } - - function lastIndexOf(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; - } - - function concat() { - var array = slice.call(this, 0), item; - for (var i = 0, length = arguments.length; i < length; i++) { - item = arguments[i]; - if (Object.isArray(item) && !('callee' in item)) { - for (var j = 0, arrayLength = item.length; j < arrayLength; j++) - array.push(item[j]); - } else { - array.push(item); - } - } - return array; - } - - Object.extend(arrayProto, Enumerable); - - if (!arrayProto._reverse) - arrayProto._reverse = arrayProto.reverse; - - Object.extend(arrayProto, { - _each: _each, - clear: clear, - first: first, - last: last, - compact: compact, - flatten: flatten, - without: without, - reverse: reverse, - uniq: uniq, - intersect: intersect, - clone: clone, - toArray: clone, - size: size, - inspect: inspect - }); - - var CONCAT_ARGUMENTS_BUGGY = (function() { - return [].concat(arguments)[0][0] !== 1; - })(1,2) - - if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; - - if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; - if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; -})(); -function $H(object) { - return new Hash(object); -}; - -var Hash = Class.create(Enumerable, (function() { - function initialize(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - } - - - function _each(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - } - - function set(key, value) { - return this._object[key] = value; - } - - function get(key) { - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - } - - function unset(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - } - - function toObject() { - return Object.clone(this._object); - } - - - - function keys() { - return this.pluck('key'); - } - - function values() { - return this.pluck('value'); - } - - function index(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - } - - function merge(object) { - return this.clone().update(object); - } - - function update(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - } - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); - } - - function toQueryString() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; - - if (values && typeof values == 'object') { - if (Object.isArray(values)) { - var queryValues = []; - for (var i = 0, len = values.length, value; i < len; i++) { - value = values[i]; - queryValues.push(toQueryPair(key, value)); - } - return results.concat(queryValues); - } - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - } - - function inspect() { - return '#'; - } - - function clone() { - return new Hash(this); - } - - return { - initialize: initialize, - _each: _each, - set: set, - get: get, - unset: unset, - toObject: toObject, - toTemplateReplacements: toObject, - keys: keys, - values: values, - index: index, - merge: merge, - update: update, - toQueryString: toQueryString, - inspect: inspect, - toJSON: toObject, - clone: clone - }; -})()); - -Hash.from = $H; -Object.extend(Number.prototype, (function() { - function toColorPart() { - return this.toPaddedString(2, 16); - } - - function succ() { - return this + 1; - } - - function times(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - } - - function toPaddedString(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - } - - function abs() { - return Math.abs(this); - } - - function round() { - return Math.round(this); - } - - function ceil() { - return Math.ceil(this); - } - - function floor() { - return Math.floor(this); - } - - return { - toColorPart: toColorPart, - succ: succ, - times: times, - toPaddedString: toPaddedString, - abs: abs, - round: round, - ceil: ceil, - floor: floor - }; -})()); - -function $R(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -} - -var ObjectRange = Class.create(Enumerable, (function() { - function initialize(start, end, exclusive) { - this.start = start; - this.end = end; - this.exclusive = exclusive; - } - - function _each(iterator) { - var value = this.start; - while (this.include(value)) { - iterator(value); - value = value.succ(); - } - } - - function include(value) { - if (value < this.start) - return false; - if (this.exclusive) - return value < this.end; - return value <= this.end; - } - - return { - initialize: initialize, - _each: _each, - include: include - }; -})()); - - - -var Abstract = { }; - - -var Try = { - these: function() { - var returnValue; - - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - - return returnValue; - } -}; - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new XMLHttpRequest()}, - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')} - ) || false; - }, - - activeRequestCount: 0 -}; - -Ajax.Responders = { - responders: [], - - _each: function(iterator) { - this.responders._each(iterator); - }, - - register: function(responder) { - if (!this.include(responder)) - this.responders.push(responder); - }, - - unregister: function(responder) { - this.responders = this.responders.without(responder); - }, - - dispatch: function(callback, request, transport, json) { - this.each(function(responder) { - if (Object.isFunction(responder[callback])) { - try { - responder[callback].apply(responder, [request, transport, json]); - } catch (e) { } - } - }); - } -}; - -Object.extend(Ajax.Responders, Enumerable); - -Ajax.Responders.register({ - onCreate: function() { Ajax.activeRequestCount++ }, - onComplete: function() { Ajax.activeRequestCount-- } -}); -Ajax.Base = Class.create({ - initialize: function(options) { - this.options = { - method: 'post', - asynchronous: true, - contentType: 'application/x-www-form-urlencoded', - encoding: 'UTF-8', - parameters: '', - evalJSON: true, - evalJS: true - }; - Object.extend(this.options, options || { }); - - this.options.method = this.options.method.toLowerCase(); - - if (Object.isHash(this.options.parameters)) - this.options.parameters = this.options.parameters.toObject(); - } -}); -Ajax.Request = Class.create(Ajax.Base, { - _complete: false, - - initialize: function($super, url, options) { - $super(options); - this.transport = Ajax.getTransport(); - this.request(url); - }, - - request: function(url) { - this.url = url; - this.method = this.options.method; - var params = Object.isString(this.options.parameters) ? - this.options.parameters : - Object.toQueryString(this.options.parameters); - - if (!['get', 'post'].include(this.method)) { - params += (params ? '&' : '') + "_method=" + this.method; - this.method = 'post'; - } - - if (params && this.method === 'get') { - this.url += (this.url.include('?') ? '&' : '?') + params; - } - - this.parameters = params.toQueryParams(); - - try { - var response = new Ajax.Response(this); - if (this.options.onCreate) this.options.onCreate(response); - Ajax.Responders.dispatch('onCreate', this, response); - - this.transport.open(this.method.toUpperCase(), this.url, - this.options.asynchronous); - - if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); - - this.transport.onreadystatechange = this.onStateChange.bind(this); - this.setRequestHeaders(); - - this.body = this.method == 'post' ? (this.options.postBody || params) : null; - this.transport.send(this.body); - - /* Force Firefox to handle ready state 4 for synchronous requests */ - if (!this.options.asynchronous && this.transport.overrideMimeType) - this.onStateChange(); - - } - catch (e) { - this.dispatchException(e); - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState > 1 && !((readyState == 4) && this._complete)) - this.respondToReadyState(this.transport.readyState); - }, - - setRequestHeaders: function() { - var headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'X-Prototype-Version': Prototype.Version, - 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' - }; - - if (this.method == 'post') { - headers['Content-type'] = this.options.contentType + - (this.options.encoding ? '; charset=' + this.options.encoding : ''); - - /* Force "Connection: close" for older Mozilla browsers to work - * around a bug where XMLHttpRequest sends an incorrect - * Content-length header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType && - (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) - headers['Connection'] = 'close'; - } - - if (typeof this.options.requestHeaders == 'object') { - var extras = this.options.requestHeaders; - - if (Object.isFunction(extras.push)) - for (var i = 0, length = extras.length; i < length; i += 2) - headers[extras[i]] = extras[i+1]; - else - $H(extras).each(function(pair) { headers[pair.key] = pair.value }); - } - - for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); - }, - - success: function() { - var status = this.getStatus(); - return !status || (status >= 200 && status < 300) || status == 304; - }, - - getStatus: function() { - try { - if (this.transport.status === 1223) return 204; - return this.transport.status || 0; - } catch (e) { return 0 } - }, - - respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); - - if (state == 'Complete') { - try { - this._complete = true; - (this.options['on' + response.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - var contentType = response.getHeader('Content-type'); - if (this.options.evalJS == 'force' - || (this.options.evalJS && this.isSameOrigin() && contentType - && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) - this.evalResponse(); - } - - try { - (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); - Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - if (state == 'Complete') { - this.transport.onreadystatechange = Prototype.emptyFunction; - } - }, - - isSameOrigin: function() { - var m = this.url.match(/^\s*https?:\/\/[^\/]*/); - return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ - protocol: location.protocol, - domain: document.domain, - port: location.port ? ':' + location.port : '' - })); - }, - - getHeader: function(name) { - try { - return this.transport.getResponseHeader(name) || null; - } catch (e) { return null; } - }, - - evalResponse: function() { - try { - return eval((this.transport.responseText || '').unfilterJSON()); - } catch (e) { - this.dispatchException(e); - } - }, - - dispatchException: function(exception) { - (this.options.onException || Prototype.emptyFunction)(this, exception); - Ajax.Responders.dispatch('onException', this, exception); - } -}); - -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - - - - - - - - -Ajax.Response = Class.create({ - initialize: function(request){ - this.request = request; - var transport = this.transport = request.transport, - readyState = this.readyState = transport.readyState; - - if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { - this.status = this.getStatus(); - this.statusText = this.getStatusText(); - this.responseText = String.interpret(transport.responseText); - this.headerJSON = this._getHeaderJSON(); - } - - if (readyState == 4) { - var xml = transport.responseXML; - this.responseXML = Object.isUndefined(xml) ? null : xml; - this.responseJSON = this._getResponseJSON(); - } - }, - - status: 0, - - statusText: '', - - getStatus: Ajax.Request.prototype.getStatus, - - getStatusText: function() { - try { - return this.transport.statusText || ''; - } catch (e) { return '' } - }, - - getHeader: Ajax.Request.prototype.getHeader, - - getAllHeaders: function() { - try { - return this.getAllResponseHeaders(); - } catch (e) { return null } - }, - - getResponseHeader: function(name) { - return this.transport.getResponseHeader(name); - }, - - getAllResponseHeaders: function() { - return this.transport.getAllResponseHeaders(); - }, - - _getHeaderJSON: function() { - var json = this.getHeader('X-JSON'); - if (!json) return null; - json = decodeURIComponent(escape(json)); - try { - return json.evalJSON(this.request.options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - }, - - _getResponseJSON: function() { - var options = this.request.options; - if (!options.evalJSON || (options.evalJSON != 'force' && - !(this.getHeader('Content-type') || '').include('application/json')) || - this.responseText.blank()) - return null; - try { - return this.responseText.evalJSON(options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - } -}); - -Ajax.Updater = Class.create(Ajax.Request, { - initialize: function($super, container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) - }; - - options = Object.clone(options); - var onComplete = options.onComplete; - options.onComplete = (function(response, json) { - this.updateContent(response.responseText); - if (Object.isFunction(onComplete)) onComplete(response, json); - }).bind(this); - - $super(url, options); - }, - - updateContent: function(responseText) { - var receiver = this.container[this.success() ? 'success' : 'failure'], - options = this.options; - - if (!options.evalScripts) responseText = responseText.stripScripts(); - - if (receiver = $(receiver)) { - if (options.insertion) { - if (Object.isString(options.insertion)) { - var insertion = { }; insertion[options.insertion] = responseText; - receiver.insert(insertion); - } - else options.insertion(receiver, responseText); - } - else receiver.update(responseText); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { - initialize: function($super, container, url, options) { - $super(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.decay = (this.options.decay || 1); - - this.updater = { }; - this.container = container; - this.url = url; - - this.start(); - }, - - start: function() { - this.options.onComplete = this.updateComplete.bind(this); - this.onTimerEvent(); - }, - - stop: function() { - this.updater.options.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Prototype.emptyFunction).apply(this, arguments); - }, - - updateComplete: function(response) { - if (this.options.decay) { - this.decay = (response.responseText == this.lastText ? - this.decay * this.options.decay : 1); - - this.lastText = response.responseText; - } - this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); - }, - - onTimerEvent: function() { - this.updater = new Ajax.Updater(this.container, this.url, this.options); - } -}); - - -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - - - -(function(global) { - function shouldUseCache(tagName, attributes) { - if (tagName === 'select') return false; - if ('type' in attributes) return false; - return true; - } - - var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ - try { - var el = document.createElement(''); - return el.tagName.toLowerCase() === 'input' && el.name === 'x'; - } - catch(err) { - return false; - } - })(); - - var element = global.Element; - - global.Element = function(tagName, attributes) { - attributes = attributes || { }; - tagName = tagName.toLowerCase(); - var cache = Element.cache; - - if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { - tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; - return Element.writeAttribute(document.createElement(tagName), attributes); - } - - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - - var node = shouldUseCache(tagName, attributes) ? - cache[tagName].cloneNode(false) : document.createElement(tagName); - - return Element.writeAttribute(node, attributes); - }; - - Object.extend(global.Element, element || { }); - if (element) global.Element.prototype = element.prototype; - -})(this); - -Element.idCounter = 1; -Element.cache = { }; - -Element._purgeElement = function(element) { - var uid = element._prototypeUID; - if (uid) { - Element.stopObserving(element); - element._prototypeUID = void 0; - delete Element.Storage[uid]; - } -} - -Element.Methods = { - visible: function(element) { - return $(element).style.display != 'none'; - }, - - toggle: function(element) { - element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); - return element; - }, - - hide: function(element) { - element = $(element); - element.style.display = 'none'; - return element; - }, - - show: function(element) { - element = $(element); - element.style.display = ''; - return element; - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - return element; - }, - - update: (function(){ - - var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ - var el = document.createElement("select"), - isBuggy = true; - el.innerHTML = ""; - if (el.options && el.options[0]) { - isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; - } - el = null; - return isBuggy; - })(); - - var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ - try { - var el = document.createElement("table"); - if (el && el.tBodies) { - el.innerHTML = "test"; - var isBuggy = typeof el.tBodies[0] == "undefined"; - el = null; - return isBuggy; - } - } catch (e) { - return true; - } - })(); - - var LINK_ELEMENT_INNERHTML_BUGGY = (function() { - try { - var el = document.createElement('div'); - el.innerHTML = ""; - var isBuggy = (el.childNodes.length === 0); - el = null; - return isBuggy; - } catch(e) { - return true; - } - })(); - - var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || - TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; - - var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { - var s = document.createElement("script"), - isBuggy = false; - try { - s.appendChild(document.createTextNode("")); - isBuggy = !s.firstChild || - s.firstChild && s.firstChild.nodeType !== 3; - } catch (e) { - isBuggy = true; - } - s = null; - return isBuggy; - })(); - - - function update(element, content) { - element = $(element); - var purgeElement = Element._purgeElement; - - var descendants = element.getElementsByTagName('*'), - i = descendants.length; - while (i--) purgeElement(descendants[i]); - - if (content && content.toElement) - content = content.toElement(); - - if (Object.isElement(content)) - return element.update().insert(content); - - content = Object.toHTML(content); - - var tagName = element.tagName.toUpperCase(); - - if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { - element.text = content; - return element; - } - - if (ANY_INNERHTML_BUGGY) { - if (tagName in Element._insertionTranslations.tags) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { - element.appendChild(node) - }); - } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true); - nodes.each(function(node) { element.appendChild(node) }); - } - else { - element.innerHTML = content.stripScripts(); - } - } - else { - element.innerHTML = content.stripScripts(); - } - - content.evalScripts.bind(content).defer(); - return element; - } - - return update; - })(), - - replace: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { - content = Object.toHTML(content); - var range = element.ownerDocument.createRange(); - range.selectNode(element); - content.evalScripts.bind(content).defer(); - content = range.createContextualFragment(content.stripScripts()); - } - element.parentNode.replaceChild(content, element); - return element; - }, - - insert: function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - - wrap: function(element, wrapper, attributes) { - element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); - if (element.parentNode) - element.parentNode.replaceChild(wrapper, element); - wrapper.appendChild(element); - return wrapper; - }, - - inspect: function(element) { - element = $(element); - var result = '<' + element.tagName.toLowerCase(); - $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), - attribute = pair.last(), - value = (element[property] || '').toString(); - if (value) result += ' ' + attribute + '=' + value.inspect(true); - }); - return result + '>'; - }, - - recursivelyCollect: function(element, property, maximumLength) { - element = $(element); - maximumLength = maximumLength || -1; - var elements = []; - - while (element = element[property]) { - if (element.nodeType == 1) - elements.push(Element.extend(element)); - if (elements.length == maximumLength) - break; - } - - return elements; - }, - - ancestors: function(element) { - return Element.recursivelyCollect(element, 'parentNode'); - }, - - descendants: function(element) { - return Element.select(element, "*"); - }, - - firstDescendant: function(element) { - element = $(element).firstChild; - while (element && element.nodeType != 1) element = element.nextSibling; - return $(element); - }, - - immediateDescendants: function(element) { - var results = [], child = $(element).firstChild; - while (child) { - if (child.nodeType === 1) { - results.push(Element.extend(child)); - } - child = child.nextSibling; - } - return results; - }, - - previousSiblings: function(element, maximumLength) { - return Element.recursivelyCollect(element, 'previousSibling'); - }, - - nextSiblings: function(element) { - return Element.recursivelyCollect(element, 'nextSibling'); - }, - - siblings: function(element) { - element = $(element); - return Element.previousSiblings(element).reverse() - .concat(Element.nextSiblings(element)); - }, - - match: function(element, selector) { - element = $(element); - if (Object.isString(selector)) - return Prototype.Selector.match(element, selector); - return selector.match(element); - }, - - up: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(element.parentNode); - var ancestors = Element.ancestors(element); - return Object.isNumber(expression) ? ancestors[expression] : - Prototype.Selector.find(ancestors, expression, index); - }, - - down: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return Element.firstDescendant(element); - return Object.isNumber(expression) ? Element.descendants(element)[expression] : - Element.select(element, expression)[index || 0]; - }, - - previous: function(element, expression, index) { - element = $(element); - if (Object.isNumber(expression)) index = expression, expression = false; - if (!Object.isNumber(index)) index = 0; - - if (expression) { - return Prototype.Selector.find(element.previousSiblings(), expression, index); - } else { - return element.recursivelyCollect("previousSibling", index + 1)[index]; - } - }, - - next: function(element, expression, index) { - element = $(element); - if (Object.isNumber(expression)) index = expression, expression = false; - if (!Object.isNumber(index)) index = 0; - - if (expression) { - return Prototype.Selector.find(element.nextSiblings(), expression, index); - } else { - var maximumLength = Object.isNumber(index) ? index + 1 : 1; - return element.recursivelyCollect("nextSibling", index + 1)[index]; - } - }, - - - select: function(element) { - element = $(element); - var expressions = Array.prototype.slice.call(arguments, 1).join(', '); - return Prototype.Selector.select(expressions, element); - }, - - adjacent: function(element) { - element = $(element); - var expressions = Array.prototype.slice.call(arguments, 1).join(', '); - return Prototype.Selector.select(expressions, element.parentNode).without(element); - }, - - identify: function(element) { - element = $(element); - var id = Element.readAttribute(element, 'id'); - if (id) return id; - do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); - Element.writeAttribute(element, 'id', id); - return id; - }, - - readAttribute: function(element, name) { - element = $(element); - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } - } - return element.getAttribute(name); - }, - - writeAttribute: function(element, name, value) { - element = $(element); - var attributes = { }, t = Element._attributeTranslations.write; - - if (typeof name == 'object') attributes = name; - else attributes[name] = Object.isUndefined(value) ? true : value; - - for (var attr in attributes) { - name = t.names[attr] || attr; - value = attributes[attr]; - if (t.values[attr]) name = t.values[attr](element, value); - if (value === false || value === null) - element.removeAttribute(name); - else if (value === true) - element.setAttribute(name, name); - else element.setAttribute(name, value); - } - return element; - }, - - getHeight: function(element) { - return Element.getDimensions(element).height; - }, - - getWidth: function(element) { - return Element.getDimensions(element).width; - }, - - classNames: function(element) { - return new Element.ClassNames(element); - }, - - hasClassName: function(element, className) { - if (!(element = $(element))) return; - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || - new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }, - - addClassName: function(element, className) { - if (!(element = $(element))) return; - if (!Element.hasClassName(element, className)) - element.className += (element.className ? ' ' : '') + className; - return element; - }, - - removeClassName: function(element, className) { - if (!(element = $(element))) return; - element.className = element.className.replace( - new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); - return element; - }, - - toggleClassName: function(element, className) { - if (!(element = $(element))) return; - return Element[Element.hasClassName(element, className) ? - 'removeClassName' : 'addClassName'](element, className); - }, - - cleanWhitespace: function(element) { - element = $(element); - var node = element.firstChild; - while (node) { - var nextNode = node.nextSibling; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - element.removeChild(node); - node = nextNode; - } - return element; - }, - - empty: function(element) { - return $(element).innerHTML.blank(); - }, - - descendantOf: function(element, ancestor) { - element = $(element), ancestor = $(ancestor); - - if (element.compareDocumentPosition) - return (element.compareDocumentPosition(ancestor) & 8) === 8; - - if (ancestor.contains) - return ancestor.contains(element) && ancestor !== element; - - while (element = element.parentNode) - if (element == ancestor) return true; - - return false; - }, - - scrollTo: function(element) { - element = $(element); - var pos = Element.cumulativeOffset(element); - window.scrollTo(pos[0], pos[1]); - return element; - }, - - getStyle: function(element, style) { - element = $(element); - style = style == 'float' ? 'cssFloat' : style.camelize(); - var value = element.style[style]; - if (!value || value == 'auto') { - var css = document.defaultView.getComputedStyle(element, null); - value = css ? css[style] : null; - } - if (style == 'opacity') return value ? parseFloat(value) : 1.0; - return value == 'auto' ? null : value; - }, - - getOpacity: function(element) { - return $(element).getStyle('opacity'); - }, - - setStyle: function(element, styles) { - element = $(element); - var elementStyle = element.style, match; - if (Object.isString(styles)) { - element.style.cssText += ';' + styles; - return styles.include('opacity') ? - element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; - } - for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]); - else - elementStyle[(property == 'float' || property == 'cssFloat') ? - (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : - property] = styles[property]; - - return element; - }, - - setOpacity: function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - return element; - }, - - makePositioned: function(element) { - element = $(element); - var pos = Element.getStyle(element, 'position'); - if (pos == 'static' || !pos) { - element._madePositioned = true; - element.style.position = 'relative'; - if (Prototype.Browser.Opera) { - element.style.top = 0; - element.style.left = 0; - } - } - return element; - }, - - undoPositioned: function(element) { - element = $(element); - if (element._madePositioned) { - element._madePositioned = undefined; - element.style.position = - element.style.top = - element.style.left = - element.style.bottom = - element.style.right = ''; - } - return element; - }, - - makeClipping: function(element) { - element = $(element); - if (element._overflow) return element; - element._overflow = Element.getStyle(element, 'overflow') || 'auto'; - if (element._overflow !== 'hidden') - element.style.overflow = 'hidden'; - return element; - }, - - undoClipping: function(element) { - element = $(element); - if (!element._overflow) return element; - element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; - element._overflow = null; - return element; - }, - - clonePosition: function(element, source) { - var options = Object.extend({ - setLeft: true, - setTop: true, - setWidth: true, - setHeight: true, - offsetTop: 0, - offsetLeft: 0 - }, arguments[2] || { }); - - source = $(source); - var p = Element.viewportOffset(source), delta = [0, 0], parent = null; - - element = $(element); - - if (Element.getStyle(element, 'position') == 'absolute') { - parent = Element.getOffsetParent(element); - delta = Element.viewportOffset(parent); - } - - if (parent == document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; - if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; - if (options.setWidth) element.style.width = source.offsetWidth + 'px'; - if (options.setHeight) element.style.height = source.offsetHeight + 'px'; - return element; - } -}; - -Object.extend(Element.Methods, { - getElementsBySelector: Element.Methods.select, - - childElements: Element.Methods.immediateDescendants -}); - -Element._attributeTranslations = { - write: { - names: { - className: 'class', - htmlFor: 'for' - }, - values: { } - } -}; - -if (Prototype.Browser.Opera) { - Element.Methods.getStyle = Element.Methods.getStyle.wrap( - function(proceed, element, style) { - switch (style) { - case 'height': case 'width': - if (!Element.visible(element)) return null; - - var dim = parseInt(proceed(element, style), 10); - - if (dim !== element['offset' + style.capitalize()]) - return dim + 'px'; - - var properties; - if (style === 'height') { - properties = ['border-top-width', 'padding-top', - 'padding-bottom', 'border-bottom-width']; - } - else { - properties = ['border-left-width', 'padding-left', - 'padding-right', 'border-right-width']; - } - return properties.inject(dim, function(memo, property) { - var val = proceed(element, property); - return val === null ? memo : memo - parseInt(val, 10); - }) + 'px'; - default: return proceed(element, style); - } - } - ); - - Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( - function(proceed, element, attribute) { - if (attribute === 'title') return element.title; - return proceed(element, attribute); - } - ); -} - -else if (Prototype.Browser.IE) { - Element.Methods.getStyle = function(element, style) { - element = $(element); - style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); - var value = element.style[style]; - if (!value && element.currentStyle) value = element.currentStyle[style]; - - if (style == 'opacity') { - if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) - if (value[1]) return parseFloat(value[1]) / 100; - return 1.0; - } - - if (value == 'auto') { - if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset' + style.capitalize()] + 'px'; - return null; - } - return value; - }; - - Element.Methods.setOpacity = function(element, value) { - function stripAlpha(filter){ - return filter.replace(/alpha\([^\)]*\)/gi,''); - } - element = $(element); - var currentStyle = element.currentStyle; - if ((currentStyle && !currentStyle.hasLayout) || - (!currentStyle && element.style.zoom == 'normal')) - element.style.zoom = 1; - - var filter = element.getStyle('filter'), style = element.style; - if (value == 1 || value === '') { - (filter = stripAlpha(filter)) ? - style.filter = filter : style.removeAttribute('filter'); - return element; - } else if (value < 0.00001) value = 0; - style.filter = stripAlpha(filter) + - 'alpha(opacity=' + (value * 100) + ')'; - return element; - }; - - Element._attributeTranslations = (function(){ - - var classProp = 'className', - forProp = 'for', - el = document.createElement('div'); - - el.setAttribute(classProp, 'x'); - - if (el.className !== 'x') { - el.setAttribute('class', 'x'); - if (el.className === 'x') { - classProp = 'class'; - } - } - el = null; - - el = document.createElement('label'); - el.setAttribute(forProp, 'x'); - if (el.htmlFor !== 'x') { - el.setAttribute('htmlFor', 'x'); - if (el.htmlFor === 'x') { - forProp = 'htmlFor'; - } - } - el = null; - - return { - read: { - names: { - 'class': classProp, - 'className': classProp, - 'for': forProp, - 'htmlFor': forProp - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute); - }, - _getAttr2: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: (function(){ - - var el = document.createElement('div'), f; - el.onclick = Prototype.emptyFunction; - var value = el.getAttribute('onclick'); - - if (String(value).indexOf('{') > -1) { - f = function(element, attribute) { - attribute = element.getAttribute(attribute); - if (!attribute) return null; - attribute = attribute.toString(); - attribute = attribute.split('{')[1]; - attribute = attribute.split('}')[0]; - return attribute.strip(); - }; - } - else if (value === '') { - f = function(element, attribute) { - attribute = element.getAttribute(attribute); - if (!attribute) return null; - return attribute.strip(); - }; - } - el = null; - return f; - })(), - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - return element.title; - } - } - } - } - })(); - - Element._attributeTranslations.write = { - names: Object.extend({ - cellpadding: 'cellPadding', - cellspacing: 'cellSpacing' - }, Element._attributeTranslations.read.names), - values: { - checked: function(element, value) { - element.checked = !!value; - }, - - style: function(element, value) { - element.style.cssText = value ? value : ''; - } - } - }; - - Element._attributeTranslations.has = {}; - - $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { - Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; - Element._attributeTranslations.has[attr.toLowerCase()] = attr; - }); - - (function(v) { - Object.extend(v, { - href: v._getAttr2, - src: v._getAttr2, - type: v._getAttr, - action: v._getAttrNode, - disabled: v._flag, - checked: v._flag, - readonly: v._flag, - multiple: v._flag, - onload: v._getEv, - onunload: v._getEv, - onclick: v._getEv, - ondblclick: v._getEv, - onmousedown: v._getEv, - onmouseup: v._getEv, - onmouseover: v._getEv, - onmousemove: v._getEv, - onmouseout: v._getEv, - onfocus: v._getEv, - onblur: v._getEv, - onkeypress: v._getEv, - onkeydown: v._getEv, - onkeyup: v._getEv, - onsubmit: v._getEv, - onreset: v._getEv, - onselect: v._getEv, - onchange: v._getEv - }); - })(Element._attributeTranslations.read.values); - - if (Prototype.BrowserFeatures.ElementExtensions) { - (function() { - function _descendants(element) { - var nodes = element.getElementsByTagName('*'), results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName !== "!") // Filter out comment nodes. - results.push(node); - return results; - } - - Element.Methods.down = function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? _descendants(element)[expression] : - Element.select(element, expression)[index || 0]; - } - })(); - } - -} - -else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; - }; -} - -else if (Prototype.Browser.WebKit) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - - if (value == 1) - if (element.tagName.toUpperCase() == 'IMG' && element.width) { - element.width++; element.width--; - } else try { - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch (e) { } - - return element; - }; -} - -if ('outerHTML' in document.documentElement) { - Element.Methods.replace = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - element.parentNode.replaceChild(content, element); - return element; - } - - content = Object.toHTML(content); - var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - - if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(), - fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - parent.removeChild(element); - if (nextSibling) - fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); - else - fragments.each(function(node) { parent.appendChild(node) }); - } - else element.outerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -Element._returnOffset = function(l, t) { - var result = [l, t]; - result.left = l; - result.top = t; - return result; -}; - -Element._getContentFromAnonymousElement = function(tagName, html, force) { - var div = new Element('div'), - t = Element._insertionTranslations.tags[tagName]; - - var workaround = false; - if (t) workaround = true; - else if (force) { - workaround = true; - t = ['', '', 0]; - } - - if (workaround) { - div.innerHTML = ' ' + t[0] + html + t[1]; - div.removeChild(div.firstChild); - for (var i = t[2]; i--; ) { - div = div.firstChild; - } - } - else { - div.innerHTML = html; - } - return $A(div.childNodes); -}; - -Element._insertionTranslations = { - before: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - top: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - bottom: function(element, node) { - element.appendChild(node); - }, - after: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - tags: { - TABLE: ['', '
', 1], - TBODY: ['', '
', 2], - TR: ['', '
', 3], - TD: ['
', '
', 4], - SELECT: ['', 1] - } -}; - -(function() { - var tags = Element._insertionTranslations.tags; - Object.extend(tags, { - THEAD: tags.TBODY, - TFOOT: tags.TBODY, - TH: tags.TD - }); -})(); - -Element.Methods.Simulated = { - hasAttribute: function(element, attribute) { - attribute = Element._attributeTranslations.has[attribute] || attribute; - var node = $(element).getAttributeNode(attribute); - return !!(node && node.specified); - } -}; - -Element.Methods.ByTag = { }; - -Object.extend(Element, Element.Methods); - -(function(div) { - - if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = div['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; - } - - div = null; - -})(document.createElement('div')); - -Element.extend = (function() { - - function checkDeficiency(tagName) { - if (typeof window.Element != 'undefined') { - var proto = window.Element.prototype; - if (proto) { - var id = '_' + (Math.random()+'').slice(2), - el = document.createElement(tagName); - proto[id] = 'x'; - var isBuggy = (el[id] !== 'x'); - delete proto[id]; - el = null; - return isBuggy; - } - } - return false; - } - - function extendElementWith(element, methods) { - for (var property in methods) { - var value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } - } - - var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); - - if (Prototype.BrowserFeatures.SpecificElementExtensions) { - if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { - return function(element) { - if (element && typeof element._extendedByPrototype == 'undefined') { - var t = element.tagName; - if (t && (/^(?:object|applet|embed)$/i.test(t))) { - extendElementWith(element, Element.Methods); - extendElementWith(element, Element.Methods.Simulated); - extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); - } - } - return element; - } - } - return Prototype.K; - } - - var Methods = { }, ByTag = Element.Methods.ByTag; - - var extend = Object.extend(function(element) { - if (!element || typeof element._extendedByPrototype != 'undefined' || - element.nodeType != 1 || element == window) return element; - - var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(); - - if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - - extendElementWith(element, methods); - - element._extendedByPrototype = Prototype.emptyFunction; - return element; - - }, { - refresh: function() { - if (!Prototype.BrowserFeatures.ElementExtensions) { - Object.extend(Methods, Element.Methods); - Object.extend(Methods, Element.Methods.Simulated); - } - } - }); - - extend.refresh(); - return extend; -})(); - -if (document.documentElement.hasAttribute) { - Element.hasAttribute = function(element, attribute) { - return element.hasAttribute(attribute); - }; -} -else { - Element.hasAttribute = Element.Methods.Simulated.hasAttribute; -} - -Element.addMethods = function(methods) { - var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; - - if (!methods) { - Object.extend(Form, Form.Methods); - Object.extend(Form.Element, Form.Element.Methods); - Object.extend(Element.Methods.ByTag, { - "FORM": Object.clone(Form.Methods), - "INPUT": Object.clone(Form.Element.Methods), - "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods), - "BUTTON": Object.clone(Form.Element.Methods) - }); - } - - if (arguments.length == 2) { - var tagName = methods; - methods = arguments[1]; - } - - if (!tagName) Object.extend(Element.Methods, methods || { }); - else { - if (Object.isArray(tagName)) tagName.each(extend); - else extend(tagName); - } - - function extend(tagName) { - tagName = tagName.toUpperCase(); - if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = { }; - Object.extend(Element.Methods.ByTag[tagName], methods); - } - - function copy(methods, destination, onlyIfAbsent) { - onlyIfAbsent = onlyIfAbsent || false; - for (var property in methods) { - var value = methods[property]; - if (!Object.isFunction(value)) continue; - if (!onlyIfAbsent || !(property in destination)) - destination[property] = value.methodize(); - } - } - - function findDOMClass(tagName) { - var klass; - var trans = { - "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", - "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", - "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", - "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", - "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": - "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": - "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": - "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": - "FrameSet", "IFRAME": "IFrame" - }; - if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName.capitalize() + 'Element'; - if (window[klass]) return window[klass]; - - var element = document.createElement(tagName), - proto = element['__proto__'] || element.constructor.prototype; - - element = null; - return proto; - } - - var elementPrototype = window.HTMLElement ? HTMLElement.prototype : - Element.prototype; - - if (F.ElementExtensions) { - copy(Element.Methods, elementPrototype); - copy(Element.Methods.Simulated, elementPrototype, true); - } - - if (F.SpecificElementExtensions) { - for (var tag in Element.Methods.ByTag) { - var klass = findDOMClass(tag); - if (Object.isUndefined(klass)) continue; - copy(T[tag], klass.prototype); - } - } - - Object.extend(Element, Element.Methods); - delete Element.ByTag; - - if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = { }; -}; - - -document.viewport = { - - getDimensions: function() { - return { width: this.getWidth(), height: this.getHeight() }; - }, - - getScrollOffsets: function() { - return Element._returnOffset( - window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - } -}; - -(function(viewport) { - var B = Prototype.Browser, doc = document, element, property = {}; - - function getRootElement() { - if (B.WebKit && !doc.evaluate) - return document; - - if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) - return document.body; - - return document.documentElement; - } - - function define(D) { - if (!element) element = getRootElement(); - - property[D] = 'client' + D; - - viewport['get' + D] = function() { return element[property[D]] }; - return viewport['get' + D](); - } - - viewport.getWidth = define.curry('Width'); - - viewport.getHeight = define.curry('Height'); -})(document.viewport); - - -Element.Storage = { - UID: 1 -}; - -Element.addMethods({ - getStorage: function(element) { - if (!(element = $(element))) return; - - var uid; - if (element === window) { - uid = 0; - } else { - if (typeof element._prototypeUID === "undefined") - element._prototypeUID = Element.Storage.UID++; - uid = element._prototypeUID; - } - - if (!Element.Storage[uid]) - Element.Storage[uid] = $H(); - - return Element.Storage[uid]; - }, - - store: function(element, key, value) { - if (!(element = $(element))) return; - - if (arguments.length === 2) { - Element.getStorage(element).update(key); - } else { - Element.getStorage(element).set(key, value); - } - - return element; - }, - - retrieve: function(element, key, defaultValue) { - if (!(element = $(element))) return; - var hash = Element.getStorage(element), value = hash.get(key); - - if (Object.isUndefined(value)) { - hash.set(key, defaultValue); - value = defaultValue; - } - - return value; - }, - - clone: function(element, deep) { - if (!(element = $(element))) return; - var clone = element.cloneNode(deep); - clone._prototypeUID = void 0; - if (deep) { - var descendants = Element.select(clone, '*'), - i = descendants.length; - while (i--) { - descendants[i]._prototypeUID = void 0; - } - } - return Element.extend(clone); - }, - - purge: function(element) { - if (!(element = $(element))) return; - var purgeElement = Element._purgeElement; - - purgeElement(element); - - var descendants = element.getElementsByTagName('*'), - i = descendants.length; - - while (i--) purgeElement(descendants[i]); - - return null; - } -}); - -(function() { - - function toDecimal(pctString) { - var match = pctString.match(/^(\d+)%?$/i); - if (!match) return null; - return (Number(match[1]) / 100); - } - - function getPixelValue(value, property, context) { - var element = null; - if (Object.isElement(value)) { - element = value; - value = element.getStyle(property); - } - - if (value === null) { - return null; - } - - if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { - return window.parseFloat(value); - } - - var isPercentage = value.include('%'), isViewport = (context === document.viewport); - - if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) { - var style = element.style.left, rStyle = element.runtimeStyle.left; - element.runtimeStyle.left = element.currentStyle.left; - element.style.left = value || 0; - value = element.style.pixelLeft; - element.style.left = style; - element.runtimeStyle.left = rStyle; - - return value; - } - - if (element && isPercentage) { - context = context || element.parentNode; - var decimal = toDecimal(value); - var whole = null; - var position = element.getStyle('position'); - - var isHorizontal = property.include('left') || property.include('right') || - property.include('width'); - - var isVertical = property.include('top') || property.include('bottom') || - property.include('height'); - - if (context === document.viewport) { - if (isHorizontal) { - whole = document.viewport.getWidth(); - } else if (isVertical) { - whole = document.viewport.getHeight(); - } - } else { - if (isHorizontal) { - whole = $(context).measure('width'); - } else if (isVertical) { - whole = $(context).measure('height'); - } - } - - return (whole === null) ? 0 : whole * decimal; - } - - return 0; - } - - function toCSSPixels(number) { - if (Object.isString(number) && number.endsWith('px')) { - return number; - } - return number + 'px'; - } - - function isDisplayed(element) { - var originalElement = element; - while (element && element.parentNode) { - var display = element.getStyle('display'); - if (display === 'none') { - return false; - } - element = $(element.parentNode); - } - return true; - } - - var hasLayout = Prototype.K; - if ('currentStyle' in document.documentElement) { - hasLayout = function(element) { - if (!element.currentStyle.hasLayout) { - element.style.zoom = 1; - } - return element; - }; - } - - function cssNameFor(key) { - if (key.include('border')) key = key + '-width'; - return key.camelize(); - } - - Element.Layout = Class.create(Hash, { - initialize: function($super, element, preCompute) { - $super(); - this.element = $(element); - - Element.Layout.PROPERTIES.each( function(property) { - this._set(property, null); - }, this); - - if (preCompute) { - this._preComputing = true; - this._begin(); - Element.Layout.PROPERTIES.each( this._compute, this ); - this._end(); - this._preComputing = false; - } - }, - - _set: function(property, value) { - return Hash.prototype.set.call(this, property, value); - }, - - set: function(property, value) { - throw "Properties of Element.Layout are read-only."; - }, - - get: function($super, property) { - var value = $super(property); - return value === null ? this._compute(property) : value; - }, - - _begin: function() { - if (this._prepared) return; - - var element = this.element; - if (isDisplayed(element)) { - this._prepared = true; - return; - } - - var originalStyles = { - position: element.style.position || '', - width: element.style.width || '', - visibility: element.style.visibility || '', - display: element.style.display || '' - }; - - element.store('prototype_original_styles', originalStyles); - - var position = element.getStyle('position'), - width = element.getStyle('width'); - - if (width === "0px" || width === null) { - element.style.display = 'block'; - width = element.getStyle('width'); - } - - var context = (position === 'fixed') ? document.viewport : - element.parentNode; - - element.setStyle({ - position: 'absolute', - visibility: 'hidden', - display: 'block' - }); - - var positionedWidth = element.getStyle('width'); - - var newWidth; - if (width && (positionedWidth === width)) { - newWidth = getPixelValue(element, 'width', context); - } else if (position === 'absolute' || position === 'fixed') { - newWidth = getPixelValue(element, 'width', context); - } else { - var parent = element.parentNode, pLayout = $(parent).getLayout(); - - newWidth = pLayout.get('width') - - this.get('margin-left') - - this.get('border-left') - - this.get('padding-left') - - this.get('padding-right') - - this.get('border-right') - - this.get('margin-right'); - } - - element.setStyle({ width: newWidth + 'px' }); - - this._prepared = true; - }, - - _end: function() { - var element = this.element; - var originalStyles = element.retrieve('prototype_original_styles'); - element.store('prototype_original_styles', null); - element.setStyle(originalStyles); - this._prepared = false; - }, - - _compute: function(property) { - var COMPUTATIONS = Element.Layout.COMPUTATIONS; - if (!(property in COMPUTATIONS)) { - throw "Property not found."; - } - - return this._set(property, COMPUTATIONS[property].call(this, this.element)); - }, - - toObject: function() { - var args = $A(arguments); - var keys = (args.length === 0) ? Element.Layout.PROPERTIES : - args.join(' ').split(' '); - var obj = {}; - keys.each( function(key) { - if (!Element.Layout.PROPERTIES.include(key)) return; - var value = this.get(key); - if (value != null) obj[key] = value; - }, this); - return obj; - }, - - toHash: function() { - var obj = this.toObject.apply(this, arguments); - return new Hash(obj); - }, - - toCSS: function() { - var args = $A(arguments); - var keys = (args.length === 0) ? Element.Layout.PROPERTIES : - args.join(' ').split(' '); - var css = {}; - - keys.each( function(key) { - if (!Element.Layout.PROPERTIES.include(key)) return; - if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; - - var value = this.get(key); - if (value != null) css[cssNameFor(key)] = value + 'px'; - }, this); - return css; - }, - - inspect: function() { - return "#"; - } - }); - - Object.extend(Element.Layout, { - PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), - - COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), - - COMPUTATIONS: { - 'height': function(element) { - if (!this._preComputing) this._begin(); - - var bHeight = this.get('border-box-height'); - if (bHeight <= 0) { - if (!this._preComputing) this._end(); - return 0; - } - - var bTop = this.get('border-top'), - bBottom = this.get('border-bottom'); - - var pTop = this.get('padding-top'), - pBottom = this.get('padding-bottom'); - - if (!this._preComputing) this._end(); - - return bHeight - bTop - bBottom - pTop - pBottom; - }, - - 'width': function(element) { - if (!this._preComputing) this._begin(); - - var bWidth = this.get('border-box-width'); - if (bWidth <= 0) { - if (!this._preComputing) this._end(); - return 0; - } - - var bLeft = this.get('border-left'), - bRight = this.get('border-right'); - - var pLeft = this.get('padding-left'), - pRight = this.get('padding-right'); - - if (!this._preComputing) this._end(); - - return bWidth - bLeft - bRight - pLeft - pRight; - }, - - 'padding-box-height': function(element) { - var height = this.get('height'), - pTop = this.get('padding-top'), - pBottom = this.get('padding-bottom'); - - return height + pTop + pBottom; - }, - - 'padding-box-width': function(element) { - var width = this.get('width'), - pLeft = this.get('padding-left'), - pRight = this.get('padding-right'); - - return width + pLeft + pRight; - }, - - 'border-box-height': function(element) { - if (!this._preComputing) this._begin(); - var height = element.offsetHeight; - if (!this._preComputing) this._end(); - return height; - }, - - 'border-box-width': function(element) { - if (!this._preComputing) this._begin(); - var width = element.offsetWidth; - if (!this._preComputing) this._end(); - return width; - }, - - 'margin-box-height': function(element) { - var bHeight = this.get('border-box-height'), - mTop = this.get('margin-top'), - mBottom = this.get('margin-bottom'); - - if (bHeight <= 0) return 0; - - return bHeight + mTop + mBottom; - }, - - 'margin-box-width': function(element) { - var bWidth = this.get('border-box-width'), - mLeft = this.get('margin-left'), - mRight = this.get('margin-right'); - - if (bWidth <= 0) return 0; - - return bWidth + mLeft + mRight; - }, - - 'top': function(element) { - var offset = element.positionedOffset(); - return offset.top; - }, - - 'bottom': function(element) { - var offset = element.positionedOffset(), - parent = element.getOffsetParent(), - pHeight = parent.measure('height'); - - var mHeight = this.get('border-box-height'); - - return pHeight - mHeight - offset.top; - }, - - 'left': function(element) { - var offset = element.positionedOffset(); - return offset.left; - }, - - 'right': function(element) { - var offset = element.positionedOffset(), - parent = element.getOffsetParent(), - pWidth = parent.measure('width'); - - var mWidth = this.get('border-box-width'); - - return pWidth - mWidth - offset.left; - }, - - 'padding-top': function(element) { - return getPixelValue(element, 'paddingTop'); - }, - - 'padding-bottom': function(element) { - return getPixelValue(element, 'paddingBottom'); - }, - - 'padding-left': function(element) { - return getPixelValue(element, 'paddingLeft'); - }, - - 'padding-right': function(element) { - return getPixelValue(element, 'paddingRight'); - }, - - 'border-top': function(element) { - return getPixelValue(element, 'borderTopWidth'); - }, - - 'border-bottom': function(element) { - return getPixelValue(element, 'borderBottomWidth'); - }, - - 'border-left': function(element) { - return getPixelValue(element, 'borderLeftWidth'); - }, - - 'border-right': function(element) { - return getPixelValue(element, 'borderRightWidth'); - }, - - 'margin-top': function(element) { - return getPixelValue(element, 'marginTop'); - }, - - 'margin-bottom': function(element) { - return getPixelValue(element, 'marginBottom'); - }, - - 'margin-left': function(element) { - return getPixelValue(element, 'marginLeft'); - }, - - 'margin-right': function(element) { - return getPixelValue(element, 'marginRight'); - } - } - }); - - if ('getBoundingClientRect' in document.documentElement) { - Object.extend(Element.Layout.COMPUTATIONS, { - 'right': function(element) { - var parent = hasLayout(element.getOffsetParent()); - var rect = element.getBoundingClientRect(), - pRect = parent.getBoundingClientRect(); - - return (pRect.right - rect.right).round(); - }, - - 'bottom': function(element) { - var parent = hasLayout(element.getOffsetParent()); - var rect = element.getBoundingClientRect(), - pRect = parent.getBoundingClientRect(); - - return (pRect.bottom - rect.bottom).round(); - } - }); - } - - Element.Offset = Class.create({ - initialize: function(left, top) { - this.left = left.round(); - this.top = top.round(); - - this[0] = this.left; - this[1] = this.top; - }, - - relativeTo: function(offset) { - return new Element.Offset( - this.left - offset.left, - this.top - offset.top - ); - }, - - inspect: function() { - return "#".interpolate(this); - }, - - toString: function() { - return "[#{left}, #{top}]".interpolate(this); - }, - - toArray: function() { - return [this.left, this.top]; - } - }); - - function getLayout(element, preCompute) { - return new Element.Layout(element, preCompute); - } - - function measure(element, property) { - return $(element).getLayout().get(property); - } - - function getDimensions(element) { - element = $(element); - var display = Element.getStyle(element, 'display'); - - if (display && display !== 'none') { - return { width: element.offsetWidth, height: element.offsetHeight }; - } - - var style = element.style; - var originalStyles = { - visibility: style.visibility, - position: style.position, - display: style.display - }; - - var newStyles = { - visibility: 'hidden', - display: 'block' - }; - - if (originalStyles.position !== 'fixed') - newStyles.position = 'absolute'; - - Element.setStyle(element, newStyles); - - var dimensions = { - width: element.offsetWidth, - height: element.offsetHeight - }; - - Element.setStyle(element, originalStyles); - - return dimensions; - } - - function getOffsetParent(element) { - element = $(element); - - if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) - return $(document.body); - - var isInline = (Element.getStyle(element, 'display') === 'inline'); - if (!isInline && element.offsetParent) return $(element.offsetParent); - - while ((element = element.parentNode) && element !== document.body) { - if (Element.getStyle(element, 'position') !== 'static') { - return isHtml(element) ? $(document.body) : $(element); - } - } - - return $(document.body); - } - - - function cumulativeOffset(element) { - element = $(element); - var valueT = 0, valueL = 0; - if (element.parentNode) { - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - } - return new Element.Offset(valueL, valueT); - } - - function positionedOffset(element) { - element = $(element); - - var layout = element.getLayout(); - - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (isBody(element)) break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - - valueL -= layout.get('margin-top'); - valueT -= layout.get('margin-left'); - - return new Element.Offset(valueL, valueT); - } - - function cumulativeScrollOffset(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return new Element.Offset(valueL, valueT); - } - - function viewportOffset(forElement) { - element = $(element); - var valueT = 0, valueL = 0, docBody = document.body; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == docBody && - Element.getStyle(element, 'position') == 'absolute') break; - } while (element = element.offsetParent); - - element = forElement; - do { - if (element != docBody) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - return new Element.Offset(valueL, valueT); - } - - function absolutize(element) { - element = $(element); - - if (Element.getStyle(element, 'position') === 'absolute') { - return element; - } - - var offsetParent = getOffsetParent(element); - var eOffset = element.viewportOffset(), - pOffset = offsetParent.viewportOffset(); - - var offset = eOffset.relativeTo(pOffset); - var layout = element.getLayout(); - - element.store('prototype_absolutize_original_styles', { - left: element.getStyle('left'), - top: element.getStyle('top'), - width: element.getStyle('width'), - height: element.getStyle('height') - }); - - element.setStyle({ - position: 'absolute', - top: offset.top + 'px', - left: offset.left + 'px', - width: layout.get('width') + 'px', - height: layout.get('height') + 'px' - }); - - return element; - } - - function relativize(element) { - element = $(element); - if (Element.getStyle(element, 'position') === 'relative') { - return element; - } - - var originalStyles = - element.retrieve('prototype_absolutize_original_styles'); - - if (originalStyles) element.setStyle(originalStyles); - return element; - } - - if (Prototype.Browser.IE) { - getOffsetParent = getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - - if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) - return $(document.body); - - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - positionedOffset = positionedOffset.wrap(function(proceed, element) { - element = $(element); - if (!element.parentNode) return new Element.Offset(0, 0); - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - hasLayout(offsetParent); - - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - }); - } else if (Prototype.Browser.Webkit) { - cumulativeOffset = function(element) { - element = $(element); - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return new Element.Offset(valueL, valueT); - }; - } - - - Element.addMethods({ - getLayout: getLayout, - measure: measure, - getDimensions: getDimensions, - getOffsetParent: getOffsetParent, - cumulativeOffset: cumulativeOffset, - positionedOffset: positionedOffset, - cumulativeScrollOffset: cumulativeScrollOffset, - viewportOffset: viewportOffset, - absolutize: absolutize, - relativize: relativize - }); - - function isBody(element) { - return element.nodeName.toUpperCase() === 'BODY'; - } - - function isHtml(element) { - return element.nodeName.toUpperCase() === 'HTML'; - } - - function isDocument(element) { - return element.nodeType === Node.DOCUMENT_NODE; - } - - function isDetached(element) { - return element !== document.body && - !Element.descendantOf(element, document.body); - } - - if ('getBoundingClientRect' in document.documentElement) { - Element.addMethods({ - viewportOffset: function(element) { - element = $(element); - if (isDetached(element)) return new Element.Offset(0, 0); - - var rect = element.getBoundingClientRect(), - docEl = document.documentElement; - return new Element.Offset(rect.left - docEl.clientLeft, - rect.top - docEl.clientTop); - } - }); - } -})(); -window.$$ = function() { - var expression = $A(arguments).join(', '); - return Prototype.Selector.select(expression, document); -}; - -Prototype.Selector = (function() { - - function select() { - throw new Error('Method "Prototype.Selector.select" must be defined.'); - } - - function match() { - throw new Error('Method "Prototype.Selector.match" must be defined.'); - } - - function find(elements, expression, index) { - index = index || 0; - var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; - - for (i = 0; i < length; i++) { - if (match(elements[i], expression) && index == matchIndex++) { - return Element.extend(elements[i]); - } - } - } - - function extendElements(elements) { - for (var i = 0, length = elements.length; i < length; i++) { - Element.extend(elements[i]); - } - return elements; - } - - - var K = Prototype.K; - - return { - select: select, - match: match, - find: find, - extendElements: (Element.extend === K) ? K : extendElements, - extendElement: Element.extend - }; -})(); -/*! - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true; - -[0, 0].sort(function(){ - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function(selector, context, results, seed) { - results = results || []; - var origContext = context = context || document; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), - soFar = selector; - - while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) - selector += parts.shift(); - - set = posProcess( selector, set ); - } - } - } else { - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } - - if ( context ) { - var ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } - - while ( parts.length ) { - var cur = parts.pop(), pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - throw "Syntax error, unrecognized expression: " + (cur || selector); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } - - return results; -}; - -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; - -Sizzle.find = function(expr, context, isXML){ - var set, match; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice(1,1); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context, isXML ); - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = context.getElementsByTagName("*"); - } - - return {set: set, expr: expr}; -}; - -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.match[ type ].exec( expr )) != null ) { - var filter = Expr.filter[ type ], found, item; - anyFound = false; - - if ( curLoop == result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - if ( expr == old ) { - if ( anyFound == null ) { - throw "Syntax error, unrecognized expression: " + expr; - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ - }, - leftMatch: {}, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag && !isXML ) { - part = part.toUpperCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - ">": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string"; - - if ( isPartStr && !/\W/.test(part) ) { - part = isXML ? part : part.toUpperCase(); - - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName === part ? parent : false; - } - } - } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( !/\W/.test(part) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } - - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( typeof part === "string" && !/\W/.test(part) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } - - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - find: { - ID: function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? [m] : []; - } - }, - NAME: function(match, context, isXML){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); - } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { - if ( !inplace ) - result.push( elem ); - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - for ( var i = 0; curLoop[i] === false; i++ ){} - return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); - }, - CHILD: function(match){ - if ( match[1] == "nth" ) { - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - - match[0] = done++; - - return match; - }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); - } - return false; - } - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!Sizzle( match[3], elem ).length; - }, - header: function(elem){ - return /h\d/i.test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; - }, - input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); - } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; - }, - gt: function(elem, i, match){ - return i > match[3] - 0; - }, - nth: function(elem, i, match){ - return match[3] - 0 == i; - }, - eq: function(elem, i, match){ - return match[3] - 0 == i; - } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; - - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { - return false; - } - } - - return true; - } - }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) return false; - } - if ( type == 'first') return true; - node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) return false; - } - return true; - case 'nth': - var first = match[2], last = match[3]; - - if ( first == 1 && last == 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - if ( first == 0 ) { - return diff == 0; - } else { - return ( diff % first == 0 && diff / first >= 0 ); - } - } - }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value != check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); -} - -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 ); - -} catch(e){ - makeArray = function(array, results) { - var ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - } else { - for ( var i = 0; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - if ( a == b ) { - hasDuplicate = true; - } - return 0; - } - - var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( "sourceIndex" in document.documentElement ) { - sortOrder = function( a, b ) { - if ( !a.sourceIndex || !b.sourceIndex ) { - if ( a == b ) { - hasDuplicate = true; - } - return 0; - } - - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - if ( !a.ownerDocument || !b.ownerDocument ) { - if ( a == b ) { - hasDuplicate = true; - } - return 0; - } - - var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); - aRange.setStart(a, 0); - aRange.setEnd(a, 0); - bRange.setStart(b, 0); - bRange.setEnd(b, 0); - var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} - -(function(){ - var form = document.createElement("div"), - id = "script" + (new Date).getTime(); - form.innerHTML = ""; - - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); - - if ( !!document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; - } - }; - - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - root = form = null; // release memory in IE -})(); - -(function(){ - - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); - - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - div.innerHTML = ""; - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); - }; - } - - div = null; // release memory in IE -})(); - -if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

"; - - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; - - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - div = null; // release memory in IE -})(); - -if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ - var div = document.createElement("div"); - div.innerHTML = "
"; - - if ( div.getElementsByClassName("e").length === 0 ) - return; - - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) - return; - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - div = null; // release memory in IE -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ){ - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -var contains = document.compareDocumentPosition ? function(a, b){ - return a.compareDocumentPosition(b) & 16; -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; - -var isXML = function(elem){ - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; -}; - -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; - - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - - -window.Sizzle = Sizzle; - -})(); - -Prototype._original_property = window.Sizzle; - -;(function(engine) { - var extendElements = Prototype.Selector.extendElements; - - function select(selector, scope) { - return extendElements(engine(selector, scope || document)); - } - - function match(element, selector) { - return engine.matches(selector, [element]).length == 1; - } - - Prototype.Selector.engine = engine; - Prototype.Selector.select = select; - Prototype.Selector.match = match; -})(Sizzle); - -window.Sizzle = Prototype._original_property; -delete Prototype._original_property; - -var Form = { - reset: function(form) { - form = $(form); - form.reset(); - return form; - }, - - serializeElements: function(elements, options) { - if (typeof options != 'object') options = { hash: !!options }; - else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit, accumulator, initial; - - if (options.hash) { - initial = {}; - accumulator = function(result, key, value) { - if (key in result) { - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } else result[key] = value; - return result; - }; - } else { - initial = ''; - accumulator = function(result, key, value) { - return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value); - } - } - - return elements.inject(initial, function(result, element) { - if (!element.disabled && element.name) { - key = element.name; value = $(element).getValue(); - if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && - submit !== false && (!submit || key == submit) && (submitted = true)))) { - result = accumulator(result, key, value); - } - } - return result; - }); - } -}; - -Form.Methods = { - serialize: function(form, options) { - return Form.serializeElements(Form.getElements(form), options); - }, - - getElements: function(form) { - var elements = $(form).getElementsByTagName('*'), - element, - arr = [ ], - serializers = Form.Element.Serializers; - for (var i = 0; element = elements[i]; i++) { - arr.push(element); - } - return arr.inject([], function(elements, child) { - if (serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - }) - }, - - getInputs: function(form, typeName, name) { - form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) return $A(inputs).map(Element.extend); - - for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || (name && input.name != name)) - continue; - matchingInputs.push(Element.extend(input)); - } - - return matchingInputs; - }, - - disable: function(form) { - form = $(form); - Form.getElements(form).invoke('disable'); - return form; - }, - - enable: function(form) { - form = $(form); - Form.getElements(form).invoke('enable'); - return form; - }, - - findFirstElement: function(form) { - var elements = $(form).getElements().findAll(function(element) { - return 'hidden' != element.type && !element.disabled; - }); - var firstByIndex = elements.findAll(function(element) { - return element.hasAttribute('tabIndex') && element.tabIndex >= 0; - }).sortBy(function(element) { return element.tabIndex }).first(); - - return firstByIndex ? firstByIndex : elements.find(function(element) { - return /^(?:input|select|textarea)$/i.test(element.tagName); - }); - }, - - focusFirstElement: function(form) { - form = $(form); - var element = form.findFirstElement(); - if (element) element.activate(); - return form; - }, - - request: function(form, options) { - form = $(form), options = Object.clone(options || { }); - - var params = options.parameters, action = form.readAttribute('action') || ''; - if (action.blank()) action = window.location.href; - options.parameters = form.serialize(true); - - if (params) { - if (Object.isString(params)) params = params.toQueryParams(); - Object.extend(options.parameters, params); - } - - if (form.hasAttribute('method') && !options.method) - options.method = form.method; - - return new Ajax.Request(action, options); - } -}; - -/*--------------------------------------------------------------------------*/ - - -Form.Element = { - focus: function(element) { - $(element).focus(); - return element; - }, - - select: function(element) { - $(element).select(); - return element; - } -}; - -Form.Element.Methods = { - - serialize: function(element) { - element = $(element); - if (!element.disabled && element.name) { - var value = element.getValue(); - if (value != undefined) { - var pair = { }; - pair[element.name] = value; - return Object.toQueryString(pair); - } - } - return ''; - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - return Form.Element.Serializers[method](element); - }, - - setValue: function(element, value) { - element = $(element); - var method = element.tagName.toLowerCase(); - Form.Element.Serializers[method](element, value); - return element; - }, - - clear: function(element) { - $(element).value = ''; - return element; - }, - - present: function(element) { - return $(element).value != ''; - }, - - activate: function(element) { - element = $(element); - try { - element.focus(); - if (element.select && (element.tagName.toLowerCase() != 'input' || - !(/^(?:button|reset|submit)$/i.test(element.type)))) - element.select(); - } catch (e) { } - return element; - }, - - disable: function(element) { - element = $(element); - element.disabled = true; - return element; - }, - - enable: function(element) { - element = $(element); - element.disabled = false; - return element; - } -}; - -/*--------------------------------------------------------------------------*/ - -var Field = Form.Element; - -var $F = Form.Element.Methods.getValue; - -/*--------------------------------------------------------------------------*/ - -Form.Element.Serializers = (function() { - function input(element, value) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - return inputSelector(element, value); - default: - return valueSelector(element, value); - } - } - - function inputSelector(element, value) { - if (Object.isUndefined(value)) - return element.checked ? element.value : null; - else element.checked = !!value; - } - - function valueSelector(element, value) { - if (Object.isUndefined(value)) return element.value; - else element.value = value; - } - - function select(element, value) { - if (Object.isUndefined(value)) - return (element.type === 'select-one' ? selectOne : selectMany)(element); - - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } - } - else opt.selected = value.include(currentValue); - } - } - - function selectOne(element) { - var index = element.selectedIndex; - return index >= 0 ? optionValue(element.options[index]) : null; - } - - function selectMany(element) { - var values, length = element.length; - if (!length) return null; - - for (var i = 0, values = []; i < length; i++) { - var opt = element.options[i]; - if (opt.selected) values.push(optionValue(opt)); - } - return values; - } - - function optionValue(opt) { - return Element.hasAttribute(opt, 'value') ? opt.value : opt.text; - } - - return { - input: input, - inputSelector: inputSelector, - textarea: valueSelector, - select: select, - selectOne: selectOne, - selectMany: selectMany, - optionValue: optionValue, - button: valueSelector - }; -})(); - -/*--------------------------------------------------------------------------*/ - - -Abstract.TimedObserver = Class.create(PeriodicalExecuter, { - initialize: function($super, element, frequency, callback) { - $super(callback, frequency); - this.element = $(element); - this.lastValue = this.getValue(); - }, - - execute: function() { - var value = this.getValue(); - if (Object.isString(this.lastValue) && Object.isString(value) ? - this.lastValue != value : String(this.lastValue) != String(value)) { - this.callback(this.element, value); - this.lastValue = value; - } - } -}); - -Form.Element.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); - -/*--------------------------------------------------------------------------*/ - -Abstract.EventObserver = Class.create({ - initialize: function(element, callback) { - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - if (this.element.tagName.toLowerCase() == 'form') - this.registerFormCallbacks(); - else - this.registerCallback(this.element); - }, - - onElementEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - }, - - registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback, this); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - Event.observe(element, 'click', this.onElementEvent.bind(this)); - break; - default: - Event.observe(element, 'change', this.onElementEvent.bind(this)); - break; - } - } - } -}); - -Form.Element.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); -(function() { - - var Event = { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: {} - }; - - var docEl = document.documentElement; - var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl - && 'onmouseleave' in docEl; - - - - var isIELegacyEvent = function(event) { return false; }; - - if (window.attachEvent) { - if (window.addEventListener) { - isIELegacyEvent = function(event) { - return !(event instanceof window.Event); - }; - } else { - isIELegacyEvent = function(event) { return true; }; - } - } - - var _isButton; - - function _isButtonForDOMEvents(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - } - - var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; - function _isButtonForLegacyEvents(event, code) { - return event.button === legacyButtonMap[code]; - } - - function _isButtonForWebKit(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 2 || (event.which == 1 && event.metaKey); - case 2: return event.which == 3; - default: return false; - } - } - - if (window.attachEvent) { - if (!window.addEventListener) { - _isButton = _isButtonForLegacyEvents; - } else { - _isButton = function(event, code) { - return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) : - _isButtonForDOMEvents(event, code); - } - } - } else if (Prototype.Browser.WebKit) { - _isButton = _isButtonForWebKit; - } else { - _isButton = _isButtonForDOMEvents; - } - - function isLeftClick(event) { return _isButton(event, 0) } - - function isMiddleClick(event) { return _isButton(event, 1) } - - function isRightClick(event) { return _isButton(event, 2) } - - function element(event) { - event = Event.extend(event); - - var node = event.target, type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; - } - - if (node.nodeType == Node.TEXT_NODE) - node = node.parentNode; - - return Element.extend(node); - } - - function findElement(event, expression) { - var element = Event.element(event); - - if (!expression) return element; - while (element) { - if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { - return Element.extend(element); - } - element = element.parentNode; - } - } - - function pointer(event) { - return { x: pointerX(event), y: pointerY(event) }; - } - - function pointerX(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0 }; - - return event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)); - } - - function pointerY(event) { - var docElement = document.documentElement, - body = document.body || { scrollTop: 0 }; - - return event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)); - } - - - function stop(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - - event.stopped = true; - } - - - Event.Methods = { - isLeftClick: isLeftClick, - isMiddleClick: isMiddleClick, - isRightClick: isRightClick, - - element: element, - findElement: findElement, - - pointer: pointer, - pointerX: pointerX, - pointerY: pointerY, - - stop: stop - }; - - var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { - m[name] = Event.Methods[name].methodize(); - return m; - }); - - if (window.attachEvent) { - function _relatedTarget(event) { - var element; - switch (event.type) { - case 'mouseover': - case 'mouseenter': - element = event.fromElement; - break; - case 'mouseout': - case 'mouseleave': - element = event.toElement; - break; - default: - return null; - } - return Element.extend(element); - } - - var additionalMethods = { - stopPropagation: function() { this.cancelBubble = true }, - preventDefault: function() { this.returnValue = false }, - inspect: function() { return '[object Event]' } - }; - - Event.extend = function(event, element) { - if (!event) return false; - - if (!isIELegacyEvent(event)) return event; - - if (event._extendedByPrototype) return event; - event._extendedByPrototype = Prototype.emptyFunction; - - var pointer = Event.pointer(event); - - Object.extend(event, { - target: event.srcElement || element, - relatedTarget: _relatedTarget(event), - pageX: pointer.x, - pageY: pointer.y - }); - - Object.extend(event, methods); - Object.extend(event, additionalMethods); - - return event; - }; - } else { - Event.extend = Prototype.K; - } - - if (window.addEventListener) { - Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; - Object.extend(Event.prototype, methods); - } - - function _createResponder(element, eventName, handler) { - var registry = Element.retrieve(element, 'prototype_event_registry'); - - if (Object.isUndefined(registry)) { - CACHE.push(element); - registry = Element.retrieve(element, 'prototype_event_registry', $H()); - } - - var respondersForEvent = registry.get(eventName); - if (Object.isUndefined(respondersForEvent)) { - respondersForEvent = []; - registry.set(eventName, respondersForEvent); - } - - if (respondersForEvent.pluck('handler').include(handler)) return false; - - var responder; - if (eventName.include(":")) { - responder = function(event) { - if (Object.isUndefined(event.eventName)) - return false; - - if (event.eventName !== eventName) - return false; - - Event.extend(event, element); - handler.call(element, event); - }; - } else { - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && - (eventName === "mouseenter" || eventName === "mouseleave")) { - if (eventName === "mouseenter" || eventName === "mouseleave") { - responder = function(event) { - Event.extend(event, element); - - var parent = event.relatedTarget; - while (parent && parent !== element) { - try { parent = parent.parentNode; } - catch(e) { parent = element; } - } - - if (parent === element) return; - - handler.call(element, event); - }; - } - } else { - responder = function(event) { - Event.extend(event, element); - handler.call(element, event); - }; - } - } - - responder.handler = handler; - respondersForEvent.push(responder); - return responder; - } - - function _destroyCache() { - for (var i = 0, length = CACHE.length; i < length; i++) { - Event.stopObserving(CACHE[i]); - CACHE[i] = null; - } - } - - var CACHE = []; - - if (Prototype.Browser.IE) - window.attachEvent('onunload', _destroyCache); - - if (Prototype.Browser.WebKit) - window.addEventListener('unload', Prototype.emptyFunction, false); - - - var _getDOMEventName = Prototype.K, - translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; - - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { - _getDOMEventName = function(eventName) { - return (translations[eventName] || eventName); - }; - } - - function observe(element, eventName, handler) { - element = $(element); - - var responder = _createResponder(element, eventName, handler); - - if (!responder) return element; - - if (eventName.include(':')) { - if (element.addEventListener) - element.addEventListener("dataavailable", responder, false); - else { - element.attachEvent("ondataavailable", responder); - element.attachEvent("onlosecapture", responder); - } - } else { - var actualEventName = _getDOMEventName(eventName); - - if (element.addEventListener) - element.addEventListener(actualEventName, responder, false); - else - element.attachEvent("on" + actualEventName, responder); - } - - return element; - } - - function stopObserving(element, eventName, handler) { - element = $(element); - - var registry = Element.retrieve(element, 'prototype_event_registry'); - if (!registry) return element; - - if (!eventName) { - registry.each( function(pair) { - var eventName = pair.key; - stopObserving(element, eventName); - }); - return element; - } - - var responders = registry.get(eventName); - if (!responders) return element; - - if (!handler) { - responders.each(function(r) { - stopObserving(element, eventName, r.handler); - }); - return element; - } - - var i = responders.length, responder; - while (i--) { - if (responders[i].handler === handler) { - responder = responders[i]; - break; - } - } - if (!responder) return element; - - if (eventName.include(':')) { - if (element.removeEventListener) - element.removeEventListener("dataavailable", responder, false); - else { - element.detachEvent("ondataavailable", responder); - element.detachEvent("onlosecapture", responder); - } - } else { - var actualEventName = _getDOMEventName(eventName); - if (element.removeEventListener) - element.removeEventListener(actualEventName, responder, false); - else - element.detachEvent('on' + actualEventName, responder); - } - - registry.set(eventName, responders.without(responder)); - - return element; - } - - function fire(element, eventName, memo, bubble) { - element = $(element); - - if (Object.isUndefined(bubble)) - bubble = true; - - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent('HTMLEvents'); - event.initEvent('dataavailable', bubble, true); - } else { - event = document.createEventObject(); - event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) - element.dispatchEvent(event); - else - element.fireEvent(event.eventType, event); - - return Event.extend(event); - } - - Event.Handler = Class.create({ - initialize: function(element, eventName, selector, callback) { - this.element = $(element); - this.eventName = eventName; - this.selector = selector; - this.callback = callback; - this.handler = this.handleEvent.bind(this); - }, - - start: function() { - Event.observe(this.element, this.eventName, this.handler); - return this; - }, - - stop: function() { - Event.stopObserving(this.element, this.eventName, this.handler); - return this; - }, - - handleEvent: function(event) { - var element = Event.findElement(event, this.selector); - if (element) this.callback.call(this.element, event, element); - } - }); - - function on(element, eventName, selector, callback) { - element = $(element); - if (Object.isFunction(selector) && Object.isUndefined(callback)) { - callback = selector, selector = null; - } - - return new Event.Handler(element, eventName, selector, callback).start(); - } - - Object.extend(Event, Event.Methods); - - Object.extend(Event, { - fire: fire, - observe: observe, - stopObserving: stopObserving, - on: on - }); - - Element.addMethods({ - fire: fire, - - observe: observe, - - stopObserving: stopObserving, - - on: on - }); - - Object.extend(document, { - fire: fire.methodize(), - - observe: observe.methodize(), - - stopObserving: stopObserving.methodize(), - - on: on.methodize(), - - loaded: false - }); - - if (window.Event) Object.extend(window.Event, Event); - else window.Event = Event; -})(); - -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ - - var timer; - - function fireContentLoadedEvent() { - if (document.loaded) return; - if (timer) window.clearTimeout(timer); - document.loaded = true; - document.fire('dom:loaded'); - } - - function checkReadyState() { - if (document.readyState === 'complete') { - document.stopObserving('readystatechange', checkReadyState); - fireContentLoadedEvent(); - } - } - - function pollDoScroll() { - try { document.documentElement.doScroll('left'); } - catch(e) { - timer = pollDoScroll.defer(); - return; - } - fireContentLoadedEvent(); - } - - if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); - } else { - document.observe('readystatechange', checkReadyState); - if (window == top) - timer = pollDoScroll.defer(); - } - - Event.observe(window, 'load', fireContentLoadedEvent); -})(); - - -Element.addMethods(); -/*------------------------------- DEPRECATED -------------------------------*/ - -Hash.toQueryString = Object.toQueryString; - -var Toggle = { display: Element.toggle }; - -Element.Methods.childOf = Element.Methods.descendantOf; - -var Insertion = { - Before: function(element, content) { - return Element.insert(element, {before:content}); - }, - - Top: function(element, content) { - return Element.insert(element, {top:content}); - }, - - Bottom: function(element, content) { - return Element.insert(element, {bottom:content}); - }, - - After: function(element, content) { - return Element.insert(element, {after:content}); - } -}; - -var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); - -var Position = { - includeScrollOffsets: false, - - prepare: function() { - this.deltaX = window.pageXOffset - || document.documentElement.scrollLeft - || document.body.scrollLeft - || 0; - this.deltaY = window.pageYOffset - || document.documentElement.scrollTop - || document.body.scrollTop - || 0; - }, - - within: function(element, x, y) { - if (this.includeScrollOffsets) - return this.withinIncludingScrolloffsets(element, x, y); - this.xcomp = x; - this.ycomp = y; - this.offset = Element.cumulativeOffset(element); - - return (y >= this.offset[1] && - y < this.offset[1] + element.offsetHeight && - x >= this.offset[0] && - x < this.offset[0] + element.offsetWidth); - }, - - withinIncludingScrolloffsets: function(element, x, y) { - var offsetcache = Element.cumulativeScrollOffset(element); - - this.xcomp = x + offsetcache[0] - this.deltaX; - this.ycomp = y + offsetcache[1] - this.deltaY; - this.offset = Element.cumulativeOffset(element); - - return (this.ycomp >= this.offset[1] && - this.ycomp < this.offset[1] + element.offsetHeight && - this.xcomp >= this.offset[0] && - this.xcomp < this.offset[0] + element.offsetWidth); - }, - - overlap: function(mode, element) { - if (!mode) return 0; - if (mode == 'vertical') - return ((this.offset[1] + element.offsetHeight) - this.ycomp) / - element.offsetHeight; - if (mode == 'horizontal') - return ((this.offset[0] + element.offsetWidth) - this.xcomp) / - element.offsetWidth; - }, - - - cumulativeOffset: Element.Methods.cumulativeOffset, - - positionedOffset: Element.Methods.positionedOffset, - - absolutize: function(element) { - Position.prepare(); - return Element.absolutize(element); - }, - - relativize: function(element) { - Position.prepare(); - return Element.relativize(element); - }, - - realOffset: Element.Methods.cumulativeScrollOffset, - - offsetParent: Element.Methods.getOffsetParent, - - page: Element.Methods.viewportOffset, - - clone: function(source, target, options) { - options = options || { }; - return Element.clonePosition(target, source, options); - } -}; - -/*--------------------------------------------------------------------------*/ - -if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ - function iter(name) { - return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; - } - - instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? - function(element, className) { - className = className.toString().strip(); - var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); - return cond ? document._getElementsByXPath('.//*' + cond, element) : []; - } : function(element, className) { - className = className.toString().strip(); - var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); - if (!classNames && !className) return elements; - - var nodes = $(element).getElementsByTagName('*'); - className = ' ' + className + ' '; - - for (var i = 0, child, cn; child = nodes[i]; i++) { - if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || - (classNames && classNames.all(function(name) { - return !name.toString().blank() && cn.include(' ' + name + ' '); - })))) - elements.push(Element.extend(child)); - } - return elements; - }; - - return function(className, parentElement) { - return $(parentElement || document.body).getElementsByClassName(className); - }; -}(Element.Methods); - -/*--------------------------------------------------------------------------*/ - -Element.ClassNames = Class.create(); -Element.ClassNames.prototype = { - initialize: function(element) { - this.element = $(element); - }, - - _each: function(iterator) { - this.element.className.split(/\s+/).select(function(name) { - return name.length > 0; - })._each(iterator); - }, - - set: function(className) { - this.element.className = className; - }, - - add: function(classNameToAdd) { - if (this.include(classNameToAdd)) return; - this.set($A(this).concat(classNameToAdd).join(' ')); - }, - - remove: function(classNameToRemove) { - if (!this.include(classNameToRemove)) return; - this.set($A(this).without(classNameToRemove).join(' ')); - }, - - toString: function() { - return $A(this).join(' '); - } -}; - -Object.extend(Element.ClassNames.prototype, Enumerable); - -/*--------------------------------------------------------------------------*/ - -(function() { - window.Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - }, - - findElements: function(rootElement) { - return Prototype.Selector.select(this.expression, rootElement); - }, - - match: function(element) { - return Prototype.Selector.match(element, this.expression); - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } - }); - - Object.extend(Selector, { - matchElements: function(elements, expression) { - var match = Prototype.Selector.match, - results = []; - - for (var i = 0, length = elements.length; i < length; i++) { - var element = elements[i]; - if (match(element, expression)) { - results.push(Element.extend(element)); - } - } - return results; - }, - - findElement: function(elements, expression, index) { - index = index || 0; - var matchIndex = 0, element; - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (Prototype.Selector.match(element, expression) && index === matchIndex++) { - return Element.extend(element); - } - } - }, - - findChildElements: function(element, expressions) { - var selector = expressions.toArray().join(', '); - return Prototype.Selector.select(selector, element || document); - } - }); -})(); diff --git a/tests/javascript/small.js b/tests/javascript/small.js deleted file mode 100644 index b0f8a3c..0000000 --- a/tests/javascript/small.js +++ /dev/null @@ -1,9 +0,0 @@ -var app = { - foo: function(){}, - bar: function(){} -} - -var dsads = { - fdfsd: function(){}, - dsadas: function(){} -} diff --git a/tests/jptest 天使ã®ãŸã¾ã”æ°´ã«æ£²ã‚€/test.cpp b/tests/jptest 天使ã®ãŸã¾ã”æ°´ã«æ£²ã‚€/test.cpp deleted file mode 100644 index 45103f9..0000000 --- a/tests/jptest 天使ã®ãŸã¾ã”æ°´ã«æ£²ã‚€/test.cpp +++ /dev/null @@ -1,597 +0,0 @@ -/* Test file for C++ language. - * Attempt to include as many aspects of the C++ language as possible. - * Do not include things tested in test.c since that shares the - * same language. - * - * $Id: test.cpp,v 1.22 2008/05/17 20:12:55 zappo Exp $ - * - */ - -/* An include test */ -#include - -#include - -#include "c++-test.hh" - -#include - -double var1 = 1.2; - -int simple1(int a) { - -} - -struct foo1 { - int test; -}; - -struct foo2 : public foo1 { - const int foo21(int a, int b); - const int foo22(int a, int b) { return 1 } -}; - -/* Classes */ -class class1 { -private: - int var11; - struct foo1 var12; -public: - int p_var11; - struct foo p_var12; -}; - -class i_class1 : public class1 { -private: - int var11; - struct foo var12; -public: - int p_var11; - struct foo p_var12; -}; - -class class2 { -private: - int var21; - struct foo var22; -public: - int p_var21; - struct foo p_var22; -}; - -class i_class2 : public class1, public class2 { -private: - int var21; - struct foo var22; -protected: - int pt_var21; -public: - int p_var21; - struct foo p_var22; -}; - -class class3 { - /* A class with strange things in it */ -public: - class3(); /* A constructor */ - enum embedded_foo_enum { - a, b, c - } embed1; - struct embedded_bar_struct { - int a; - int b; - } embed2; - class embedded_baz_class { - embedded_baz_class(); - ~embedded_baz_class(); - } embed3; - ~class3(); /* destructor */ - - /* Methods */ - int method_for_class3(int a, char b); - - int inline_method(int c) { return c; } - - /* Operators */ - class3& operator^= (const class3& something); - - /* Funny declmods */ - const class3 * const method_const_ptr_ptr(const int * const argconst) const = 0; -}; - -class3::class3() -{ - /* Constructor outside the definition. */ -} - -int class3::method_for_class3(int a, char b) -{ -} - -int class3::method1_for_class3( int a, int &b) -{ - int cvariablename; - class3 fooy[]; - class3 moose = new class3; - - // Complktion testing line should find external members. - a = fooy[1].me ; - b = cv ; - - if (fooy.emb) { - simple1(c); - } - - cos(10); - abs(10); - - return 1; -} - -char class3::method2_for_class3( int a, int b) throw ( exception1 ) -{ - return 'a'; -} - -void *class3::method3_for_class3( int a, int b) throw ( exception1, exception2 ) -{ - int q = a; - return "Moose"; -} - -void *class3::method31_for_class3( int a, int b) throw ( ) -{ - int q = a; - return "Moose"; -} - -void *class3::method4_for_class3( int a, int b) reentrant -{ - class3 ct; - - ct.method5_for_class3(1,a); - - pritf(); -} - -/* - * A method on class3. - */ -void *class3::method5_for_class3( int a, int b) const -{ -} - -/* - * Namespace parsing tests - */ -namespace NS { - class class_in_namespace { - int equiv(const NS::class_in_namespace *) const; - }; -} - -int NS::class_in_namespace::equiv(const NS::class_in_namespace *cin) const -{ - return 0; -} - -// Stuff Klaus found. -// Inheritance w/out a specifying for public. -class class4 : class1 { - // Pure virtual methods. - void virtual print () const = 0; - -public: - // The whacky constructor type - class4() - try : class1(args) - { - // constructor body - } - catch () - { - - } - - -}; - -class class5 : public virtual class4 { - // Virtual inheritance -}; - -class class6 : class1 { - // Mutable - mutable int i; -}; - -/* Namespaces */ -namespace namespace1 { - void ns_method1() { } - - class n_class1 { - public: - void method11(int a) { } - }; - - /* This shouldn't parse due to missing semicolon. */ - class _n_class2 : public n_class1 { - void n_c2_method1(int a, int b) { } - }; - - // Macros in the namespace -#define NSMACRO 1 - - // Template in the namespace - template T nsti1(const Foo& foo); - template<> int nsti1(const Foo& foo); - -} - -namespace namespace2 { - - using namespace1::n_class1; - -} - -/* Initializers */ -void tinitializers1(): inita1(False), - inita2(False) -{ - inita1= 1; -} - -/* How about Extern C type things. */ -int funny_prototype(int ,int b,float c) -{ - -} - -extern "C" -int extern_c_1(int a, int b) -{ - - funny_prototype(1,2,3.4); - - printf("Moose", ); - - return 1; -} - -extern "C" { - - int extern_c_2(int a, int b) - { - return 1; - } - -} - -// Some operator stuff -class Action -{ - // Problems!! operator() and operator[] can not be parsed with semantic - // 1.4.2 but with latest c.by - virtual void operator()(int i, char *p ) = 0; - virtual String& operator[]() = 0; - virtual void operator!() = 0; - virtual void operator->() = 0; - virtual T& operator+=(); - virtual T& operator*(); - virtual T& operator*=(); -}; - -// class with namespace qualified parents -class Multiinherit : public virtual POA::Parent, - public virtual POA::Parent1, - Parent -{ -private: - int i; - -public: - Multiinherit(); - ~Multiinherit(); - - // method with a list of qualified exceptions - void* throwtest() - throw(Exception0, - Testnamespace::Exception1, - Testnamespace::Excpetion2, - Testnamespace::testnamespace1::Exception3); - -}; - -void* -Multiinherit::throwtest() - throw (Exception0, - Testnamespace::Exception1, - Testnamespace::Excpetion2, - Testnamespace::testnamespace1::Exception3) -{ - return; -} - -// Jens Rock : Nested classes or structs defined -// outside of the containing class/struct. -class container -{ - public: - struct contained; - container(); - ~container(); -}; - -struct container::contained -{ - public: - contained(); - ~contained(); -}; - -/* - * Ok, how about some template stuff. - */ -template > -const CT& max (const CT& a, const CT& b) -{ - return a < b ? b : a; -} - -// Arne Schmitz found this one -std::vector &a, &b, &c; - -class TemplateUsingClass -{ - typedef TestClassMap::iterator iterator; - typedef map TestClassMap; - - // typedefs with const and volatile - typedef const map const_TestClassMap; - typedef TestClassMap::iterator volatile volatile_iterator; - - map mapclassvarthingy; -}; - -template T ti1(const Foo& foo); -template<> int ti1(const Foo& foo); - - -// ----------------------------------- -// Now some namespace and related stuff -// ----------------------------------- - -using CORBA::LEX::get_token; -using Namespace1; - -using namespace POA::std; -using namespace Test; - - - -namespace Parser -{ - namespace - { - using Lexer::get_test; - string str = ""; - } - - namespace XXX - { - - class Foobar : public virtual POA::Parent, - public virtual POA::Parent1, - private POA::list, - private map - { - int i; - list >::const_iterator l; - public: - - Foobar(); - ~Foobar(); - }; - } - - - void test_function(int i); - -}; - -// unnamed namespaces - even nested -namespace -{ - namespace - { - using Lexer::get_test; - string str = ""; - class FooClass - { - FooClass(); - }; - } - - // some builtin types - long long ll = 0; - long double d = 0.0; - unsigned test; - unsigned long int **uli = 0; - signed si = 0; - signed short ss = 0; - short int i = 0; - long int li = 0; - - // expressions with namespace/class-qualifyiers - ORB_var cGlobalOrb = ORB::_nil(); - ORB_var1 cGlobalOrb1 = ORB::_test; - - class Testclass - { - #define TEST 0 - ini i; - - public: - - Testclass(); - ~Testclass(); - }; - - static void test_function(unsigned int i); - -}; - - -// outside method implementations which should be grouped to type Test -XXX& -Test::waiting() -{ - return; -} - -void -Test::print() -{ - return; -} - -// outside method implementations with namespaces which should be grouped to -// their complete (incl. namespace) types -void* -Parser::XXX::Foobar::wait(int i, const char const * const * p) -{ - return; -} - -void* -Namespace1::Test::wait1(int i) -{ - return; -} - -int -Namespace1::Test::waiting(int i) -{ - return; -} - -// a class with some outside implementations which should all be grouped to -// this class declaration -class ClassWithExternals -{ -private: - int i; - -public: - ClassWithExternals(); - ~ClassWithExternals(); - void non_nil(); -}; - - -// Foobar is not displayed; seems that semantic tries to add this to the class -// Foobar but can not find/display it, because contained in the namespace above. -void -Foobar::non_nil() -{ - return; -} - -// are correctly grouped to the ClassWithExternals class -void -ClassWithExternals::non_nil() -{ - String s = "lödfjg dlfgkdlfkgjdl"; - return; -} - -ClassWithExternals::ClassWithExternals() -{ - return; -} - -void -ClassWithExternals::~ClassWithExternals() -{ - return; -} - - -// ------------------------------- -// Now some macro and define stuff -// ------------------------------- - -#define TEST 0 -#define TEST1 "String" - -// The first backslash makes this macro unmatched syntax with semantic 1.4.2! -// With flexing \+newline as nothing all is working fine! -#define MZK_ENTER(METHOD) \ -{ \ - CzkMethodLog lMethodLog(METHOD,"Framework");\ -} - -#define ZK_ASSERTM(METHOD,ASSERTION,MESSAGE) \ - { if(!(ASSERTION))\ - {\ - std::ostringstream lMesgStream; \ - lMesgStream << "Assertion failed: " \ - << MESSAGE; \ - CzkLogManager::doLog(CzkLogManager::FATAL,"",METHOD, \ - "Assert",lMesgStream); \ - assert(ASSERTION);\ - }\ - } - -// Test if not newline-backslashes are handled correctly -string s = "My \"quoted\" string"; - -// parsed fine as macro -#define FOO (arg) method(arg, "foo"); - -// With semantic 1.4.2 this parsed as macro BAR *and* function method. -// With latest c.bnf at least one-liner macros can be parsed correctly. -#define BAR (arg) CzkMessageLog method(arg, "bar"); - -// some const and volatile stuff -char * p1 = "Hello"; // 1. variable Pointer, variable Data -const char * p2 = "Hello"; // 2. variable pointer, constant data -char * const p3 = "Hello"; // 3. constant pointer, variable data -const char * const p4 = "Hello"; // 4. constant pointer, constant data - -// Case 2 and 4 can exchange first "const" and "char" -char const * p21 = "Hello"; // variable pointer, constant data -char const * const p41 = "Hello"; // constant pointer, constant data - -char volatile a = 0; // a volatile char -void foo(bar const &arg); // a reference to a const bar -int foobar(bar const * const p); // a const pointer to a const bar -int foobar(bar const volatile * const p); // a const pointer to a const bar -int foobar3(char* p); // a const pointer to a const bar - -// Should not be parsed because this is invalid code -int const & const r3 = i; - -boolean i = 0; -boolean & r1 = i; -boolean const & r2 = i; - -// const * sequences can be very long in C++ ;-) -char const * const * const * const * ppp; - -// complex function declarationen with named pointer-arguments -const char** foobar1(volatile char const * const **p); -const char** foobar11(volatile Test::Namespace::Char const * const **p); - -// complex function declarationen with unnamed pointer-arguments -const char* foobar2(const char***); -const char* foobar21(const Test::Namespace::Char***); - -// string literal parsing even with wchar_t -char const *p = "string1"; -char const *q = "string1" "str\"ing2" "string3"; -wchar_t testc = L'a'; - -wchar_t const *wp = L"string with a \" in it"; -wchar_t const *wq = L"string \n\t\"test" L"string2"; -wchar_t const *wr = L"string L"; diff --git a/tests/mxml/main.mxml b/tests/mxml/main.mxml deleted file mode 100644 index 9f3defb..0000000 --- a/tests/mxml/main.mxml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/ocaml/fifteen.ml b/tests/ocaml/fifteen.ml deleted file mode 100644 index 8160726..0000000 --- a/tests/ocaml/fifteen.ml +++ /dev/null @@ -1,98 +0,0 @@ -(* $Id: fifteen.ml,v 1.8 2001/09/06 08:47:55 garrigue Exp $ *) - -open StdLabels -open Gaux -open Gtk -open GObj -open GMain - -class position ~init_x ~init_y ~min_x ~min_y ~max_x ~max_y = object - val mutable x = init_x - val mutable y = init_y - method current = (x, y) - method up () = if y > min_y then y <- y-1 else (); (x, y) - method down () = if y < max_y then y <- y+1 else (); (x, y) - method left () = if x > min_x then x <- x-1 else (); (x, y) - method right () = if x < max_x then x <- x+1 else (); (x, y) -end - -let game_init () = (* generate initial puzzle state *) - let rec game_aux acc rest n_invert = - let len = List.length rest in - if len=0 then - if n_invert mod 2 = 0 then - acc (* to be solvable, n_invert must be even *) - else - (List.hd (List.tl acc))::(List.hd acc)::(List.tl (List.tl acc)) - else begin - let rec extract n xs = - if (n=0) then (List.hd xs, List.tl xs) - else - let (ans, ys) = extract (n-1) (List.tl xs) in - (ans, List.hd xs :: ys) in - let ran = Random.int len in - let (elm, rest1) = extract ran rest in - let rec count p xs = match xs with - [] -> 0 - | y :: ys -> let acc = count p ys in - if p y then 1+acc else acc - in - let new_n_invert = count (fun x -> elm > x) acc in - game_aux (elm :: acc) rest1 (n_invert+new_n_invert) - end in - let rec from n = if n=0 then [] else n :: from (n-1) in - game_aux [] (from 15) 0 - -let _ = Random.init (int_of_float (Sys.time () *. 1000.)) -let window = GWindow.window () -let _ = window#connect#destroy ~callback:GMain.Main.quit - -let tbl = GPack.table ~rows:4 ~columns:4 ~homogeneous:true ~packing:window#add () -let dummy = GMisc.label ~text:"" ~packing:(tbl#attach ~left:3 ~top:3) () -let arr = Array.create_matrix ~dimx:4 ~dimy:4 dummy -let init = game_init () -let _ = - for i = 0 to 15 do - let j = i mod 4 in - let k = i/4 in - let frame = - GBin.frame ~shadow_type:`OUT ~width:32 ~height:32 - ~packing:(tbl#attach ~left:j ~top:k) () in - if i < 15 then - arr.(j).(k) <- - GMisc.label ~text:(string_of_int (List.nth init i)) - ~packing:frame#add () - done -let pos = new position ~init_x:3 ~init_y:3 ~min_x:0 ~min_y:0 ~max_x:3 ~max_y:3 - -open GdkKeysyms - -let _ = - window#event#connect#key_press ~callback: - begin fun ev -> - let (x0, y0) = pos#current in - let wid0 = arr.(x0).(y0) in - let key = GdkEvent.Key.keyval ev in - if key = _q || key = _Escape then (Main.quit (); exit 0) else - let (x1, y1) = - if key = _h || key = _Left then - pos#right () - else if key = _j || key = _Down then - pos#up () - else if key = _k || key = _Up then - pos#down () - else if key = _l || key = _Right then - pos#left () - else (x0, y0) - in - let wid1 = arr.(x1).(y1) in - wid0#set_text (wid1#text); - wid1#set_text ""; - true - end - -let main () = - window#show (); - Main.main () - -let _ = main () diff --git a/tests/ocaml/heap.ml b/tests/ocaml/heap.ml deleted file mode 100644 index 50a9855..0000000 --- a/tests/ocaml/heap.ml +++ /dev/null @@ -1,153 +0,0 @@ -(************************************************************************) -(* v * The Coq Proof Assistant / The Coq Development Team *) -(* t -> int -end - -module type S =sig - - (* Type of functional heaps *) - type t - - (* Type of elements *) - type elt - - (* The empty heap *) - val empty : t - - (* [add x h] returns a new heap containing the elements of [h], plus [x]; - complexity $O(log(n))$ *) - val add : elt -> t -> t - - (* [maximum h] returns the maximum element of [h]; raises [EmptyHeap] - when [h] is empty; complexity $O(1)$ *) - val maximum : t -> elt - - (* [remove h] returns a new heap containing the elements of [h], except - the maximum of [h]; raises [EmptyHeap] when [h] is empty; - complexity $O(log(n))$ *) - val remove : t -> t - - (* usual iterators and combinators; elements are presented in - arbitrary order *) - val iter : (elt -> unit) -> t -> unit - - val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a - -end - -exception EmptyHeap - -(*s Functional implementation *) - -module Functional(X : Ordered) = struct - - (* Heaps are encoded as complete binary trees, i.e., binary trees - which are full expect, may be, on the bottom level where it is filled - from the left. - These trees also enjoy the heap property, namely the value of any node - is greater or equal than those of its left and right subtrees. - - There are 4 kinds of complete binary trees, denoted by 4 constructors: - [FFF] for a full binary tree (and thus 2 full subtrees); - [PPF] for a partial tree with a partial left subtree and a full - right subtree; - [PFF] for a partial tree with a full left subtree and a full right subtree - (but of different heights); - and [PFP] for a partial tree with a full left subtree and a partial - right subtree. *) - - type t = - | Empty - | FFF of t * X.t * t (* full (full, full) *) - | PPF of t * X.t * t (* partial (partial, full) *) - | PFF of t * X.t * t (* partial (full, full) *) - | PFP of t * X.t * t (* partial (full, partial) *) - - type elt = X.t - - let empty = Empty - - (* smart constructors for insertion *) - let p_f l x r = match l with - | Empty | FFF _ -> PFF (l, x, r) - | _ -> PPF (l, x, r) - - let pf_ l x = function - | Empty | FFF _ as r -> FFF (l, x, r) - | r -> PFP (l, x, r) - - let rec add x = function - | Empty -> - FFF (Empty, x, Empty) - (* insertion to the left *) - | FFF (l, y, r) | PPF (l, y, r) -> - if X.compare x y > 0 then p_f (add y l) x r else p_f (add x l) y r - (* insertion to the right *) - | PFF (l, y, r) | PFP (l, y, r) -> - if X.compare x y > 0 then pf_ l x (add y r) else pf_ l y (add x r) - - let maximum = function - | Empty -> raise EmptyHeap - | FFF (_, x, _) | PPF (_, x, _) | PFF (_, x, _) | PFP (_, x, _) -> x - - (* smart constructors for removal; note that they are different - from the ones for insertion! *) - let p_f l x r = match l with - | Empty | FFF _ -> FFF (l, x, r) - | _ -> PPF (l, x, r) - - let pf_ l x = function - | Empty | FFF _ as r -> PFF (l, x, r) - | r -> PFP (l, x, r) - - let rec remove = function - | Empty -> - raise EmptyHeap - | FFF (Empty, _, Empty) -> - Empty - | PFF (l, _, Empty) -> - l - (* remove on the left *) - | PPF (l, x, r) | PFF (l, x, r) -> - let xl = maximum l in - let xr = maximum r in - let l' = remove l in - if X.compare xl xr >= 0 then - p_f l' xl r - else - p_f l' xr (add xl (remove r)) - (* remove on the right *) - | FFF (l, x, r) | PFP (l, x, r) -> - let xl = maximum l in - let xr = maximum r in - let r' = remove r in - if X.compare xl xr > 0 then - pf_ (add xr (remove l)) xl r' - else - pf_ l xr r' - - let rec iter f = function - | Empty -> - () - | FFF (l, x, r) | PPF (l, x, r) | PFF (l, x, r) | PFP (l, x, r) -> - iter f l; f x; iter f r - - let rec fold f h x0 = match h with - | Empty -> - x0 - | FFF (l, x, r) | PPF (l, x, r) | PFF (l, x, r) | PFP (l, x, r) -> - fold f l (fold f r (f x x0)) - -end diff --git a/tests/ocaml/kb.ml b/tests/ocaml/kb.ml deleted file mode 100644 index 093d918..0000000 --- a/tests/ocaml/kb.ml +++ /dev/null @@ -1,294 +0,0 @@ -(* ========================================================================= *) -(* Knuth-Bendix completion done by HOL inference. John Harrison 2005 *) -(* *) -(* This was written by fairly mechanical modification of the code at *) -(* *) -(* http://www.cl.cam.ac.uk/users/jrh/atp/order.ml *) -(* http://www.cl.cam.ac.uk/users/jrh/atp/completion.ml *) -(* *) -(* for HOL's slightly different term structure, with ad hoc term *) -(* manipulations replaced by inference on equational theorems. We also have *) -(* the optimization of throwing left-reducible rules back into the set of *) -(* critical pairs. However, we don't prioritize smaller critical pairs or *) -(* anything like that; this is still a very naive implementation. *) -(* *) -(* For something very similar done 15 years ago, see Konrad Slind's Master's *) -(* thesis: "An Implementation of Higher Order Logic", U Calgary 1991. *) -(* ========================================================================= *) - -let is_realvar w x = is_var x & not(mem x w);; - -let rec real_strip w tm = - if mem tm w then tm,[] else - let l,r = dest_comb tm in - let f,args = real_strip w l in f,args@[r];; - -(* ------------------------------------------------------------------------- *) -(* Construct a weighting function. *) -(* ------------------------------------------------------------------------- *) - -let weight lis (f,n) (g,m) = - let i = index f lis and j = index g lis in - i > j or i = j & n > m;; - -(* ------------------------------------------------------------------------- *) -(* Generic lexicographic ordering function. *) -(* ------------------------------------------------------------------------- *) - -let rec lexord ord l1 l2 = - match (l1,l2) with - (h1::t1,h2::t2) -> if ord h1 h2 then length t1 = length t2 - else h1 = h2 & lexord ord t1 t2 - | _ -> false;; - -(* ------------------------------------------------------------------------- *) -(* Lexicographic path ordering. Note that we also use the weights *) -(* to define the set of constants, so they don't literally have to be *) -(* constants in the HOL sense. *) -(* ------------------------------------------------------------------------- *) - -let rec lpo_gt w s t = - if is_realvar w t then not(s = t) & mem t (frees s) - else if is_realvar w s or is_abs s or is_abs t then false else - let f,fargs = real_strip w s and g,gargs = real_strip w t in - exists (fun si -> lpo_ge w si t) fargs or - forall (lpo_gt w s) gargs & - (f = g & lexord (lpo_gt w) fargs gargs or - weight w (f,length fargs) (g,length gargs)) -and lpo_ge w s t = (s = t) or lpo_gt w s t;; - -(* ------------------------------------------------------------------------- *) -(* Unification. Again we have the weights "w" fixing the set of constants. *) -(* ------------------------------------------------------------------------- *) - -let rec istriv w env x t = - if is_realvar w t then t = x or defined env t & istriv w env x (apply env t) - else if is_const t then false else - let f,args = strip_comb t in - exists (istriv w env x) args & failwith "cyclic";; - -let rec unify w env tp = - match tp with - ((Var(_,_) as x),t) | (t,(Var(_,_) as x)) when not(mem x w) -> - if defined env x then unify w env (apply env x,t) - else if istriv w env x t then env else (x|->t) env - | (Comb(f,x),Comb(g,y)) -> unify w (unify w env (x,y)) (f,g) - | (s,t) -> if s = t then env else failwith "unify: not unifiable";; - -(* ------------------------------------------------------------------------- *) -(* Full unification, unravelling graph into HOL-style instantiation list. *) -(* ------------------------------------------------------------------------- *) - -let fullunify w (s,t) = - let env = unify w undefined (s,t) in - let th = map (fun (x,t) -> (t,x)) (graph env) in - let rec subs t = - let t' = vsubst th t in - if t' = t then t else subs t' in - map (fun (t,x) -> (subs t,x)) th;; - -(* ------------------------------------------------------------------------- *) -(* Construct "overlaps": ways of rewriting subterms using unification. *) -(* ------------------------------------------------------------------------- *) - -let LIST_MK_COMB f ths = rev_itlist (fun s t -> MK_COMB(t,s)) ths (REFL f);; - -let rec listcases fn rfn lis acc = - match lis with - [] -> acc - | h::t -> fn h (fun i h' -> rfn i (h'::map REFL t)) @ - listcases fn (fun i t' -> rfn i (REFL h::t')) t acc;; - -let rec overlaps w th tm rfn = - let l,r = dest_eq(concl th) in - if not (is_comb tm) then [] else - let f,args = strip_comb tm in - listcases (overlaps w th) (fun i a -> rfn i (LIST_MK_COMB f a)) args - (try [rfn (fullunify w (l,tm)) th] with Failure _ -> []);; - -(* ------------------------------------------------------------------------- *) -(* Rename variables canonically to avoid clashes or remove redundancy. *) -(* ------------------------------------------------------------------------- *) - -let fixvariables s th = - let fvs = subtract (frees(concl th)) (freesl(hyp th)) in - let gvs = map2 (fun v n -> mk_var(s^string_of_int n,type_of v)) - fvs (1--(length fvs)) in - INST (zip gvs fvs) th;; - -let renamepair (th1,th2) = fixvariables "x" th1,fixvariables "y" th2;; - -(* ------------------------------------------------------------------------- *) -(* Find all critical pairs. *) -(* ------------------------------------------------------------------------- *) - -let crit1 w eq1 eq2 = - let l1,r1 = dest_eq(concl eq1) - and l2,r2 = dest_eq(concl eq2) in - overlaps w eq1 l2 (fun i th -> TRANS (SYM(INST i th)) (INST i eq2));; - -let thm_union l1 l2 = - itlist (fun th ths -> let th' = fixvariables "x" th in - let tm = concl th' in - if exists (fun th'' -> concl th'' = tm) ths then ths - else th'::ths) - l1 l2;; - -let critical_pairs w tha thb = - let th1,th2 = renamepair (tha,thb) in - if concl th1 = concl th2 then crit1 w th1 th2 else - filter (fun th -> let l,r = dest_eq(concl th) in l <> r) - (thm_union (crit1 w th1 th2) (thm_union (crit1 w th2 th1) []));; - -(* ------------------------------------------------------------------------- *) -(* Normalize an equation and try to orient it. *) -(* ------------------------------------------------------------------------- *) - -let normalize_and_orient w eqs th = - let th' = GEN_REWRITE_RULE TOP_DEPTH_CONV eqs th in - let s',t' = dest_eq(concl th') in - if lpo_ge w s' t' then th' else if lpo_ge w t' s' then SYM th' - else failwith "Can't orient equation";; - -(* ------------------------------------------------------------------------- *) -(* Print out status report to reduce user boredom. *) -(* ------------------------------------------------------------------------- *) - -let status(eqs,crs) eqs0 = - if eqs = eqs0 & (length crs) mod 1000 <> 0 then () else - (print_string(string_of_int(length eqs)^" equations and "^ - string_of_int(length crs)^" pending critical pairs"); - print_newline());; - -(* ------------------------------------------------------------------------- *) -(* Basic completion, throwing back left-reducible rules. *) -(* ------------------------------------------------------------------------- *) - -let left_reducible eqs eq = - can (CHANGED_CONV(GEN_REWRITE_CONV (LAND_CONV o ONCE_DEPTH_CONV) eqs)) - (concl eq);; - -let rec complete w (eqs,crits) = - match crits with - (eq::ocrits) -> - let trip = - try let eq' = normalize_and_orient w eqs eq in - let s',t' = dest_eq(concl eq') in - if s' = t' then (eqs,ocrits) else - let crits',eqs' = partition(left_reducible [eq']) eqs in - let eqs'' = eq'::eqs' in - eqs'', - ocrits @ crits' @ itlist ((@) o critical_pairs w eq') eqs'' [] - with Failure _ -> - if exists (can (normalize_and_orient w eqs)) ocrits - then (eqs,ocrits@[eq]) - else failwith "complete: no orientable equations" in - status trip eqs; complete w trip - | [] -> eqs;; - -(* ------------------------------------------------------------------------- *) -(* Overall completion. *) -(* ------------------------------------------------------------------------- *) - -let complete_equations wts eqs = - let eqs' = map (normalize_and_orient wts []) eqs in - complete wts ([],eqs');; - -(* ------------------------------------------------------------------------- *) -(* Knuth-Bendix example 4: the inverse property. *) -(* ------------------------------------------------------------------------- *) - -complete_equations [`1`; `(*):num->num->num`; `i:num->num`] - [SPEC_ALL(ASSUME `!a b. i(a) * a * b = b`)];; - -(* ------------------------------------------------------------------------- *) -(* Knuth-Bendix example 6: central groupoids. *) -(* ------------------------------------------------------------------------- *) - -complete_equations [`(*):num->num->num`] - [SPEC_ALL(ASSUME `!a b c. (a * b) * (b * c) = b`)];; - -(* ------------------------------------------------------------------------- *) -(* Knuth-Bendix example 9: cancellation law. *) -(* ------------------------------------------------------------------------- *) - -complete_equations - [`1`; `( * ):num->num->num`; `(+):num->num->num`; `(-):num->num->num`] - (map SPEC_ALL (CONJUNCTS (ASSUME - `(!a b:num. a - a * b = b) /\ - (!a b:num. a * b - b = a) /\ - (!a. a * 1 = a) /\ - (!a. 1 * a = a)`)));; - -(* ------------------------------------------------------------------------- *) -(* Another example: pure congruence closure (no variables). *) -(* ------------------------------------------------------------------------- *) - -complete_equations [`c:A`; `f:A->A`] - (map SPEC_ALL (CONJUNCTS (ASSUME - `((f(f(f(f(f c))))) = c:A) /\ (f(f(f c)) = c)`)));; - -(* ------------------------------------------------------------------------- *) -(* Knuth-Bendix example 1: group theory. *) -(* ------------------------------------------------------------------------- *) - -let eqs = map SPEC_ALL (CONJUNCTS (ASSUME - `(!x. 1 * x = x) /\ (!x. i(x) * x = 1) /\ - (!x y z. (x * y) * z = x * y * z)`));; - -complete_equations [`1`; `(*):num->num->num`; `i:num->num`] eqs;; - -(* ------------------------------------------------------------------------- *) -(* Near-rings (from Aichinger's Diplomarbeit). *) -(* ------------------------------------------------------------------------- *) - -let eqs = map SPEC_ALL (CONJUNCTS (ASSUME - `(!x. 0 + x = x) /\ - (!x. neg x + x = 0) /\ - (!x y z. (x + y) + z = x + y + z) /\ - (!x y z. (x * y) * z = x * y * z) /\ - (!x y z. (x + y) * z = (x * z) + (y * z))`));; - -let nreqs = -complete_equations - [`0`; `(+):num->num->num`; `neg:num->num`; `( * ):num->num->num`] eqs;; - -(*** This weighting also works OK, though the system is a bit bigger - -let nreqs = -complete_equations - [`0`; `(+):num->num->num`; `( * ):num->num->num`; `INV`] eqs;; - -****) - -(* ------------------------------------------------------------------------- *) -(* A "completion" tactic. *) -(* ------------------------------------------------------------------------- *) - -let COMPLETE_TAC w th = - let eqs = map SPEC_ALL (CONJUNCTS(SPEC_ALL th)) in - let eqs' = complete_equations w eqs in - MAP_EVERY (ASSUME_TAC o GEN_ALL) eqs';; - -(* ------------------------------------------------------------------------- *) -(* Solve example problems in gr *) - -g `(!x. 1 * x = x) /\ - (!x. i(x) * x = 1) /\ - (!x y z. (x * y) * z = x * y * z) - ==> !x y. i(y) * i(i(i(x * i(y)))) * x = 1`;; - -e (DISCH_THEN(COMPLETE_TAC [`1`; `(*):num->num->num`; `i:num->num`]));; -e (ASM_REWRITE_TAC[]);; - -g `(!x. 0 + x = x) /\ - (!x. neg x + x = 0) /\ - (!x y z. (x + y) + z = x + y + z) /\ - (!x y z. (x * y) * z = x * y * z) /\ - (!x y z. (x + y) * z = (x * z) + (y * z)) - ==> (neg 0 * (x * y + z + neg(neg(w + z))) + neg(neg b + neg a) = - a + b)`;; - -e (DISCH_THEN(COMPLETE_TAC - [`0`; `(+):num->num->num`; `neg:num->num`; `( * ):num->num->num`]));; -e (ASM_REWRITE_TAC[]);; diff --git a/tests/ocaml/xmlParser.mli b/tests/ocaml/xmlParser.mli deleted file mode 100644 index 941bce3..0000000 --- a/tests/ocaml/xmlParser.mli +++ /dev/null @@ -1,82 +0,0 @@ -(* - * Xml Light, an small Xml parser/printer with DTD support. - * Copyright (C) 2003 Nicolas Cannasse (ncannasse@motion-twin.com) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library has the special exception on linking described in file - * README. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - *) - -(** Xml Light Parser - - While basic parsing functions can be used in the {!Xml} module, this module - is providing a way to create, configure and run an Xml parser. - -*) - -(** Abstract type for an Xml parser. *) -type t - -(** Several kind of resources can contain Xml documents. *) -type source = - | SFile of string - | SChannel of in_channel - | SString of string - | SLexbuf of Lexing.lexbuf - -(** This function returns a new parser with default options. *) -val make : unit -> t - -(** This function enable or disable automatic DTD proving with the parser. - Note that Xml documents having no reference to a DTD are never proved - when parsed (but you can prove them later using the {!Dtd} module - {i (by default, prove is true)}. *) -val prove : t -> bool -> unit - -(** When parsing an Xml document from a file using the {!Xml.parse_file} - function, the DTD file if declared by the Xml document has to be in the - same directory as the xml file. When using other parsing functions, - such as on a string or on a channel, the parser will raise everytime - {!Xml.File_not_found} if a DTD file is needed and prove enabled. To enable - the DTD loading of the file, the user have to configure the Xml parser - with a [resolve] function which is taking as argument the DTD filename and - is returning a checked DTD. The user can then implement any kind of DTD - loading strategy, and can use the {!Dtd} module functions to parse and check - the DTD file {i (by default, the resolve function is raising} - {!Xml.File_not_found}). *) -val resolve : t -> (string -> Dtd.checked) -> unit - -(** When a Xml document is parsed, the parser will check that the end of the - document is reached, so for example parsing [""] will fail instead - of returning only the A element. You can turn off this check by setting - [check_eof] to [false] {i (by default, check_eof is true)}. *) -val check_eof : t -> bool -> unit - -(** Once the parser is configurated, you can run the parser on a any kind - of xml document source to parse its contents into an Xml data structure. *) -val parse : t -> source -> Xml.xml - -(** When several PCData elements are separed by a \n (or \r\n), you can - either split the PCData in two distincts PCData or merge them with \n - as seperator into one PCData. The default behavior is to concat the - PCData, but this can be changed for a given parser with this flag. *) -val concat_pcdata : t -> bool -> unit - -(**/**) - -(* internal usage only... *) -val _raises : (Xml.error_msg -> Lexing.lexbuf -> exn) -> (string -> exn) -> (Dtd.parse_error_msg -> Lexing.lexbuf -> exn) -> unit diff --git a/tests/php/Crawler.php b/tests/php/Crawler.php deleted file mode 100644 index b408d7d..0000000 --- a/tests/php/Crawler.php +++ /dev/null @@ -1,721 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler; - -use Symfony\Component\CssSelector\CssSelector; - -/** - * Crawler eases navigation of a list of \DOMNode objects. - * - * @author Fabien Potencier - * - * @api - */ -class Crawler extends \SplObjectStorage -{ - /** - * @var string The current URI or the base href value - */ - private $uri; - - /** - * Constructor. - * - * @param mixed $node A Node to use as the base for the crawling - * @param string $uri The current URI or the base href value - * - * @api - */ - public function __construct($node = null, $uri = null) - { - $this->uri = $uri; - - $this->add($node); - } - - /** - * Removes all the nodes. - * - * @api - */ - public function clear() - { - $this->removeAll($this); - } - - /** - * Adds a node to the current list of nodes. - * - * This method uses the appropriate specialized add*() method based - * on the type of the argument. - * - * @param null|\DOMNodeList|array|\DOMNode $node A node - * - * @api - */ - public function add($node) - { - if ($node instanceof \DOMNodeList) { - $this->addNodeList($node); - } elseif (is_array($node)) { - $this->addNodes($node); - } elseif (is_string($node)) { - $this->addContent($node); - } elseif (is_object($node)) { - $this->addNode($node); - } - } - - /** - * Adds HTML/XML content. - * - * @param string $content A string to parse as HTML/XML - * @param null|string $type The content type of the string - * - * @return null|void - */ - public function addContent($content, $type = null) - { - if (empty($type)) { - $type = 'text/html'; - } - - // DOM only for HTML/XML content - if (!preg_match('/(x|ht)ml/i', $type, $matches)) { - return null; - } - - $charset = 'ISO-8859-1'; - if (false !== $pos = strpos($type, 'charset=')) { - $charset = substr($type, $pos + 8); - if (false !== $pos = strpos($charset, ';')) { - $charset = substr($charset, 0, $pos); - } - } - - if ('x' === $matches[1]) { - $this->addXmlContent($content, $charset); - } else { - $this->addHtmlContent($content, $charset); - } - } - - /** - * Adds an HTML content to the list of nodes. - * - * The libxml errors are disabled when the content is parsed. - * - * If you want to get parsing errors, be sure to enable - * internal errors via libxml_use_internal_errors(true) - * and then, get the errors via libxml_get_errors(). Be - * sure to clear errors with libxml_clear_errors() afterward. - * - * @param string $content The HTML content - * @param string $charset The charset - * - * @api - */ - public function addHtmlContent($content, $charset = 'UTF-8') - { - $dom = new \DOMDocument('1.0', $charset); - $dom->validateOnParse = true; - - if (function_exists('mb_convert_encoding')) { - $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); - } - - $current = libxml_use_internal_errors(true); - @$dom->loadHTML($content); - libxml_use_internal_errors($current); - - $this->addDocument($dom); - - $base = $this->filterXPath('descendant-or-self::base')->extract(array('href')); - - if (count($base)) { - $this->uri = current($base); - } - } - - /** - * Adds an XML content to the list of nodes. - * - * The libxml errors are disabled when the content is parsed. - * - * If you want to get parsing errors, be sure to enable - * internal errors via libxml_use_internal_errors(true) - * and then, get the errors via libxml_get_errors(). Be - * sure to clear errors with libxml_clear_errors() afterward. - * - * @param string $content The XML content - * @param string $charset The charset - * - * @api - */ - public function addXmlContent($content, $charset = 'UTF-8') - { - $dom = new \DOMDocument('1.0', $charset); - $dom->validateOnParse = true; - - // remove the default namespace to make XPath expressions simpler - $current = libxml_use_internal_errors(true); - @$dom->loadXML(str_replace('xmlns', 'ns', $content)); - libxml_use_internal_errors($current); - - $this->addDocument($dom); - } - - /** - * Adds a \DOMDocument to the list of nodes. - * - * @param \DOMDocument $dom A \DOMDocument instance - * - * @api - */ - public function addDocument(\DOMDocument $dom) - { - if ($dom->documentElement) { - $this->addNode($dom->documentElement); - } - } - - /** - * Adds a \DOMNodeList to the list of nodes. - * - * @param \DOMNodeList $nodes A \DOMNodeList instance - * - * @api - */ - public function addNodeList(\DOMNodeList $nodes) - { - foreach ($nodes as $node) { - $this->addNode($node); - } - } - - /** - * Adds an array of \DOMNode instances to the list of nodes. - * - * @param array $nodes An array of \DOMNode instances - * - * @api - */ - public function addNodes(array $nodes) - { - foreach ($nodes as $node) { - $this->add($node); - } - } - - /** - * Adds a \DOMNode instance to the list of nodes. - * - * @param \DOMNode $node A \DOMNode instance - * - * @api - */ - public function addNode(\DOMNode $node) - { - if ($node instanceof \DOMDocument) { - $this->attach($node->documentElement); - } else { - $this->attach($node); - } - } - - /** - * Returns a node given its position in the node list. - * - * @param integer $position The position - * - * @return Crawler A new instance of the Crawler with the selected node, or an empty Crawler if it does not exist. - * - * @api - */ - public function eq($position) - { - foreach ($this as $i => $node) { - if ($i == $position) { - return new static($node, $this->uri); - } - } - - return new static(null, $this->uri); - } - - /** - * Calls an anonymous function on each node of the list. - * - * The anonymous function receives the position and the node as arguments. - * - * Example: - * - * $crawler->filter('h1')->each(function ($node, $i) - * { - * return $node->nodeValue; - * }); - * - * @param \Closure $closure An anonymous function - * - * @return array An array of values returned by the anonymous function - * - * @api - */ - public function each(\Closure $closure) - { - $data = array(); - foreach ($this as $i => $node) { - $data[] = $closure($node, $i); - } - - return $data; - } - - /** - * Reduces the list of nodes by calling an anonymous function. - * - * To remove a node from the list, the anonymous function must return false. - * - * @param \Closure $closure An anonymous function - * - * @return Crawler A Crawler instance with the selected nodes. - * - * @api - */ - public function reduce(\Closure $closure) - { - $nodes = array(); - foreach ($this as $i => $node) { - if (false !== $closure($node, $i)) { - $nodes[] = $node; - } - } - - return new static($nodes, $this->uri); - } - - /** - * Returns the first node of the current selection - * - * @return Crawler A Crawler instance with the first selected node - * - * @api - */ - public function first() - { - return $this->eq(0); - } - - /** - * Returns the last node of the current selection - * - * @return Crawler A Crawler instance with the last selected node - * - * @api - */ - public function last() - { - return $this->eq(count($this) - 1); - } - - /** - * Returns the siblings nodes of the current selection - * - * @return Crawler A Crawler instance with the sibling nodes - * - * @throws \InvalidArgumentException When current node is empty - * - * @api - */ - public function siblings() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return new static($this->sibling($this->getNode(0)->parentNode->firstChild), $this->uri); - } - - /** - * Returns the next siblings nodes of the current selection - * - * @return Crawler A Crawler instance with the next sibling nodes - * - * @throws \InvalidArgumentException When current node is empty - * - * @api - */ - public function nextAll() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return new static($this->sibling($this->getNode(0)), $this->uri); - } - - /** - * Returns the previous sibling nodes of the current selection - * - * @return Crawler A Crawler instance with the previous sibling nodes - * - * @api - */ - public function previousAll() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return new static($this->sibling($this->getNode(0), 'previousSibling'), $this->uri); - } - - /** - * Returns the parents nodes of the current selection - * - * @return Crawler A Crawler instance with the parents nodes of the current selection - * - * @throws \InvalidArgumentException When current node is empty - * - * @api - */ - public function parents() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0); - $nodes = array(); - - while ($node = $node->parentNode) { - if (1 === $node->nodeType && '_root' !== $node->nodeName) { - $nodes[] = $node; - } - } - - return new static($nodes, $this->uri); - } - - /** - * Returns the children nodes of the current selection - * - * @return Crawler A Crawler instance with the children nodes - * - * @throws \InvalidArgumentException When current node is empty - * - * @api - */ - public function children() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return new static($this->sibling($this->getNode(0)->firstChild), $this->uri); - } - - /** - * Returns the attribute value of the first node of the list. - * - * @param string $attribute The attribute name - * - * @return string The attribute value - * - * @throws \InvalidArgumentException When current node is empty - * - * @api - */ - public function attr($attribute) - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->getNode(0)->getAttribute($attribute); - } - - /** - * Returns the node value of the first node of the list. - * - * @return string The node value - * - * @throws \InvalidArgumentException When current node is empty - * - * @api - */ - public function text() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->getNode(0)->nodeValue; - } - - /** - * Extracts information from the list of nodes. - * - * You can extract attributes or/and the node value (_text). - * - * Example: - * - * $crawler->filter('h1 a')->extract(array('_text', 'href')); - * - * @param array $attributes An array of attributes - * - * @return array An array of extracted values - * - * @api - */ - public function extract($attributes) - { - $attributes = (array) $attributes; - - $data = array(); - foreach ($this as $node) { - $elements = array(); - foreach ($attributes as $attribute) { - if ('_text' === $attribute) { - $elements[] = $node->nodeValue; - } else { - $elements[] = $node->getAttribute($attribute); - } - } - - $data[] = count($attributes) > 1 ? $elements : $elements[0]; - } - - return $data; - } - - /** - * Filters the list of nodes with an XPath expression. - * - * @param string $xpath An XPath expression - * - * @return Crawler A new instance of Crawler with the filtered list of nodes - * - * @api - */ - public function filterXPath($xpath) - { - $document = new \DOMDocument('1.0', 'UTF-8'); - $root = $document->appendChild($document->createElement('_root')); - foreach ($this as $node) { - $root->appendChild($document->importNode($node, true)); - } - - $domxpath = new \DOMXPath($document); - - return new static($domxpath->query($xpath), $this->uri); - } - - /** - * Filters the list of nodes with a CSS selector. - * - * This method only works if you have installed the CssSelector Symfony Component. - * - * @param string $selector A CSS selector - * - * @return Crawler A new instance of Crawler with the filtered list of nodes - * - * @throws \RuntimeException if the CssSelector Component is not available - * - * @api - */ - public function filter($selector) - { - if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) { - // @codeCoverageIgnoreStart - throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).'); - // @codeCoverageIgnoreEnd - } - - return $this->filterXPath(CssSelector::toXPath($selector)); - } - - /** - * Selects links by name or alt value for clickable images. - * - * @param string $value The link text - * - * @return Crawler A new instance of Crawler with the filtered list of nodes - * - * @api - */ - public function selectLink($value) - { - $xpath = sprintf('//a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s)] ', static::xpathLiteral(' '.$value.' ')). - sprintf('| //a/img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]/ancestor::a', static::xpathLiteral(' '.$value.' ')); - - return $this->filterXPath($xpath); - } - - /** - * Selects a button by name or alt value for images. - * - * @param string $value The button text - * - * @return Crawler A new instance of Crawler with the filtered list of nodes - * - * @api - */ - public function selectButton($value) - { - $xpath = sprintf('//input[((@type="submit" or @type="button") and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', static::xpathLiteral(' '.$value.' ')). - sprintf('or (@type="image" and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id="%s" or @name="%s"] ', static::xpathLiteral(' '.$value.' '), $value, $value). - sprintf('| //button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id="%s" or @name="%s"]', static::xpathLiteral(' '.$value.' '), $value, $value); - - return $this->filterXPath($xpath); - } - - /** - * Returns a Link object for the first node in the list. - * - * @param string $method The method for the link (get by default) - * - * @return Link A Link instance - * - * @throws \InvalidArgumentException If the current node list is empty - * - * @api - */ - public function link($method = 'get') - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0); - - return new Link($node, $this->uri, $method); - } - - /** - * Returns an array of Link objects for the nodes in the list. - * - * @return array An array of Link instances - * - * @api - */ - public function links() - { - $links = array(); - foreach ($this as $node) { - $links[] = new Link($node, $this->uri, 'get'); - } - - return $links; - } - - /** - * Returns a Form object for the first node in the list. - * - * @param array $values An array of values for the form fields - * @param string $method The method for the form - * - * @return Form A Form instance - * - * @throws \InvalidArgumentException If the current node list is empty - * - * @api - */ - public function form(array $values = null, $method = null) - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $form = new Form($this->getNode(0), $this->uri, $method); - - if (null !== $values) { - $form->setValues($values); - } - - return $form; - } - - /** - * Converts string for XPath expressions. - * - * Escaped characters are: quotes (") and apostrophe ('). - * - * Examples: - * - * echo Crawler::xpathLiteral('foo " bar'); - * //prints 'foo " bar' - * - * echo Crawler::xpathLiteral("foo ' bar"); - * //prints "foo ' bar" - * - * echo Crawler::xpathLiteral('a\'b"c'); - * //prints concat('a', "'", 'b"c') - * - * - * @param string $s String to be escaped - * - * @return string Converted string - * - */ - public static function xpathLiteral($s) - { - if (false === strpos($s, "'")) { - return sprintf("'%s'", $s); - } - - if (false === strpos($s, '"')) { - return sprintf('"%s"', $s); - } - - $string = $s; - $parts = array(); - while (true) { - if (false !== $pos = strpos($string, "'")) { - $parts[] = sprintf("'%s'", substr($string, 0, $pos)); - $parts[] = "\"'\""; - $string = substr($string, $pos + 1); - } else { - $parts[] = "'$string'"; - break; - } - } - - return sprintf("concat(%s)", implode($parts, ', ')); - } - - private function getNode($position) - { - foreach ($this as $i => $node) { - if ($i == $position) { - return $node; - } - // @codeCoverageIgnoreStart - } - - return null; - // @codeCoverageIgnoreEnd - } - - private function sibling($node, $siblingDir = 'nextSibling') - { - $nodes = array(); - - do { - if ($node !== $this->getNode(0) && $node->nodeType === 1) { - $nodes[] = $node; - } - } while ($node = $node->$siblingDir); - - return $nodes; - } -} diff --git a/tests/php/Link.php b/tests/php/Link.php deleted file mode 100644 index 1804111..0000000 --- a/tests/php/Link.php +++ /dev/null @@ -1,157 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler; - -/** - * Link represents an HTML link (an HTML a tag). - * - * @author Fabien Potencier - * - * @api - */ -class Link -{ - /** - * @var \DOMNode A \DOMNode instance - */ - protected $node; - /** - * @var string The method to use for the link - */ - protected $method; - /** - * @var string The URI of the page where the link is embedded (or the base href) - */ - protected $currentUri; - - /** - * Constructor. - * - * @param \DOMNode $node A \DOMNode instance - * @param string $currentUri The URI of the page where the link is embedded (or the base href) - * @param string $method The method to use for the link (get by default) - * - * @throws \InvalidArgumentException if the node is not a link - * - * @api - */ - public function __construct(\DOMNode $node, $currentUri, $method = 'GET') - { - if (!in_array(substr($currentUri, 0, 4), array('http', 'file'))) { - throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri)); - } - - $this->setNode($node); - $this->method = $method ? strtoupper($method) : null; - $this->currentUri = $currentUri; - } - - /** - * Gets the node associated with this link. - * - * @return \DOMNode A \DOMNode instance - */ - public function getNode() - { - return $this->node; - } - - /** - * Gets the method associated with this link. - * - * @return string The method - * - * @api - */ - public function getMethod() - { - return $this->method; - } - - /** - * Gets the URI associated with this link. - * - * @return string The URI - * - * @api - */ - public function getUri() - { - $uri = trim($this->getRawUri()); - - // absolute URL? - if (0 === strpos($uri, 'http')) { - return $uri; - } - - // empty URI - if (!$uri) { - return $this->currentUri; - } - - // only an anchor - if ('#' === $uri[0]) { - $baseUri = $this->currentUri; - if (false !== $pos = strpos($baseUri, '#')) { - $baseUri = substr($baseUri, 0, $pos); - } - - return $baseUri.$uri; - } - - // only a query string - if ('?' === $uri[0]) { - $baseUri = $this->currentUri; - - // remove the query string from the current uri - if (false !== $pos = strpos($baseUri, '?')) { - $baseUri = substr($baseUri, 0, $pos); - } - - return $baseUri.$uri; - } - - // absolute path - if ('/' === $uri[0]) { - return preg_replace('#^(.*?//[^/]+)(?:\/.*)?$#', '$1', $this->currentUri).$uri; - } - - // relative path - return substr($this->currentUri, 0, strrpos($this->currentUri, '/') + 1).$uri; - } - - /** - * Returns raw uri data - * - * @return string - */ - protected function getRawUri() - { - return $this->node->getAttribute('href'); - } - - /** - * Sets current \DOMNode instance - * - * @param \DOMNode $node A \DOMNode instance - * - * @throws \LogicException If given node is not an anchor - */ - protected function setNode(\DOMNode $node) - { - if ('a' != $node->nodeName) { - throw new \LogicException(sprintf('Unable to click on a "%s" tag.', $node->nodeName)); - } - - $this->node = $node; - } -} diff --git a/tests/php/gda-clean.php b/tests/php/gda-clean.php deleted file mode 100644 index ea8b1d2..0000000 --- a/tests/php/gda-clean.php +++ /dev/null @@ -1,23 +0,0 @@ -"); -$reply->addChild ("status", "OK"); -echo gda_add_hash ($init_shared, $reply->asXml()); -session_destroy (); - -?> diff --git a/tests/python/bcontroller.py b/tests/python/bcontroller.py deleted file mode 100755 index ea7b8b3..0000000 --- a/tests/python/bcontroller.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python -# -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is standalone Firefox Windows performance test. -# -# The Initial Developer of the Original Code is Google Inc. -# Portions created by the Initial Developer are Copyright (C) 2006 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Alice Nodelman (original author) -# -# Alternatively, the contents of this file may be used under the terms of -# either the GNU General Public License Version 2 or later (the "GPL"), or -# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - -__author__ = 'anodelman@mozilla.com (Alice Nodelman)' - -import os -import time -import subprocess -import threading -import platform -from ffprocess_linux import LinuxProcess -from ffprocess_mac import MacProcess -from ffprocess_win32 import Win32Process -from utils import talosError -import sys -import getopt - -import stat - - -if platform.system() == "Linux": - platform_type = 'linux_' - ffprocess = LinuxProcess() -elif platform.system() in ("Windows", "Microsoft"): - import win32pdh - import win32api - import win32event - import win32con - platform_type = 'win_' - ffprocess = Win32Process() -elif platform.system() == "Darwin": - platform_type = 'mac_' - ffprocess = MacProcess() - -class BrowserWaiter(threading.Thread): - - def __init__(self, command, log, mod, deviceManager = None): - self.command = command - self.log = log - self.mod = mod - self.endTime = -1 - self.returncode = -1 - self.deviceManager = deviceManager - threading.Thread.__init__(self) - self.start() - - def run(self): - if self.mod: - if (self.deviceManager.__class__.__name__ == "WinmoProcess"): - if (self.mod == "str(int(time.time()*1000))"): - self.command += self.deviceManager.getCurrentTime() - else: - self.command = self.command + eval(self.mod) - - if (self.deviceManager.__class__.__name__ == "WinmoProcess"): - retVal = self.deviceManager.launchProcess(self.command, timeout=600) - if retVal <> None: - self.deviceManager.getFile(retVal, self.log) - self.returncode = 0 - else: - self.returncode = 1 - else: #blocking call to system - self.returncode = os.system(self.command + " > " + self.log) - - self.endTime = int(time.time()*1000) - - def hasTime(self): - return self.endTime > -1 - - def getTime(self): - return self.endTime - - def getReturn(self): - def innerMethod(self): - pass - return self.returncode - - def testMethod2(self): - class InnerClass: - def innerInnerMethod(self): - pass - return - - class Test: - def testMethod(self): - pass - -class BrowserController: - - def __init__(self, command, mod, name, child_process, - timeout, log, host='', port=20701, root=''): - global ffprocess - self.command = command - self.mod = mod - self.process_name = name - self.child_process = child_process - self.browser_wait = timeout - self.log = log - self.timeout = 1200 #no output from the browser in 20 minutes = failure - self.host = host - self.port = port - self.root = root - if (host <> ''): - from ffprocess_winmo import WinmoProcess - platform_type = 'win_' - ffprocess = WinmoProcess(host, port, root) - - self.ffprocess = ffprocess - - def run(self): - self.bwaiter = BrowserWaiter(self.command, self.log, self.mod, self.ffprocess) - noise = 0 - prev_size = 0 - while not self.bwaiter.hasTime(): - if noise > self.timeout: # check for frozen browser - try: - ffprocess.cleanupProcesses(self.process_name, self.child_process, self.browser_wait) - except talosError, te: - os.abort() #kill myself off because something horrible has happened - os.chmod(self.log, 0777) - results_file = open(self.log, "a") - results_file.write("\n__FAILbrowser frozen__FAIL\n") - results_file.close() - return - time.sleep(1) - try: - open(self.log, "r").close() #HACK FOR WINDOWS: refresh the file information - size = os.path.getsize(self.log) - except: - size = 0 - - if size > prev_size: - prev_size = size - noise = 0 - else: - noise += 1 - - results_file = open(self.log, "a") - if self.bwaiter.getReturn() != 0: #the browser shutdown, but not cleanly - results_file.write("\n__FAILbrowser non-zero return code (%d)__FAIL\n" % self.bwaiter.getReturn()) - return - results_file.write("__startSecondTimestamp%d__endSecondTimestamp\n" % self.bwaiter.getTime()) - results_file.close() - return - - -def main(argv=None): - - command = "" - name = "firefox" #default - child_process = "plugin-container" #default - timeout = "" - log = "" - mod = "" - host = "" - deviceRoot = "" - port = 20701 - - if argv is None: - argv = sys.argv - opts, args = getopt.getopt(argv[1:], "c:t:n:p:l:m:h:r:o", ["command=", "timeout=", "name=", "child_process=", "log=", "mod=", "host=", "deviceRoot=", "port="]) - - # option processing - for option, value in opts: - if option in ("-c", "--command"): - command = value - if option in ("-t", "--timeout"): - timeout = int(value) - if option in ("-n", "--name"): - name = value - if option in ("-p", "--child_process"): - child_process = value - if option in ("-l", "--log"): - log = value - if option in ("-m", "--mod"): - mod = value - if option in ("-h", "--host"): - host = value - if option in ("-r", "--deviceRoot"): - deviceRoot = value - if option in ("-o", "--port"): - port = value - - if command and timeout and log: - bcontroller = BrowserController(command, mod, name, child_process, timeout, log, host, port, deviceRoot) - bcontroller.run() - else: - print "\nFAIL: no command\n" - sys.stdout.flush() - - class mainClass: - def mainClassMethod(self): - pass - pass - - def mainMethod(self): - class mainMethodClass: - pass - pass - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tests/python/functions.py b/tests/python/functions.py deleted file mode 100644 index 32ec0eb..0000000 --- a/tests/python/functions.py +++ /dev/null @@ -1,697 +0,0 @@ -""" -Makefile functions. -""" - -import parser, util -import subprocess, os, logging -from globrelative import glob -from cStringIO import StringIO - -log = logging.getLogger('pymake.data') - -class Function(object): - """ - An object that represents a function call. This class is always subclassed - with the following methods and attributes: - - minargs = minimum # of arguments - maxargs = maximum # of arguments (0 means unlimited) - - def resolve(self, makefile, variables, fd, setting) - Calls the function - calls fd.write() with strings - """ - - __slots__ = ('_arguments', 'loc') - - def __init__(self, loc): - self._arguments = [] - self.loc = loc - assert self.minargs > 0 - - def __getitem__(self, key): - return self._arguments[key] - - def setup(self): - argc = len(self._arguments) - - if argc < self.minargs: - raise data.DataError("Not enough arguments to function %s, requires %s" % (self.name, self.minargs), self.loc) - - assert self.maxargs == 0 or argc <= self.maxargs, "Parser screwed up, gave us too many args" - - def append(self, arg): - assert isinstance(arg, (data.Expansion, data.StringExpansion)) - self._arguments.append(arg) - - def __len__(self): - return len(self._arguments) - -class VariableRef(Function): - __slots__ = ('vname', 'loc') - - def __init__(self, loc, vname): - self.loc = loc - assert isinstance(vname, (data.Expansion, data.StringExpansion)) - self.vname = vname - - def setup(self): - assert False, "Shouldn't get here" - - def resolve(self, makefile, variables, fd, setting): - vname = self.vname.resolvestr(makefile, variables, setting) - if vname in setting: - raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc) - - flavor, source, value = variables.get(vname) - if value is None: - log.debug("%s: variable '%s' was not set" % (self.loc, vname)) - return - - value.resolve(makefile, variables, fd, setting + [vname]) - -class SubstitutionRef(Function): - """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)""" - - __slots__ = ('loc', 'vname', 'substfrom', 'substto') - - def __init__(self, loc, varname, substfrom, substto): - self.loc = loc - self.vname = varname - self.substfrom = substfrom - self.substto = substto - - def setup(self): - assert False, "Shouldn't get here" - - def resolve(self, makefile, variables, fd, setting): - vname = self.vname.resolvestr(makefile, variables, setting) - if vname in setting: - raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc) - - substfrom = self.substfrom.resolvestr(makefile, variables, setting) - substto = self.substto.resolvestr(makefile, variables, setting) - - flavor, source, value = variables.get(vname) - if value is None: - log.debug("%s: variable '%s' was not set" % (self.loc, vname)) - return - - f = data.Pattern(substfrom) - if not f.ispattern(): - f = data.Pattern('%' + substfrom) - substto = '%' + substto - - fd.write(' '.join([f.subst(substto, word, False) - for word in value.resolvesplit(makefile, variables, setting + [vname])])) - -class SubstFunction(Function): - name = 'subst' - minargs = 3 - maxargs = 3 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - s = self._arguments[0].resolvestr(makefile, variables, setting) - r = self._arguments[1].resolvestr(makefile, variables, setting) - d = self._arguments[2].resolvestr(makefile, variables, setting) - fd.write(d.replace(s, r)) - -class PatSubstFunction(Function): - name = 'patsubst' - minargs = 3 - maxargs = 3 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - s = self._arguments[0].resolvestr(makefile, variables, setting) - r = self._arguments[1].resolvestr(makefile, variables, setting) - - p = data.Pattern(s) - fd.write(' '.join([p.subst(r, word, False) - for word in self._arguments[2].resolvesplit(makefile, variables, setting)])) - -class StripFunction(Function): - name = 'strip' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - util.joiniter(fd, self._arguments[0].resolvesplit(makefile, variables, setting)) - -class FindstringFunction(Function): - name = 'findstring' - minargs = 2 - maxargs = 2 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - s = self._arguments[0].resolvestr(makefile, variables, setting) - r = self._arguments[1].resolvestr(makefile, variables, setting) - if r.find(s) == -1: - return - fd.write(s) - -class FilterFunction(Function): - name = 'filter' - minargs = 2 - maxargs = 2 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - plist = [data.Pattern(p) - for p in self._arguments[0].resolvesplit(makefile, variables, setting)] - - fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting) - if util.any((p.match(w) for p in plist))])) - -class FilteroutFunction(Function): - name = 'filter-out' - minargs = 2 - maxargs = 2 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - plist = [data.Pattern(p) - for p in self._arguments[0].resolvesplit(makefile, variables, setting)] - - fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting) - if not util.any((p.match(w) for p in plist))])) - -class SortFunction(Function): - name = 'sort' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - d = list(self._arguments[0].resolvesplit(makefile, variables, setting)) - d.sort() - util.joiniter(fd, d) - -class WordFunction(Function): - name = 'word' - minargs = 2 - maxargs = 2 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - n = self._arguments[0].resolvestr(makefile, variables, setting) - # TODO: provide better error if this doesn't convert - n = int(n) - words = list(self._arguments[1].resolvesplit(makefile, variables, setting)) - if n < 1 or n > len(words): - return - fd.write(words[n - 1]) - -class WordlistFunction(Function): - name = 'wordlist' - minargs = 3 - maxargs = 3 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - nfrom = self._arguments[0].resolvestr(makefile, variables, setting) - nto = self._arguments[1].resolvestr(makefile, variables, setting) - # TODO: provide better errors if this doesn't convert - nfrom = int(nfrom) - nto = int(nto) - - words = list(self._arguments[2].resolvesplit(makefile, variables, setting)) - - if nfrom < 1: - nfrom = 1 - if nto < 1: - nto = 1 - - util.joiniter(fd, words[nfrom - 1:nto]) - -class WordsFunction(Function): - name = 'words' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - fd.write(str(len(self._arguments[0].resolvesplit(makefile, variables, setting)))) - -class FirstWordFunction(Function): - name = 'firstword' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - l = self._arguments[0].resolvesplit(makefile, variables, setting) - if len(l): - fd.write(l[0]) - -class LastWordFunction(Function): - name = 'lastword' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - l = self._arguments[0].resolvesplit(makefile, variables, setting) - if len(l): - fd.write(l[-1]) - -def pathsplit(path, default='./'): - """ - Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart - is ./ - """ - dir, slash, file = util.strrpartition(path, '/') - if dir == '': - return default, file - - return dir + slash, file - -class DirFunction(Function): - name = 'dir' - minargs = 1 - maxargs = 1 - - def resolve(self, makefile, variables, fd, setting): - fd.write(' '.join([pathsplit(path)[0] - for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) - -class NotDirFunction(Function): - name = 'notdir' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - fd.write(' '.join([pathsplit(path)[1] - for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) - -class SuffixFunction(Function): - name = 'suffix' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - @staticmethod - def suffixes(words): - for w in words: - dir, file = pathsplit(w) - base, dot, suffix = util.strrpartition(file, '.') - if base != '': - yield dot + suffix - - def resolve(self, makefile, variables, fd, setting): - util.joiniter(fd, self.suffixes(self._arguments[0].resolvesplit(makefile, variables, setting))) - -class BasenameFunction(Function): - name = 'basename' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - @staticmethod - def basenames(words): - for w in words: - dir, file = pathsplit(w, '') - base, dot, suffix = util.strrpartition(file, '.') - if dot == '': - base = suffix - - yield dir + base - - def resolve(self, makefile, variables, fd, setting): - util.joiniter(fd, self.basenames(self._arguments[0].resolvesplit(makefile, variables, setting))) - -class AddSuffixFunction(Function): - name = 'addprefix' - minargs = 2 - maxargs = 2 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - suffix = self._arguments[0].resolvestr(makefile, variables, setting) - - fd.write(' '.join([w + suffix for w in self._arguments[1].resolvesplit(makefile, variables, setting)])) - -class AddPrefixFunction(Function): - name = 'addsuffix' - minargs = 2 - maxargs = 2 - - def resolve(self, makefile, variables, fd, setting): - prefix = self._arguments[0].resolvestr(makefile, variables, setting) - - fd.write(' '.join([prefix + w for w in self._arguments[1].resolvesplit(makefile, variables, setting)])) - -class JoinFunction(Function): - name = 'join' - minargs = 2 - maxargs = 2 - - __slots__ = Function.__slots__ - - @staticmethod - def iterjoin(l1, l2): - for i in xrange(0, max(len(l1), len(l2))): - i1 = i < len(l1) and l1[i] or '' - i2 = i < len(l2) and l2[i] or '' - yield i1 + i2 - - def resolve(self, makefile, variables, fd, setting): - list1 = list(self._arguments[0].resolvesplit(makefile, variables, setting)) - list2 = list(self._arguments[1].resolvesplit(makefile, variables, setting)) - - util.joiniter(fd, self.iterjoin(list1, list2)) - -class WildcardFunction(Function): - name = 'wildcard' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - patterns = self._arguments[0].resolvesplit(makefile, variables, setting) - - fd.write(' '.join([x.replace('\\','/') - for p in patterns - for x in glob(makefile.workdir, p)])) - - __slots__ = Function.__slots__ - -class RealpathFunction(Function): - name = 'realpath' - minargs = 1 - maxargs = 1 - - def resolve(self, makefile, variables, fd, setting): - fd.write(' '.join([os.path.realpath(os.path.join(makefile.workdir, path)).replace('\\', '/') - for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) - -class AbspathFunction(Function): - name = 'abspath' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - assert os.path.isabs(makefile.workdir) - fd.write(' '.join([util.normaljoin(makefile.workdir, path).replace('\\', '/') - for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) - -class IfFunction(Function): - name = 'if' - minargs = 1 - maxargs = 3 - - __slots__ = Function.__slots__ - - def setup(self): - Function.setup(self) - self._arguments[0].lstrip() - self._arguments[0].rstrip() - - def resolve(self, makefile, variables, fd, setting): - condition = self._arguments[0].resolvestr(makefile, variables, setting) - - if len(condition): - self._arguments[1].resolve(makefile, variables, fd, setting) - elif len(self._arguments) > 2: - return self._arguments[2].resolve(makefile, variables, fd, setting) - -class OrFunction(Function): - name = 'or' - minargs = 1 - maxargs = 0 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - for arg in self._arguments: - r = arg.resolvestr(makefile, variables, setting) - if r != '': - fd.write(r) - return - -class AndFunction(Function): - name = 'and' - minargs = 1 - maxargs = 0 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - r = '' - - for arg in self._arguments: - r = arg.resolvestr(makefile, variables, setting) - if r == '': - return - - fd.write(r) - -class ForEachFunction(Function): - name = 'foreach' - minargs = 3 - maxargs = 3 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - vname = self._arguments[0].resolvestr(makefile, variables, setting) - e = self._arguments[2] - - v = data.Variables(parent=variables) - firstword = True - - for w in self._arguments[1].resolvesplit(makefile, variables, setting): - if firstword: - firstword = False - else: - fd.write(' ') - - v.set(vname, data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, w) - e.resolve(makefile, v, fd, setting) - -class CallFunction(Function): - name = 'call' - minargs = 1 - maxargs = 0 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - vname = self._arguments[0].resolvestr(makefile, variables, setting) - if vname in setting: - raise data.DataError("Recursively setting variable '%s'" % (vname,)) - - v = data.Variables(parent=variables) - v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname) - for i in xrange(1, len(self._arguments)): - param = self._arguments[i].resolvestr(makefile, variables, setting) - v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param) - - flavor, source, e = variables.get(vname) - - if e is None: - return - - if flavor == data.Variables.FLAVOR_SIMPLE: - log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname)) - - # but we'll do it anyway - e.resolve(makefile, v, fd, setting + [vname]) - -class ValueFunction(Function): - name = 'value' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - varname = self._arguments[0].resolvestr(makefile, variables, setting) - - flavor, source, value = variables.get(varname, expand=False) - if value is not None: - fd.write(value) - -class EvalFunction(Function): - name = 'eval' - minargs = 1 - maxargs = 1 - - def resolve(self, makefile, variables, fd, setting): - if makefile.parsingfinished: - # GNU make allows variables to be set by recursive expansion during - # command execution. This seems really dumb to me, so I don't! - raise data.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc) - - stmts = parser.parsestring(self._arguments[0].resolvestr(makefile, variables, setting), - 'evaluation from %s' % self.loc) - stmts.execute(makefile) - -class OriginFunction(Function): - name = 'origin' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - vname = self._arguments[0].resolvestr(makefile, variables, setting) - - flavor, source, value = variables.get(vname) - if source is None: - r = 'undefined' - elif source == data.Variables.SOURCE_OVERRIDE: - r = 'override' - - elif source == data.Variables.SOURCE_MAKEFILE: - r = 'file' - elif source == data.Variables.SOURCE_ENVIRONMENT: - r = 'environment' - elif source == data.Variables.SOURCE_COMMANDLINE: - r = 'command line' - elif source == data.Variables.SOURCE_AUTOMATIC: - r = 'automatic' - elif source == data.Variables.SOURCE_IMPLICIT: - r = 'default' - - fd.write(r) - -class FlavorFunction(Function): - name = 'flavor' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - varname = self._arguments[0].resolvestr(makefile, variables, setting) - - flavor, source, value = variables.get(varname) - if flavor is None: - r = 'undefined' - elif flavor == data.Variables.FLAVOR_RECURSIVE: - r = 'recursive' - elif flavor == data.Variables.FLAVOR_SIMPLE: - r = 'simple' - fd.write(r) - -class ShellFunction(Function): - name = 'shell' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - #TODO: call this once up-front somewhere and save the result? - shell, msys = util.checkmsyscompat() - cline = self._arguments[0].resolvestr(makefile, variables, setting) - - log.debug("%s: running shell command '%s'" % (self.loc, cline)) - if msys: - cline = [shell, "-c", cline] - p = subprocess.Popen(cline, shell=not msys, stdout=subprocess.PIPE, cwd=makefile.workdir) - stdout, stderr = p.communicate() - - stdout = stdout.replace('\r\n', '\n') - if stdout.endswith('\n'): - stdout = stdout[:-1] - stdout = stdout.replace('\n', ' ') - - fd.write(stdout) - -class ErrorFunction(Function): - name = 'error' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - v = self._arguments[0].resolvestr(makefile, variables, setting) - raise data.DataError(v, self.loc) - -class WarningFunction(Function): - name = 'warning' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - v = self._arguments[0].resolvestr(makefile, variables, setting) - log.warning(v) - -class InfoFunction(Function): - name = 'info' - minargs = 1 - maxargs = 1 - - __slots__ = Function.__slots__ - - def resolve(self, makefile, variables, fd, setting): - v = self._arguments[0].resolvestr(makefile, variables, setting) - print v - -functionmap = { - 'subst': SubstFunction, - 'patsubst': PatSubstFunction, - 'strip': StripFunction, - 'findstring': FindstringFunction, - 'filter': FilterFunction, - 'filter-out': FilteroutFunction, - 'sort': SortFunction, - 'word': WordFunction, - 'wordlist': WordlistFunction, - 'words': WordsFunction, - 'firstword': FirstWordFunction, - 'lastword': LastWordFunction, - 'dir': DirFunction, - 'notdir': NotDirFunction, - 'suffix': SuffixFunction, - 'basename': BasenameFunction, - 'addsuffix': AddSuffixFunction, - 'addprefix': AddPrefixFunction, - 'join': JoinFunction, - 'wildcard': WildcardFunction, - 'realpath': RealpathFunction, - 'abspath': AbspathFunction, - 'if': IfFunction, - 'or': OrFunction, - 'and': AndFunction, - 'foreach': ForEachFunction, - 'call': CallFunction, - 'value': ValueFunction, - 'eval': EvalFunction, - 'origin': OriginFunction, - 'flavor': FlavorFunction, - 'shell': ShellFunction, - 'error': ErrorFunction, - 'warning': WarningFunction, - 'info': InfoFunction, -} - -import data diff --git a/tests/python/mlstring.py b/tests/python/mlstring.py deleted file mode 100755 index 71b2b9e..0000000 --- a/tests/python/mlstring.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python - -class ClassName(object): - """docstring for ClassName""" - def __init__(self, arg): - super(ClassName, self).__init__() - self.arg = arg - def function1(self): - foo = """ -foostring - """ - def function2(self): - pass diff --git a/tests/python/models.py b/tests/python/models.py deleted file mode 100644 index c33c9b6..0000000 --- a/tests/python/models.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.db import models -from django.contrib.auth.models import User -from django.contrib import admin -from string import join -from settings import MEDIA_ROOT - -class Forum(models.Model): - title = models.CharField(max_length=60) - def __unicode__(self): - return self.title - -class Thread(models.Model): - title = models.CharField(max_length=60) - created = models.DateTimeField(auto_now_add=True) - creator = models.ForeignKey(User, blank=True, null=True) - forum = models.ForeignKey(Forum) - - def __unicode__(self): - return unicode(self.creator) + " - " + self.title - -class Post(models.Model): - title = models.CharField(max_length=60) - created = models.DateTimeField(auto_now_add=True) - creator = models.ForeignKey(User, blank=True, null=True) - thread = models.ForeignKey(Thread) - body = models.TextField(max_length=10000) - - def __unicode__(self): - return u"%s - %s - %s" % (self.creator, self.thread, self.title) - - def short(self): - return u"%s - %s\n%s" % (self.creator, self.title, self.created.strftime("%b %d, %I:%M %p")) - short.allow_tags = True - -### Admin - -class ForumAdmin(admin.ModelAdmin): - pass - -class ThreadAdmin(admin.ModelAdmin): - list_display = ["title", "forum", "creator", "created"] - list_filter = ["forum", "creator"] - -class PostAdmin(admin.ModelAdmin): - search_fields = ["title", "creator"] - list_display = ["title", "thread", "creator", "created"] - -admin.site.register(Forum, ForumAdmin) -admin.site.register(Thread, ThreadAdmin) -admin.site.register(Post, PostAdmin) diff --git a/tests/python/multiline.py b/tests/python/multiline.py deleted file mode 100644 index f27bd9f..0000000 --- a/tests/python/multiline.py +++ /dev/null @@ -1,3 +0,0 @@ -def foo(a, b, \ - c, d): - pass diff --git a/tests/python/tabindent.py b/tests/python/tabindent.py deleted file mode 100644 index 6f3f9d9..0000000 --- a/tests/python/tabindent.py +++ /dev/null @@ -1,3 +0,0 @@ -def find_heading(self, position=0, direction=Direction.FORWARD, \ - heading=Heading, connect_with_document=True): - pass diff --git a/tests/python/test.py b/tests/python/test.py deleted file mode 100644 index 41f6fa2..0000000 --- a/tests/python/test.py +++ /dev/null @@ -1,23 +0,0 @@ -import foo -import bar - -defaultdict(lambda: 0) -classofhello = 0 - -class test(foo, bar): - class Inner: -# pass - def foo(self): - print "Inner" -# pass - -def test(): - class Inner2: - def bar(self): - print "Inner2" -# t = Inner2() -# pass - -#print "Test" -#r = test.Inner2() -#test() diff --git a/tests/python/test2.py b/tests/python/test2.py deleted file mode 100644 index 83f9c7c..0000000 --- a/tests/python/test2.py +++ /dev/null @@ -1,3 +0,0 @@ -from test import * - -r = test.Inner() diff --git a/tests/python/vis.py b/tests/python/vis.py deleted file mode 100755 index 4e8f82a..0000000 --- a/tests/python/vis.py +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env python2.6 - -from __future__ import division - -import gtk - -import numpy as np -import matplotlib -matplotlib.use('GTKAgg') -import matplotlib.cm as cm -import matplotlib.pyplot as plt -from matplotlib.widgets import Button, RadioButtons -from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas - -win = gtk.Window() -win.connect("destroy", gtk.main_quit) -win.set_default_size(600,600) -win.set_title("Resource Visualisation") - -fig = plt.figure(figsize=(8,8)) -ax = fig.add_axes([0.1, 0.2, 0.8, 0.75], projection='polar') - -rax = fig.add_axes([0.7, 0.05, 0.2, 0.05]) -rax.grid(False) -rax.set_xticks([]) -rax.set_yticks([]) - -moax = fig.add_axes([0.1, 0.02, 0.25, 0.1]) -moax.grid(False) -moax.set_xticks([]) -moax.set_yticks([]) - -logax = fig.add_axes([0.4, 0.02, 0.25, 0.1]) -logax.grid(False) -logax.set_xticks([]) -logax.set_yticks([]) - -canvas = FigureCanvas(fig) -win.add(canvas) - -class ResVis(object): - def __init__(self): - self.dirdata = self.load_data('dirs.dat') - self.userdata = self.load_data('users.dat') - - self.mode = 'dir' - self.log = True - self.draw_dir('root') - - def load_data(self, filename): - data = {} - for line in open(filename, 'r').readlines(): - entry = line.split(None, 3) - - if len(entry) > 3: # current entry has subentries - entry[3] = entry[3].split() - entry[3].sort(key=str.lower) - else: - entry.append([]) - - data[entry[0]] = [entry[0], float(entry[1]), float(entry[2]), - entry[3]] - return data - - def load_dir(self, dirname): - curdirdata = [] - for d in self.dirdata[dirname][3]: - curdirdata.append(self.dirdata[d]) - return curdirdata - - def load_user(self, username): - curdata = [] - for u in self.userdata[username][3]: - curdata.append(self.userdata[u]) - return curdata - - def draw_dir(self, dirname='root'): - self.curdir = dirname - self.reset_ax() - ax.set_title('Directory size') - ax.set_xlabel(dirname, weight='bold') - - curdir = self.load_dir(dirname) - self.draw_data(curdir) - - def draw_user(self, username='root'): - self.curuser = username - self.reset_ax() - ax.set_title('Resource usage') - ax.set_xlabel(username, weight='bold') - - user = self.load_user(username) - self.draw_data(user) - - def reset_ax(self): - ax.cla() - - #ax.axis('off') - #ax.set_axis_off() - #ax.set_yscale('log') - ax.grid(False) - ax.set_xticks([]) # edge - ax.set_yticks([]) # radius - #ax.set_xlabel('Size') - #ax.set_ylabel('Number of files') - - def draw_data(self, data): - totalsize = sum(zip(*data)[1]) # get sum of subentry sizes - unit = 1.5 * np.pi / totalsize - - angle = 0.5 * np.pi - if self.log: - maxy = max(map(np.log2, zip(*data)[2])) - else: - maxy = max(zip(*data)[2]) - for d in data: - relangle = unit * d[1] - - if len(d[3]) > 0 or self.mode == 'user': - # scale colours since the legend occupies a quarter - scaledangle = (angle - 0.5*np.pi) * (2 / 1.5) - colour = cm.hsv(scaledangle/(2*np.pi)) - else: -# colour = cm.Greys(scaledangle/(2*np.pi)) - colour = "#999999" - - if self.log: - # take logarithm to accomodate for big differences - y = np.log2(d[2]) - else: - y = d[2] - - bar = ax.bar(angle, y, width=relangle, bottom=maxy*0.2, - color=colour, label=d[0], - picker=True) - angle += relangle - - if self.mode == 'dir': - desc = '{0}\n{1}G'.format(d[0], d[1]) - elif self.mode == 'user': - desc = '{0}\n{1}%'.format(d[0], d[1]) - self.draw_desc(bar[0], d, desc) - - self.draw_legend(maxy) - - fig.canvas.draw() - - def draw_desc(self, bar, data, text): - # show description in center of bar - bbox = bar.get_bbox() - x = bbox.xmin + (bbox.xmax - bbox.xmin) / 2 - y = bbox.ymin + (bbox.ymax - bbox.ymin) / 2 - ax.text(x, y, text, horizontalalignment='center', - verticalalignment='center', weight='bold') - - def draw_legend(self, maxy): - ax.annotate('', xy=(0, maxy*0.3), xytext=(0.5*np.pi, maxy*0.3), - arrowprops=dict( - arrowstyle='<->', - connectionstyle='angle3,angleA=0,angleB=-90', - linewidth=3)) - ax.annotate('', xy=(0.04*np.pi, maxy*0.35), xytext=(0.04*np.pi, maxy), - arrowprops=dict( - arrowstyle='<->', - connectionstyle='arc3', - linewidth=3)) - - if self.mode == 'dir': - xtext = 'Size' - ytext = 'Number of files' - elif self.mode == 'user': - xtext = 'Processor usage' - ytext = 'Memory usage' - - ax.text(0.3*np.pi, maxy*0.35, xtext, weight='normal') - ax.text(0.06*np.pi, maxy*0.4, ytext, weight='normal', rotation=8) - - if self.mode == 'dir': - ax.text(0.3*np.pi, maxy*0.6, 'Grey dirs do not\nhave subdirs.') - - def on_pick(self, event): - clicked = event.artist.get_label() - - if self.mode == 'dir' and len(self.dirdata[clicked][3]) > 0: - self.draw_dir(clicked) - elif self.mode == 'user' and len(self.userdata[clicked][3]) > 0: - self.draw_user(clicked) - - def on_rootclick(self, event): - if self.mode == 'dir': - self.draw_dir('root') - elif self.mode == 'user': - self.draw_user('root') - - def on_modeclick(self, mode): - if mode == 'Directory size': - self.mode = 'dir' - self.draw_dir('root') - elif mode == 'Resource usage': - self.mode = 'user' - self.draw_user('root') - - def on_logclick(self, mode): - self.log = mode == 'Logarithmic' - if self.mode == 'dir': - self.draw_dir(self.curdir) - if self.mode == 'user': - self.draw_user(self.curuser) - -vis = ResVis() - -root = Button(rax, 'Home') -root.on_clicked(vis.on_rootclick) -mode = RadioButtons(moax, ('Directory size', 'Resource usage')) -mode.on_clicked(vis.on_modeclick) -log = RadioButtons(logax, ('Logarithmic', 'Linear')) -log.on_clicked(vis.on_logclick) - -fig.canvas.mpl_connect('pick_event', vis.on_pick) - -#plt.show() - -win.show_all() -gtk.main() - diff --git a/tests/ruby/cache_key.rb b/tests/ruby/cache_key.rb deleted file mode 100644 index 3bd3c21..0000000 --- a/tests/ruby/cache_key.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Class2 - def self.fun1() - value=if foo - bar - end - end - - def self.fun2() - end -end diff --git a/tests/ruby/singleton.rb b/tests/ruby/singleton.rb deleted file mode 100644 index bb454ee..0000000 --- a/tests/ruby/singleton.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Foo - def a - end - - def b - end - - def Foo.c(item) - end - - def self.d - end -end diff --git a/tests/ruby/testcase.rb b/tests/ruby/testcase.rb deleted file mode 100644 index 5a1c2b2..0000000 --- a/tests/ruby/testcase.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Class1 - def fun1() - foo = if bar - baz - end - end - - def fun2() - end -end diff --git a/tests/sql/backup.sql b/tests/sql/backup.sql deleted file mode 100644 index ff8e371..0000000 --- a/tests/sql/backup.sql +++ /dev/null @@ -1,27 +0,0 @@ -DECLARE @name VARCHAR(50) -- database name -DECLARE @path VARCHAR(256) -- path for backup files -DECLARE @fileName VARCHAR(256) -- filename for backup -DECLARE @fileDate VARCHAR(20) -- used for file name - -SET @path = 'C:\Backup\' - -SELECT @fileDate = CONVERT(VARCHAR(20),GETDATE(),112) - -DECLARE db_cursor CURSOR FOR -SELECT name -FROM master.dbo.sysdatabases -WHERE name NOT IN ('master','model','msdb','tempdb') - -OPEN db_cursor -FETCH NEXT FROM db_cursor INTO @name - -WHILE @@FETCH_STATUS = 0 -BEGIN - SET @fileName = @path + @name + '_' + @fileDate + '.BAK' - BACKUP DATABASE @name TO DISK = @fileName - - FETCH NEXT FROM db_cursor INTO @name -END - -CLOSE db_cursor -DEALLOCATE db_cursor diff --git a/tests/sql/ex.sql b/tests/sql/ex.sql deleted file mode 100644 index 26ff4a2..0000000 --- a/tests/sql/ex.sql +++ /dev/null @@ -1,17 +0,0 @@ -select user_id, count(*) as how_many -from bboard -where not exists (select 1 from - bboard_authorized_maintainers bam - where bam.user_id = bboard.user_id) -and posting_time + 60 > sysdate -group by user_id -order by how_many desc; - -select user_id, count(*) as how_many -from bboard -where posting_time + 60 > sysdate -group by user_id -having not exists (select 1 from - bboard_authorized_maintainers bam - where bam.user_id = bboard.user_id) -order by how_many desc; diff --git a/tests/tex/nonascii.c b/tests/tex/nonascii.c deleted file mode 100644 index aed03e1..0000000 --- a/tests/tex/nonascii.c +++ /dev/null @@ -1,4 +0,0 @@ -int Plodenie = 0; -char *KlíÄenie = "blah"; -short Zrenie; -long Žatva; diff --git a/tests/tex/nonascii.tex b/tests/tex/nonascii.tex deleted file mode 100644 index d0cabe2..0000000 --- a/tests/tex/nonascii.tex +++ /dev/null @@ -1,24 +0,0 @@ -\documentclass{book} - -\usepackage[slovak]{babel} -\usepackage[utf8]{inputenc} - -\begin{document} - -\chapter{Plodenie} -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, - -\chapter{KlíÄenie} -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, - -\chapter{Zrenie} -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, - -\chapter{Žatva} -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, - -\end{document} diff --git a/tests/tex/outputtest.txt b/tests/tex/outputtest.txt deleted file mode 100644 index 5c776f6..0000000 --- a/tests/tex/outputtest.txt +++ /dev/null @@ -1,6 +0,0 @@ - -ctags-exuberant: Warning: Unsupported parameter 'l' for --tex-kinds option -Kl enie /tmp/vPIhyz9/3.tex /^\\chapter{KlíÄenie}$/;" c line:12 -Plodenie /tmp/vPIhyz9/3.tex /^\\chapter{Plodenie}$/;" c line:8 -Zrenie /tmp/vPIhyz9/3.tex /^\\chapter{Zrenie}$/;" c line:16 -atva /tmp/vPIhyz9/3.tex /^\\chapter{Žatva}$/;" c line:20 diff --git a/tests/tex/test.txt b/tests/tex/test.txt deleted file mode 100644 index f448889..0000000 --- a/tests/tex/test.txt +++ /dev/null @@ -1,4 +0,0 @@ -CHAP Plodenie nonascii.tex /^\\chapter{Plodenie}$/;" s line:8 -CHAP KlíÄenie nonascii.tex /^\\chapter{KlíÄenie}$/;" s line:12 -CHAP Zrenie nonascii.tex /^\\chapter{Zrenie}$/;" s line:16 -CHAP Žatva nonascii.tex /^\\chapter{Žatva}$/;" s line:20 diff --git a/tests/vala/closures.vala b/tests/vala/closures.vala deleted file mode 100644 index d2be8e0..0000000 --- a/tests/vala/closures.vala +++ /dev/null @@ -1,23 +0,0 @@ -delegate int Func (); - -[CCode (has_target = false)] -delegate void NoTargetFunc (); - -int A (int k, Func x1, Func x2, Func x3, Func x4, Func x5) { - Func B = null; - B = () => { - k = k - 1; - return A (k, B, x1, x2, x3, x4); - }; - return k <= 0 ? x4 () + x5 () : B (); -} - -void B ([CCode (array_length = false, array_null_terminated = true)] int[] array, NoTargetFunc func) { - Func C = () => { array = null; func (); return 0; }; -} - -void main () { - int result = A (10, () => 1, () => -1, () => -1, () => 1, () => 0); - assert (result == -67); -} - diff --git a/tests/vala/generics.vala b/tests/vala/generics.vala deleted file mode 100644 index b465523..0000000 --- a/tests/vala/generics.vala +++ /dev/null @@ -1,22 +0,0 @@ -interface Foo : Object { - public void foo (owned T bar) { - bar = null; - } -} - -class Baz : Object, Foo { -} - -void foo (owned T bar) { - bar = null; -} - -void main () { - var bar = new Object (); - foo (bar); - assert (bar.ref_count == 1); - - var baz = new Baz (); - baz.foo (bar); - assert (baz.ref_count == 1); -} diff --git a/tests/vala/gnomine.vala b/tests/vala/gnomine.vala deleted file mode 100644 index 1d4b6f8..0000000 --- a/tests/vala/gnomine.vala +++ /dev/null @@ -1,874 +0,0 @@ -public class GnoMine : Gtk.Application -{ - /* Settings keys */ - private Settings settings; - private const string KEY_XSIZE = "xsize"; - private const int XSIZE_MIN = 4; - private const int XSIZE_MAX = 100; - private const string KEY_YSIZE = "ysize"; - private const int YSIZE_MIN = 4; - private const int YSIZE_MAX = 100; - private const string KEY_NMINES = "nmines"; - private const string KEY_MODE = "mode"; - private const string KEY_USE_QUESTION_MARKS = "use-question-marks"; - private const string KEY_USE_OVERMINE_WARNING = "use-overmine-warning"; - private const string KEY_USE_AUTOFLAG = "use-autoflag"; - - /* Faces for new game button */ - private Gtk.ToolButton face_button; - private Gtk.Image win_face_image; - private Gtk.Image sad_face_image; - private Gtk.Image smile_face_image; - private Gtk.Image cool_face_image; - private Gtk.Image worried_face_image; - - /* Main window */ - private Gtk.Window window; - - /* Minefield being played */ - private Minefield minefield; - - /* Minefield widget */ - private MinefieldView minefield_view; - - private Gtk.Dialog? pref_dialog = null; - private Gtk.Label flag_label; - private Gtk.SpinButton n_mines_spin; - private GnomeGamesSupport.Clock clock; - private SimpleAction pause; - private SimpleAction hint; - private Gtk.Action hint_action; - private GnomeGamesSupport.FullscreenAction fullscreen_action; - private GnomeGamesSupport.PauseAction pause_action; - private Gtk.AspectFrame new_game_screen; - private Gtk.AspectFrame custom_game_screen; - private bool is_new_game_screen; - - private const GLib.ActionEntry[] action_entries = - { - { "new-game", new_game_cb }, - { "hint", hint_cb }, - { "pause", toggle_pause_cb }, - { "fullscreen", fullscreen_cb }, - { "scores", scores_cb }, - { "preferences", preferences_cb }, - { "quit", quit_cb }, - { "help", help_cb }, - { "about", about_cb } - }; - - private const GnomeGamesSupport.ScoresCategory scorecats[] = - { - {"Small", NC_("board size", "Small") }, - {"Medium", NC_("board size", "Medium") }, - {"Large", NC_("board size", "Large") }, - {"Custom", NC_("board size", "Custom") } - }; - - private GnomeGamesSupport.Scores highscores; - - public GnoMine () - { - Object (application_id: "org.gnome.gnomine", flags: ApplicationFlags.FLAGS_NONE); - } - - protected override void startup () - { - base.startup (); - - Environment.set_application_name (_("Mines")); - - settings = new Settings ("org.gnome.gnomine"); - - highscores = new GnomeGamesSupport.Scores ("gnomine", scorecats, "board size", null, 0 /* default category */, GnomeGamesSupport.ScoreStyle.TIME_ASCENDING); - - Gtk.Window.set_default_icon_name ("gnome-mines"); - - add_action_entries (action_entries, this); - hint = lookup_action ("hint") as SimpleAction; - hint.set_enabled (false); - pause = lookup_action ("pause") as SimpleAction; - pause.set_enabled (false); - - var builder = new Gtk.Builder (); - try - { - builder.add_from_file (Path.build_filename (DATA_DIRECTORY, "gnomine.ui")); - } - catch (Error e) - { - error ("Unable to build menus: %s", e.message); - } - set_app_menu (builder.get_object ("gnomine-menu") as MenuModel); - - window = new Gtk.ApplicationWindow (this); - window.title = _("Mines"); - - GnomeGamesSupport.settings_bind_window_state ("/org/gnome/gnomine/", window); - add_window (window); - - GnomeGamesSupport.stock_init (); - - var main_vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); - window.add (main_vbox); - main_vbox.show (); - - var action_group = new Gtk.ActionGroup ("group"); - action_group.set_translation_domain (GETTEXT_PACKAGE); - action_group.add_actions (actions, this); - - var ui_manager = new Gtk.UIManager (); - ui_manager.insert_action_group (action_group, 0); - try - { - ui_manager.add_ui_from_string (ui_description, -1); - } - catch (Error e) - { - } - hint_action = action_group.get_action ("Hint"); - hint_action.is_important = true; - hint_action.set_sensitive (false); - - action_group.get_action ("NewGame").is_important = true; - - fullscreen_action = new GnomeGamesSupport.FullscreenAction ("Fullscreen", window); - action_group.add_action_with_accel (fullscreen_action, null); - - pause_action = new GnomeGamesSupport.PauseAction ("PauseGame"); - pause_action.set_sensitive (false); - pause_action.state_changed.connect (pause_cb); - action_group.add_action_with_accel (pause_action, null); - - window.add_accel_group (ui_manager.get_accel_group ()); - - var status_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10); - status_box.show (); - - /* show the numbers of total and remaining mines */ - flag_label = new Gtk.Label (""); - flag_label.show (); - - status_box.pack_start (flag_label, false, false, 0); - - /* game clock */ - var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); - box.show (); - status_box.pack_start (box, false, false, 0); - - var label = new Gtk.Label (_("Time: ")); - box.pack_start (label, false, false, 0); - label.show (); - - clock = new GnomeGamesSupport.Clock (); - clock.show (); - box.pack_start (clock, false, false, 0); - - var status_alignment = new Gtk.Alignment (1.0f, 0.5f, 0.0f, 0.0f); - status_alignment.add (status_box); - status_alignment.show(); - - var status_item = new Gtk.ToolItem (); - status_item.set_expand (true); - status_item.add (status_alignment); - status_item.show(); - - /* create fancy faces */ - win_face_image = load_face_image ("face-win.svg"); - sad_face_image = load_face_image ("face-sad.svg"); - smile_face_image = load_face_image ("face-smile.svg"); - cool_face_image = load_face_image ("face-cool.svg"); - worried_face_image = load_face_image ("face-worried.svg"); - - /* initialize toolbar */ - var toolbar = (Gtk.Toolbar) ui_manager.get_widget ("/Toolbar"); - toolbar.show_arrow = false; - face_button = (Gtk.ToolButton) ui_manager.get_widget ("/Toolbar/NewGame"); - toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_PRIMARY_TOOLBAR); - /* replace the dull new-game icon with fancy faces */ - set_face_image (smile_face_image); - - toolbar.insert (status_item, -1); - main_vbox.pack_start (toolbar, false, false, 0); - - var view_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); - view_box.border_width = 3; - view_box.show (); - main_vbox.pack_start (view_box, true, true, 0); - - minefield_view = new MinefieldView (); - minefield_view.set_use_question_marks (settings.get_boolean (KEY_USE_QUESTION_MARKS)); - minefield_view.set_use_overmine_warning (settings.get_boolean (KEY_USE_OVERMINE_WARNING)); - minefield_view.set_use_autoflag (settings.get_boolean (KEY_USE_AUTOFLAG)); - minefield_view.button_press_event.connect (view_button_press_event); - minefield_view.look.connect (look_cb); - minefield_view.unlook.connect (unlook_cb); - view_box.pack_start (minefield_view, true, true, 0); - - /* New game screen */ - new_game_screen = new Gtk.AspectFrame (_("Field Size"), 0.5f, 0.5f, 1.0f, false); - new_game_screen.set_shadow_type (Gtk.ShadowType.NONE); - new_game_screen.set_size_request(200, 200); - - var new_game_table = new Gtk.Table (2, 2, true); - new_game_screen.add (new_game_table); - - var button_small = new Gtk.Button (); - new_game_table.attach_defaults (button_small, 0, 1, 0, 1); - button_small.clicked.connect (small_size_clicked_cb); - - label = new Gtk.Label (null); - label.set_markup (make_minefield_description ("#0000ff", 8, 8, 10)); - label.set_justify (Gtk.Justification.CENTER); - button_small.add (label); - - var button_medium = new Gtk.Button (); - new_game_table.attach_defaults (button_medium, 1, 2, 0, 1); - button_medium.clicked.connect (medium_size_clicked_cb); - - label = new Gtk.Label (null); - label.set_markup (make_minefield_description ("#00a000", 16, 16, 40)); - label.set_justify (Gtk.Justification.CENTER); - button_medium.add (label); - - var button_large = new Gtk.Button (); - new_game_table.attach_defaults (button_large, 0, 1, 1, 2); - button_large.clicked.connect (large_size_clicked_cb); - - label = new Gtk.Label (null); - label.set_markup (make_minefield_description ("#ff0000", 30, 16, 99)); - label.set_justify (Gtk.Justification.CENTER); - button_large.add (label); - - var button_custom = new Gtk.Button (); - new_game_table.attach_defaults (button_custom, 1, 2, 1, 2); - button_custom.clicked.connect (show_custom_game_screen); - - label = new Gtk.Label (null); - label.set_markup_with_mnemonic ("?\n" + dpgettext2 (null, "board size", "_Custom") + ""); - label.set_justify (Gtk.Justification.CENTER); - button_custom.add (label); - - new_game_screen.show_all (); - view_box.pack_start (new_game_screen, true, true, 0); - - /* Custom game screen */ - custom_game_screen = new Gtk.AspectFrame ("", 0.5f, 0.5f, 0.0f, true); - custom_game_screen.set_shadow_type (Gtk.ShadowType.NONE); - - var custom_game_frame = new GnomeGamesSupport.Frame (_("Custom Size")); - custom_game_screen.add (custom_game_frame); - - var custom_field_grid = new Gtk.Grid (); - custom_field_grid.set_row_spacing (6); - custom_field_grid.set_column_spacing (12); - custom_game_frame.add (custom_field_grid); - - label = new Gtk.Label.with_mnemonic (_("H_orizontal:")); - label.set_alignment (0, 0.5f); - custom_field_grid.attach (label, 0, 0, 1, 1); - - var field_width_entry = new Gtk.SpinButton.with_range (XSIZE_MIN, XSIZE_MAX, 1); - field_width_entry.value_changed.connect (xsize_spin_cb); - field_width_entry.set_value (settings.get_int (KEY_XSIZE)); - custom_field_grid.attach (field_width_entry, 1, 0, 1, 1); - label.set_mnemonic_widget (field_width_entry); - - label = new Gtk.Label.with_mnemonic (_("_Vertical:")); - label.set_alignment (0, 0.5f); - custom_field_grid.attach (label, 0, 1, 1, 1); - - var field_height_entry = new Gtk.SpinButton.with_range (YSIZE_MIN, YSIZE_MAX, 1); - field_height_entry.value_changed.connect (ysize_spin_cb); - field_height_entry.set_value (settings.get_int (KEY_YSIZE)); - custom_field_grid.attach (field_height_entry, 1, 1, 1, 1); - label.set_mnemonic_widget (field_height_entry); - - label = new Gtk.Label.with_mnemonic (_("_Number of mines:")); - label.set_alignment (0, 0.5f); - custom_field_grid.attach (label, 0, 2, 1, 1); - - n_mines_spin = new Gtk.SpinButton.with_range (1, XSIZE_MAX * YSIZE_MAX, 1); - n_mines_spin.value_changed.connect (n_mines_spin_cb); - n_mines_spin.set_value (settings.get_int (KEY_NMINES)); - custom_field_grid.attach (n_mines_spin, 1, 2, 1, 1); - - set_n_mines_limit (); - label.set_mnemonic_widget (n_mines_spin); - - var hbox = new Gtk.HBox (false, 5); - custom_field_grid.attach (hbox, 0, 3, 2, 1); - - var button_back = new Gtk.Button.from_stock (Gtk.Stock.CANCEL); - button_back.clicked.connect (show_new_game_screen); - hbox.pack_start (button_back, true, true); - - button_custom = new Gtk.Button.with_mnemonic ("_Play Game"); - button_custom.set_image (new Gtk.Image.from_stock (Gtk.Stock.GO_FORWARD, Gtk.IconSize.BUTTON)); - button_custom.clicked.connect (custom_size_clicked_cb); - hbox.pack_start (button_custom, true, true); - - custom_game_screen.show_all (); - custom_game_screen.hide (); - view_box.pack_start (custom_game_screen, true, false); - } - - private string make_minefield_description (string color, int width, int height, int n_mines) - { - var size_label = "%d × %d".printf (width, height); - var mines_label = ngettext ("%d mine", "%d mines", n_mines).printf (n_mines); - return "%s\n%s".printf (color, size_label, mines_label); - } - - private const Gtk.ActionEntry actions[] = - { - {"NewGame", GnomeGamesSupport.STOCK_NEW_GAME, null, null, N_("Start a new game"), new_game_cb}, - {"Hint", GnomeGamesSupport.STOCK_HINT, null, null, N_("Show a hint"), hint_cb} - }; - - private const string ui_description = - "" + - " " + - " " + - " " + - " " + - " " + - " " + - ""; - - public void start () - { - window.show (); - show_new_game_screen (); - set_face_image (smile_face_image); - } - - public override void activate () - { - window.show (); - } - - private Gtk.Image load_face_image (string name) - { - var image = new Gtk.Image (); - var filename = Path.build_filename (DATA_DIRECTORY, name); - - if (filename != null) - image.set_from_file (filename); - - image.show (); - - return image; - } - - private void set_face_image (Gtk.Image face_image) - { - face_button.set_icon_widget (face_image); - } - - private bool view_button_press_event (Gtk.Widget widget, Gdk.EventButton event) - { - /* Cancel pause on click */ - if (pause_action.get_is_paused ()) - { - pause_action.set_is_paused (false); - return true; - } - - return false; - } - - private void quit_cb () - { - window.destroy (); - } - - private void update_flag_label () - { - flag_label.set_text ("Flags: %u/%u".printf (minefield.n_flags, minefield.n_mines)); - } - - /* Show the high scores dialog - creating it if necessary. If pos is - * greater than 0 the appropriate score is highlighted. If the score isn't - * a high score and this isn't a direct request to see the scores, we - * only show a simple dialog. */ - private int show_scores (int pos, bool endofgame) - { - if (endofgame && (pos <= 0)) - { - var dialog = new Gtk.MessageDialog.with_markup (window, - Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.INFO, - Gtk.ButtonsType.NONE, - "%s\n%s", - _("The Mines Have Been Cleared!"), - _("Great work, but unfortunately your score did not make the top ten.")); - dialog.add_buttons (Gtk.Stock.QUIT, Gtk.ResponseType.REJECT, - _("_New Game"), Gtk.ResponseType.ACCEPT, null); - dialog.set_default_response (Gtk.ResponseType.ACCEPT); - dialog.set_title (""); - var result = dialog.run (); - dialog.destroy (); - return result; - } - else - { - var dialog = new GnomeGamesSupport.ScoresDialog (window, highscores, _("Mines Scores")); - dialog.set_category_description (_("Size:")); - - if (pos > 0) - { - dialog.set_hilight (pos); - var message = "%s\n\n%s".printf (_("Congratulations!"), pos == 1 ? _("Your score is the best!") : _("Your score has made the top ten.")); - dialog.set_message (message); - } - else - dialog.set_message (null); - - if (endofgame) - dialog.set_buttons (GnomeGamesSupport.ScoresButtons.QUIT_BUTTON | GnomeGamesSupport.ScoresButtons.NEW_GAME_BUTTON); - else - dialog.set_buttons (0); - var result = dialog.run (); - dialog.destroy (); - return result; - } - } - - private void fullscreen_cb () - { - fullscreen_action.set_is_fullscreen (!fullscreen_action.get_is_fullscreen ()); - } - - private void scores_cb () - { - show_scores (0, false); - } - - private void show_custom_game_screen () - { - is_new_game_screen = false; - custom_game_screen.show (); - minefield_view.hide (); - new_game_screen.hide (); - } - - private void show_new_game_screen () - { - if (is_new_game_screen) - return; - - if (minefield != null && minefield.n_cleared > 0 && !minefield.exploded && !minefield.is_complete) - { - var dialog = new Gtk.MessageDialog (window, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "%s", _("Cancel current game?")); - dialog.add_buttons (_("Start New Game"), Gtk.ResponseType.ACCEPT, - _("Keep Current Game"), Gtk.ResponseType.REJECT, - null); - var result = dialog.run (); - dialog.destroy (); - if (result == Gtk.ResponseType.REJECT) - return; - } - - minefield = null; - - is_new_game_screen = true; - custom_game_screen.hide (); - minefield_view.hide (); - new_game_screen.show (); - flag_label.set_text(""); - clock.stop (); - clock.reset (); - set_face_image (smile_face_image); - - hint.set_enabled (false); - hint_action.set_sensitive (false); - pause.set_enabled (false); - pause_action.set_sensitive (false); - - minefield_view.paused = false; - pause_action.set_is_paused (false); - } - - private void new_game () - { - is_new_game_screen = false; - custom_game_screen.hide (); - minefield_view.show (); - new_game_screen.hide (); - - clock.reset (); - set_face_image (smile_face_image); - - int x, y, n; - var score_key = ""; - switch (settings.get_int (KEY_MODE)) - { - case 0: - x = 8; - y = 8; - n = 10; - score_key = "Small"; - break; - case 1: - x = 16; - y = 16; - n = 40; - score_key = "Medium"; - break; - case 2: - x = 30; - y = 16; - n = 99; - score_key = "Large"; - break; - default: - case 3: - x = settings.get_int (KEY_XSIZE).clamp (XSIZE_MIN, XSIZE_MAX); - y = settings.get_int (KEY_YSIZE).clamp (YSIZE_MIN, YSIZE_MAX); - n = settings.get_int (KEY_NMINES).clamp (1, x * y - 10); - score_key = "Custom"; - break; - } - - highscores.set_category (score_key); - if (minefield != null) - SignalHandler.disconnect_by_func (minefield, null, this); - minefield = new Minefield (x, y, n); - minefield.marks_changed.connect (marks_changed_cb); - minefield.explode.connect (explode_cb); - minefield.cleared.connect (cleared_cb); - - minefield_view.minefield = minefield; - - update_flag_label (); - - hint.set_enabled (true); - hint_action.set_sensitive (true); - pause.set_enabled (true); - pause_action.set_sensitive (true); - - minefield_view.paused = false; - pause_action.set_is_paused (false); - } - - private void hint_cb () - { - uint x, y; - minefield.hint (out x, out y); - - /* There is a ten second penalty for accepting a hint. */ - minefield.clear_mine (x, y); - clock.add_seconds (10); - } - - private void new_game_cb () - { - if (is_new_game_screen) - new_game (); - else - show_new_game_screen (); - } - - private void toggle_pause_cb () - { - pause_action.set_is_paused (!pause_action.get_is_paused ()); - } - - private void pause_cb () - { - if (pause_action.get_is_paused ()) - { - minefield_view.paused = true; - hint.set_enabled (false); - hint_action.set_sensitive (false); - clock.stop (); - } - else - { - minefield_view.paused = false; - hint.set_enabled (true); - hint_action.set_sensitive (true); - clock.start (); - } - } - - private void marks_changed_cb (Minefield minefield) - { - update_flag_label (); - clock.start (); - } - - private void explode_cb (Minefield minefield) - { - set_face_image (sad_face_image); - hint.set_enabled (false); - hint_action.set_sensitive (false); - pause.set_enabled (false); - pause_action.set_sensitive (false); - clock.stop (); - } - - private void cleared_cb (Minefield minefield) - { - clock.stop (); - - set_face_image (win_face_image); - - var seconds = clock.get_seconds (); - var pos = highscores.add_time_score ((float) (seconds / 60) + (float) (seconds % 60) / 100); - - if (show_scores (pos, true) == Gtk.ResponseType.REJECT) - window.destroy (); - else - show_new_game_screen (); - } - - private void look_cb (MinefieldView minefield_view) - { - set_face_image (worried_face_image); - clock.start (); - } - - private void unlook_cb (MinefieldView minefield_view) - { - set_face_image (cool_face_image); - } - - private void about_cb () - { - string[] authors = - { - _("Main game:"), - "Pista", - "Szekeres Istvan", - "Robert Ancell", - "", - _("Score:"), - "Horacio J. Pe\xc3\xb1a", - "", - _("Resizing and SVG support:"), - "Steve Chaplin", - "Callum McKenzie", - null - }; - - string[] artists = - { - _("Faces:"), - "tigert", - "Lapo Calamandrei and Ulisse Perusin", - "", - _("Graphics:"), - "Richard Hoelscher", - null - }; - - string[] documenters = - { - "Callum McKenzie", - null - }; - - Gtk.show_about_dialog (window, - "name", _("Mines"), - "version", VERSION, - "comments", - _("The popular logic puzzle minesweeper. Clear mines from a board using hints from squares you have already uncovered.\n\nMines is a part of GNOME Games."), - "copyright", - "Copyright \xc2\xa9 1997-2008 Free Software Foundation, Inc.", - "license", GnomeGamesSupport.get_license (_("Mines")), - "authors", authors, - "artists", artists, - "documenters", documenters, - "translator-credits", _("translator-credits"), - "logo-icon-name", "gnomine", "website", - "http://www.gnome.org/projects/gnome-games/", - "website-label", _("GNOME Games web site"), - "wrap-license", true, null); - } - - private void set_n_mines_limit () - { - /* Fix up the maximum number of mines so that there is always at least - * ten free spaces. Nine are so we can clear at least the immediate - * eight neighbours at the start and one more so the game isn't over - * immediately. */ - var max_mines = settings.get_int (KEY_XSIZE) * settings.get_int (KEY_YSIZE) - 10; - if (settings.get_int (KEY_NMINES) > max_mines) - { - settings.set_int (KEY_NMINES, max_mines); - n_mines_spin.set_value (max_mines); - } - n_mines_spin.set_range (1, max_mines); - } - - private void xsize_spin_cb (Gtk.SpinButton spin) - { - var xsize = spin.get_value_as_int (); - if (xsize == settings.get_int (KEY_XSIZE)) - return; - - settings.set_int (KEY_XSIZE, xsize); - set_n_mines_limit (); - } - - private void ysize_spin_cb (Gtk.SpinButton spin) - { - var ysize = spin.get_value_as_int (); - if (ysize == settings.get_int (KEY_YSIZE)) - return; - - settings.set_int (KEY_YSIZE, ysize); - set_n_mines_limit (); - } - - private void n_mines_spin_cb (Gtk.SpinButton spin) - { - var n_mines = spin.get_value_as_int (); - if (n_mines == settings.get_int (KEY_NMINES)) - return; - - settings.set_int (KEY_NMINES, n_mines); - } - - private void use_question_toggle_cb (Gtk.ToggleButton button) - { - var use_question_marks = button.get_active (); - settings.set_boolean (KEY_USE_QUESTION_MARKS, use_question_marks); - minefield_view.set_use_question_marks (use_question_marks); - } - - private void use_overmine_toggle_cb (Gtk.ToggleButton button) - { - var use_overmine_warning = button.get_active (); - settings.set_boolean (KEY_USE_OVERMINE_WARNING, use_overmine_warning); - minefield_view.set_use_overmine_warning (use_overmine_warning); - } - - private Gtk.Dialog create_preferences () - { - var vbox = new Gtk.VBox (false, 5); - - var frame = new GnomeGamesSupport.Frame (_("Flags")); - vbox.pack_start (frame, false, false); - - var flag_options_vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - flag_options_vbox.show (); - frame.add (flag_options_vbox); - - var question_toggle = new Gtk.CheckButton.with_mnemonic (_("_Use \"I'm not sure\" flags")); - question_toggle.toggled.connect (use_question_toggle_cb); - question_toggle.set_active (settings.get_boolean (KEY_USE_QUESTION_MARKS)); - flag_options_vbox.pack_start (question_toggle, false, true, 0); - - var overmine_toggle = new Gtk.CheckButton.with_mnemonic (_("_Warn if too many flags placed")); - overmine_toggle.toggled.connect (use_overmine_toggle_cb); - overmine_toggle.set_active (settings.get_boolean (KEY_USE_OVERMINE_WARNING)); - flag_options_vbox.pack_start (overmine_toggle, false, true, 0); - - var dialog = new Gtk.Dialog.with_buttons (_("Mines Preferences"), - window, - 0, - Gtk.Stock.CLOSE, - Gtk.ResponseType.CLOSE, null); - dialog.set_border_width (5); - dialog.set_resizable (false); - var box = (Gtk.Box) dialog.get_content_area (); - box.set_spacing (2); - box.pack_start (vbox, false, false, 0); - - dialog.response.connect (pref_response_cb); - dialog.delete_event.connect (pref_delete_event_cb); - - vbox.show_all (); - - return dialog; - } - - private void set_mode (int mode) - { - if (mode != settings.get_int (KEY_MODE)) - settings.set_int (KEY_MODE, mode); - - new_game (); - } - - private void small_size_clicked_cb () - { - set_mode (0); - } - - private void medium_size_clicked_cb () - { - set_mode (1); - } - - private void large_size_clicked_cb () - { - set_mode (2); - } - - private void custom_size_clicked_cb () - { - set_mode (3); - } - - private void pref_response_cb (Gtk.Dialog dialog, int response_id) - { - pref_dialog.hide (); - } - - private bool pref_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event) - { - pref_dialog.hide (); - return true; - } - - private void preferences_cb () - { - if (pref_dialog == null) - pref_dialog = create_preferences (); - pref_dialog.present (); - } - - private void help_cb () - { - try - { - Gtk.show_uri (window.get_screen (), "help:gnomine", Gtk.get_current_event_time ()); - } - catch (Error e) - { - warning ("Failed to show help: %s", e.message); - } - } - - - public static int main (string[] args) - { - Intl.setlocale (LocaleCategory.ALL, ""); - Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - Intl.textdomain (GETTEXT_PACKAGE); - - GnomeGamesSupport.scores_startup (); - - var context = new OptionContext (""); - context.set_translation_domain (GETTEXT_PACKAGE); - context.add_group (Gtk.get_option_group (true)); - - try - { - context.parse (ref args); - } - catch (Error e) - { - stderr.printf ("%s\n", e.message); - return Posix.EXIT_FAILURE; - } - - var app = new GnoMine (); - return app.run (); - } -} diff --git a/tests/vala/hashset.vala b/tests/vala/hashset.vala deleted file mode 100644 index 01452c6..0000000 --- a/tests/vala/hashset.vala +++ /dev/null @@ -1,206 +0,0 @@ -/* hashset.vala - * - * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald - * Copyright (C) 1997-2000 GLib Team and others - * Copyright (C) 2007-2009 Jürg Billeter - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Author: - * Jürg Billeter - */ - -using GLib; - -/** - * Hashtable implementation of the Set interface. - */ -public class Vala.HashSet : Set { - public override int size { - get { return _nnodes; } - } - - public HashFunc hash_func { - set { _hash_func = value; } - } - - public EqualFunc equal_func { - set { _equal_func = value; } - } - - private int _array_size; - private int _nnodes; - private Node[] _nodes; - - // concurrent modification protection - private int _stamp = 0; - - private HashFunc _hash_func; - private EqualFunc _equal_func; - - private const int MIN_SIZE = 11; - private const int MAX_SIZE = 13845163; - - public HashSet (HashFunc hash_func = GLib.direct_hash, EqualFunc equal_func = GLib.direct_equal) { - this.hash_func = hash_func; - this.equal_func = equal_func; - _array_size = MIN_SIZE; - _nodes = new Node[_array_size]; - } - - private Node** lookup_node (G key) { - uint hash_value = _hash_func (key); - Node** node = &_nodes[hash_value % _array_size]; - while ((*node) != null && (hash_value != (*node)->key_hash || !_equal_func ((*node)->key, key))) { - node = &((*node)->next); - } - return node; - } - - public override bool contains (G key) { - Node** node = lookup_node (key); - return (*node != null); - } - - public override Type get_element_type () { - return typeof (G); - } - - public override Vala.Iterator iterator () { - return new Iterator (this); - } - - public override bool add (G key) { - Node** node = lookup_node (key); - if (*node != null) { - return false; - } else { - uint hash_value = _hash_func (key); - *node = new Node (key, hash_value); - _nnodes++; - resize (); - _stamp++; - return true; - } - } - - public override bool remove (G key) { - Node** node = lookup_node (key); - if (*node != null) { - Node next = (owned) (*node)->next; - - (*node)->key = null; - delete *node; - - *node = (owned) next; - - _nnodes--; - resize (); - _stamp++; - return true; - } - return false; - } - - public override void clear () { - for (int i = 0; i < _array_size; i++) { - Node node = (owned) _nodes[i]; - while (node != null) { - Node next = (owned) node.next; - node.key = null; - node = (owned) next; - } - } - _nnodes = 0; - resize (); - } - - private void resize () { - if ((_array_size >= 3 * _nnodes && _array_size >= MIN_SIZE) || - (3 * _array_size <= _nnodes && _array_size < MAX_SIZE)) { - int new_array_size = (int) SpacedPrimes.closest (_nnodes); - new_array_size = new_array_size.clamp (MIN_SIZE, MAX_SIZE); - - Node[] new_nodes = new Node[new_array_size]; - - for (int i = 0; i < _array_size; i++) { - Node node; - Node next = null; - for (node = (owned) _nodes[i]; node != null; node = (owned) next) { - next = (owned) node.next; - uint hash_val = node.key_hash % new_array_size; - node.next = (owned) new_nodes[hash_val]; - new_nodes[hash_val] = (owned) node; - } - } - _nodes = (owned) new_nodes; - _array_size = new_array_size; - } - } - - ~HashSet () { - clear (); - } - - [Compact] - private class Node { - public G key; - public Node next; - public uint key_hash; - - public Node (owned G k, uint hash) { - key = (owned) k; - key_hash = hash; - } - } - - private class Iterator : Vala.Iterator { - public HashSet set { - set { - _set = value; - _stamp = _set._stamp; - } - } - - private HashSet _set; - private int _index = -1; - private weak Node _node; - - // concurrent modification protection - private int _stamp = 0; - - public Iterator (HashSet set) { - this.set = set; - } - - public override bool next () { - if (_node != null) { - _node = _node.next; - } - while (_node == null && _index + 1 < _set._array_size) { - _index++; - _node = _set._nodes[_index]; - } - return (_node != null); - } - - public override G? get () { - assert (_stamp == _set._stamp); - assert (_node != null); - return _node.key; - } - } -} - diff --git a/tests/vala/namespaces.vala b/tests/vala/namespaces.vala deleted file mode 100644 index b35db59..0000000 --- a/tests/vala/namespaces.vala +++ /dev/null @@ -1,35 +0,0 @@ -using Foo.Sub; - -public class GlobalTestClass { - public GlobalTestClass() { - } -} - -namespace Maman { - static int main () { - stdout.printf ("Namespace Test\n"); - - Bar.run (); - - new GlobalTestClass(); - - var obj = new ClassInNestedNamespace (); - - return 0; - } - - class Bar : Object { - public static void run () { - stdout.printf ("Class in Namespace Test\n"); - } - } -} - -public class Foo.Sub.ClassInNestedNamespace { -} - - -void main () { - Maman.main (); -} - diff --git a/tests/vala/structs.vala b/tests/vala/structs.vala deleted file mode 100644 index 543975a..0000000 --- a/tests/vala/structs.vala +++ /dev/null @@ -1,86 +0,0 @@ -using GLib; - -struct SimpleStruct { - public int field; - public int array[10]; -} - -public struct PublicStruct { - public int field; -} - -struct StructWithPrivateField { - private int field; - - public void test () { - field = 1; - stdout.printf ("StructWithPrivateField: field = %d\n", field); - } -} - -struct StructWithCreationMethod { - public StructWithCreationMethod () { - stdout.printf ("StructWithCreationMethod\n"); - } - - public int field; -} - -struct StructWithNamedCreationMethod { - public StructWithNamedCreationMethod.named () { - stdout.printf ("StructWithNamedCreationMethod\n"); - } - - public int field; -} - -void test_in_parameter (SimpleStruct st) { - stdout.printf ("test_in_parameter: st.field = %d\n", st.field); -} - -void test_in_nullable_parameter (SimpleStruct? st) { - assert (st.field == 1); -} - -void test_ref_parameter (ref SimpleStruct st) { - stdout.printf ("test_ref_parameter: st.field = %d\n", st.field); - st.field++; - st.array[0] = 10; -} - -void test_out_parameter (out SimpleStruct st) { - st = SimpleStruct (); - st.field = 3; -} - -void main () { - stdout.printf ("Structs Test:\n"); - - stdout.printf ("new SimpleStruct ()\n"); - var simple_struct = SimpleStruct (); - stdout.printf ("new PublicStruct ()\n"); - var public_struct = PublicStruct (); - stdout.printf ("new StructWithCreationMethod ()\n"); - var struct_with_creation_method = StructWithCreationMethod (); - stdout.printf ("new StructWithNamedCreationMethod ()\n"); - var struct_with_named_creation_method = StructWithNamedCreationMethod.named (); - - stdout.printf ("new SimpleStruct () { field = 1 }\n"); - simple_struct = SimpleStruct () { field = 1 }; - stdout.printf ("simple_struct.field = %d\n", simple_struct.field); - - test_in_parameter (simple_struct); - test_in_parameter ({1}); - test_in_nullable_parameter (simple_struct); - test_ref_parameter (ref simple_struct); - stdout.printf ("after test_ref_parameter: st.field = %d\n", simple_struct.field); - assert (simple_struct.array[0] == 10); - test_out_parameter (out simple_struct); - stdout.printf ("after test_out_parameter: st.field = %d\n", simple_struct.field); - - var struct_with_private_field = StructWithPrivateField (); - struct_with_private_field.test (); - - stdout.printf (".\n"); -} - diff --git a/tests/vala/valacodewriter.vala b/tests/vala/valacodewriter.vala deleted file mode 100644 index f79c2af..0000000 --- a/tests/vala/valacodewriter.vala +++ /dev/null @@ -1,1605 +0,0 @@ -/* valacodewriter.vala - * - * Copyright (C) 2006-2010 Jürg Billeter - * Copyright (C) 2006-2008 Raffaele Sandrini - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Author: - * Jürg Billeter - * Raffaele Sandrini - */ - - -/** - * Code visitor generating Vala API file for the public interface. - */ -public class Vala.CodeWriter : CodeVisitor { - private CodeContext context; - - FileStream stream; - - int indent; - /* at begin of line */ - bool bol = true; - - Scope current_scope; - - CodeWriterType type; - - string? override_header = null; - string? header_to_override = null; - - public CodeWriter (CodeWriterType type = CodeWriterType.EXTERNAL) { - this.type = type; - } - - /** - * Allows overriding of a specific cheader in the output - * @param original orignal cheader to override - * @param replacement cheader to replace original with - */ - public void set_cheader_override (string original, string replacement) - { - header_to_override = original; - override_header = replacement; - } - - /** - * Writes the public interface of the specified code context into the - * specified file. - * - * @param context a code context - * @param filename a relative or absolute filename - */ - public void write_file (CodeContext context, string filename) { - var file_exists = FileUtils.test (filename, FileTest.EXISTS); - var temp_filename = filename + ".valatmp"; - this.context = context; - - if (file_exists) { - stream = FileStream.open (temp_filename, "w"); - } else { - stream = FileStream.open (filename, "w"); - } - - if (stream == null) { - Report.error (null, "unable to open `%s' for writing".printf (filename)); - return; - } - - var header = context.version_header ? - "/* %s generated by %s %s, do not modify. */".printf (Path.get_basename (filename), Environment.get_prgname (), Config.BUILD_VERSION) : - "/* %s generated by %s, do not modify. */".printf (Path.get_basename (filename), Environment.get_prgname ()); - write_string (header); - write_newline (); - write_newline (); - - current_scope = context.root.scope; - - context.accept (this); - - current_scope = null; - - stream = null; - - if (file_exists) { - var changed = true; - - try { - var old_file = new MappedFile (filename, false); - var new_file = new MappedFile (temp_filename, false); - var len = old_file.get_length (); - if (len == new_file.get_length ()) { - if (Memory.cmp (old_file.get_contents (), new_file.get_contents (), len) == 0) { - changed = false; - } - } - old_file = null; - new_file = null; - } catch (FileError e) { - // assume changed if mmap comparison doesn't work - } - - if (changed) { - FileUtils.rename (temp_filename, filename); - } else { - FileUtils.unlink (temp_filename); - } - } - - } - - public override void visit_using_directive (UsingDirective ns) { - if (type == CodeWriterType.FAST) { - write_string ("using %s;\n".printf (ns.namespace_symbol.name)); - } - } - - public override void visit_namespace (Namespace ns) { - if (ns.external_package) { - return; - } - - if (ns.name == null) { - ns.accept_children (this); - return; - } - - write_attributes (ns); - - write_indent (); - write_string ("namespace "); - write_identifier (ns.name); - write_begin_block (); - - current_scope = ns.scope; - - visit_sorted (ns.get_namespaces ()); - visit_sorted (ns.get_classes ()); - visit_sorted (ns.get_interfaces ()); - visit_sorted (ns.get_structs ()); - visit_sorted (ns.get_enums ()); - visit_sorted (ns.get_error_domains ()); - visit_sorted (ns.get_delegates ()); - visit_sorted (ns.get_fields ()); - visit_sorted (ns.get_constants ()); - visit_sorted (ns.get_methods ()); - - current_scope = current_scope.parent_scope; - - write_end_block (); - write_newline (); - } - - private string get_cheaders (Symbol sym) { - string cheaders = ""; - if (type != CodeWriterType.FAST && !sym.external_package) { - cheaders = sym.get_attribute_string ("CCode", "cheader_filename") ?? ""; - if (cheaders == "" && sym.parent_symbol != null && sym.parent_symbol != context.root) { - cheaders = get_cheaders (sym.parent_symbol); - } - if (cheaders == "" && sym.source_reference != null && !sym.external_package) { - cheaders = sym.source_reference.file.get_cinclude_filename (); - } - - if (header_to_override != null) { - cheaders = cheaders.replace (header_to_override, override_header).replace (",,", ","); - } - } - return cheaders; - } - - public override void visit_class (Class cl) { - if (cl.external_package) { - return; - } - - if (!check_accessibility (cl)) { - return; - } - - write_attributes (cl); - - write_indent (); - write_accessibility (cl); - if (cl.is_abstract) { - write_string ("abstract "); - } - write_string ("class "); - write_identifier (cl.name); - - write_type_parameters (cl.get_type_parameters ()); - - var base_types = cl.get_base_types (); - if (base_types.size > 0) { - write_string (" : "); - - bool first = true; - foreach (DataType base_type in base_types) { - if (!first) { - write_string (", "); - } else { - first = false; - } - write_type (base_type); - } - } - write_begin_block (); - - current_scope = cl.scope; - - visit_sorted (cl.get_classes ()); - visit_sorted (cl.get_structs ()); - visit_sorted (cl.get_enums ()); - visit_sorted (cl.get_delegates ()); - visit_sorted (cl.get_fields ()); - visit_sorted (cl.get_constants ()); - visit_sorted (cl.get_methods ()); - visit_sorted (cl.get_properties ()); - visit_sorted (cl.get_signals ()); - - if (cl.constructor != null) { - cl.constructor.accept (this); - } - - current_scope = current_scope.parent_scope; - - write_end_block (); - write_newline (); - } - - void visit_sorted (List symbols) { - if (type != CodeWriterType.EXTERNAL) { - // order of virtual methods matters for fast vapis - foreach (Symbol sym in symbols) { - sym.accept (this); - } - return; - } - - var sorted_symbols = new ArrayList (); - foreach (Symbol sym in symbols) { - int left = 0; - int right = sorted_symbols.size - 1; - if (left > right || sym.name < sorted_symbols[left].name) { - sorted_symbols.insert (0, sym); - } else if (sym.name > sorted_symbols[right].name) { - sorted_symbols.add (sym); - } else { - while (right - left > 1) { - int i = (right + left) / 2; - if (sym.name > sorted_symbols[i].name) { - left = i; - } else { - right = i; - } - } - sorted_symbols.insert (left + 1, sym); - } - } - foreach (Symbol sym in sorted_symbols) { - sym.accept (this); - } - } - - public override void visit_struct (Struct st) { - if (st.external_package) { - return; - } - - if (!check_accessibility (st)) { - return; - } - - write_attributes (st); - - write_indent (); - write_accessibility (st); - write_string ("struct "); - write_identifier (st.name); - - write_type_parameters (st.get_type_parameters ()); - - if (st.base_type != null) { - write_string (" : "); - write_type (st.base_type); - } - - write_begin_block (); - - current_scope = st.scope; - - foreach (Field field in st.get_fields ()) { - field.accept (this); - } - visit_sorted (st.get_constants ()); - visit_sorted (st.get_methods ()); - visit_sorted (st.get_properties ()); - - current_scope = current_scope.parent_scope; - - write_end_block (); - write_newline (); - } - - public override void visit_interface (Interface iface) { - if (iface.external_package) { - return; - } - - if (!check_accessibility (iface)) { - return; - } - - write_attributes (iface); - - write_indent (); - write_accessibility (iface); - write_string ("interface "); - write_identifier (iface.name); - - write_type_parameters (iface.get_type_parameters ()); - - var prerequisites = iface.get_prerequisites (); - if (prerequisites.size > 0) { - write_string (" : "); - - bool first = true; - foreach (DataType prerequisite in prerequisites) { - if (!first) { - write_string (", "); - } else { - first = false; - } - write_type (prerequisite); - } - } - write_begin_block (); - - current_scope = iface.scope; - - visit_sorted (iface.get_classes ()); - visit_sorted (iface.get_structs ()); - visit_sorted (iface.get_enums ()); - visit_sorted (iface.get_delegates ()); - visit_sorted (iface.get_fields ()); - visit_sorted (iface.get_constants ()); - visit_sorted (iface.get_methods ()); - visit_sorted (iface.get_properties ()); - visit_sorted (iface.get_signals ()); - - current_scope = current_scope.parent_scope; - - write_end_block (); - write_newline (); - } - - public override void visit_enum (Enum en) { - if (en.external_package) { - return; - } - - if (!check_accessibility (en)) { - return; - } - - write_attributes (en); - - write_indent (); - write_accessibility (en); - write_string ("enum "); - write_identifier (en.name); - write_begin_block (); - - bool first = true; - foreach (EnumValue ev in en.get_values ()) { - if (first) { - first = false; - } else { - write_string (","); - write_newline (); - } - - write_attributes (ev); - - write_indent (); - write_identifier (ev.name); - - if (type == CodeWriterType.FAST && ev.value != null) { - write_string(" = "); - ev.value.accept (this); - } - } - - if (!first) { - if (en.get_methods ().size > 0 || en.get_constants ().size > 0) { - write_string (";"); - } - write_newline (); - } - - current_scope = en.scope; - foreach (Method m in en.get_methods ()) { - m.accept (this); - } - foreach (Constant c in en.get_constants ()) { - c.accept (this); - } - current_scope = current_scope.parent_scope; - - write_end_block (); - write_newline (); - } - - public override void visit_error_domain (ErrorDomain edomain) { - if (edomain.external_package) { - return; - } - - if (!check_accessibility (edomain)) { - return; - } - - write_attributes (edomain); - - write_indent (); - write_accessibility (edomain); - write_string ("errordomain "); - write_identifier (edomain.name); - write_begin_block (); - - bool first = true; - foreach (ErrorCode ecode in edomain.get_codes ()) { - if (first) { - first = false; - } else { - write_string (","); - write_newline (); - } - - write_attributes (ecode); - - write_indent (); - write_identifier (ecode.name); - } - - if (!first) { - if (edomain.get_methods ().size > 0) { - write_string (";"); - } - write_newline (); - } - - current_scope = edomain.scope; - foreach (Method m in edomain.get_methods ()) { - m.accept (this); - } - current_scope = current_scope.parent_scope; - - write_end_block (); - write_newline (); - } - - public override void visit_constant (Constant c) { - if (c.external_package) { - return; - } - - if (!check_accessibility (c)) { - return; - } - - write_attributes (c); - - write_indent (); - write_accessibility (c); - write_string ("const "); - - write_type (c.type_reference); - - write_string (" "); - write_identifier (c.name); - if (type == CodeWriterType.FAST && c.value != null) { - write_string(" = "); - c.value.accept (this); - } - write_string (";"); - write_newline (); - } - - public override void visit_field (Field f) { - if (f.external_package) { - return; - } - - if (!check_accessibility (f)) { - return; - } - - write_attributes (f); - - write_indent (); - write_accessibility (f); - - if (f.binding == MemberBinding.STATIC) { - write_string ("static "); - } else if (f.binding == MemberBinding.CLASS) { - write_string ("class "); - } - - if (f.variable_type.is_weak ()) { - write_string ("weak "); - } - - write_type (f.variable_type); - - write_string (" "); - write_identifier (f.name); - write_string (";"); - write_newline (); - } - - private void write_error_domains (List error_domains) { - if (error_domains.size > 0) { - write_string (" throws "); - - bool first = true; - foreach (DataType type in error_domains) { - if (!first) { - write_string (", "); - } else { - first = false; - } - - write_type (type); - } - } - } - - private void write_params (List params) { - write_string ("("); - - int i = 1; - foreach (Parameter param in params) { - if (i > 1) { - write_string (", "); - } - - if (param.ellipsis) { - write_string ("..."); - continue; - } - - write_attributes (param); - - if (param.params_array) { - write_string ("params "); - } - - if (param.direction == ParameterDirection.IN) { - if (param.variable_type.value_owned) { - write_string ("owned "); - } - } else { - if (param.direction == ParameterDirection.REF) { - write_string ("ref "); - } else if (param.direction == ParameterDirection.OUT) { - write_string ("out "); - } - if (param.variable_type.is_weak ()) { - write_string ("unowned "); - } - } - - write_type (param.variable_type); - - write_string (" "); - write_identifier (param.name); - - if (param.initializer != null) { - write_string (" = "); - param.initializer.accept (this); - } - - i++; - } - - write_string (")"); - } - - public override void visit_delegate (Delegate cb) { - if (cb.external_package) { - return; - } - - if (!check_accessibility (cb)) { - return; - } - - write_attributes (cb); - - write_indent (); - - write_accessibility (cb); - write_string ("delegate "); - - write_return_type (cb.return_type); - - write_string (" "); - write_identifier (cb.name); - - write_type_parameters (cb.get_type_parameters ()); - - write_string (" "); - - write_params (cb.get_parameters ()); - - write_error_domains (cb.get_error_types ()); - - write_string (";"); - - write_newline (); - } - - public override void visit_constructor (Constructor c) { - if (type != CodeWriterType.DUMP) { - return; - } - - write_indent (); - write_string ("construct"); - write_code_block (c.body); - write_newline (); - } - - public override void visit_method (Method m) { - if (m.external_package) { - return; - } - - // don't write interface implementation unless it's an abstract or virtual method - if (!check_accessibility (m) || (m.base_interface_method != null && !m.is_abstract && !m.is_virtual)) { - if (type != CodeWriterType.DUMP) { - return; - } - } - - write_attributes (m); - - write_indent (); - write_accessibility (m); - - if (m is CreationMethod) { - if (m.coroutine) { - write_string ("async "); - } - - var datatype = (TypeSymbol) m.parent_symbol; - write_identifier (datatype.name); - if (m.name != ".new") { - write_string ("."); - write_identifier (m.name); - } - write_string (" "); - } else { - if (m.binding == MemberBinding.STATIC) { - write_string ("static "); - } else if (m.binding == MemberBinding.CLASS) { - write_string ("class "); - } else if (m.is_abstract) { - write_string ("abstract "); - } else if (m.is_virtual) { - write_string ("virtual "); - } else if (m.overrides) { - write_string ("override "); - } - - if (m.hides) { - write_string ("new "); - } - - if (m.coroutine) { - write_string ("async "); - } - - write_return_type (m.return_type); - write_string (" "); - - write_identifier (m.name); - - write_type_parameters (m.get_type_parameters ()); - - write_string (" "); - } - - write_params (m.get_parameters ()); - - if (context.profile != Profile.DOVA) { - write_error_domains (m.get_error_types ()); - } - - write_code_block (m.body); - - write_newline (); - } - - public override void visit_creation_method (CreationMethod m) { - visit_method (m); - } - - public override void visit_property (Property prop) { - if (!check_accessibility (prop) || (prop.base_interface_property != null && !prop.is_abstract && !prop.is_virtual)) { - return; - } - - write_attributes (prop); - - write_indent (); - write_accessibility (prop); - - if (prop.binding == MemberBinding.STATIC) { - write_string ("static "); - } else if (prop.is_abstract) { - write_string ("abstract "); - } else if (prop.is_virtual) { - write_string ("virtual "); - } else if (prop.overrides) { - write_string ("override "); - } - - write_type (prop.property_type); - - write_string (" "); - write_identifier (prop.name); - write_string (" {"); - if (prop.get_accessor != null) { - write_attributes (prop.get_accessor); - - write_property_accessor_accessibility (prop.get_accessor); - - if (context.profile != Profile.DOVA && prop.get_accessor.value_type.is_disposable ()) { - write_string (" owned"); - } - - write_string (" get"); - write_code_block (prop.get_accessor.body); - } - if (prop.set_accessor != null) { - write_attributes (prop.set_accessor); - - write_property_accessor_accessibility (prop.set_accessor); - - if (context.profile != Profile.DOVA && prop.set_accessor.value_type.value_owned) { - write_string (" owned"); - } - - if (prop.set_accessor.writable) { - write_string (" set"); - } - if (prop.set_accessor.construction) { - write_string (" construct"); - } - write_code_block (prop.set_accessor.body); - } - write_string (" }"); - write_newline (); - } - - public override void visit_signal (Signal sig) { - if (!check_accessibility (sig)) { - return; - } - - write_attributes (sig); - - write_indent (); - write_accessibility (sig); - - if (sig.is_virtual) { - write_string ("virtual "); - } - - write_string ("signal "); - - write_return_type (sig.return_type); - - write_string (" "); - write_identifier (sig.name); - - write_string (" "); - - write_params (sig.get_parameters ()); - - write_string (";"); - - write_newline (); - } - - public override void visit_block (Block b) { - write_begin_block (); - - foreach (Statement stmt in b.get_statements ()) { - stmt.accept (this); - } - - write_end_block (); - } - - public override void visit_empty_statement (EmptyStatement stmt) { - } - - public override void visit_declaration_statement (DeclarationStatement stmt) { - write_indent (); - stmt.declaration.accept (this); - write_string (";"); - write_newline (); - } - - public override void visit_local_variable (LocalVariable local) { - write_type (local.variable_type); - write_string (" "); - write_identifier (local.name); - if (local.initializer != null) { - write_string (" = "); - local.initializer.accept (this); - } - } - - public override void visit_initializer_list (InitializerList list) { - write_string ("{"); - - bool first = true; - foreach (Expression initializer in list.get_initializers ()) { - if (!first) { - write_string (", "); - } else { - write_string (" "); - } - first = false; - initializer.accept (this); - } - write_string (" }"); - } - - public override void visit_expression_statement (ExpressionStatement stmt) { - write_indent (); - stmt.expression.accept (this); - write_string (";"); - write_newline (); - } - - public override void visit_if_statement (IfStatement stmt) { - write_indent (); - write_string ("if ("); - stmt.condition.accept (this); - write_string (")"); - stmt.true_statement.accept (this); - if (stmt.false_statement != null) { - write_string (" else"); - stmt.false_statement.accept (this); - } - write_newline (); - } - - public override void visit_switch_statement (SwitchStatement stmt) { - write_indent (); - write_string ("switch ("); - stmt.expression.accept (this); - write_string (") {"); - write_newline (); - - foreach (SwitchSection section in stmt.get_sections ()) { - section.accept (this); - } - - write_indent (); - write_string ("}"); - write_newline (); - } - - public override void visit_switch_section (SwitchSection section) { - foreach (SwitchLabel label in section.get_labels ()) { - label.accept (this); - } - - visit_block (section); - } - - public override void visit_switch_label (SwitchLabel label) { - if (label.expression != null) { - write_indent (); - write_string ("case "); - label.expression.accept (this); - write_string (":"); - write_newline (); - } else { - write_indent (); - write_string ("default:"); - write_newline (); - } - } - - public override void visit_loop (Loop stmt) { - write_indent (); - write_string ("loop"); - stmt.body.accept (this); - write_newline (); - } - - public override void visit_while_statement (WhileStatement stmt) { - write_indent (); - write_string ("while ("); - stmt.condition.accept (this); - write_string (")"); - stmt.body.accept (this); - write_newline (); - } - - public override void visit_do_statement (DoStatement stmt) { - write_indent (); - write_string ("do"); - stmt.body.accept (this); - write_string ("while ("); - stmt.condition.accept (this); - write_string (");"); - write_newline (); - } - - public override void visit_for_statement (ForStatement stmt) { - write_indent (); - write_string ("for ("); - - bool first = true; - foreach (Expression initializer in stmt.get_initializer ()) { - if (!first) { - write_string (", "); - } - first = false; - initializer.accept (this); - } - write_string ("; "); - - stmt.condition.accept (this); - write_string ("; "); - - first = true; - foreach (Expression iterator in stmt.get_iterator ()) { - if (!first) { - write_string (", "); - } - first = false; - iterator.accept (this); - } - - write_string (")"); - stmt.body.accept (this); - write_newline (); - } - - public override void visit_foreach_statement (ForeachStatement stmt) { - } - - public override void visit_break_statement (BreakStatement stmt) { - write_indent (); - write_string ("break;"); - write_newline (); - } - - public override void visit_continue_statement (ContinueStatement stmt) { - write_indent (); - write_string ("continue;"); - write_newline (); - } - - public override void visit_return_statement (ReturnStatement stmt) { - write_indent (); - write_string ("return"); - if (stmt.return_expression != null) { - write_string (" "); - stmt.return_expression.accept (this); - } - write_string (";"); - write_newline (); - } - - public override void visit_yield_statement (YieldStatement y) { - write_indent (); - write_string ("yield"); - if (y.yield_expression != null) { - write_string (" "); - y.yield_expression.accept (this); - } - write_string (";"); - write_newline (); - } - - public override void visit_throw_statement (ThrowStatement stmt) { - write_indent (); - write_string ("throw"); - if (stmt.error_expression != null) { - write_string (" "); - stmt.error_expression.accept (this); - } - write_string (";"); - write_newline (); - } - - public override void visit_try_statement (TryStatement stmt) { - write_indent (); - write_string ("try"); - stmt.body.accept (this); - foreach (var clause in stmt.get_catch_clauses ()) { - clause.accept (this); - } - if (stmt.finally_body != null) { - write_string (" finally"); - stmt.finally_body.accept (this); - } - write_newline (); - } - - public override void visit_catch_clause (CatchClause clause) { - var type_name = clause.error_type == null ? "GLib.Error" : clause.error_type.to_string (); - var var_name = clause.variable_name == null ? "_" : clause.variable_name; - write_string (" catch (%s %s)".printf (type_name, var_name)); - clause.body.accept (this); - } - - public override void visit_lock_statement (LockStatement stmt) { - write_indent (); - write_string ("lock ("); - stmt.resource.accept (this); - write_string (")"); - if (stmt.body == null) { - write_string (";"); - } else { - stmt.body.accept (this); - } - write_newline (); - } - - public override void visit_delete_statement (DeleteStatement stmt) { - write_indent (); - write_string ("delete "); - stmt.expression.accept (this); - write_string (";"); - write_newline (); - } - - public override void visit_array_creation_expression (ArrayCreationExpression expr) { - write_string ("new "); - write_type (expr.element_type); - write_string ("["); - - bool first = true; - foreach (Expression size in expr.get_sizes ()) { - if (!first) { - write_string (", "); - } - first = false; - - size.accept (this); - } - - write_string ("]"); - - if (expr.initializer_list != null) { - write_string (" "); - expr.initializer_list.accept (this); - } - } - - public override void visit_boolean_literal (BooleanLiteral lit) { - write_string (lit.value.to_string ()); - } - - public override void visit_character_literal (CharacterLiteral lit) { - write_string (lit.value); - } - - public override void visit_integer_literal (IntegerLiteral lit) { - write_string (lit.value); - } - - public override void visit_real_literal (RealLiteral lit) { - write_string (lit.value); - } - - public override void visit_string_literal (StringLiteral lit) { - write_string (lit.value); - } - - public override void visit_null_literal (NullLiteral lit) { - write_string ("null"); - } - - public override void visit_member_access (MemberAccess expr) { - if (expr.inner != null) { - expr.inner.accept (this); - write_string ("."); - } - write_identifier (expr.member_name); - } - - public override void visit_method_call (MethodCall expr) { - expr.call.accept (this); - write_string (" ("); - - bool first = true; - foreach (Expression arg in expr.get_argument_list ()) { - if (!first) { - write_string (", "); - } - first = false; - - arg.accept (this); - } - - write_string (")"); - } - - public override void visit_element_access (ElementAccess expr) { - expr.container.accept (this); - write_string ("["); - - bool first = true; - foreach (Expression index in expr.get_indices ()) { - if (!first) { - write_string (", "); - } - first = false; - - index.accept (this); - } - - write_string ("]"); - } - - public override void visit_slice_expression (SliceExpression expr) { - expr.container.accept (this); - write_string ("["); - expr.start.accept (this); - write_string (":"); - expr.stop.accept (this); - write_string ("]"); - } - - public override void visit_base_access (BaseAccess expr) { - write_string ("base"); - } - - public override void visit_postfix_expression (PostfixExpression expr) { - expr.inner.accept (this); - if (expr.increment) { - write_string ("++"); - } else { - write_string ("--"); - } - } - - public override void visit_object_creation_expression (ObjectCreationExpression expr) { - if (!expr.struct_creation) { - write_string ("new "); - } - - write_type (expr.type_reference); - - if (expr.symbol_reference.name != ".new") { - write_string ("."); - write_string (expr.symbol_reference.name); - } - - write_string (" ("); - - bool first = true; - foreach (Expression arg in expr.get_argument_list ()) { - if (!first) { - write_string (", "); - } - first = false; - - arg.accept (this); - } - - write_string (")"); - } - - public override void visit_sizeof_expression (SizeofExpression expr) { - write_string ("sizeof ("); - write_type (expr.type_reference); - write_string (")"); - } - - public override void visit_typeof_expression (TypeofExpression expr) { - write_string ("typeof ("); - write_type (expr.type_reference); - write_string (")"); - } - - public override void visit_unary_expression (UnaryExpression expr) { - switch (expr.operator) { - case UnaryOperator.PLUS: - write_string ("+"); - break; - case UnaryOperator.MINUS: - write_string ("-"); - break; - case UnaryOperator.LOGICAL_NEGATION: - write_string ("!"); - break; - case UnaryOperator.BITWISE_COMPLEMENT: - write_string ("~"); - break; - case UnaryOperator.INCREMENT: - write_string ("++"); - break; - case UnaryOperator.DECREMENT: - write_string ("--"); - break; - case UnaryOperator.REF: - write_string ("ref "); - break; - case UnaryOperator.OUT: - write_string ("out "); - break; - default: - assert_not_reached (); - } - expr.inner.accept (this); - } - - public override void visit_cast_expression (CastExpression expr) { - if (expr.is_non_null_cast) { - write_string ("(!) "); - expr.inner.accept (this); - return; - } - - if (!expr.is_silent_cast) { - write_string ("("); - write_type (expr.type_reference); - write_string (") "); - } - - expr.inner.accept (this); - - if (expr.is_silent_cast) { - write_string (" as "); - write_type (expr.type_reference); - } - } - - public override void visit_pointer_indirection (PointerIndirection expr) { - write_string ("*"); - expr.inner.accept (this); - } - - public override void visit_addressof_expression (AddressofExpression expr) { - write_string ("&"); - expr.inner.accept (this); - } - - public override void visit_reference_transfer_expression (ReferenceTransferExpression expr) { - write_string ("(owned) "); - expr.inner.accept (this); - } - - public override void visit_binary_expression (BinaryExpression expr) { - expr.left.accept (this); - - switch (expr.operator) { - case BinaryOperator.PLUS: - write_string (" + "); - break; - case BinaryOperator.MINUS: - write_string (" - "); - break; - case BinaryOperator.MUL: - write_string (" * "); - break; - case BinaryOperator.DIV: - write_string (" / "); - break; - case BinaryOperator.MOD: - write_string (" % "); - break; - case BinaryOperator.SHIFT_LEFT: - write_string (" << "); - break; - case BinaryOperator.SHIFT_RIGHT: - write_string (" >> "); - break; - case BinaryOperator.LESS_THAN: - write_string (" < "); - break; - case BinaryOperator.GREATER_THAN: - write_string (" > "); - break; - case BinaryOperator.LESS_THAN_OR_EQUAL: - write_string (" <= "); - break; - case BinaryOperator.GREATER_THAN_OR_EQUAL: - write_string (" >= "); - break; - case BinaryOperator.EQUALITY: - write_string (" == "); - break; - case BinaryOperator.INEQUALITY: - write_string (" != "); - break; - case BinaryOperator.BITWISE_AND: - write_string (" & "); - break; - case BinaryOperator.BITWISE_OR: - write_string (" | "); - break; - case BinaryOperator.BITWISE_XOR: - write_string (" ^ "); - break; - case BinaryOperator.AND: - write_string (" && "); - break; - case BinaryOperator.OR: - write_string (" || "); - break; - case BinaryOperator.IN: - write_string (" in "); - break; - case BinaryOperator.COALESCE: - write_string (" ?? "); - break; - default: - assert_not_reached (); - } - - expr.right.accept (this); - } - - public override void visit_type_check (TypeCheck expr) { - expr.expression.accept (this); - write_string (" is "); - write_type (expr.type_reference); - } - - public override void visit_conditional_expression (ConditionalExpression expr) { - expr.condition.accept (this); - write_string ("?"); - expr.true_expression.accept (this); - write_string (":"); - expr.false_expression.accept (this); - } - - public override void visit_lambda_expression (LambdaExpression expr) { - write_string ("("); - var params = expr.get_parameters (); - int i = 1; - foreach (var param in params) { - if (i > 1) { - write_string (", "); - } - - if (param.direction == ParameterDirection.REF) { - write_string ("ref "); - } else if (param.direction == ParameterDirection.OUT) { - write_string ("out "); - } - - write_identifier (param.name); - - i++; - } - write_string (") =>"); - if (expr.statement_body != null) { - expr.statement_body.accept (this); - } else if (expr.expression_body != null) { - expr.expression_body.accept (this); - } - } - - public override void visit_assignment (Assignment a) { - a.left.accept (this); - write_string (" = "); - a.right.accept (this); - } - - private void write_indent () { - int i; - - if (!bol) { - stream.putc ('\n'); - } - - for (i = 0; i < indent; i++) { - stream.putc ('\t'); - } - - bol = false; - } - - private void write_identifier (string s) { - char* id = (char*)s; - int id_length = (int)s.length; - if (context.profile != Profile.DOVA && - (Vala.Scanner.get_identifier_or_keyword (id, id_length) != Vala.TokenType.IDENTIFIER || - s.get_char ().isdigit ())) { - stream.putc ('@'); - } - write_string (s); - } - - private void write_return_type (DataType type) { - if (type.is_weak ()) { - write_string ("unowned "); - } - - write_type (type); - } - - private void write_type (DataType type) { - write_string (type.to_qualified_string (current_scope)); - } - - private void write_string (string s) { - stream.printf ("%s", s); - bol = false; - } - - private void write_newline () { - stream.putc ('\n'); - bol = true; - } - - void write_code_block (Block? block) { - if (block == null || type != CodeWriterType.DUMP) { - write_string (";"); - return; - } - - block.accept (this); - } - - private void write_begin_block () { - if (!bol) { - stream.putc (' '); - } else { - write_indent (); - } - stream.putc ('{'); - write_newline (); - indent++; - } - - private void write_end_block () { - indent--; - write_indent (); - stream.printf ("}"); - } - - private bool check_accessibility (Symbol sym) { - switch (type) { - case CodeWriterType.EXTERNAL: - return sym.access == SymbolAccessibility.PUBLIC || - sym.access == SymbolAccessibility.PROTECTED; - - case CodeWriterType.INTERNAL: - case CodeWriterType.FAST: - return sym.access == SymbolAccessibility.INTERNAL || - sym.access == SymbolAccessibility.PUBLIC || - sym.access == SymbolAccessibility.PROTECTED; - - case CodeWriterType.DUMP: - return true; - - default: - assert_not_reached (); - } - } - - private void write_attributes (CodeNode node) { - var sym = node as Symbol; - - var need_cheaders = type != CodeWriterType.FAST && sym != null && !(sym is Namespace) && sym.parent_symbol is Namespace; - - var attributes = new GLib.Sequence (); - foreach (var attr in node.attributes) { - attributes.insert_sorted (attr, (a, b) => strcmp (a.name, b.name)); - } - if (need_cheaders && node.get_attribute ("CCode") == null) { - attributes.insert_sorted (new Attribute ("CCode"), (a, b) => strcmp (a.name, b.name)); - } - - var iter = attributes.get_begin_iter (); - while (!iter.is_end ()) { - unowned Attribute attr = iter.get (); - iter = iter.next (); - - var keys = new GLib.Sequence (); - foreach (var key in attr.args.get_keys ()) { - if (key == "cheader_filename" && sym is Namespace) { - continue; - } - keys.insert_sorted (key, (CompareDataFunc) strcmp); - } - if (need_cheaders && attr.name == "CCode" && !attr.has_argument ("cheader_filename")) { - keys.insert_sorted ("cheader_filename", (CompareDataFunc) strcmp); - } - - if (attr.name == "CCode" && keys.get_length () == 0) { - // only cheader_filename on namespace - continue; - } - - if (!(node is Parameter)) { - write_indent (); - } - - stream.printf ("[%s", attr.name); - if (keys.get_length () > 0) { - stream.printf (" ("); - - string separator = ""; - var arg_iter = keys.get_begin_iter (); - while (!arg_iter.is_end ()) { - unowned string arg_name = arg_iter.get (); - arg_iter = arg_iter.next (); - if (arg_name == "cheader_filename") { - stream.printf ("%scheader_filename = \"%s\"", separator, get_cheaders (sym)); - } else { - stream.printf ("%s%s = %s", separator, arg_name, attr.args.get (arg_name)); - } - separator = ", "; - } - - stream.printf (")"); - } - stream.printf ("]"); - if (node is Parameter) { - write_string (" "); - } else { - write_newline (); - } - } - } - - private void write_accessibility (Symbol sym) { - if (sym.access == SymbolAccessibility.PUBLIC) { - write_string ("public "); - } else if (sym.access == SymbolAccessibility.PROTECTED) { - write_string ("protected "); - } else if (sym.access == SymbolAccessibility.INTERNAL) { - write_string ("internal "); - } else if (sym.access == SymbolAccessibility.PRIVATE) { - write_string ("private "); - } - - if (type != CodeWriterType.EXTERNAL && sym.external && !sym.external_package) { - write_string ("extern "); - } - } - - void write_property_accessor_accessibility (Symbol sym) { - if (sym.access == SymbolAccessibility.PROTECTED) { - write_string (" protected"); - } else if (sym.access == SymbolAccessibility.INTERNAL) { - write_string (" internal"); - } else if (sym.access == SymbolAccessibility.PRIVATE) { - write_string (" private"); - } - } - - void write_type_parameters (List type_params) { - if (type_params.size > 0) { - write_string ("<"); - bool first = true; - foreach (TypeParameter type_param in type_params) { - if (first) { - first = false; - } else { - write_string (","); - } - write_identifier (type_param.name); - } - write_string (">"); - } - } -} - -public enum Vala.CodeWriterType { - EXTERNAL, - INTERNAL, - FAST, - DUMP -} diff --git a/tests/vala/valaparser.vala b/tests/vala/valaparser.vala deleted file mode 100644 index 76ea0b9..0000000 --- a/tests/vala/valaparser.vala +++ /dev/null @@ -1,3595 +0,0 @@ -/* valaparser.vala - * - * Copyright (C) 2006-2011 Jürg Billeter - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Author: - * Jürg Billeter - */ - -using GLib; - -/** - * Code visitor parsing all Vala source files. - */ -public class Vala.Parser : CodeVisitor { - Scanner scanner; - - CodeContext context; - - // token buffer - TokenInfo[] tokens; - // index of current token in buffer - int index; - // number of tokens in buffer - int size; - - Comment comment; - - const int BUFFER_SIZE = 32; - - static List _empty_type_parameter_list; - - struct TokenInfo { - public TokenType type; - public SourceLocation begin; - public SourceLocation end; - } - - enum ModifierFlags { - NONE, - ABSTRACT = 1 << 0, - CLASS = 1 << 1, - EXTERN = 1 << 2, - INLINE = 1 << 3, - NEW = 1 << 4, - OVERRIDE = 1 << 5, - STATIC = 1 << 6, - VIRTUAL = 1 << 7, - ASYNC = 1 << 8, - SEALED = 1 << 9 - } - - public Parser () { - tokens = new TokenInfo[BUFFER_SIZE]; - } - - /** - * Parses all .vala and .vapi source files in the specified code - * context and builds a code tree. - * - * @param context a code context - */ - public void parse (CodeContext context) { - this.context = context; - context.accept (this); - } - - public override void visit_source_file (SourceFile source_file) { - if (context.run_output || source_file.filename.has_suffix (".vala") || source_file.filename.has_suffix (".vapi")) { - parse_file (source_file); - } - } - - inline bool next () { - index = (index + 1) % BUFFER_SIZE; - size--; - if (size <= 0) { - SourceLocation begin, end; - TokenType type = scanner.read_token (out begin, out end); - tokens[index].type = type; - tokens[index].begin = begin; - tokens[index].end = end; - size = 1; - } - return (tokens[index].type != TokenType.EOF); - } - - inline void prev () { - index = (index - 1 + BUFFER_SIZE) % BUFFER_SIZE; - size++; - assert (size <= BUFFER_SIZE); - } - - inline TokenType current () { - return tokens[index].type; - } - - inline bool accept (TokenType type) { - if (current () == type) { - next (); - return true; - } - return false; - } - - string get_error (string msg) { - var begin = get_location (); - next (); - Report.error (get_src (begin), "syntax error, " + msg); - return msg; - } - - inline bool expect (TokenType type) throws ParseError { - if (accept (type)) { - return true; - } - - throw new ParseError.SYNTAX (get_error ("expected %s".printf (type.to_string ()))); - } - - inline SourceLocation get_location () { - return tokens[index].begin; - } - - string get_current_string () { - return ((string) tokens[index].begin.pos).substring (0, (int) (tokens[index].end.pos - tokens[index].begin.pos)); - } - - string get_last_string () { - int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE; - return ((string) tokens[last_index].begin.pos).substring (0, (int) (tokens[last_index].end.pos - tokens[last_index].begin.pos)); - } - - SourceReference get_src (SourceLocation begin) { - int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE; - - return new SourceReference (scanner.source_file, begin.line, begin.column, tokens[last_index].end.line, tokens[last_index].end.column); - } - - SourceReference get_current_src () { - return new SourceReference (scanner.source_file, tokens[index].begin.line, tokens[index].begin.column, tokens[index].end.line, tokens[index].end.column); - } - - SourceReference get_last_src () { - int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE; - - return new SourceReference (scanner.source_file, tokens[last_index].begin.line, tokens[last_index].begin.column, tokens[last_index].end.line, tokens[last_index].end.column); - } - - void rollback (SourceLocation location) { - while (tokens[index].begin.pos != location.pos) { - index = (index - 1 + BUFFER_SIZE) % BUFFER_SIZE; - size++; - if (size > BUFFER_SIZE) { - scanner.seek (location); - size = 0; - index = 0; - - next (); - } - } - } - - void skip_identifier () throws ParseError { - // also accept keywords as identifiers where there is no conflict - switch (current ()) { - case TokenType.ABSTRACT: - case TokenType.AS: - case TokenType.ASYNC: - case TokenType.BASE: - case TokenType.BREAK: - case TokenType.CASE: - case TokenType.CATCH: - case TokenType.CLASS: - case TokenType.CONST: - case TokenType.CONSTRUCT: - case TokenType.CONTINUE: - case TokenType.DEFAULT: - case TokenType.DELEGATE: - case TokenType.DELETE: - case TokenType.DO: - case TokenType.DYNAMIC: - case TokenType.ELSE: - case TokenType.ENUM: - case TokenType.ENSURES: - case TokenType.ERRORDOMAIN: - case TokenType.EXTERN: - case TokenType.FALSE: - case TokenType.FINALLY: - case TokenType.FOR: - case TokenType.FOREACH: - case TokenType.GET: - case TokenType.IDENTIFIER: - case TokenType.IF: - case TokenType.IN: - case TokenType.INLINE: - case TokenType.INTERFACE: - case TokenType.INTERNAL: - case TokenType.IS: - case TokenType.LOCK: - case TokenType.NAMESPACE: - case TokenType.NEW: - case TokenType.NULL: - case TokenType.OUT: - case TokenType.OVERRIDE: - case TokenType.OWNED: - case TokenType.PARAMS: - case TokenType.PRIVATE: - case TokenType.PROTECTED: - case TokenType.PUBLIC: - case TokenType.REF: - case TokenType.REQUIRES: - case TokenType.RETURN: - case TokenType.SEALED: - case TokenType.SET: - case TokenType.SIGNAL: - case TokenType.SIZEOF: - case TokenType.STATIC: - case TokenType.STRUCT: - case TokenType.SWITCH: - case TokenType.THIS: - case TokenType.THROW: - case TokenType.THROWS: - case TokenType.TRUE: - case TokenType.TRY: - case TokenType.TYPEOF: - case TokenType.UNOWNED: - case TokenType.USING: - case TokenType.VAR: - case TokenType.VIRTUAL: - case TokenType.VOID: - case TokenType.VOLATILE: - case TokenType.WEAK: - case TokenType.WHILE: - case TokenType.YIELD: - next (); - return; - case TokenType.INTEGER_LITERAL: - case TokenType.REAL_LITERAL: - // also accept integer and real literals - // as long as they contain at least one character - // and no decimal point - // for example, 2D and 3D - string id = get_current_string (); - if (id[id.length - 1].isalpha () && !("." in id)) { - next (); - return; - } - break; - default: - throw new ParseError.SYNTAX (get_error ("expected identifier")); - } - } - - string parse_identifier () throws ParseError { - skip_identifier (); - return get_last_string (); - } - - Expression parse_literal () throws ParseError { - var begin = get_location (); - - switch (current ()) { - case TokenType.TRUE: - next (); - return new BooleanLiteral (true, get_src (begin)); - case TokenType.FALSE: - next (); - return new BooleanLiteral (false, get_src (begin)); - case TokenType.INTEGER_LITERAL: - next (); - return new IntegerLiteral (get_last_string (), get_src (begin)); - case TokenType.REAL_LITERAL: - next (); - return new RealLiteral (get_last_string (), get_src (begin)); - case TokenType.CHARACTER_LITERAL: - next (); - // FIXME validate and unescape here and just pass unichar to CharacterLiteral - var lit = new CharacterLiteral (get_last_string (), get_src (begin)); - if (lit.error) { - Report.error (lit.source_reference, "invalid character literal"); - } - return lit; - case TokenType.REGEX_LITERAL: - next (); - string match_part = get_last_string (); - SourceReference src_begin = get_src (begin); - expect (TokenType.CLOSE_REGEX_LITERAL); - string close_token = get_last_string (); - return new RegexLiteral ("%s/%s".printf (close_token, match_part), src_begin); - case TokenType.STRING_LITERAL: - next (); - return new StringLiteral (get_last_string (), get_src (begin)); - case TokenType.TEMPLATE_STRING_LITERAL: - next (); - return new StringLiteral ("\"%s\"".printf (get_last_string ()), get_src (begin)); - case TokenType.VERBATIM_STRING_LITERAL: - next (); - string raw_string = get_last_string (); - string escaped_string = raw_string.substring (3, raw_string.length - 6).escape (""); - return new StringLiteral ("\"%s\"".printf (escaped_string), get_src (begin)); - case TokenType.NULL: - next (); - return new NullLiteral (get_src (begin)); - default: - throw new ParseError.SYNTAX (get_error ("expected literal")); - } - } - - public void parse_file (SourceFile source_file) { - scanner = new Scanner (source_file); - parse_file_comments (); - - index = -1; - size = 0; - - next (); - - - try { - parse_using_directives (context.root); - parse_declarations (context.root, true); - if (accept (TokenType.CLOSE_BRACE)) { - // only report error if it's not a secondary error - if (context.report.get_errors () == 0) { - Report.error (get_last_src (), "unexpected `}'"); - } - } - } catch (ParseError e) { - // already reported - } - - scanner = null; - } - - void parse_file_comments () { - scanner.parse_file_comments (); - } - - void skip_symbol_name () throws ParseError { - do { - skip_identifier (); - } while (accept (TokenType.DOT) || accept (TokenType.DOUBLE_COLON)); - } - - UnresolvedSymbol parse_symbol_name () throws ParseError { - var begin = get_location (); - UnresolvedSymbol sym = null; - do { - string name = parse_identifier (); - if (name == "global" && accept (TokenType.DOUBLE_COLON)) { - // global::Name - // qualified access to global symbol - name = parse_identifier (); - sym = new UnresolvedSymbol (sym, name, get_src (begin)); - sym.qualified = true; - continue; - } - sym = new UnresolvedSymbol (sym, name, get_src (begin)); - } while (accept (TokenType.DOT)); - return sym; - } - - void skip_type () throws ParseError { - accept (TokenType.DYNAMIC); - accept (TokenType.OWNED); - accept (TokenType.UNOWNED); - accept (TokenType.WEAK); - if (accept (TokenType.VOID)) { - } else { - skip_symbol_name (); - skip_type_argument_list (); - } - while (accept (TokenType.STAR)) { - } - accept (TokenType.INTERR); - while (accept (TokenType.OPEN_BRACKET)) { - do { - // required for decision between expression and declaration statement - if (current () != TokenType.COMMA && current () != TokenType.CLOSE_BRACKET) { - parse_expression (); - } - } while (accept (TokenType.COMMA)); - expect (TokenType.CLOSE_BRACKET); - accept (TokenType.INTERR); - } - accept (TokenType.OP_NEG); - accept (TokenType.HASH); - } - - DataType parse_type (bool owned_by_default, bool can_weak_ref) throws ParseError { - var begin = get_location (); - - bool is_dynamic = accept (TokenType.DYNAMIC); - - bool value_owned = owned_by_default; - - if (owned_by_default) { - if (context.profile == Profile.DOVA) { - if (can_weak_ref && accept (TokenType.WEAK)) { - value_owned = false; - } - } else if (accept (TokenType.UNOWNED)) { - value_owned = false; - } else if (accept (TokenType.WEAK)) { - if (!can_weak_ref && !context.deprecated) { - Report.warning (get_last_src (), "deprecated syntax, use `unowned` modifier"); - } - value_owned = false; - } - } else { - value_owned = (context.profile != Profile.DOVA && accept (TokenType.OWNED)); - } - - DataType type; - - if (!is_dynamic && value_owned == owned_by_default && accept (TokenType.VOID)) { - type = new VoidType (get_src (begin)); - } else { - var sym = parse_symbol_name (); - List type_arg_list = parse_type_argument_list (false); - - type = new UnresolvedType.from_symbol (sym, get_src (begin)); - if (type_arg_list != null) { - foreach (DataType type_arg in type_arg_list) { - type.add_type_argument (type_arg); - } - } - } - - while (accept (TokenType.STAR)) { - type = new PointerType (type, get_src (begin)); - } - - if (!(type is PointerType)) { - type.nullable = accept (TokenType.INTERR); - } - - // array brackets in types are read from right to left, - // this is more logical, especially when nullable arrays - // or pointers are involved - while (accept (TokenType.OPEN_BRACKET)) { - bool invalid_array = false; - int array_rank = 0; - do { - array_rank++; - // required for decision between expression and declaration statement - if (current () != TokenType.COMMA && current () != TokenType.CLOSE_BRACKET) { - parse_expression (); - // only used for parsing, reject use as real type - invalid_array = true; - } - } while (context.profile != Profile.DOVA && accept (TokenType.COMMA)); - expect (TokenType.CLOSE_BRACKET); - - // arrays contain strong references by default - type.value_owned = true; - - var array_type = new ArrayType (type, array_rank, get_src (begin)); - array_type.nullable = accept (TokenType.INTERR); - array_type.invalid_syntax = invalid_array; - - type = array_type; - } - - if (accept (TokenType.OP_NEG)) { - Report.warning (get_last_src (), "obsolete syntax, types are non-null by default"); - } - - if (!owned_by_default) { - if (context.profile != Profile.DOVA && accept (TokenType.HASH)) { - if (!context.deprecated) { - Report.warning (get_last_src (), "deprecated syntax, use `owned` modifier"); - } - value_owned = true; - } - } - - if (type is PointerType) { - value_owned = false; - } - - type.is_dynamic = is_dynamic; - type.value_owned = value_owned; - return type; - } - - DataType? parse_inline_array_type (DataType? type) throws ParseError { - var begin = get_location (); - - // inline-allocated array - if (type != null && accept (TokenType.OPEN_BRACKET)) { - int array_length = -1; - - if (current () != TokenType.CLOSE_BRACKET) { - if (current () != TokenType.INTEGER_LITERAL) { - throw new ParseError.SYNTAX (get_error ("expected `]' or integer literal")); - } - - var length_literal = (IntegerLiteral) parse_literal (); - array_length = int.parse (length_literal.value); - } - expect (TokenType.CLOSE_BRACKET); - - var array_type = new ArrayType (type, 1, get_src (begin)); - array_type.inline_allocated = true; - if (array_length > 0) { - array_type.fixed_length = true; - array_type.length = array_length; - } - array_type.value_owned = type.value_owned; - - return array_type; - } - return type; - } - - List parse_argument_list () throws ParseError { - var list = new ArrayList (); - if (current () != TokenType.CLOSE_PARENS) { - do { - list.add (parse_argument ()); - } while (accept (TokenType.COMMA)); - } - return list; - } - - Expression parse_argument () throws ParseError { - var begin = get_location (); - - if (accept (TokenType.REF)) { - var inner = parse_expression (); - return new UnaryExpression (UnaryOperator.REF, inner, get_src (begin)); - } else if (accept (TokenType.OUT)) { - var inner = parse_expression (); - return new UnaryExpression (UnaryOperator.OUT, inner, get_src (begin)); - } else { - var expr = parse_expression (); - var ma = expr as MemberAccess; - if (ma != null && ma.inner == null && accept (TokenType.COLON)) { - // named argument - expr = parse_expression (); - return new NamedArgument (ma.member_name, expr, get_src (begin)); - } else { - return expr; - } - } - } - - Expression parse_primary_expression () throws ParseError { - var begin = get_location (); - - Expression expr; - - switch (current ()) { - case TokenType.TRUE: - case TokenType.FALSE: - case TokenType.INTEGER_LITERAL: - case TokenType.REAL_LITERAL: - case TokenType.CHARACTER_LITERAL: - case TokenType.STRING_LITERAL: - case TokenType.REGEX_LITERAL: - case TokenType.TEMPLATE_STRING_LITERAL: - case TokenType.VERBATIM_STRING_LITERAL: - case TokenType.NULL: - expr = parse_literal (); - break; - case TokenType.OPEN_BRACE: - if (context.profile == Profile.DOVA) { - expr = parse_set_literal (); - } else { - expr = parse_initializer (); - } - break; - case TokenType.OPEN_BRACKET: - if (context.profile == Profile.DOVA) { - expr = parse_list_literal (); - } else { - expr = parse_simple_name (); - } - break; - case TokenType.OPEN_PARENS: - expr = parse_tuple (); - break; - case TokenType.OPEN_TEMPLATE: - expr = parse_template (); - break; - case TokenType.OPEN_REGEX_LITERAL: - expr = parse_regex_literal (); - break; - case TokenType.THIS: - expr = parse_this_access (); - break; - case TokenType.BASE: - expr = parse_base_access (); - break; - case TokenType.NEW: - expr = parse_object_or_array_creation_expression (); - break; - case TokenType.YIELD: - expr = parse_yield_expression (); - break; - case TokenType.SIZEOF: - expr = parse_sizeof_expression (); - break; - case TokenType.TYPEOF: - expr = parse_typeof_expression (); - break; - default: - expr = parse_simple_name (); - break; - } - - // process primary expressions that start with an inner primary expression - bool found = true; - while (found) { - switch (current ()) { - case TokenType.DOT: - expr = parse_member_access (begin, expr); - break; - case TokenType.OP_PTR: - if (context.profile == Profile.DOVA) { - found = false; - } else { - expr = parse_pointer_member_access (begin, expr); - } - break; - case TokenType.OPEN_PARENS: - expr = parse_method_call (begin, expr); - break; - case TokenType.OPEN_BRACKET: - expr = parse_element_access (begin, expr); - break; - case TokenType.OPEN_BRACE: - var ma = expr as MemberAccess; - if (context.profile == Profile.DOVA && ma != null) { - expr = parse_object_literal (begin, ma); - } else { - found = false; - } - break; - case TokenType.OP_INC: - expr = parse_post_increment_expression (begin, expr); - break; - case TokenType.OP_DEC: - expr = parse_post_decrement_expression (begin, expr); - break; - default: - found = false; - break; - } - } - - return expr; - } - - Expression parse_simple_name () throws ParseError { - var begin = get_location (); - string id = parse_identifier (); - bool qualified = false; - if (id == "global" && accept (TokenType.DOUBLE_COLON)) { - id = parse_identifier (); - qualified = true; - } - List type_arg_list = parse_type_argument_list (true); - var expr = new MemberAccess (null, id, get_src (begin)); - expr.qualified = qualified; - if (type_arg_list != null) { - foreach (DataType type_arg in type_arg_list) { - expr.add_type_argument (type_arg); - } - } - return expr; - } - - Expression parse_tuple () throws ParseError { - var begin = get_location (); - - expect (TokenType.OPEN_PARENS); - var expr_list = new ArrayList (); - if (current () != TokenType.CLOSE_PARENS) { - do { - expr_list.add (parse_expression ()); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - if (expr_list.size != 1) { - var tuple = new Tuple (get_src (begin)); - foreach (Expression expr in expr_list) { - tuple.add_expression (expr); - } - return tuple; - } - return expr_list.get (0); - } - - Expression parse_template () throws ParseError { - var begin = get_location (); - var template = new Template (); - - expect (TokenType.OPEN_TEMPLATE); - while (current () != TokenType.CLOSE_TEMPLATE) { - template.add_expression (parse_expression ()); - expect (TokenType.COMMA); - } - expect (TokenType.CLOSE_TEMPLATE); - - template.source_reference = get_src (begin); - return template; - } - - Expression parse_regex_literal () throws ParseError { - expect (TokenType.OPEN_REGEX_LITERAL); - - var expr = parse_literal (); - - return expr; - } - - Expression parse_member_access (SourceLocation begin, Expression inner) throws ParseError { - expect (TokenType.DOT); - string id = parse_identifier (); - List type_arg_list = parse_type_argument_list (true); - var expr = new MemberAccess (inner, id, get_src (begin)); - if (type_arg_list != null) { - foreach (DataType type_arg in type_arg_list) { - expr.add_type_argument (type_arg); - } - } - return expr; - } - - Expression parse_pointer_member_access (SourceLocation begin, Expression inner) throws ParseError { - expect (TokenType.OP_PTR); - string id = parse_identifier (); - List type_arg_list = parse_type_argument_list (true); - var expr = new MemberAccess.pointer (inner, id, get_src (begin)); - if (type_arg_list != null) { - foreach (DataType type_arg in type_arg_list) { - expr.add_type_argument (type_arg); - } - } - return expr; - } - - Expression parse_method_call (SourceLocation begin, Expression inner) throws ParseError { - expect (TokenType.OPEN_PARENS); - var arg_list = parse_argument_list (); - expect (TokenType.CLOSE_PARENS); - var init_list = parse_object_initializer (); - - if (init_list.size > 0 && inner is MemberAccess) { - // struct creation expression - var member = (MemberAccess) inner; - member.creation_member = true; - - var expr = new ObjectCreationExpression (member, get_src (begin)); - expr.struct_creation = true; - foreach (Expression arg in arg_list) { - expr.add_argument (arg); - } - foreach (MemberInitializer initializer in init_list) { - expr.add_member_initializer (initializer); - } - return expr; - } else { - var expr = new MethodCall (inner, get_src (begin)); - foreach (Expression arg in arg_list) { - expr.add_argument (arg); - } - return expr; - } - } - - Expression parse_element_access (SourceLocation begin, Expression inner) throws ParseError { - expect (TokenType.OPEN_BRACKET); - var index_list = parse_expression_list (); - Expression? stop = null; - if (index_list.size == 1 && accept (TokenType.COLON)) { - // slice expression - stop = parse_expression (); - } - expect (TokenType.CLOSE_BRACKET); - - if (stop == null) { - var expr = new ElementAccess (inner, get_src (begin)); - foreach (Expression index in index_list) { - expr.append_index (index); - } - return expr; - } else { - return new SliceExpression (inner, index_list[0], stop, get_src (begin)); - } - } - - List parse_expression_list () throws ParseError { - var list = new ArrayList (); - do { - list.add (parse_expression ()); - } while (accept (TokenType.COMMA)); - return list; - } - - Expression parse_this_access () throws ParseError { - var begin = get_location (); - expect (TokenType.THIS); - return new MemberAccess (null, "this", get_src (begin)); - } - - Expression parse_base_access () throws ParseError { - var begin = get_location (); - expect (TokenType.BASE); - return new BaseAccess (get_src (begin)); - } - - Expression parse_post_increment_expression (SourceLocation begin, Expression inner) throws ParseError { - expect (TokenType.OP_INC); - return new PostfixExpression (inner, true, get_src (begin)); - } - - Expression parse_post_decrement_expression (SourceLocation begin, Expression inner) throws ParseError { - expect (TokenType.OP_DEC); - return new PostfixExpression (inner, false, get_src (begin)); - } - - Expression parse_object_or_array_creation_expression () throws ParseError { - var begin = get_location (); - expect (TokenType.NEW); - var member = parse_member_name (); - if (accept (TokenType.OPEN_PARENS)) { - var expr = parse_object_creation_expression (begin, member); - return expr; - } else { - bool is_pointer_type = false; - while (accept (TokenType.STAR)) { - is_pointer_type = true; - } - if (!is_pointer_type) { - accept (TokenType.INTERR); - } - if (accept (TokenType.OPEN_BRACKET)) { - rollback (begin); - var expr = parse_array_creation_expression (); - return expr; - } else { - throw new ParseError.SYNTAX (get_error ("expected ( or [")); - } - } - } - - Expression parse_object_creation_expression (SourceLocation begin, MemberAccess member) throws ParseError { - member.creation_member = true; - var arg_list = parse_argument_list (); - expect (TokenType.CLOSE_PARENS); - var init_list = parse_object_initializer (); - - var expr = new ObjectCreationExpression (member, get_src (begin)); - foreach (Expression arg in arg_list) { - expr.add_argument (arg); - } - foreach (MemberInitializer initializer in init_list) { - expr.add_member_initializer (initializer); - } - return expr; - } - - Expression parse_object_literal (SourceLocation begin, MemberAccess member) throws ParseError { - member.creation_member = true; - - var expr = new ObjectCreationExpression (member, get_src (begin)); - - expect (TokenType.OPEN_BRACE); - - do { - var member_begin = get_location (); - string id = parse_identifier (); - expect (TokenType.COLON); - var member_expr = parse_expression (); - - expr.add_member_initializer (new MemberInitializer (id, member_expr, get_src (member_begin))); - } while (accept (TokenType.COMMA)); - - expect (TokenType.CLOSE_BRACE); - - return expr; - } - - Expression parse_array_creation_expression () throws ParseError { - var begin = get_location (); - expect (TokenType.NEW); - var member = parse_member_name (); - DataType element_type = UnresolvedType.new_from_expression (member); - bool is_pointer_type = false; - while (accept (TokenType.STAR)) { - element_type = new PointerType (element_type, get_src (begin)); - is_pointer_type = true; - } - if (!is_pointer_type) { - if (accept (TokenType.INTERR)) { - element_type.nullable = true; - } - } - expect (TokenType.OPEN_BRACKET); - - bool size_specified = false; - List size_specifier_list = null; - bool first = true; - do { - if (!first) { - // array of arrays: new T[][42] - - if (size_specified) { - throw new ParseError.SYNTAX (get_error ("size of inner arrays must not be specified in array creation expression")); - } - - element_type = new ArrayType (element_type, size_specifier_list.size, element_type.source_reference); - } else { - first = false; - } - - size_specifier_list = new ArrayList (); - do { - Expression size = null; - if (current () != TokenType.CLOSE_BRACKET && current () != TokenType.COMMA) { - size = parse_expression (); - size_specified = true; - } - size_specifier_list.add (size); - } while (context.profile != Profile.DOVA && accept (TokenType.COMMA)); - expect (TokenType.CLOSE_BRACKET); - } while (accept (TokenType.OPEN_BRACKET)); - - InitializerList initializer = null; - if (context.profile != Profile.DOVA && current () == TokenType.OPEN_BRACE) { - initializer = parse_initializer (); - } - var expr = new ArrayCreationExpression (element_type, size_specifier_list.size, initializer, get_src (begin)); - if (size_specified) { - foreach (Expression size in size_specifier_list) { - expr.append_size (size); - } - } - return expr; - } - - List parse_object_initializer () throws ParseError { - var list = new ArrayList (); - if (context.profile != Profile.DOVA && accept (TokenType.OPEN_BRACE)) { - do { - list.add (parse_member_initializer ()); - } while (accept (TokenType.COMMA)); - expect (TokenType.CLOSE_BRACE); - } - return list; - } - - MemberInitializer parse_member_initializer () throws ParseError { - var begin = get_location (); - string id = parse_identifier (); - expect (TokenType.ASSIGN); - var expr = parse_expression (); - - return new MemberInitializer (id, expr, get_src (begin)); - } - - Expression parse_yield_expression () throws ParseError { - expect (TokenType.YIELD); - var expr = parse_expression (); - - var call = expr as MethodCall; - if (call == null) { - Report.error (expr.source_reference, "syntax error, expected method call"); - throw new ParseError.SYNTAX ("expected method call"); - } - - call.is_yield_expression = true; - return call; - } - - Expression parse_sizeof_expression () throws ParseError { - var begin = get_location (); - expect (TokenType.SIZEOF); - expect (TokenType.OPEN_PARENS); - var type = parse_type (true, false); - expect (TokenType.CLOSE_PARENS); - - return new SizeofExpression (type, get_src (begin)); - } - - Expression parse_typeof_expression () throws ParseError { - var begin = get_location (); - expect (TokenType.TYPEOF); - expect (TokenType.OPEN_PARENS); - var type = parse_type (true, false); - expect (TokenType.CLOSE_PARENS); - - return new TypeofExpression (type, get_src (begin)); - } - - UnaryOperator get_unary_operator (TokenType token_type) { - switch (token_type) { - case TokenType.PLUS: return UnaryOperator.PLUS; - case TokenType.MINUS: return UnaryOperator.MINUS; - case TokenType.OP_NEG: return UnaryOperator.LOGICAL_NEGATION; - case TokenType.TILDE: return UnaryOperator.BITWISE_COMPLEMENT; - case TokenType.OP_INC: return UnaryOperator.INCREMENT; - case TokenType.OP_DEC: return UnaryOperator.DECREMENT; - default: return UnaryOperator.NONE; - } - } - - Expression parse_unary_expression () throws ParseError { - var begin = get_location (); - var operator = get_unary_operator (current ()); - if (operator != UnaryOperator.NONE) { - next (); - var op = parse_unary_expression (); - return new UnaryExpression (operator, op, get_src (begin)); - } - switch (current ()) { - case TokenType.HASH: - if (!context.deprecated) { - Report.warning (get_last_src (), "deprecated syntax, use `(owned)` cast"); - } - next (); - var op = parse_unary_expression (); - return new ReferenceTransferExpression (op, get_src (begin)); - case TokenType.OPEN_PARENS: - next (); - switch (current ()) { - case TokenType.OWNED: - // (owned) foo - next (); - if (accept (TokenType.CLOSE_PARENS)) { - var op = parse_unary_expression (); - return new ReferenceTransferExpression (op, get_src (begin)); - } - break; - case TokenType.VOID: - case TokenType.DYNAMIC: - case TokenType.IDENTIFIER: - var type = parse_type (true, false); - if (accept (TokenType.CLOSE_PARENS)) { - // check follower to decide whether to create cast expression - switch (current ()) { - case TokenType.OP_NEG: - case TokenType.TILDE: - case TokenType.OPEN_PARENS: - case TokenType.TRUE: - case TokenType.FALSE: - case TokenType.INTEGER_LITERAL: - case TokenType.REAL_LITERAL: - case TokenType.CHARACTER_LITERAL: - case TokenType.STRING_LITERAL: - case TokenType.TEMPLATE_STRING_LITERAL: - case TokenType.VERBATIM_STRING_LITERAL: - case TokenType.REGEX_LITERAL: - case TokenType.NULL: - case TokenType.THIS: - case TokenType.BASE: - case TokenType.NEW: - case TokenType.YIELD: - case TokenType.SIZEOF: - case TokenType.TYPEOF: - case TokenType.IDENTIFIER: - case TokenType.PARAMS: - var inner = parse_unary_expression (); - return new CastExpression (inner, type, get_src (begin), false); - default: - break; - } - } - break; - case TokenType.OP_NEG: - next (); - if (accept (TokenType.CLOSE_PARENS)) { - // (!) non-null cast - var inner = parse_unary_expression (); - return new CastExpression.non_null (inner, get_src (begin)); - } - break; - default: - break; - } - // no cast expression - rollback (begin); - break; - case TokenType.STAR: - next (); - var op = parse_unary_expression (); - return new PointerIndirection (op, get_src (begin)); - case TokenType.BITWISE_AND: - next (); - var op = parse_unary_expression (); - return new AddressofExpression (op, get_src (begin)); - default: - break; - } - - var expr = parse_primary_expression (); - return expr; - } - - BinaryOperator get_binary_operator (TokenType token_type) { - switch (token_type) { - case TokenType.STAR: return BinaryOperator.MUL; - case TokenType.DIV: return BinaryOperator.DIV; - case TokenType.PERCENT: return BinaryOperator.MOD; - case TokenType.PLUS: return BinaryOperator.PLUS; - case TokenType.MINUS: return BinaryOperator.MINUS; - case TokenType.OP_LT: return BinaryOperator.LESS_THAN; - case TokenType.OP_GT: return BinaryOperator.GREATER_THAN; - case TokenType.OP_LE: return BinaryOperator.LESS_THAN_OR_EQUAL; - case TokenType.OP_GE: return BinaryOperator.GREATER_THAN_OR_EQUAL; - case TokenType.OP_EQ: return BinaryOperator.EQUALITY; - case TokenType.OP_NE: return BinaryOperator.INEQUALITY; - default: return BinaryOperator.NONE; - } - } - - Expression parse_multiplicative_expression () throws ParseError { - var begin = get_location (); - var left = parse_unary_expression (); - bool found = true; - while (found) { - var operator = get_binary_operator (current ()); - switch (operator) { - case BinaryOperator.MUL: - case BinaryOperator.DIV: - case BinaryOperator.MOD: - next (); - var right = parse_unary_expression (); - left = new BinaryExpression (operator, left, right, get_src (begin)); - break; - default: - found = false; - break; - } - } - return left; - } - - Expression parse_additive_expression () throws ParseError { - var begin = get_location (); - var left = parse_multiplicative_expression (); - bool found = true; - while (found) { - var operator = get_binary_operator (current ()); - switch (operator) { - case BinaryOperator.PLUS: - case BinaryOperator.MINUS: - next (); - var right = parse_multiplicative_expression (); - left = new BinaryExpression (operator, left, right, get_src (begin)); - break; - default: - found = false; - break; - } - } - return left; - } - - Expression parse_shift_expression () throws ParseError { - var begin = get_location (); - var left = parse_additive_expression (); - bool found = true; - while (found) { - switch (current ()) { - case TokenType.OP_SHIFT_LEFT: - next (); - var right = parse_additive_expression (); - left = new BinaryExpression (BinaryOperator.SHIFT_LEFT, left, right, get_src (begin)); - break; - // don't use OP_SHIFT_RIGHT to support >> for nested generics - case TokenType.OP_GT: - char* first_gt_pos = tokens[index].begin.pos; - next (); - // only accept >> when there is no space between the two > signs - if (current () == TokenType.OP_GT && tokens[index].begin.pos == first_gt_pos + 1) { - next (); - var right = parse_additive_expression (); - left = new BinaryExpression (BinaryOperator.SHIFT_RIGHT, left, right, get_src (begin)); - } else { - prev (); - found = false; - } - break; - default: - found = false; - break; - } - } - return left; - } - - Expression parse_relational_expression () throws ParseError { - var begin = get_location (); - var left = parse_shift_expression (); - - bool first = true; - bool found = true; - while (found) { - var operator = get_binary_operator (current ()); - switch (operator) { - case BinaryOperator.LESS_THAN: - case BinaryOperator.LESS_THAN_OR_EQUAL: - case BinaryOperator.GREATER_THAN_OR_EQUAL: - next (); - var right = parse_shift_expression (); - left = new BinaryExpression (operator, left, right, get_src (begin)); - if (!first) { - var be = (BinaryExpression) left; - be.chained = true; - if (!context.experimental) { - Report.warning (left.source_reference, "chained relational expressions are experimental"); - } - } - first = false; - break; - case BinaryOperator.GREATER_THAN: - next (); - // ignore >> and >>= (two tokens due to generics) - if (current () != TokenType.OP_GT && current () != TokenType.OP_GE) { - var right = parse_shift_expression (); - left = new BinaryExpression (operator, left, right, get_src (begin)); - if (!first) { - var be = (BinaryExpression) left; - be.chained = true; - if (!context.experimental) { - Report.warning (left.source_reference, "chained relational expressions are experimental"); - } - } - first = false; - } else { - prev (); - found = false; - } - break; - default: - switch (current ()) { - case TokenType.IS: - next (); - var type = parse_type (true, false); - left = new TypeCheck (left, type, get_src (begin)); - break; - case TokenType.AS: - next (); - var type = parse_type (true, false); - left = new CastExpression (left, type, get_src (begin), true); - break; - default: - found = false; - break; - } - break; - } - } - return left; - } - - Expression parse_equality_expression () throws ParseError { - var begin = get_location (); - var left = parse_relational_expression (); - bool found = true; - while (found) { - var operator = get_binary_operator (current ()); - switch (operator) { - case BinaryOperator.EQUALITY: - case BinaryOperator.INEQUALITY: - next (); - var right = parse_relational_expression (); - left = new BinaryExpression (operator, left, right, get_src (begin)); - break; - default: - found = false; - break; - } - } - return left; - } - - Expression parse_and_expression () throws ParseError { - var begin = get_location (); - var left = parse_equality_expression (); - while (accept (TokenType.BITWISE_AND)) { - var right = parse_equality_expression (); - left = new BinaryExpression (BinaryOperator.BITWISE_AND, left, right, get_src (begin)); - } - return left; - } - - Expression parse_exclusive_or_expression () throws ParseError { - var begin = get_location (); - var left = parse_and_expression (); - while (accept (TokenType.CARRET)) { - var right = parse_and_expression (); - left = new BinaryExpression (BinaryOperator.BITWISE_XOR, left, right, get_src (begin)); - } - return left; - } - - Expression parse_inclusive_or_expression () throws ParseError { - var begin = get_location (); - var left = parse_exclusive_or_expression (); - while (accept (TokenType.BITWISE_OR)) { - var right = parse_exclusive_or_expression (); - left = new BinaryExpression (BinaryOperator.BITWISE_OR, left, right, get_src (begin)); - } - return left; - } - - Expression parse_in_expression () throws ParseError { - var begin = get_location (); - var left = parse_inclusive_or_expression (); - while (accept (TokenType.IN)) { - var right = parse_inclusive_or_expression (); - left = new BinaryExpression (BinaryOperator.IN, left, right, get_src (begin)); - } - return left; - } - - Expression parse_conditional_and_expression () throws ParseError { - var begin = get_location (); - var left = parse_in_expression (); - while (accept (TokenType.OP_AND)) { - var right = parse_in_expression (); - left = new BinaryExpression (BinaryOperator.AND, left, right, get_src (begin)); - } - return left; - } - - Expression parse_conditional_or_expression () throws ParseError { - var begin = get_location (); - var left = parse_conditional_and_expression (); - while (accept (TokenType.OP_OR)) { - var right = parse_conditional_and_expression (); - left = new BinaryExpression (BinaryOperator.OR, left, right, get_src (begin)); - } - return left; - } - - Expression parse_coalescing_expression () throws ParseError { - var begin = get_location (); - var left = parse_conditional_or_expression (); - if (accept (TokenType.OP_COALESCING)) { - var right = parse_coalescing_expression (); - return new BinaryExpression (BinaryOperator.COALESCE, left, right, get_src (begin)); - } else { - return left; - } - } - - Expression parse_conditional_expression () throws ParseError { - var begin = get_location (); - var condition = parse_coalescing_expression (); - if (accept (TokenType.INTERR)) { - var true_expr = parse_expression (); - expect (TokenType.COLON); - var false_expr = parse_expression (); - return new ConditionalExpression (condition, true_expr, false_expr, get_src (begin)); - } else { - return condition; - } - } - - Parameter parse_lambda_parameter () throws ParseError { - var begin = get_location (); - var direction = ParameterDirection.IN; - if (accept (TokenType.OUT)) { - direction = ParameterDirection.OUT; - } else if (accept (TokenType.REF)) { - direction = ParameterDirection.REF; - } - - string id = parse_identifier (); - - var param = new Parameter (id, null, get_src (begin)); - param.direction = direction; - return param; - } - - Expression parse_lambda_expression () throws ParseError { - var begin = get_location (); - List params = new ArrayList (); - if (accept (TokenType.OPEN_PARENS)) { - if (current () != TokenType.CLOSE_PARENS) { - do { - params.add (parse_lambda_parameter ()); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - } else { - params.add (parse_lambda_parameter ()); - } - expect (TokenType.LAMBDA); - - LambdaExpression lambda; - if (current () == TokenType.OPEN_BRACE) { - var block = parse_block (); - lambda = new LambdaExpression.with_statement_body (block, get_src (begin)); - } else { - var expr = parse_expression (); - lambda = new LambdaExpression (expr, get_src (begin)); - } - foreach (var param in params) { - lambda.add_parameter (param); - } - return lambda; - } - - AssignmentOperator get_assignment_operator (TokenType token_type) { - switch (token_type) { - case TokenType.ASSIGN: return AssignmentOperator.SIMPLE; - case TokenType.ASSIGN_ADD: return AssignmentOperator.ADD; - case TokenType.ASSIGN_SUB: return AssignmentOperator.SUB; - case TokenType.ASSIGN_BITWISE_OR: return AssignmentOperator.BITWISE_OR; - case TokenType.ASSIGN_BITWISE_AND: return AssignmentOperator.BITWISE_AND; - case TokenType.ASSIGN_BITWISE_XOR: return AssignmentOperator.BITWISE_XOR; - case TokenType.ASSIGN_DIV: return AssignmentOperator.DIV; - case TokenType.ASSIGN_MUL: return AssignmentOperator.MUL; - case TokenType.ASSIGN_PERCENT: return AssignmentOperator.PERCENT; - case TokenType.ASSIGN_SHIFT_LEFT: return AssignmentOperator.SHIFT_LEFT; - default: return AssignmentOperator.NONE; - } - } - - Expression parse_expression () throws ParseError { - if (is_lambda_expression ()) { - return parse_lambda_expression (); - } - - var begin = get_location (); - - Expression expr = parse_conditional_expression (); - - while (true) { - var operator = get_assignment_operator (current ()); - if (operator != AssignmentOperator.NONE) { - next (); - var rhs = parse_expression (); - expr = new Assignment (expr, rhs, operator, get_src (begin)); - } else if (current () == TokenType.OP_GT) { // >>= - char* first_gt_pos = tokens[index].begin.pos; - next (); - // only accept >>= when there is no space between the two > signs - if (current () == TokenType.OP_GE && tokens[index].begin.pos == first_gt_pos + 1) { - next (); - var rhs = parse_expression (); - expr = new Assignment (expr, rhs, AssignmentOperator.SHIFT_RIGHT, get_src (begin)); - } else { - prev (); - break; - } - } else { - break; - } - } - - return expr; - } - - void parse_statements (Block block) throws ParseError { - while (current () != TokenType.CLOSE_BRACE - && current () != TokenType.CASE - && current () != TokenType.DEFAULT - && current () != TokenType.EOF) { - try { - Statement stmt = null; - bool is_decl = false; - - comment = scanner.pop_comment (); - switch (current ()) { - case TokenType.OPEN_BRACE: - stmt = parse_block (); - break; - case TokenType.SEMICOLON: - stmt = parse_empty_statement (); - break; - case TokenType.IF: - stmt = parse_if_statement (); - break; - case TokenType.SWITCH: - stmt = parse_switch_statement (); - break; - case TokenType.WHILE: - stmt = parse_while_statement (); - break; - case TokenType.DO: - stmt = parse_do_statement (); - break; - case TokenType.FOR: - stmt = parse_for_statement (); - break; - case TokenType.FOREACH: - stmt = parse_foreach_statement (); - break; - case TokenType.BREAK: - stmt = parse_break_statement (); - break; - case TokenType.CONTINUE: - stmt = parse_continue_statement (); - break; - case TokenType.RETURN: - stmt = parse_return_statement (); - break; - case TokenType.YIELD: - stmt = parse_yield_statement (); - break; - case TokenType.THROW: - stmt = parse_throw_statement (); - break; - case TokenType.TRY: - stmt = parse_try_statement (); - break; - case TokenType.LOCK: - stmt = parse_lock_statement (); - break; - case TokenType.DELETE: - stmt = parse_delete_statement (); - break; - case TokenType.VAR: - is_decl = true; - parse_local_variable_declarations (block); - break; - case TokenType.CONST: - is_decl = true; - parse_local_constant_declarations (block); - break; - case TokenType.OP_INC: - case TokenType.OP_DEC: - case TokenType.BASE: - case TokenType.THIS: - case TokenType.OPEN_PARENS: - case TokenType.STAR: - case TokenType.NEW: - stmt = parse_expression_statement (); - break; - default: - bool is_expr = is_expression (); - if (is_expr) { - stmt = parse_expression_statement (); - } else { - is_decl = true; - parse_local_variable_declarations (block); - } - break; - } - - if (!is_decl) { - if (context.profile == Profile.DOVA && stmt is ReturnStatement) { - // split - // return foo; - // into - // result = foo; - // return; - var ret_stmt = (ReturnStatement) stmt; - if (ret_stmt.return_expression != null) { - var assignment = new Assignment (new MemberAccess.simple ("result", stmt.source_reference), ret_stmt.return_expression, AssignmentOperator.SIMPLE, stmt.source_reference); - ret_stmt.return_expression = null; - block.add_statement (new ExpressionStatement (assignment, stmt.source_reference)); - } - } - block.add_statement (stmt); - } - } catch (ParseError e) { - if (recover () != RecoveryState.STATEMENT_BEGIN) { - // beginning of next declaration or end of file reached - // return what we have so far - break; - } - } - } - } - - bool is_expression () throws ParseError { - var begin = get_location (); - - // decide between declaration and expression statement - skip_type (); - switch (current ()) { - // invocation expression - case TokenType.OPEN_PARENS: - // postfix increment - case TokenType.OP_INC: - // postfix decrement - case TokenType.OP_DEC: - // assignments - case TokenType.ASSIGN: - case TokenType.ASSIGN_ADD: - case TokenType.ASSIGN_BITWISE_AND: - case TokenType.ASSIGN_BITWISE_OR: - case TokenType.ASSIGN_BITWISE_XOR: - case TokenType.ASSIGN_DIV: - case TokenType.ASSIGN_MUL: - case TokenType.ASSIGN_PERCENT: - case TokenType.ASSIGN_SHIFT_LEFT: - case TokenType.ASSIGN_SUB: - case TokenType.OP_GT: // >>= - // member access - case TokenType.DOT: - // pointer member access - case TokenType.OP_PTR: - rollback (begin); - return true; - default: - rollback (begin); - return false; - } - } - - bool is_lambda_expression () { - var begin = get_location (); - - switch (current ()) { - case TokenType.OUT: - case TokenType.REF: - next (); - if (accept (TokenType.IDENTIFIER) && accept (TokenType.LAMBDA)) { - rollback (begin); - return true; - } - break; - case TokenType.IDENTIFIER: - next (); - if (accept (TokenType.LAMBDA)) { - rollback (begin); - return true; - } - break; - case TokenType.OPEN_PARENS: - next (); - if (current () != TokenType.CLOSE_PARENS) { - do { - if (current () == TokenType.OUT || current () == TokenType.REF) { - next (); - } - if (!accept (TokenType.IDENTIFIER)) { - rollback (begin); - return false; - } - } while (accept (TokenType.COMMA)); - } - if (accept (TokenType.CLOSE_PARENS) && accept (TokenType.LAMBDA)) { - rollback (begin); - return true; - } - break; - } - - rollback (begin); - return false; - } - - Block parse_embedded_statement () throws ParseError { - if (current () == TokenType.OPEN_BRACE) { - var block = parse_block (); - return block; - } - - comment = scanner.pop_comment (); - - var block = new Block (get_src (get_location ())); - - var stmt = parse_embedded_statement_without_block (); - if (context.profile == Profile.DOVA && stmt is ReturnStatement) { - // split - // return foo; - // into - // result = foo; - // return; - var ret_stmt = (ReturnStatement) stmt; - if (ret_stmt.return_expression != null) { - var assignment = new Assignment (new MemberAccess.simple ("result"), ret_stmt.return_expression); - ret_stmt.return_expression = null; - block.add_statement (new ExpressionStatement (assignment)); - } - } - block.add_statement (stmt); - - return block; - - } - - Statement parse_embedded_statement_without_block () throws ParseError { - switch (current ()) { - case TokenType.SEMICOLON: return parse_empty_statement (); - case TokenType.IF: return parse_if_statement (); - case TokenType.SWITCH: return parse_switch_statement (); - case TokenType.WHILE: return parse_while_statement (); - case TokenType.DO: return parse_do_statement (); - case TokenType.FOR: return parse_for_statement (); - case TokenType.FOREACH: return parse_foreach_statement (); - case TokenType.BREAK: return parse_break_statement (); - case TokenType.CONTINUE: return parse_continue_statement (); - case TokenType.RETURN: return parse_return_statement (); - case TokenType.YIELD: return parse_yield_statement (); - case TokenType.THROW: return parse_throw_statement (); - case TokenType.TRY: return parse_try_statement (); - case TokenType.LOCK: return parse_lock_statement (); - case TokenType.DELETE: return parse_delete_statement (); - case TokenType.VAR: - case TokenType.CONST: - throw new ParseError.SYNTAX (get_error ("embedded statement cannot be declaration ")); - case TokenType.OP_INC: - case TokenType.OP_DEC: - case TokenType.BASE: - case TokenType.THIS: - case TokenType.OPEN_PARENS: - case TokenType.STAR: - case TokenType.NEW: - return parse_expression_statement (); - default: - if (is_expression ()) { - return parse_expression_statement (); - } else { - throw new ParseError.SYNTAX (get_error ("embedded statement cannot be declaration")); - } - } - } - - Block parse_block () throws ParseError { - var begin = get_location (); - expect (TokenType.OPEN_BRACE); - var block = new Block (get_src (begin)); - parse_statements (block); - if (!accept (TokenType.CLOSE_BRACE)) { - // only report error if it's not a secondary error - if (context.report.get_errors () == 0) { - Report.error (get_current_src (), "expected `}'"); - } - } - - block.source_reference.last_line = get_current_src ().last_line; - block.source_reference.last_column = get_current_src ().last_column; - - return block; - } - - Statement parse_empty_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.SEMICOLON); - return new EmptyStatement (get_src (begin)); - } - - void parse_local_variable_declarations (Block block) throws ParseError { - DataType variable_type; - if (accept (TokenType.VAR)) { - variable_type = null; - } else { - variable_type = parse_type (true, true); - } - do { - if (variable_type == null && accept (TokenType.OPEN_PARENS)) { - // tuple - var begin = get_location (); - - string[] identifiers = {}; - do { - identifiers += parse_identifier (); - } while (accept (TokenType.COMMA)); - expect (TokenType.CLOSE_PARENS); - - expect (TokenType.ASSIGN); - var tuple = parse_expression (); - var tuple_local = new LocalVariable (null, CodeNode.get_temp_name (), tuple, get_src (begin)); - block.add_statement (new DeclarationStatement (tuple_local, tuple_local.source_reference)); - - for (int i = 0; i < identifiers.length; i++) { - var temp_access = new MemberAccess.simple (tuple_local.name, tuple_local.source_reference); - var ea = new ElementAccess (temp_access, tuple_local.source_reference); - ea.append_index (new IntegerLiteral (i.to_string ())); - var local = new LocalVariable (null, identifiers[i], ea, tuple_local.source_reference); - block.add_statement (new DeclarationStatement (local, local.source_reference)); - } - - continue; - } - - DataType type_copy = null; - if (variable_type != null) { - type_copy = variable_type.copy (); - } - var local = parse_local_variable (type_copy); - block.add_statement (new DeclarationStatement (local, local.source_reference)); - } while (accept (TokenType.COMMA)); - expect (TokenType.SEMICOLON); - } - - LocalVariable parse_local_variable (DataType? variable_type) throws ParseError { - var begin = get_location (); - string id = parse_identifier (); - - var type = parse_inline_array_type (variable_type); - - Expression initializer = null; - if (accept (TokenType.ASSIGN)) { - initializer = parse_expression (); - } - return new LocalVariable (type, id, initializer, get_src (begin)); - } - - void parse_local_constant_declarations (Block block) throws ParseError { - expect (TokenType.CONST); - var constant_type = parse_type (false, false); - do { - DataType type_copy = constant_type.copy (); - var local = parse_local_constant (type_copy); - block.add_statement (new DeclarationStatement (local, local.source_reference)); - block.add_local_constant (local); - local.active = false; - } while (accept (TokenType.COMMA)); - expect (TokenType.SEMICOLON); - } - - Constant parse_local_constant (DataType constant_type) throws ParseError { - var begin = get_location (); - string id = parse_identifier (); - - var type = parse_inline_array_type (constant_type); - - expect (TokenType.ASSIGN); - var initializer = parse_expression (); - - return new Constant (id, type, initializer, get_src (begin)); - } - - Statement parse_expression_statement () throws ParseError { - var begin = get_location (); - var expr = parse_statement_expression (); - expect (TokenType.SEMICOLON); - return new ExpressionStatement (expr, get_src (begin)); - } - - Expression parse_statement_expression () throws ParseError { - // invocation expression, assignment, - // or pre/post increment/decrement expression - var expr = parse_expression (); - return expr; - } - - Statement parse_if_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.IF); - expect (TokenType.OPEN_PARENS); - var condition = parse_expression (); - expect (TokenType.CLOSE_PARENS); - var src = get_src (begin); - var true_stmt = parse_embedded_statement (); - Block false_stmt = null; - if (accept (TokenType.ELSE)) { - false_stmt = parse_embedded_statement (); - } - return new IfStatement (condition, true_stmt, false_stmt, src); - } - - Statement parse_switch_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.SWITCH); - expect (TokenType.OPEN_PARENS); - var condition = parse_expression (); - expect (TokenType.CLOSE_PARENS); - var stmt = new SwitchStatement (condition, get_src (begin)); - expect (TokenType.OPEN_BRACE); - while (current () != TokenType.CLOSE_BRACE) { - var section = new SwitchSection (get_src (begin)); - do { - if (accept (TokenType.CASE)) { - section.add_label (new SwitchLabel (parse_expression (), get_src (begin))); - } else { - expect (TokenType.DEFAULT); - section.add_label (new SwitchLabel.with_default (get_src (begin))); - } - expect (TokenType.COLON); - } while (current () == TokenType.CASE || current () == TokenType.DEFAULT); - parse_statements (section); - stmt.add_section (section); - } - expect (TokenType.CLOSE_BRACE); - return stmt; - } - - Statement parse_while_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.WHILE); - expect (TokenType.OPEN_PARENS); - var condition = parse_expression (); - expect (TokenType.CLOSE_PARENS); - var body = parse_embedded_statement (); - return new WhileStatement (condition, body, get_src (begin)); - } - - Statement parse_do_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.DO); - var body = parse_embedded_statement (); - expect (TokenType.WHILE); - expect (TokenType.OPEN_PARENS); - var condition = parse_expression (); - expect (TokenType.CLOSE_PARENS); - expect (TokenType.SEMICOLON); - return new DoStatement (body, condition, get_src (begin)); - } - - Statement parse_for_statement () throws ParseError { - var begin = get_location (); - Block block = null; - expect (TokenType.FOR); - expect (TokenType.OPEN_PARENS); - var initializer_list = new ArrayList (); - if (!accept (TokenType.SEMICOLON)) { - bool is_expr; - switch (current ()) { - case TokenType.VAR: - is_expr = false; - break; - case TokenType.OP_INC: - case TokenType.OP_DEC: - is_expr = true; - break; - default: - is_expr = is_expression (); - break; - } - - if (is_expr) { - do { - initializer_list.add (parse_statement_expression ()); - } while (accept (TokenType.COMMA)); - expect (TokenType.SEMICOLON); - } else { - // variable declaration in initializer - block = new Block (get_src (begin)); - parse_local_variable_declarations (block); - } - } - Expression condition = null; - if (current () != TokenType.SEMICOLON) { - condition = parse_expression (); - } - expect (TokenType.SEMICOLON); - var iterator_list = new ArrayList (); - if (current () != TokenType.CLOSE_PARENS) { - do { - iterator_list.add (parse_statement_expression ()); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - var src = get_src (begin); - var body = parse_embedded_statement (); - var stmt = new ForStatement (condition, body, src); - foreach (Expression init in initializer_list) { - stmt.add_initializer (init); - } - foreach (Expression iter in iterator_list) { - stmt.add_iterator (iter); - } - if (block != null) { - block.add_statement (stmt); - return block; - } else { - return stmt; - } - } - - Statement parse_foreach_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.FOREACH); - expect (TokenType.OPEN_PARENS); - DataType type = null; - if (!accept (TokenType.VAR)) { - type = parse_type (true, true); - if (accept (TokenType.IN)) { - Report.error (type.source_reference, "syntax error, expected var or type"); - throw new ParseError.SYNTAX ("expected var or type"); - } - } - string id = parse_identifier (); - expect (TokenType.IN); - var collection = parse_expression (); - expect (TokenType.CLOSE_PARENS); - var src = get_src (begin); - var body = parse_embedded_statement (); - return new ForeachStatement (type, id, collection, body, src); - } - - Statement parse_break_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.BREAK); - expect (TokenType.SEMICOLON); - return new BreakStatement (get_src (begin)); - } - - Statement parse_continue_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.CONTINUE); - expect (TokenType.SEMICOLON); - return new ContinueStatement (get_src (begin)); - } - - Statement parse_return_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.RETURN); - Expression expr = null; - if (current () != TokenType.SEMICOLON) { - expr = parse_expression (); - } - expect (TokenType.SEMICOLON); - return new ReturnStatement (expr, get_src (begin)); - } - - Statement parse_yield_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.YIELD); - if (current () != TokenType.SEMICOLON && current () != TokenType.RETURN) { - // yield expression - prev (); - return parse_expression_statement (); - } - Expression expr = null; - if (accept (TokenType.RETURN)) { - expr = parse_expression (); - } - expect (TokenType.SEMICOLON); - return new YieldStatement (expr, get_src (begin)); - } - - Statement parse_throw_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.THROW); - var expr = parse_expression (); - expect (TokenType.SEMICOLON); - return new ThrowStatement (expr, get_src (begin)); - } - - Statement parse_try_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.TRY); - var try_block = parse_block (); - Block finally_clause = null; - var catch_clauses = new ArrayList (); - if (current () == TokenType.CATCH) { - parse_catch_clauses (catch_clauses); - if (current () == TokenType.FINALLY) { - finally_clause = parse_finally_clause (); - } - } else { - finally_clause = parse_finally_clause (); - } - var stmt = new TryStatement (try_block, finally_clause, get_src (begin)); - foreach (CatchClause clause in catch_clauses) { - stmt.add_catch_clause (clause); - } - return stmt; - } - - void parse_catch_clauses (List catch_clauses) throws ParseError { - while (accept (TokenType.CATCH)) { - var begin = get_location (); - DataType type = null; - string id = null; - if (accept (TokenType.OPEN_PARENS)) { - type = parse_type (true, true); - id = parse_identifier (); - expect (TokenType.CLOSE_PARENS); - } - var block = parse_block (); - catch_clauses.add (new CatchClause (type, id, block, get_src (begin))); - } - } - - Block parse_finally_clause () throws ParseError { - expect (TokenType.FINALLY); - var block = parse_block (); - return block; - } - - Statement parse_lock_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.LOCK); - expect (TokenType.OPEN_PARENS); - var expr = parse_expression (); - expect (TokenType.CLOSE_PARENS); - var stmt = parse_embedded_statement (); - return new LockStatement (expr, stmt, get_src (begin)); - } - - Statement parse_delete_statement () throws ParseError { - var begin = get_location (); - expect (TokenType.DELETE); - var expr = parse_expression (); - expect (TokenType.SEMICOLON); - return new DeleteStatement (expr, get_src (begin)); - } - - string parse_attribute_value () throws ParseError { - switch (current ()) { - case TokenType.NULL: - case TokenType.TRUE: - case TokenType.FALSE: - case TokenType.INTEGER_LITERAL: - case TokenType.REAL_LITERAL: - case TokenType.STRING_LITERAL: - next (); - return get_last_string (); - case TokenType.MINUS: - next (); - switch (current ()) { - case TokenType.INTEGER_LITERAL: - case TokenType.REAL_LITERAL: - next (); - return "-" + get_last_string (); - default: - throw new ParseError.SYNTAX (get_error ("expected number")); - } - default: - throw new ParseError.SYNTAX (get_error ("expected literal")); - } - } - - List? parse_attributes () throws ParseError { - if (current () != TokenType.OPEN_BRACKET) { - return null; - } - var attrs = new ArrayList (); - while (accept (TokenType.OPEN_BRACKET)) { - do { - var begin = get_location (); - string id = parse_identifier (); - var attr = new Attribute (id, get_src (begin)); - if (accept (TokenType.OPEN_PARENS)) { - if (current () != TokenType.CLOSE_PARENS) { - do { - id = parse_identifier (); - expect (TokenType.ASSIGN); - attr.add_argument (id, parse_attribute_value ()); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - } - attrs.add (attr); - } while (accept (TokenType.COMMA)); - expect (TokenType.CLOSE_BRACKET); - } - return attrs; - } - - void set_attributes (CodeNode node, List? attributes) { - if (attributes != null) { - foreach (Attribute attr in (List) attributes) { - if (node.get_attribute (attr.name) != null) { - Report.error (attr.source_reference, "duplicate attribute `%s`".printf (attr.name)); - } - node.attributes.append (attr); - } - } - } - - void parse_main_block (Symbol parent) throws ParseError { - var begin = get_location (); - - var method = new Method ("main", new VoidType (), get_src (begin)); - method.body = new Block (get_src (begin)); - parse_statements (method.body); - if (current () != TokenType.EOF) { - Report.error (get_current_src (), "expected end of file"); - } - - method.body.source_reference.last_line = get_current_src ().last_line; - method.body.source_reference.last_column = get_current_src ().last_column; - - if (!context.experimental && context.profile != Profile.DOVA) { - Report.warning (method.source_reference, "main blocks are experimental"); - } - - parent.add_method (method); - } - - void parse_declaration (Symbol parent, bool root = false) throws ParseError { - comment = scanner.pop_comment (); - var attrs = parse_attributes (); - - var begin = get_location (); - - TokenType last_keyword = current (); - - while (is_declaration_keyword (current ())) { - last_keyword = current (); - next (); - } - - switch (current ()) { - case TokenType.CONSTRUCT: - if (context.profile == Profile.GOBJECT) { - rollback (begin); - parse_constructor_declaration (parent, attrs); - return; - } - break; - case TokenType.TILDE: - rollback (begin); - parse_destructor_declaration (parent, attrs); - return; - case TokenType.OPEN_BRACE: - case TokenType.SEMICOLON: - case TokenType.IF: - case TokenType.SWITCH: - case TokenType.WHILE: - case TokenType.DO: - case TokenType.FOR: - case TokenType.FOREACH: - case TokenType.BREAK: - case TokenType.CONTINUE: - case TokenType.RETURN: - case TokenType.YIELD: - case TokenType.THROW: - case TokenType.TRY: - case TokenType.LOCK: - case TokenType.DELETE: - case TokenType.VAR: - case TokenType.OP_INC: - case TokenType.OP_DEC: - case TokenType.BASE: - case TokenType.THIS: - case TokenType.OPEN_PARENS: - case TokenType.STAR: - case TokenType.NEW: - // statement - if (attrs != null) { - // no attributes allowed before statements - throw new ParseError.SYNTAX (get_error ("expected statement")); - } - if (!root) { - throw new ParseError.SYNTAX (get_error ("statements outside blocks allowed only in root namespace")); - } - rollback (begin); - parse_main_block (parent); - return; - default: - if (root) { - bool is_expr = is_expression (); - if (is_expr) { - rollback (begin); - parse_main_block (parent); - return; - } - } - - skip_type (); - switch (current ()) { - case TokenType.OPEN_BRACE: - case TokenType.SEMICOLON: - case TokenType.COLON: - rollback (begin); - switch (last_keyword) { - case TokenType.CLASS: - parse_class_declaration (parent, attrs); - return; - case TokenType.ENUM: - parse_enum_declaration (parent, attrs); - return; - case TokenType.ERRORDOMAIN: - parse_errordomain_declaration (parent, attrs); - return; - case TokenType.INTERFACE: - parse_interface_declaration (parent, attrs); - return; - case TokenType.NAMESPACE: - parse_namespace_declaration (parent, attrs); - return; - case TokenType.STRUCT: - parse_struct_declaration (parent, attrs); - return; - default: - break; - } - break; - case TokenType.OPEN_PARENS: - rollback (begin); - parse_creation_method_declaration (parent, attrs); - return; - default: - skip_type (); // might contain type parameter list - switch (current ()) { - case TokenType.OPEN_PARENS: - rollback (begin); - switch (last_keyword) { - case TokenType.DELEGATE: - parse_delegate_declaration (parent, attrs); - return; - case TokenType.SIGNAL: - parse_signal_declaration (parent, attrs); - return; - default: - parse_method_declaration (parent, attrs); - return; - } - case TokenType.ASSIGN: - case TokenType.SEMICOLON: - rollback (begin); - switch (last_keyword) { - case TokenType.CONST: - parse_constant_declaration (parent, attrs); - return; - default: - parse_field_declaration (parent, attrs); - return; - } - case TokenType.OPEN_BRACE: - case TokenType.THROWS: - rollback (begin); - parse_property_declaration (parent, attrs); - return; - default: - break; - } - break; - } - break; - } - - rollback (begin); - - throw new ParseError.SYNTAX (get_error ("expected declaration")); - } - - void parse_declarations (Symbol parent, bool root = false) throws ParseError { - if (!root) { - expect (TokenType.OPEN_BRACE); - } - while (current () != TokenType.CLOSE_BRACE && current () != TokenType.EOF) { - try { - parse_declaration (parent, (parent == context.root)); - } catch (ParseError e) { - int r; - do { - r = recover (); - if (r == RecoveryState.STATEMENT_BEGIN) { - next (); - } else { - break; - } - } while (true); - if (r == RecoveryState.EOF) { - return; - } - } - } - if (!root) { - if (!accept (TokenType.CLOSE_BRACE)) { - // only report error if it's not a secondary error - if (context.report.get_errors () == 0) { - Report.error (get_current_src (), "expected `}'"); - } - } - } - } - - enum RecoveryState { - EOF, - DECLARATION_BEGIN, - STATEMENT_BEGIN - } - - RecoveryState recover () { - while (current () != TokenType.EOF) { - switch (current ()) { - case TokenType.ABSTRACT: - case TokenType.CLASS: - case TokenType.CONST: - case TokenType.CONSTRUCT: - case TokenType.DELEGATE: - case TokenType.ENUM: - case TokenType.ERRORDOMAIN: - case TokenType.EXTERN: - case TokenType.INLINE: - case TokenType.INTERFACE: - case TokenType.INTERNAL: - case TokenType.NAMESPACE: - case TokenType.NEW: - case TokenType.OVERRIDE: - case TokenType.PRIVATE: - case TokenType.PROTECTED: - case TokenType.PUBLIC: - case TokenType.SEALED: - case TokenType.SIGNAL: - case TokenType.STATIC: - case TokenType.STRUCT: - case TokenType.VIRTUAL: - case TokenType.VOLATILE: - return RecoveryState.DECLARATION_BEGIN; - case TokenType.BREAK: - case TokenType.CONTINUE: - case TokenType.DELETE: - case TokenType.DO: - case TokenType.FOR: - case TokenType.FOREACH: - case TokenType.IF: - case TokenType.LOCK: - case TokenType.RETURN: - case TokenType.SWITCH: - case TokenType.THROW: - case TokenType.TRY: - case TokenType.VAR: - case TokenType.WHILE: - case TokenType.YIELD: - return RecoveryState.STATEMENT_BEGIN; - default: - next (); - break; - } - } - return RecoveryState.EOF; - } - - void parse_namespace_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - expect (TokenType.NAMESPACE); - var sym = parse_symbol_name (); - var ns = new Namespace (sym.name, get_src (begin)); - if (comment != null) { - ns.add_comment (comment); - comment = null; - } - - set_attributes (ns, attrs); - - expect (TokenType.OPEN_BRACE); - - var old_using_directives = scanner.source_file.current_using_directives; - parse_using_directives (ns); - - parse_declarations (ns, true); - - scanner.source_file.current_using_directives = old_using_directives; - - if (!accept (TokenType.CLOSE_BRACE)) { - // only report error if it's not a secondary error - if (context.report.get_errors () == 0) { - Report.error (get_current_src (), "expected `}'"); - } - } - - Symbol result = ns; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, ns.source_reference) : parent); - next.add_namespace ((Namespace) result); - result = next; - } - } - - void parse_using_directives (Namespace ns) throws ParseError { - while (accept (TokenType.USING)) { - do { - var begin = get_location (); - var sym = parse_symbol_name (); - var ns_ref = new UsingDirective (sym, get_src (begin)); - scanner.source_file.add_using_directive (ns_ref); - ns.add_using_directive (ns_ref); - } while (accept (TokenType.COMMA)); - expect (TokenType.SEMICOLON); - } - } - - void parse_class_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_type_declaration_modifiers (); - expect (TokenType.CLASS); - var sym = parse_symbol_name (); - var type_param_list = parse_type_parameter_list (); - var base_types = new ArrayList (); - if (accept (TokenType.COLON)) { - do { - base_types.add (parse_type (true, false)); - } while (accept (TokenType.COMMA)); - } - - var cl = new Class (sym.name, get_src (begin), comment); - cl.access = access; - if (ModifierFlags.ABSTRACT in flags) { - cl.is_abstract = true; - } - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - cl.external = true; - } - set_attributes (cl, attrs); - foreach (TypeParameter type_param in type_param_list) { - cl.add_type_parameter (type_param); - } - foreach (DataType base_type in base_types) { - cl.add_base_type (base_type); - } - - parse_declarations (cl); - - // ensure there is always a default construction method - if (scanner.source_file.file_type == SourceFileType.SOURCE - && cl.default_construction_method == null) { - var m = new CreationMethod (cl.name, null, cl.source_reference); - m.access = SymbolAccessibility.PUBLIC; - m.body = new Block (cl.source_reference); - cl.add_method (m); - } - - Symbol result = cl; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, cl.source_reference) : parent); - if (result is Namespace) { - next.add_namespace ((Namespace) result); - } else { - next.add_class ((Class) result); - } - result = next; - } - } - - void parse_constant_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - expect (TokenType.CONST); - var type = parse_type (false, false); - string id = parse_identifier (); - - type = parse_inline_array_type (type); - - Expression initializer = null; - if (accept (TokenType.ASSIGN)) { - initializer = parse_expression (); - } - expect (TokenType.SEMICOLON); - - // constant arrays don't own their element - var array_type = type as ArrayType; - if (array_type != null) { - array_type.element_type.value_owned = false; - } - - var c = new Constant (id, type, initializer, get_src (begin), comment); - c.access = access; - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - c.external = true; - } - if (ModifierFlags.NEW in flags) { - c.hides = true; - } - set_attributes (c, attrs); - - parent.add_constant (c); - } - - void parse_field_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - if (context.profile == Profile.DOVA) { - accept (TokenType.VOLATILE); - } - var type = parse_type (true, true); - string id = parse_identifier (); - - type = parse_inline_array_type (type); - - var f = new Field (id, type, null, get_src (begin), comment); - f.access = access; - set_attributes (f, attrs); - if (ModifierFlags.STATIC in flags) { - f.binding = MemberBinding.STATIC; - } else if (ModifierFlags.CLASS in flags) { - f.binding = MemberBinding.CLASS; - } - if (ModifierFlags.ABSTRACT in flags - || ModifierFlags.VIRTUAL in flags - || ModifierFlags.OVERRIDE in flags) { - Report.error (f.source_reference, "abstract, virtual, and override modifiers are not applicable to fields"); - } - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - f.external = true; - } - if (ModifierFlags.NEW in flags) { - f.hides = true; - } - if (accept (TokenType.ASSIGN)) { - f.initializer = parse_expression (); - } - expect (TokenType.SEMICOLON); - - parent.add_field (f); - } - - InitializerList parse_initializer () throws ParseError { - var begin = get_location (); - expect (TokenType.OPEN_BRACE); - var initializer = new InitializerList (get_src (begin)); - if (current () != TokenType.CLOSE_BRACE) { - do { - var init = parse_argument (); - initializer.append (init); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_BRACE); - return initializer; - } - - ListLiteral parse_list_literal () throws ParseError { - var begin = get_location (); - expect (TokenType.OPEN_BRACKET); - var initializer = new ListLiteral (get_src (begin)); - if (current () != TokenType.CLOSE_BRACKET) { - do { - var init = parse_expression (); - initializer.add_expression (init); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_BRACKET); - return initializer; - } - - Expression parse_set_literal () throws ParseError { - var begin = get_location (); - expect (TokenType.OPEN_BRACE); - var set = new SetLiteral (get_src (begin)); - bool first = true; - if (current () != TokenType.CLOSE_BRACE) { - do { - var expr = parse_expression (); - if (first && accept (TokenType.COLON)) { - // found colon after expression, it's a map - rollback (begin); - return parse_map_literal (); - } - first = false; - set.add_expression (expr); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_BRACE); - return set; - } - - Expression parse_map_literal () throws ParseError { - var begin = get_location (); - expect (TokenType.OPEN_BRACE); - var map = new MapLiteral (get_src (begin)); - if (current () != TokenType.CLOSE_BRACE) { - do { - var key = parse_expression (); - map.add_key (key); - expect (TokenType.COLON); - var value = parse_expression (); - map.add_value (value); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_BRACE); - return map; - } - - void parse_method_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - var type = parse_type (true, false); - string id = parse_identifier (); - var type_param_list = parse_type_parameter_list (); - var method = new Method (id, type, get_src (begin), comment); - method.access = access; - set_attributes (method, attrs); - foreach (TypeParameter type_param in type_param_list) { - method.add_type_parameter (type_param); - } - if (ModifierFlags.STATIC in flags) { - method.binding = MemberBinding.STATIC; - } else if (ModifierFlags.CLASS in flags) { - method.binding = MemberBinding.CLASS; - } - if (ModifierFlags.ASYNC in flags) { - method.coroutine = true; - } - if (ModifierFlags.NEW in flags) { - method.hides = true; - } - - if (method.binding == MemberBinding.INSTANCE) { - if (ModifierFlags.ABSTRACT in flags) { - method.is_abstract = true; - } - if (ModifierFlags.VIRTUAL in flags) { - method.is_virtual = true; - } - if (ModifierFlags.OVERRIDE in flags) { - method.overrides = true; - } - if ((method.is_abstract && method.is_virtual) - || (method.is_abstract && method.overrides) - || (method.is_virtual && method.overrides)) { - throw new ParseError.SYNTAX (get_error ("only one of `abstract', `virtual', or `override' may be specified")); - } - } else { - if (ModifierFlags.ABSTRACT in flags - || ModifierFlags.VIRTUAL in flags - || ModifierFlags.OVERRIDE in flags) { - throw new ParseError.SYNTAX (get_error ("the modifiers `abstract', `virtual', and `override' are not valid for static methods")); - } - } - - if (ModifierFlags.INLINE in flags) { - method.is_inline = true; - } - if (ModifierFlags.EXTERN in flags) { - method.external = true; - } - expect (TokenType.OPEN_PARENS); - if (current () != TokenType.CLOSE_PARENS) { - do { - var param = parse_parameter (); - method.add_parameter (param); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - if (context.profile == Profile.DOVA) { - var error_type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, "Dova"), "Error"), method.source_reference); - method.add_error_type (error_type); - if (accept (TokenType.THROWS)) { - do { - parse_type (true, false); - } while (accept (TokenType.COMMA)); - Report.warning (method.source_reference, "`throws' is ignored in the Dova profile"); - } - } else { - if (accept (TokenType.THROWS)) { - do { - method.add_error_type (parse_type (true, false)); - } while (accept (TokenType.COMMA)); - } - } - while (accept (TokenType.REQUIRES)) { - expect (TokenType.OPEN_PARENS); - method.add_precondition (parse_expression ()); - expect (TokenType.CLOSE_PARENS); - } - while (accept (TokenType.ENSURES)) { - expect (TokenType.OPEN_PARENS); - method.add_postcondition (parse_expression ()); - expect (TokenType.CLOSE_PARENS); - } - if (!accept (TokenType.SEMICOLON)) { - method.body = parse_block (); - } else if (scanner.source_file.file_type == SourceFileType.PACKAGE) { - method.external = true; - } - - parent.add_method (method); - } - - void parse_property_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - var type = parse_type (true, true); - - bool getter_owned = false; - if (context.profile == Profile.DOVA) { - getter_owned = true; - } else if (accept (TokenType.HASH)) { - if (!context.deprecated) { - Report.warning (get_last_src (), "deprecated syntax, use `owned` modifier before `get'"); - } - getter_owned = true; - } - - string id = parse_identifier (); - var prop = new Property (id, type, null, null, get_src (begin), comment); - prop.access = access; - set_attributes (prop, attrs); - if (ModifierFlags.STATIC in flags) { - prop.binding = MemberBinding.STATIC; - } else if (ModifierFlags.CLASS in flags) { - prop.binding = MemberBinding.CLASS; - } - if (ModifierFlags.ABSTRACT in flags) { - prop.is_abstract = true; - } - if (ModifierFlags.VIRTUAL in flags) { - prop.is_virtual = true; - } - if (ModifierFlags.OVERRIDE in flags) { - prop.overrides = true; - } - if (ModifierFlags.NEW in flags) { - prop.hides = true; - } - if (ModifierFlags.ASYNC in flags) { - Report.error (prop.source_reference, "async properties are not supported yet"); - } - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - prop.external = true; - } - if (context.profile == Profile.DOVA) { - } else { - if (accept (TokenType.THROWS)) { - do { - prop.add_error_type (parse_type (true, false)); - } while (accept (TokenType.COMMA)); - Report.error (prop.source_reference, "properties throwing errors are not supported yet"); - } - } - expect (TokenType.OPEN_BRACE); - while (current () != TokenType.CLOSE_BRACE) { - if (accept (TokenType.DEFAULT)) { - if (prop.initializer != null) { - throw new ParseError.SYNTAX (get_error ("property default value already defined")); - } - expect (TokenType.ASSIGN); - prop.initializer = parse_expression (); - expect (TokenType.SEMICOLON); - } else { - comment = scanner.pop_comment (); - - var accessor_begin = get_location (); - var accessor_attrs = parse_attributes (); - var accessor_access = parse_access_modifier (SymbolAccessibility.PUBLIC); - - var value_type = type.copy (); - value_type.value_owned = (context.profile != Profile.DOVA && accept (TokenType.OWNED)); - - if (accept (TokenType.GET)) { - if (prop.get_accessor != null) { - throw new ParseError.SYNTAX (get_error ("property get accessor already defined")); - } - - if (getter_owned) { - value_type.value_owned = true; - } - - Block block = null; - if (!accept (TokenType.SEMICOLON)) { - block = parse_block (); - prop.external = false; - } - prop.get_accessor = new PropertyAccessor (true, false, false, value_type, block, get_src (accessor_begin), comment); - set_attributes (prop.get_accessor, accessor_attrs); - prop.get_accessor.access = accessor_access; - } else { - bool writable, _construct; - if (accept (TokenType.SET)) { - writable = true; - _construct = (context.profile == Profile.GOBJECT) && accept (TokenType.CONSTRUCT); - } else if (context.profile == Profile.GOBJECT && accept (TokenType.CONSTRUCT)) { - _construct = true; - writable = accept (TokenType.SET); - } else { - throw new ParseError.SYNTAX (get_error ("expected get, set, or construct")); - } - if (prop.set_accessor != null) { - throw new ParseError.SYNTAX (get_error ("property set accessor already defined")); - } - Block block = null; - if (!accept (TokenType.SEMICOLON)) { - block = parse_block (); - prop.external = false; - } - prop.set_accessor = new PropertyAccessor (false, writable, _construct, value_type, block, get_src (accessor_begin), comment); - set_attributes (prop.set_accessor, accessor_attrs); - prop.set_accessor.access = accessor_access; - } - } - } - expect (TokenType.CLOSE_BRACE); - - if (!prop.is_abstract && prop.source_type == SourceFileType.SOURCE) { - bool empty_get = (prop.get_accessor != null && prop.get_accessor.body == null); - bool empty_set = (prop.set_accessor != null && prop.set_accessor.body == null); - - if (empty_get != empty_set) { - if (empty_get) { - Report.error (prop.source_reference, "property getter must have a body"); - } else if (empty_set) { - Report.error (prop.source_reference, "property setter must have a body"); - } - prop.error = true; - } - - if (empty_get && empty_set) { - /* automatic property accessor body generation */ - var variable_type = prop.property_type.copy (); - prop.field = new Field ("_%s".printf (prop.name), variable_type, prop.initializer, prop.source_reference); - prop.field.access = SymbolAccessibility.PRIVATE; - prop.field.binding = prop.binding; - } else if (prop.initializer != null) { - Report.error (prop.initializer.source_reference, "only automatic properties can have default values"); - } - } - - parent.add_property (prop); - } - - void parse_signal_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - expect (TokenType.SIGNAL); - var type = parse_type (true, false); - string id = parse_identifier (); - var sig = new Signal (id, type, get_src (begin), comment); - sig.access = access; - set_attributes (sig, attrs); - if (ModifierFlags.STATIC in flags) { - throw new ParseError.SYNTAX (get_error ("`static' modifier not allowed on signals")); - } else if (ModifierFlags.CLASS in flags) { - throw new ParseError.SYNTAX (get_error ("`class' modifier not allowed on signals")); - } - if (ModifierFlags.VIRTUAL in flags) { - sig.is_virtual = true; - } - if (ModifierFlags.NEW in flags) { - sig.hides = true; - } - expect (TokenType.OPEN_PARENS); - if (current () != TokenType.CLOSE_PARENS) { - do { - var param = parse_parameter (); - sig.add_parameter (param); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - if (!accept (TokenType.SEMICOLON)) { - sig.body = parse_block (); - } - - parent.add_signal (sig); - } - - void parse_constructor_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var flags = parse_member_declaration_modifiers (); - expect (TokenType.CONSTRUCT); - if (ModifierFlags.NEW in flags) { - throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on constructor")); - } - var c = new Constructor (get_src (begin)); - if (ModifierFlags.STATIC in flags) { - c.binding = MemberBinding.STATIC; - } else if (ModifierFlags.CLASS in flags) { - c.binding = MemberBinding.CLASS; - } - c.body = parse_block (); - - parent.add_constructor (c); - } - - void parse_destructor_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var flags = parse_member_declaration_modifiers (); - expect (TokenType.TILDE); - parse_identifier (); - expect (TokenType.OPEN_PARENS); - expect (TokenType.CLOSE_PARENS); - if (ModifierFlags.NEW in flags) { - throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on destructor")); - } - var d = new Destructor (get_src (begin)); - if (ModifierFlags.STATIC in flags) { - d.binding = MemberBinding.STATIC; - } else if (ModifierFlags.CLASS in flags) { - d.binding = MemberBinding.CLASS; - } - d.body = parse_block (); - - parent.add_destructor (d); - } - - void parse_struct_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_type_declaration_modifiers (); - expect (TokenType.STRUCT); - var sym = parse_symbol_name (); - var type_param_list = parse_type_parameter_list (); - DataType base_type = null; - if (accept (TokenType.COLON)) { - base_type = parse_type (true, false); - } - var st = new Struct (sym.name, get_src (begin), comment); - st.access = access; - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - st.external = true; - } - set_attributes (st, attrs); - foreach (TypeParameter type_param in type_param_list) { - st.add_type_parameter (type_param); - } - if (base_type != null) { - st.base_type = base_type; - } - - parse_declarations (st); - - Symbol result = st; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, st.source_reference) : parent); - if (result is Namespace) { - next.add_namespace ((Namespace) result); - } else { - next.add_struct ((Struct) result); - } - result = next; - } - } - - void parse_interface_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_type_declaration_modifiers (); - expect (TokenType.INTERFACE); - var sym = parse_symbol_name (); - var type_param_list = parse_type_parameter_list (); - var base_types = new ArrayList (); - if (accept (TokenType.COLON)) { - do { - var type = parse_type (true, false); - base_types.add (type); - } while (accept (TokenType.COMMA)); - } - var iface = new Interface (sym.name, get_src (begin), comment); - iface.access = access; - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - iface.external = true; - } - set_attributes (iface, attrs); - foreach (TypeParameter type_param in type_param_list) { - iface.add_type_parameter (type_param); - } - foreach (DataType base_type in base_types) { - iface.add_prerequisite (base_type); - } - - parse_declarations (iface); - - Symbol result = iface; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, iface.source_reference) : parent); - if (result is Namespace) { - next.add_namespace ((Namespace) result); - } else { - next.add_interface ((Interface) result); - } - result = next; - } - } - - void parse_enum_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_type_declaration_modifiers (); - expect (TokenType.ENUM); - var sym = parse_symbol_name (); - var en = new Enum (sym.name, get_src (begin), comment); - en.access = access; - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - en.external = true; - } - set_attributes (en, attrs); - - expect (TokenType.OPEN_BRACE); - do { - if (current () == TokenType.CLOSE_BRACE - && en.get_values ().size > 0) { - // allow trailing comma - break; - } - var value_attrs = parse_attributes (); - var value_begin = get_location (); - string id = parse_identifier (); - comment = scanner.pop_comment (); - - Expression value = null; - if (accept (TokenType.ASSIGN)) { - value = parse_expression (); - } - - var ev = new EnumValue (id, value, get_src (value_begin), comment); - ev.access = SymbolAccessibility.PUBLIC; - set_attributes (ev, value_attrs); - en.add_value (ev); - } while (accept (TokenType.COMMA)); - if (accept (TokenType.SEMICOLON)) { - // enum methods - while (current () != TokenType.CLOSE_BRACE) { - parse_declaration (en); - } - } - expect (TokenType.CLOSE_BRACE); - - Symbol result = en; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, en.source_reference) : parent); - if (result is Namespace) { - next.add_namespace ((Namespace) result); - } else { - next.add_enum ((Enum) result); - } - result = next; - } - } - - void parse_errordomain_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_type_declaration_modifiers (); - expect (TokenType.ERRORDOMAIN); - var sym = parse_symbol_name (); - var ed = new ErrorDomain (sym.name, get_src (begin), comment); - ed.access = access; - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - ed.external = true; - } - set_attributes (ed, attrs); - - expect (TokenType.OPEN_BRACE); - do { - if (current () == TokenType.CLOSE_BRACE - && ed.get_codes ().size > 0) { - // allow trailing comma - break; - } - var code_attrs = parse_attributes (); - var code_begin = get_location (); - string id = parse_identifier (); - comment = scanner.pop_comment (); - var ec = new ErrorCode (id, get_src (code_begin), comment); - set_attributes (ec, code_attrs); - if (accept (TokenType.ASSIGN)) { - ec.value = parse_expression (); - } - ed.add_code (ec); - } while (accept (TokenType.COMMA)); - if (accept (TokenType.SEMICOLON)) { - // errordomain methods - while (current () != TokenType.CLOSE_BRACE) { - parse_declaration (ed); - } - } - expect (TokenType.CLOSE_BRACE); - - Symbol result = ed; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, ed.source_reference) : parent); - if (result is Namespace) { - next.add_namespace ((Namespace) result); - } else { - next.add_error_domain ((ErrorDomain) result); - } - result = next; - } - } - - SymbolAccessibility parse_access_modifier (SymbolAccessibility default_access = SymbolAccessibility.PRIVATE) { - switch (current ()) { - case TokenType.PRIVATE: - next (); - return SymbolAccessibility.PRIVATE; - case TokenType.PROTECTED: - next (); - return SymbolAccessibility.PROTECTED; - case TokenType.INTERNAL: - next (); - return SymbolAccessibility.INTERNAL; - case TokenType.PUBLIC: - next (); - return SymbolAccessibility.PUBLIC; - default: - return default_access; - } - } - - ModifierFlags parse_type_declaration_modifiers () { - ModifierFlags flags = 0; - while (true) { - switch (current ()) { - case TokenType.ABSTRACT: - next (); - flags |= ModifierFlags.ABSTRACT; - break; - case TokenType.EXTERN: - next (); - flags |= ModifierFlags.EXTERN; - break; - case TokenType.SEALED: - next (); - flags |= ModifierFlags.SEALED; - break; - default: - return flags; - } - } - } - - ModifierFlags parse_member_declaration_modifiers () { - ModifierFlags flags = 0; - while (true) { - switch (current ()) { - case TokenType.ABSTRACT: - next (); - flags |= ModifierFlags.ABSTRACT; - break; - case TokenType.ASYNC: - next (); - flags |= ModifierFlags.ASYNC; - break; - case TokenType.CLASS: - next (); - flags |= ModifierFlags.CLASS; - break; - case TokenType.EXTERN: - next (); - flags |= ModifierFlags.EXTERN; - break; - case TokenType.INLINE: - next (); - flags |= ModifierFlags.INLINE; - break; - case TokenType.NEW: - next (); - flags |= ModifierFlags.NEW; - break; - case TokenType.OVERRIDE: - next (); - flags |= ModifierFlags.OVERRIDE; - break; - case TokenType.SEALED: - next (); - flags |= ModifierFlags.SEALED; - break; - case TokenType.STATIC: - next (); - flags |= ModifierFlags.STATIC; - break; - case TokenType.VIRTUAL: - next (); - flags |= ModifierFlags.VIRTUAL; - break; - default: - return flags; - } - } - } - - Parameter parse_parameter () throws ParseError { - var attrs = parse_attributes (); - var begin = get_location (); - if (accept (TokenType.ELLIPSIS)) { - // varargs - return new Parameter.with_ellipsis (get_src (begin)); - } - bool params_array = accept (TokenType.PARAMS); - var direction = ParameterDirection.IN; - if (accept (TokenType.OUT)) { - direction = ParameterDirection.OUT; - } else if (accept (TokenType.REF)) { - direction = ParameterDirection.REF; - } - - if (context.profile == Profile.DOVA) { - accept (TokenType.VOLATILE); - } - DataType type; - if (direction == ParameterDirection.IN) { - // in parameters are unowned by default - type = parse_type (false, false); - } else if (direction == ParameterDirection.REF) { - // ref parameters own the value by default - type = parse_type (true, true); - } else { - // out parameters own the value by default - type = parse_type (true, false); - } - string id = parse_identifier (); - - type = parse_inline_array_type (type); - - var param = new Parameter (id, type, get_src (begin)); - set_attributes (param, attrs); - param.direction = direction; - param.params_array = params_array; - if (accept (TokenType.ASSIGN)) { - param.initializer = parse_expression (); - } - return param; - } - - void parse_creation_method_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - var sym = parse_symbol_name (); - if (ModifierFlags.NEW in flags) { - throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on creation method")); - } - CreationMethod method; - if (sym.inner == null) { - method = new CreationMethod (sym.name, null, get_src (begin), comment); - } else { - method = new CreationMethod (sym.inner.name, sym.name, get_src (begin), comment); - } - if (ModifierFlags.EXTERN in flags) { - method.external = true; - } - if (ModifierFlags.ABSTRACT in flags - || ModifierFlags.VIRTUAL in flags - || ModifierFlags.OVERRIDE in flags) { - Report.error (method.source_reference, "abstract, virtual, and override modifiers are not applicable to creation methods"); - } - if (ModifierFlags.ASYNC in flags) { - method.coroutine = true; - } - expect (TokenType.OPEN_PARENS); - if (current () != TokenType.CLOSE_PARENS) { - do { - var param = parse_parameter (); - method.add_parameter (param); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - if (context.profile == Profile.DOVA) { - var error_type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, "Dova"), "Error"), method.source_reference); - method.add_error_type (error_type); - if (accept (TokenType.THROWS)) { - do { - parse_type (true, false); - } while (accept (TokenType.COMMA)); - Report.warning (method.source_reference, "`throws' is ignored in the Dova profile"); - } - } else { - if (accept (TokenType.THROWS)) { - do { - method.add_error_type (parse_type (true, false)); - } while (accept (TokenType.COMMA)); - } - } - while (accept (TokenType.REQUIRES)) { - expect (TokenType.OPEN_PARENS); - method.add_precondition (parse_expression ()); - expect (TokenType.CLOSE_PARENS); - } - while (accept (TokenType.ENSURES)) { - expect (TokenType.OPEN_PARENS); - method.add_postcondition (parse_expression ()); - expect (TokenType.CLOSE_PARENS); - } - method.access = access; - set_attributes (method, attrs); - if (!accept (TokenType.SEMICOLON)) { - method.body = parse_block (); - } else if (scanner.source_file.file_type == SourceFileType.PACKAGE) { - method.external = true; - } - - parent.add_method (method); - } - - void parse_delegate_declaration (Symbol parent, List? attrs) throws ParseError { - var begin = get_location (); - var access = parse_access_modifier (); - var flags = parse_member_declaration_modifiers (); - expect (TokenType.DELEGATE); - if (ModifierFlags.NEW in flags) { - throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on delegates")); - } - var type = parse_type (true, false); - var sym = parse_symbol_name (); - var type_param_list = parse_type_parameter_list (); - var d = new Delegate (sym.name, type, get_src (begin), comment); - d.access = access; - set_attributes (d, attrs); - if (ModifierFlags.STATIC in flags) { - if (!context.deprecated) { - // TODO enable warning in future releases - Report.warning (get_last_src (), "deprecated syntax, use [CCode (has_target = false)]"); - } - d.has_target = false; - } - if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { - d.external = true; - } - foreach (TypeParameter type_param in type_param_list) { - d.add_type_parameter (type_param); - } - expect (TokenType.OPEN_PARENS); - if (current () != TokenType.CLOSE_PARENS) { - do { - var param = parse_parameter (); - d.add_parameter (param); - } while (accept (TokenType.COMMA)); - } - expect (TokenType.CLOSE_PARENS); - if (context.profile == Profile.DOVA) { - var error_type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, "Dova"), "Error"), d.source_reference); - d.add_error_type (error_type); - if (accept (TokenType.THROWS)) { - do { - parse_type (true, false); - } while (accept (TokenType.COMMA)); - Report.warning (d.source_reference, "`throws' is ignored in the Dova profile"); - } - } else { - if (accept (TokenType.THROWS)) { - do { - d.add_error_type (parse_type (true, false)); - } while (accept (TokenType.COMMA)); - } - } - expect (TokenType.SEMICOLON); - - Symbol result = d; - while (sym != null) { - sym = sym.inner; - - Symbol next = (sym != null ? new Namespace (sym.name, d.source_reference) : parent); - if (result is Namespace) { - next.add_namespace ((Namespace) result); - } else { - next.add_delegate ((Delegate) result); - } - result = next; - } - } - - List parse_type_parameter_list () throws ParseError { - if (accept (TokenType.OP_LT)) { - var list = new ArrayList (); - do { - var begin = get_location (); - string id = parse_identifier (); - list.add (new TypeParameter (id, get_src (begin))); - } while (accept (TokenType.COMMA)); - expect (TokenType.OP_GT); - return list; - } else { - if (_empty_type_parameter_list == null) { - _empty_type_parameter_list = new ArrayList (); - } - return _empty_type_parameter_list; - } - } - - void skip_type_argument_list () throws ParseError { - if (accept (TokenType.OP_LT)) { - do { - skip_type (); - } while (accept (TokenType.COMMA)); - expect (TokenType.OP_GT); - } - } - - // try to parse type argument list - List? parse_type_argument_list (bool maybe_expression) throws ParseError { - var begin = get_location (); - if (accept (TokenType.OP_LT)) { - var list = new ArrayList (); - do { - switch (current ()) { - case TokenType.VOID: - case TokenType.DYNAMIC: - case TokenType.UNOWNED: - case TokenType.WEAK: - case TokenType.IDENTIFIER: - var type = parse_type (true, true); - list.add (type); - break; - default: - rollback (begin); - return null; - } - } while (accept (TokenType.COMMA)); - if (!accept (TokenType.OP_GT)) { - rollback (begin); - return null; - } - if (maybe_expression) { - // check follower to decide whether to keep type argument list - switch (current ()) { - case TokenType.OPEN_PARENS: - case TokenType.CLOSE_PARENS: - case TokenType.CLOSE_BRACKET: - case TokenType.OPEN_BRACE: - case TokenType.COLON: - case TokenType.SEMICOLON: - case TokenType.COMMA: - case TokenType.DOT: - case TokenType.INTERR: - case TokenType.OP_EQ: - case TokenType.OP_NE: - // keep type argument list - break; - default: - // interpret tokens as expression - rollback (begin); - return null; - } - } - return list; - } - return null; - } - - MemberAccess parse_member_name (Expression? base_expr = null) throws ParseError { - var begin = get_location (); - MemberAccess expr = null; - bool first = true; - do { - string id = parse_identifier (); - - // The first member access can be global:: qualified - bool qualified = false; - if (first && id == "global" && accept (TokenType.DOUBLE_COLON)) { - id = parse_identifier (); - qualified = true; - } - - List type_arg_list = parse_type_argument_list (false); - expr = new MemberAccess (expr != null ? expr : base_expr, id, get_src (begin)); - expr.qualified = qualified; - if (type_arg_list != null) { - foreach (DataType type_arg in type_arg_list) { - expr.add_type_argument (type_arg); - } - } - - first = false; - } while (accept (TokenType.DOT)); - return expr; - } - - bool is_declaration_keyword (TokenType type) { - switch (type) { - case TokenType.ABSTRACT: - case TokenType.ASYNC: - case TokenType.CLASS: - case TokenType.CONST: - case TokenType.DELEGATE: - case TokenType.ENUM: - case TokenType.ERRORDOMAIN: - case TokenType.EXTERN: - case TokenType.INLINE: - case TokenType.INTERFACE: - case TokenType.INTERNAL: - case TokenType.NAMESPACE: - case TokenType.NEW: - case TokenType.OVERRIDE: - case TokenType.PRIVATE: - case TokenType.PROTECTED: - case TokenType.PUBLIC: - case TokenType.SEALED: - case TokenType.SIGNAL: - case TokenType.STATIC: - case TokenType.STRUCT: - case TokenType.VIRTUAL: - case TokenType.VOLATILE: - return true; - default: - return false; - } - } -} - -public errordomain Vala.ParseError { - FAILED, - SYNTAX -} - diff --git a/tests/vera/arb.if.vri b/tests/vera/arb.if.vri deleted file mode 100644 index 21b8168..0000000 --- a/tests/vera/arb.if.vri +++ /dev/null @@ -1,6 +0,0 @@ -interface arb { - input clk CLOCK ; - output reset PHOLD #1 ; - output [1:0] request PHOLD #1 ; - input [1:0] grant PSAMPLE #-1 ; -} // end of interface arb diff --git a/tests/vera/class_a.vr b/tests/vera/class_a.vr deleted file mode 100644 index 5bd0a43..0000000 --- a/tests/vera/class_a.vr +++ /dev/null @@ -1,6 +0,0 @@ -// Below code can be in another file -class A { - task print () { - printf("I am in seprate file\n"); - } -} diff --git a/tests/vera/class_extension.vr b/tests/vera/class_extension.vr deleted file mode 100644 index 8f99483..0000000 --- a/tests/vera/class_extension.vr +++ /dev/null @@ -1,99 +0,0 @@ -// Virtual class for body of any driver -virtual class verif { - // This starts all the threads - virtual task startSim(); - // This stops all the threads - virtual task stopSim(); - // This prints all the stats - virtual task printStats(); - // This check if driver is done or not - virtual function bit isDone () { - isDone = 0; - } - // set the driver config - virtual task setConfig(integer item, integer value); - virtual function integer getConfig(integer item) { - getConfig = 32'hDEAD_BEAF; - } -} -// ethernet inherits verif -class ethernet extends verif { - integer min_frame_size; - integer max_frame_size; - task new() { - min_frame_size = 32'h40; - max_frame_size = 32'h200; - } - task startSim() { - printf("Starting Simulation\n"); - } - task stopSim() { - printf("Stopping Simulation\n"); - } - task printStats() { - printf("Sent normal frames %d\n",100); - printf("Sent runt frames %d\n",1); - printf("Sent oversize frames %d\n",1); - } - function bit isDone() { - isDone = 1; - } - task setConfig(integer item, integer value) { - case(item) { - 0 : min_frame_size = value; - 1 : max_frame_size = value; - } - } - function integer getConfig(integer item) { - case(item) { - 0 : getConfig = min_frame_size; - 1 : getConfig = max_frame_size; - default : { - printf("Calling super.setConfig\n"); - getConfig = super.getConfig(item); - } - } - } -} - -class ethernet2 extends ethernet { - integer min_ipg; - task new() { - min_ipg = 32'hc; - } - task setConfig(integer item, integer value) { - case(item) { - 2 : min_ipg = value; - default : { - printf("Calling super.setConfig\n"); - super.setConfig(item,value); - } - } - } - function integer getConfig(integer item) { - case(item) { - 2 : getConfig = min_ipg; - default : { - printf("Calling super.setConfig\n"); - getConfig = super.getConfig(item); - } - } - } -} - -program class_extension { - ethernet2 eth = new(); - eth.setConfig(0,32'h100); - eth.setConfig(2,32'h24); - printf ("Value of min_frame is %0x\n", eth.getConfig(0)); - printf ("Value of max_frame is %0x\n", eth.getConfig(1)); - printf ("Value of min_ipg is %0x\n", eth.getConfig(2)); - printf ("Value of unknown is %0x\n", eth.getConfig(3)); - - eth.startSim(); - while (eth.isDone() == 0) { - delay(1); - } - eth.stopSim(); - eth.printStats(); -} diff --git a/tests/vera/copy_object.vr b/tests/vera/copy_object.vr deleted file mode 100644 index 65639df..0000000 --- a/tests/vera/copy_object.vr +++ /dev/null @@ -1,29 +0,0 @@ -class A{ - integer j; - task new(){ - j = 100; - } -} - -class B { - integer i; - A a; - task new() { - i = 200; - } -} - -program copy_object { - B b1 = new(); // Create an object of class B - B b2; //Create a null variable of class B - b1.a = new; //Create an object of class A - b2 = new b1; // Create an object that is a copy of b1, - //but only copies the handle a, not the object referenced by a. - b2.i = 300; // i is changed in b2, but not b1 - printf("i in b2 = %0d\n", b2.i);// i equals 10 - printf("i in b1 = %0d\n", b1.i);// i equals 1 - //where as: - b2.a.j = 400; // Change j in the object referenced - // by a. j is shared by both b1 and b2 - printf("j is %0d in b1 and %0d in b2\n", b1.a.j, b2.a.j); -} diff --git a/tests/vera/enum_t.vr b/tests/vera/enum_t.vr deleted file mode 100644 index 93c713c..0000000 --- a/tests/vera/enum_t.vr +++ /dev/null @@ -1,13 +0,0 @@ -enum pkt_size {NORMAL, RUNT, OVERSIZE}; -enum pkt_type {UNICAST=11,MULTICAST,BROADCAST}; - -program enum_t { - pkt_size size = NORMAL; - pkt_type type = MULTICAST; - // Print the enum value - printf("Packet size is %d\n", size); - printf("Packet type is %d\n", type); - // Print the enum name - printf("Packet size is %s\n", size); - printf("Packet type is %s\n", type); -} diff --git a/tests/vera/properties.vr b/tests/vera/properties.vr deleted file mode 100644 index 72a8100..0000000 --- a/tests/vera/properties.vr +++ /dev/null @@ -1,60 +0,0 @@ -class A { - public integer data; - local integer addr; - protected integer cmd; - static integer credits; - task new() { - data = 100; - addr = 200; - cmd = 1; - credits = 10; - } - task printA() { - printf ("value of data %0d in A\n", data); - printf ("value of addr %0d in A\n", addr); - printf ("value of cmd %0d in A\n", cmd); - } -} - -class B extends A { - task printB() { - printf ("value of data %0d in B\n", data); - // Below line will give compile error - //printf ("value of addr %0d in B\n", addr); - printf ("value of cmd %0d in B\n", cmd); - } -} - -class C { - A a; - B b; - task new() { - a = new(); - b = new(); - b.data = 2; - } - task printC() { - printf ("value of data %0d in C\n", a.data); - printf ("value of data %0d in C\n", b.data); - // Below line will give compile error - //printf ("value of addr %0d in C\n", a.addr); - //printf ("value of cmd %0d in C\n", a.cmd); - //printf ("value of addr %0d in C\n", b.addr); - //printf ("value of cmd %0d in C\n", b.cmd); - } -} - -program properties { - C c = new(); - c.a.printA(); - c.b.printB(); - c.printC(); - printf("value of credits is %0d\n",c.a.credits); - printf("value of credits is %0d\n",c.b.credits); - c.a.credits ++; - printf("value of credits is %0d\n",c.a.credits); - printf("value of credits is %0d\n",c.b.credits); - c.b.credits ++; - printf("value of credits is %0d\n",c.a.credits); - printf("value of credits is %0d\n",c.b.credits); -} diff --git a/tests/vera/this_ex.vr b/tests/vera/this_ex.vr deleted file mode 100644 index 30b4ec2..0000000 --- a/tests/vera/this_ex.vr +++ /dev/null @@ -1,14 +0,0 @@ -class A { - integer i; - - task set_i(integer value) { - this.i = value; - } -} - -program this_ex { - A a = new(); - printf("value of i is %0d\n",a.i); - a.set_i(100); - printf("value of i is %0d\n",a.i); - } diff --git a/tests/vera/virtual_port.vr b/tests/vera/virtual_port.vr deleted file mode 100644 index 8c3a5af..0000000 --- a/tests/vera/virtual_port.vr +++ /dev/null @@ -1,45 +0,0 @@ -#include "vera_defines.vrh" - -// Port Declaration -port count_p { - clock; - reset; - enable; - cout; -} -// This is what connects with HDL -interface count_if0 { - input clock CLOCK; - output reset PHOLD#1; - output enable PHOLD#1; - input [3:0] cout PSAMPLE #-1; -} -// Now bind interface with Port -bind count_p count_bind { - clock count_if0.clock; - reset count_if0.reset; - enable count_if0.enable; - cout count_if0.cout; -} -// Top level program -program virtual_port { - count_p count = count_bind; - // Start the actual test here - @ (posedge count.$clock); - printf("Asserting Reset\n"); - count.$reset = 1; - count.$enable = 0; - @ (posedge count.$clock); - printf("Deasserting Reset\n"); - count.$reset = 0; - @ (posedge count.$clock); - printf("Asserting Enable\n"); - count.$enable = 1; - repeat(10) { - @ (posedge count.$clock); - printf("Counter value %x\n",count.$cout); - } - @ (posedge count.$clock); - printf("Deasserting Enable\n"); - count.$enable = 0; -} diff --git a/tests/vhdl/gcd2.vhd b/tests/vhdl/gcd2.vhd deleted file mode 100644 index 6f7dba8..0000000 --- a/tests/vhdl/gcd2.vhd +++ /dev/null @@ -1,306 +0,0 @@ ----------------------------------------------------------------------- --- GCD CALCULATOR (ESD book figure 2.11) --- Weijun Zhang, 04/2001 --- --- we can put all the components in one document(gcd2.vhd) --- or put them in separate files --- this is the example of RT level modeling (FSM + DataPath) --- the code is synthesized by Synopsys design compiler ----------------------------------------------------------------------- - --- Component: MULTIPLEXOR -------------------------------------------- - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.std_logic_arith.all; -use IEEE.std_logic_unsigned.all; - -entity mux is - port( rst, sLine: in std_logic; - load, result: in std_logic_vector( 3 downto 0 ); - output: out std_logic_vector( 3 downto 0 ) - ); -end mux; - -architecture mux_arc of mux is -begin - process( rst, sLine, load, result ) - begin - if( rst = '1' ) then - output <= "0000"; -- do nothing - elsif sLine = '0' then - output <= load; -- load inputs - else - output <= result; -- load results - end if; - end process; -end mux_arc; - --- Component: COMPARATOR --------------------------------------------- - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.std_logic_arith.all; -use IEEE.std_logic_unsigned.all; - -entity comparator is - port( rst: in std_logic; - x, y: in std_logic_vector( 3 downto 0 ); - output: out std_logic_vector( 1 downto 0 ) - ); -end comparator; - -architecture comparator_arc of comparator is -begin - process( x, y, rst ) - begin - if( rst = '1' ) then - output <= "00"; -- do nothing - elsif( x > y ) then - output <= "10"; -- if x greater - elsif( x < y ) then - output <= "01"; -- if y greater - else - output <= "11"; -- if equivalance. - end if; - end process; -end comparator_arc; - --- Component: SUBTRACTOR ---------------------------------------------- - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.std_logic_arith.all; -use IEEE.std_logic_unsigned.all; - -entity subtractor is - port( rst: in std_logic; - cmd: in std_logic_vector( 1 downto 0 ); - x, y: in std_logic_vector( 3 downto 0 ); - xout, yout: out std_logic_vector( 3 downto 0 ) - ); -end subtractor; - -architecture subtractor_arc of subtractor is -begin - process( rst, cmd, x, y ) - begin - if( rst = '1' or cmd = "00" ) then -- not active. - xout <= "0000"; - yout <= "0000"; - elsif( cmd = "10" ) then -- x is greater - xout <= ( x - y ); - yout <= y; - elsif( cmd = "01" ) then -- y is greater - xout <= x; - yout <= ( y - x ); - else - xout <= x; -- x and y are equal - yout <= y; - end if; - end process; -end subtractor_arc; - --- Component: REGISTER --------------------------------------------------- - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.std_logic_arith.all; -use IEEE.std_logic_unsigned.all; - -entity regis is - port( rst, clk, load: in std_logic; - input: in std_logic_vector( 3 downto 0 ); - output: out std_logic_vector( 3 downto 0 ) - ); -end regis; - -architecture regis_arc of regis is -begin - process( rst, clk, load, input ) - begin - if( rst = '1' ) then - output <= "0000"; - elsif( clk'event and clk = '1') then - if( load = '1' ) then - output <= input; - end if; - end if; - end process; -end regis_arc; - --- component: FSM controller -------------------------------------------- - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.std_logic_arith.all; -use IEEE.std_logic_unsigned.all; - -entity fsm is - port( rst, clk, proceed: in std_logic; - comparison: in std_logic_vector( 1 downto 0 ); - enable, xsel, ysel, xld, yld: out std_logic - ); -end fsm; - -architecture fsm_arc of fsm is - - type states is ( init, s0, s1, s2, s3, s4, s5 ); - signal nState, cState: states; - -begin - process( rst, clk ) - begin - if( rst = '1' ) then - cState <= init; - elsif( clk'event and clk = '1' ) then - cState <= nState; - end if; - end process; - - process( proceed, comparison, cState ) - begin - case cState is - - when init => if( proceed = '0' ) then - nState <= init; - else - nState <= s0; - end if; - - when s0 => enable <= '0'; - xsel <= '0'; - ysel <= '0'; - xld <= '0'; - yld <= '0'; - nState <= s1; - - when s1 => enable <= '0'; - xsel <= '0'; - ysel <= '0'; - xld <= '1'; - yld <= '1'; - nState <= s2; - - when s2 => xld <= '0'; - yld <= '0'; - if( comparison = "10" ) then - nState <= s3; - elsif( comparison = "01" ) then - nState <= s4; - elsif( comparison = "11" ) then - nState <= s5; - end if; - - when s3 => enable <= '0'; - xsel <= '1'; - ysel <= '0'; - xld <= '1'; - yld <= '0'; - nState <= s2; - - when s4 => enable <= '0'; - xsel <= '0'; - ysel <= '1'; - xld <= '0'; - yld <= '1'; - nState <= s2; - - when s5 => enable <= '1'; - xsel <= '1'; - ysel <= '1'; - xld <= '1'; - yld <= '1'; - nState <= s0; - - when others => nState <= s0; - - end case; - - end process; - -end fsm_arc; - ----------------------------------------------------------------------- --- GCD Calculator: top level design using structural modeling --- FSM + Datapath (mux, registers, subtracter and comparator) ----------------------------------------------------------------------- - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.std_logic_arith.all; -use IEEE.std_logic_unsigned.all; -use work.all; - -entity gcd is - port( rst, clk, go_i: in std_logic; - x_i, y_i: in std_logic_vector( 3 downto 0 ); - d_o: out std_logic_vector( 3 downto 0 ) - ); -end gcd; - -architecture gcd_arc of gcd is - -component fsm is - port( rst, clk, proceed: in std_logic; - comparison: in std_logic_vector( 1 downto 0 ); - enable, xsel, ysel, xld, yld: out std_logic - ); -end component; - -component mux is - port( rst, sLine: in std_logic; - load, result: in std_logic_vector( 3 downto 0 ); - output: out std_logic_vector( 3 downto 0 ) - ); -end component; - -component comparator is - port( rst: in std_logic; - x, y: in std_logic_vector( 3 downto 0 ); - output: out std_logic_vector( 1 downto 0 ) - ); -end component; - -component subtractor is - port( rst: in std_logic; - cmd: in std_logic_vector( 1 downto 0 ); - x, y: in std_logic_vector( 3 downto 0 ); - xout, yout: out std_logic_vector( 3 downto 0 ) - ); -end component; - -component regis is - port( rst, clk, load: in std_logic; - input: in std_logic_vector( 3 downto 0 ); - output: out std_logic_vector( 3 downto 0 ) - ); -end component; - -signal xld, yld, xsel, ysel, enable: std_logic; -signal comparison: std_logic_vector( 1 downto 0 ); -signal result: std_logic_vector( 3 downto 0 ); - -signal xsub, ysub, xmux, ymux, xreg, yreg: std_logic_vector( 3 downto 0 ); - -begin - - -- doing structure modeling here - - -- FSM controller - TOFSM: fsm port map( rst, clk, go_i, comparison, - enable, xsel, ysel, xld, yld ); - -- Datapath - X_MUX: mux port map( rst, xsel, x_i, xsub, xmux ); - Y_MUX: mux port map( rst, ysel, y_i, ysub, ymux ); - X_REG: regis port map( rst, clk, xld, xmux, xreg ); - Y_REG: regis port map( rst, clk, yld, ymux, yreg ); - U_COMP: comparator port map( rst, xreg, yreg, comparison ); - X_SUB: subtractor port map( rst, comparison, xreg, yreg, xsub, ysub ); - OUT_REG: regis port map( rst, clk, enable, xsub, result ); - - d_o <= result; - -end gcd_arc; - ---------------------------------------------------------------------------- diff --git a/tests/vhdl/test_multpipe.vhdl b/tests/vhdl/test_multpipe.vhdl deleted file mode 100644 index 6a7692e..0000000 --- a/tests/vhdl/test_multpipe.vhdl +++ /dev/null @@ -1,91 +0,0 @@ ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ---* * * * * * * * * * * * * * * * VHDL Source Code * * * * * * * * * * * * * * ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ---* Title : Test_MultPipe ---* Filename & Ext : test_multpipe.vhdl ---* Author : David Bishop X-XXXXX ---* Created : 1999/03/12 ---* Last modified : $Date: 1999-03-12 16:41:40-05 $ ---* WORK Library : testchip_lib ---* Description : A pipline multiplier with lots of redundant logic ---* Known Bugs : ---* : ---* RCS Summary : $Id: test_multpipe.vhdl,v 1.1 1999-03-12 16:41:40-05 bishop Exp bishop $ ---* : ---* Mod History : $Log: test_multpipe.vhdl,v $ ---* Mod History : Revision 1.1 1999-03-12 16:41:40-05 bishop ---* Mod History : Initial revision ---* Mod History : ---* : ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity test_multpipe is - generic ( width : integer := 7 ); - port ( clk : in std_ulogic; - reset : in std_ulogic; - enable : in std_ulogic; - inp1, inp2 : in std_logic_vector ( width downto 0); - sum : out std_logic_vector ( (width * 2 ) downto 0) ); -end test_multpipe; - -architecture rtl of test_multpipe is - subtype vectors is std_logic_vector (( width * 2 ) downto 0 ); - subtype unsigneds is unsigned (( width * 2 ) downto 0 ); - type stage_array is array ( width downto 0 ) of vectors; - type adder_array is array ( width downto 0 ) of unsigneds; - signal stage1, stage2 : stage_array; - signal adder_stage : adder_array; - signal reg_stage : adder_array; -begin -- rtl - - -- Fill the two arrays - build_stage1 : process ( inp1 ) - begin - stage_loop1 : for i in 0 to width loop - stage1 ( i ) <= ( others => '0' ); - stage1 ( i ) ( width + i downto i ) <= inp1; - end loop stage_loop1; - end process build_stage1; - - build_stage2 : process ( inp2 ) - begin - stage_loop2 : for i in 0 to width loop - stage2 ( i ) <= ( others => inp2 ( i ) ); - end loop stage_loop2; - end process build_stage2; - --- and the two arrays together, now you have a matrix which --- you can add up. - and_stages : process ( stage1, stage2 ) - begin - and_loop : for i in 0 to width loop - adder_stage ( i ) <= unsigned ( stage1 ( i ) and stage2 ( i ) ); - end loop and_loop; - end process and_stages; - - register_stages : process ( reset, clk ) - variable local_sum : unsigned ( sum'high downto 0 ); - begin - if reset = '0' then - reset_loop : for i in 0 to width loop - reg_stage ( i ) <= ( others => '0' ); - end loop reset_loop; - elsif rising_edge ( clk ) then - if enable = '1' then - adder_loop : for i in 0 to ( width + 1 ) / 2 loop - reg_stage ( i ) <= adder_stage ( i ) + adder_stage ( width - i ); - end loop adder_loop; - reg_stage ( 4 ) <= reg_stage ( 0 ) + reg_stage ( 3 ); - reg_stage ( 5 ) <= reg_stage ( 1 ) + reg_stage ( 2 ); - reg_stage ( 6 ) <= reg_stage ( 4 ) + reg_stage ( 5 ); - end if; - end if; - end process register_stages; - - sum <= std_logic_vector (reg_stage ( 6 )); - -end rtl; diff --git a/tests/vhdl/test_parity.vhdl b/tests/vhdl/test_parity.vhdl deleted file mode 100644 index dceafe1..0000000 --- a/tests/vhdl/test_parity.vhdl +++ /dev/null @@ -1,67 +0,0 @@ ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ---* * * * * * * * * * * * * * * * VHDL Source Code * * * * * * * * * * * * * * ---* * * Copyright (C) 1997 - Eastman Kodak Company - All Rights Reserved * * * ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ---* Title : TEST_PARITY ---* Filename & Ext : test_parity.vhdl ---* Author : David Bishop X-66788 ---* Created : 3/18/97 ---* Version : 1.2 ---* Revision Date : 97/04/15 ---* SCCSid : 1.2 04/15/97 test_parity.vhdl ---* WORK Library : testchip ---* Mod History : ---* Description : This is a parity generator which is written recursively ---* : It is designed to test the ability of Simulation and ---* : Synthesis tools to check this capability. ---* Known Bugs : ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - -library IEEE; -use IEEE.STD_LOGIC_1164.all; - --- Parity is done in a recursively called function. --- Package definition -package Parity_Pack is - function Recursive_Parity ( BUSX : std_logic_vector ) - return std_ulogic; -end Parity_Pack; - --- Package body. -package body Parity_Pack is - function Recursive_Parity ( BUSX : std_logic_vector ) - return std_ulogic is - variable Upper, Lower : std_ulogic; - variable Half : integer; - variable BUS_int : std_logic_vector ( BUSX'length - 1 downto 0 ); - variable Result : std_logic; - begin - BUS_int := BUSX; - if ( BUS_int'length = 1 ) then - Result := BUS_int ( BUS_int'left ); - elsif ( BUS_int'length = 2 ) then - Result := BUS_int ( BUS_int'right ) xor BUS_int ( BUS_int'left ); - else - Half := ( BUS_int'length + 1 ) / 2 + BUS_int'right; - Upper := Recursive_Parity ( BUS_int ( BUS_int'left downto Half )); - Lower := Recursive_Parity ( BUS_int ( Half - 1 downto BUS_int'right )); - Result := Upper xor Lower; - end if; - return Result; - end; -end Parity_Pack; - -library IEEE; -use IEEE.STD_LOGIC_1164.all; -use work.Parity_Pack.all; - -entity Test_Parity is - generic ( WIDTH : integer := 16); - port ( BUSX : in std_logic_vector ( WIDTH downto 0 ); - Parity : out std_ulogic ); -end Test_Parity; - -architecture RTL of Test_Parity is -begin - Parity <= Recursive_Parity ( BUSX ); -end RTL; diff --git a/tests/vhdl/testchip_core.vhdl b/tests/vhdl/testchip_core.vhdl deleted file mode 100644 index a2fcb0a..0000000 --- a/tests/vhdl/testchip_core.vhdl +++ /dev/null @@ -1,219 +0,0 @@ ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ---* * * * * * * * * * * * * * * * VHDL Source Code * * * * * * * * * * * * * * ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ---* Title : TESTCHIP_CORE ---* Filename & Ext : testchip_core.vhdl ---* Author : David W. Bishop ---* Created : 6/6/96 ---* Version : 1.1 ---* Revision Date : 97/12/03 ---* SCCSid : 1.1 12/03/97 testchip_core.vhdl ---* WORK Library : chiptest ---* Mod History : ---* Description : This is a test chip core, designed to test several ---* : functions in Synthesis and simulation ---* Known Bugs : ---* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - -library ieee; -use ieee.std_logic_1164.all; - -entity testchip_core is - port ( clk : in std_ulogic; - reset : in std_ulogic; - dclk : in std_ulogic; - dclk_i : out std_ulogic; - slow_count : out std_logic_vector ( 4 downto 0 ); - enable : in std_ulogic; - load : in std_ulogic; - input_data : in std_logic_vector ( 7 downto 0 ); - shout : out std_ulogic; - control : out std_logic_vector ( 1 downto 0 ); - count : out std_logic_vector ( 7 downto 0 ); - sum : out std_logic_vector ( 8 downto 0 ); - mulout : out std_logic_vector ( 14 downto 0 ); - parity : out std_ulogic ); -end testchip_core; - -architecture rtl of testchip_core is - ---------------------------------------------------------------------------------- Start with the declarations for the sub-blocks -------------------------------------------------------------------------------- - component Test_ClkGen - port ( clk : in std_ulogic; - reset : in std_ulogic; - dclk : out std_ulogic ); - end component; - - component test_counter - generic ( width : integer := 17 ); - port ( clk : in std_ulogic; - reset : in std_ulogic; - enable : in std_ulogic; - count : out std_logic_vector ( width - 1 downto 0) ); - end component; - - component test_add - generic ( width : integer := 17 ); - port ( clk : in std_ulogic; - reset : in std_ulogic; - enable : in std_ulogic; - inp1 : in std_logic_vector ( width downto 0); - inp2 : in std_logic_vector ( width downto 0); - sum : out std_logic_vector ( (width + 1) downto 0) ); - end component; - - component test_reg - generic ( width : integer := 17 ); - port ( clk : in std_ulogic; - reset : in std_ulogic; - enable : in std_ulogic; - sel : in std_ulogic; - inp1 : in std_logic_vector ( width downto 0); - inp2 : in std_logic_vector ( width downto 0); - outpt : out std_logic_vector ( width downto 0) ); - end component; - - component test_shift - generic ( width : integer := 17 ); - port ( clk : in std_ulogic; - reset : in std_ulogic; - load : in std_ulogic; - en : in std_ulogic; - inp : in std_logic_vector ( width downto 0 ); - outp : out std_ulogic ); - end component; - - component test_state - port ( clk : in std_ulogic; - reset : in std_ulogic; - con1, con2, con3 : in std_ulogic; - out1, out2 : out std_ulogic ); - end component; - - component test_multpipe - generic ( width : integer := 7 ); - port ( clk : in std_ulogic; - reset : in std_ulogic; - enable : in std_ulogic; - inp1, inp2 : in std_logic_vector ( width downto 0); - sum : out std_logic_vector ( (width * 2 ) downto 0) ); - end component; - - component test_parity - generic ( WIDTH : integer := 16); - port ( BUSX : in std_logic_vector ( WIDTH downto 0 ); - Parity : out std_ulogic ); - end component; - --- Signal declarations - signal internal_data : std_logic_vector ( input_data'high downto 0 ); - signal local_count : std_logic_vector ( input_data'high downto 0 ); - signal local_mulout : std_logic_vector ( mulout'high downto 0 ); - signal VCC, GND : std_ulogic; - -begin -- rtl - - VCC <= '1'; - GND <= '0'; - mulout <= local_mulout; - - U1 : test_clkgen - port map ( - clk => clk, - reset => reset, - dclk => dclk_i ); - - U2 : test_counter - generic map ( width => 5 ) - port map ( - clk => dclk, - reset => reset, - enable => VCC, - count => slow_count - ); - - U3 : test_reg - generic map ( width => internal_data'high ) - port map ( - clk => clk, - reset => reset, - enable => VCC, - sel => VCC, - inp1 => input_data, - inp2 => local_count, - outpt => internal_data - ); - - U4 : test_counter - generic map ( width => internal_data'high + 1 ) - port map ( - clk => clk, - reset => reset, - enable => enable, - count => local_count - ); - - U7 : test_add - generic map ( width => internal_data'high ) - port map ( - clk => clk, - reset => reset, - enable => enable, - inp1 => local_count, - inp2 => internal_data, - sum => sum - ); - - U5 : test_shift - generic map ( width => internal_data'high ) - port map ( - clk => clk, - reset => reset, - en => enable, - load => load, - inp => internal_data, - outp => shout - ); - - U9 : test_state - port map ( - clk => clk, - reset => reset, - con1 => local_count ( 0 ), - con2 => local_count ( 1 ), - con3 => local_count ( 2 ), - out1 => control ( 0 ), - out2 => control ( 1 ) - ); - - U8 : test_multpipe - generic map ( width => internal_data'high ) - port map ( - clk => clk, - reset => reset, - enable => enable, - inp1 => local_count, - inp2 => internal_data, - sum => local_mulout ); - - U6 : Test_Parity - generic map ( Width => internal_data'high ) - port map ( - BUSX => internal_data, - Parity => parity ); - - U10 : test_reg - generic map ( width => count'high ) - port map ( - clk => clk, - reset => reset, - enable => VCC, - sel => GND, - inp1 => input_data, - inp2 => local_count, - outpt => count - ); - - -end rtl;